first_fit
这个就是体验堆分配的策略,找到最合适的那个,这个uaf的漏洞利用提供了机会
1 | root@kali:~/learn/how2heap# ./first_fit |
fastbin_dup
通过演示滥用fastbin freelist来欺骗malloc返回已分配的堆指针。
其实就是fastbin的double free
1 | root@kali:~/learn/how2heap# ./fastbin_dup |
代码先free一次a,再free一次b,最后再free一次a,就把a给free了两次,最后在malloc三次,就将同一块内存分配了两次了
那么我们直接free一次a,再free一次a会怎么样呢
从下面可以看到直接退出了,可以看到检测到double free or corruption (fasttop): 0x000055f7d069e420
我们注意到fasttop这个单词,应该就是在fastbin的顶部检测到a被free了两次了
1 | root@kali:~/learn/how2heap# ./fastbin_dup2 |
fastbin_dup_into_stack
通过滥用fastbin freelist,欺骗malloc返回一个近乎任意的指针。
1 | root@kali:~/learn/how2heap# ./fastbin_dup_into_stack |
可以看到最终分配返回的已经不是堆变量,而是一个栈的地址,那么我们就有可能去覆盖栈上的东西,比如最经典的返回地址
那么是怎么覆盖的呢,一开始free a b a的顺序构造出了下面这样的free list
[ 0x55a0bfdda420, 0x55a0bfdda440, 0x55a0bfdda420 ]
我们调试看看(由于多次调试地址有所改变,但左后的一个字节应该是不变的),我们看到了我们分配的堆,我们看到fd已经有值了,但是为什么头部仍是0x21,最低位是1(后来问别人fastbin的这个位永远是1)
1 | gdb-peda$ heap |
现在list是这样的arena指向0x555555757410,0x555555757410指向0x555555757430,0x555555757430又指向0x555555757410这样,我们继续,其实这里构成了一个内循环了
首先malloc一次,arena已经指向0x0000555555757430了
1 | gdb-peda$ x /2gx 0x7ffff7dd3b00 |
思考一下:那么这里我们就可以看到这是将0x555555757410的fd写到这里来了,假如我们将0x555555757410的fd改了,那么就可以将我们需要的值写到arena的位置,之后再malloc,就返回这个地址,我们对这个地址就有写权限了(但一开始由于这个是free状态,所以我们无法改写,是实践截图如下:)
那接下来再malloc,main_arena这里又指向0x0000555555757410
1 | gdb-peda$ x /2gx 0x7ffff7dd3b00 |
那么正如作者所说
Now, we have access to 0x555555757420 while it remains at the head of the free list.
在之后将一个long long类型的局部变量赋值为0x20,这个用于伪造前一个chunk为空闲
1 | stack_var = 0x20; |
汇编看结果如下:
1 | x /gx $rbp-0x28 |
之后将stack_var地址-8的地址覆盖第一个堆块(chunk)的fd
1 | *d = (unsigned long long) (((char*)&stack_var) - sizeof(d)); |
汇编如下:
1 | 0x555555554965 <main+485>: lea rax,[rbp-0x28] |
之后可以看到第一个chunk的fd已经被覆盖了成了0x00007fffffffe4f0,这是栈上变量stack_var的向前偏移8个字节的地址
1 | gdb-peda$ x /10gx 0x555555757410 |
此时我们第3次malloc,0x00007fffffffe4f0已经写到main_arena上了
1 | gdb-peda$ x /2gx 0x7ffff7dd3b00 |
那么下次再malloc就直接从这0x00007fffffffe4f0头上取了
最终的分配就返回了0x00007fffffffe500,因为返回的是指向数据区的指针
1 | gdb-peda$ x /6gx 0x7fffffffe4f0 |
问题
- 我们能只在不malloc的情况下直接改写第一个chunk的fd吗?
这样会出现段错误
1 | root@kali:~/learn/how2heap# ./fastbin_dup_into_stack_q1 |
调试的时候不知道为啥改得不彻底,看了下原来是mov DWORD PTR [rax],edx
,是edx,可能
1 | gdb-peda$ x /10gx 0x555555756410 |
原来d声明的时候就是unsigned long long *d = malloc(8);
后来我改a为unsigned long long,free完直接改也是可以的
- 如果不伪造0x20行吗
实践告诉你是不行的
1 | *** Error in `/root/learn/how2heap/fastbin_dup_into_stack_q2': malloc(): memory corruption (fast): 0x00007fffffffe500 *** |
因为fastbin申请的时候会检测这个堆块的大小是否是当前这条单向链表上的大小
unsafe_unlink
free触发的unlink,以获得任意的写能力。
1 | Welcome to unsafe unlink 2.0! |
2017年3月增加了个检测
https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
1 | +2017-03-17 Chris Evans <scarybeasts@gmail.com> |
- 首先malloc size是0x80,避免释放后放到fasebins
- 申请两个chunk
- 将chunk0的0x20和0x28偏移伪造成fd和bk,使P->fd->bk,P->bk->fd指向自己
- 将chunk1的prev size覆盖为0x80,previous_in_use覆盖为0,那么free的时候找前一个chunk就指向我们伪造的chunk了,而且认为是free过了的,所以free chunk1就会触发unlink chunk0
unlink源码
free之前状态如下:
1 | pwndbg> x /50gx 0x555555757410 |
简单unlink代码如下:
1 | FD = P->fd; |
那么指向chunk0的指针就会被改写为0x0000555555756038
1 | p chunk0_ptr |
我们再对chunk0进行写入就可能覆盖那边指针了
1 | pwndbg> x /50gx 0x0000555555756038 |
所以作者使用chunk0_ptr3 = (uint64_t) victim_string;来覆盖chunk0指针指向victim_string
之后便可以利用chunk0_ptr[0]修改victim_string的值了
house_of_spirit
释放一个伪fastbin块,以使malloc返回一个近乎任意的指针。
1 | root@kali:~/how2heap# ./house_of_spirit |
由于是fastbin大小的chunk,所以PREV_INUSE是被忽略的,IS_MMAPPED跟NON_MAIN_ARENA有影响,设置为0即可
所以设置chunk的大小为0x40
下一个chunk的大小应该不用太在意
之后再free掉&fake_chunks2,那么fastbin的freeList上就会存储这&fake_chunks2,之后再malloc,就是返回这个&fake_chunks2地址了
poison_null_byte
溢出一个空字节
1 | root@kali:~/learn/how2heap# ./poison_null_byte |
首先申请了三个chunk
a 0x100
b 0x200
c 0x100
之后在b+0x1f0偏移处设置0x200,以便绕过检测,使 chunksize(P) == prev_size (next_chunk(P))
因为空字节溢出后chunksize(P) 就变为0x200
那么之后free(b)就可以成功了
之后申请了两个chunk
b1 0x100
b2 0x80
这两个都会覆盖原来b的位置,并将b2的内容覆盖为0x80个B,以便之后看到效果
之后free(b1),这个没什么发生,那么下一步free(c)的时候,由于c的堆结构完全没变化
所以他认为前面的chunk是空的,前面的大小是0x210,所以就跟前面0x210大小的合并
原来chunk b的地方是top chunk了
那我们再malloc 0x300,就可以获取整个一大片的内存,进而可以任意控制b2的值了
也就把BBB覆盖为DDDD了
house_of_lore
欺骗malloc,通过滥用smallbin freelist来返回一个几乎任意的指针。
1 | Welcome to the House of Lore |
先申请一个0x100的chunk
之后在栈上伪造两个chunk
stack_buffer_1的fd指向那victim——0x100的chunk,bk指向stack_buffer_2,
而stack_buffer_2的fd指向stack_buffer_1
1 | bck = victim->bk; |
因为这样可以绕过smallbin check
之后申请一个large chunk避免free的时候合并到top chunk去了
1 | void *p5 = malloc(1000); |
之后free掉 victim,就放到unsortbin list去了
1 | free((void*)victim); |
现在它的前向指针和后向指针都是null
之后申请了一个UnsortedBin和small bin都不能处理的大小
1 | void *p2 = malloc(1200); |
查找UnsortedBin的过程中,就把victim放回small bin了
之后我们改变victim的bk,再申请一个同样是0x100大小的chunk,那么就申请到了victim的位置,当然栈地址就写到了small bin list了,
最后我们再去申请一个0x100的chunk,返回的地址就在栈上了
之后作者给了一种利用的思路,就是直接通过返回的这个chunk指针,直接改写返回地址,从而绕过canary
overlapping_chunks1
堆块堆叠,就是两个或以上的堆重叠了
1 | This is a simple chunks overlapping problem |
首先申请了3个chunk,大小分别为0x100-8,0x100-8,0x80-8 内容分别用1,2,3填充
之后free chunk p2,p2就到了unsorted bin
那么之后我们伪造p2的size为0x181,这个size刚好就覆盖到p3了
之后申请了一个0x180-8大小的chunk——p4,那么我们上面伪造的大小就刚好满足要求
那么这样的p4就可以任意覆盖p3,当然使用p3也可以部分覆盖p4
overlapping_chunks_2
1 | This is a simple chunks overlapping problem |
首先申请5个1000大小的chunk,p1到p5
之后分别用A,B,C,D,E,F填充
free p4
之后改写p2的size为p2+p3的真实大小+0x10+prev_in_use(1)
其中0x10是两个堆的size的大小
再 free掉p2,它就以为p2的下一个是p4了
我们再malloc一个2000大小的——p6,那么p6跟p3就重叠了,可以互相修改
house_of_force
这个是覆盖top chunk从而使malloc返回的值能够控制
1 | Welcome to the House of Force |
先申请了256个字节的chunk,那么就有这个chunk和top chunk了
之后将top chunk的size改为0xffffffffffffffff,这样我们malloc很大的值也不用mmap了
之后申请一个0xffffffffff76db28byte的,这是一个负数,所以top chunk的大小会减去这么一个值,但是第一次申请的时候malloc还是返回原理top chunk的位置
那么当我们再次申请的时候,top chunk就返回了我们想要的地址了
那个负数是怎么得来的呢?
因为我们malloc之后,top指针会加上我们的size,所以我们只需要malloc我们想要的地址跟top指针的差别再减0x10的头部就行了
unsorted_bin_attack
1 | This file demonstrates unsorted bin attack by write a large unsigned long value into stack |
作者说通常unsorted_bin_attack是为进一步利用做准备的,比如libc中的全局变量global_max_fast,之后再执行fastbin attack
给出的例子的话是覆盖栈上的局部变量
首先申请两个chunk,为的是避免free之后合并到top chunk了
跟着我们free掉第一个,那么他就会放到unsorted bin中,而此时我们的chunk有了fd和bk指针
假如我们可以有漏洞去覆盖bk,例子是覆盖栈变量指针减0x10的地址,那么结构如下
unsort bin ——> p ——> stack
那么我们再malloc一次,那么stack的fd就会被修改为unsort bin的头,这样就能让p脱链
house_of_einherjar
1 | Welcome to House of Einherjar! |
这个可以用于有off by one的程序,从而让我们有机会修改prev_inuse
首先申请0x38个byte——chunk a,这个是为off by one做准备的,之后这样我们才有机会溢出下一个chunk的头部
之后在栈上伪造了一个fake chunk(当然实际的时候我们在heap或者bss上伪造也行,只要知道地址),fd和bk都指向自己
之后申请0xf8大小的chunk——chunk b,
我们通过a一个空字节溢出b,那么b的prev_inuse bit就置0,以为前面的chunk是空闲的了
之后我们再修改b的prev size,那么最终就会控制合并后的地址了
因为合并的时候,是先找前一个chunk在哪,我们让它计算的时候找到栈上去了
所以最后再malloc,就会返回我们想要的栈地址了
house_of_orange
1 | The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer |
这个技术是利用溢出,去破坏_IO_list_all
指针,需要泄露heap跟libc
top chunk一般最开始是0x21000大小的
首先申请0x400-16,那么top chunk就从最开始的0x21000,剩余0x20c00,当然有PREV_INUSE标志位就是0x20c01
1 | p1 = malloc(0x400-16); |
之后我们将top chunk的大小改为0xc01
接下来再malloc一个比这个size大的chunk,这时候原来的top chunk就会被free到unsort bin中
这个技术是故意让堆检测到异常,之后就会调用_IO_flush_all_lockp
,最后遍历_IO_list_all
,找到_IO_OVERFLOW
函数去掉用,这个函数是相当于虚表里面的,所以我们可以伪造一个 _IO_list_all
,让他去调用system函数
伪造_IO_list_all
,我们需要改写它的指针,它可以通过fd或者bk来计算出来
我们需要覆盖old top chunk满足chunk->bk->fd指向 _IO_list_all
,所以我们覆盖old top chunk的bk为io_list_all - 0x10
1 | top[3] = io_list_all - 0x10; |
之后将old top chunk的前8个字节覆盖为”/bin/sh\x00”
_IO_flush_all_lockp
会遍历_IO_list_all
中的文件指针,因为我们只能用main_arena的unsorted -bin- list覆盖这个地址,所以这个想法是为了控制在相应的fd - ptr上的内存。下一个文件指针的地址位于base_address 0x68。这相当于smallbin4
再将old top chunk的size设置为0x61,将其强制转换为_IO_FILE
指针
- Set mode to 0: fp->_mode <= 0
- Set write_base to 2 and write_ptr to
- fp->_IO_write_ptr > fp->_IO_write_base
1 | fp->_IO_write_base = (char *) 2; // top+0x20 |
- Finally set the jump table to controlled memory and place system there.
The jump table pointer is right after the _IO_FILE struct:
base_address+sizeof(_IO_FILE) = jump_table
1 | size_t *jump_table = &top[12]; // controlled memory |
最后通过malloc触发