堆二三事

一些关于堆的小知识点

(没有顺序,知识点是零散的,都是我自己学到一点,记录下一点)

1、当small chunk只有一个时,如果被释放了,会被划分到unsorted bin里面,如果只有一个时,该chunk的fd和bk都指向libc中的地址,可以借此泄露libc地址

如果程序没开PIE,可以试着考虑下泄露got表来获得libc地址

在高版本利用中可以借着large bin上存留libc地址进行泄露

2、当先调用了mmap函数时,malloc系列函数申请的chunk的地址是从heap的起始地址开始分配的,又有页(4KB)是内存分配的最小单位,导致heap地址的最低三位总是0x000。因此申请的chunk可以只要覆盖地址最低两位进行修改其fd和bk的值

3、在利用malloc_hook(__malloc_hook是一个弱类型的函数指针变量,指向void * function(size_t size void * caller),当调用malloc系列函数时,会判断hook指针是否为空,不为空则调用它)函数时,要使用错位偏移,即为把hook函数地址-0x23(有时候也可以找找hook地址-0x20+0x5)。此时,在写上p8(0)*3+p64(one_gadget)即可获取shell(似乎有些不准确,虽然有的确实行的通,但是更全面的应该是检验的size位置是int类型的函数,所以我们应该查看低32位的数,所以不局限-0x20,可能是0x30,0x38等等,在gdb里面调试发现即可)

4、如果释放两个fast chunk进入fast bin中,如果有UAF漏洞,修改后进的堆块fd指针指向其他的同样大小的fast chunk,那么被指向的堆块会被认为是在fast bin中的,而这时候如果这个chunk的fd上有地址数据,就可以申请到那边去(适用于要借用unsorted chunk上的脏数据,覆盖低字节地址)

5、chunk的size字段的最末位的三个比特位被用作状态标识,所以,size字段的以16进制表示时,最后一位16进制数是不能算到size里面的

6、一个chunk的大小最小是0x20(32字节64位系统下),或者0x10(16字节32位系统下)

7、在Glibc2.26之后的版本出现了Tcache机制,在64位情况下,tcache优先于其他bin链接收释放掉的0×200×410大小的堆块(32位下0xC0x200),tcache最多可以接收7个chunk,多出的部分将按照原来Glibc版本里面的方式分配。

并且从tcache分配出去的chunk并不会检查被分配位置的size大小,也就是不必像之前的版本使用错位偏移来使size符合检查

利用Tcache泄露libc有以下几种方法:

  • 申请8个大堆块,释放8个,这里堆块大小,大于fastbin范围,就是填满tcache,让第八个进入unsorted bin中
  • 有UAF的情况下,连续free 8次同一个堆块,这里堆块大小,大于fastbin范围
  • 申请大堆块,大于0×400
  • 修改堆上的Tcache管理结构,把count值改为7(count的值是有规律的,在Tcache里面,第一个字节存放的是0x20大小的堆块个数,每隔一个字节,堆块大小增加0x10,前0x40都是存放个数,后面的内容存放堆块地址),然后再次free一个该大小的chunk,注意防止chunk被top chunk合并

以上都可以让chunk的fd和bk指针指向libc中的地址而泄露地址

8、当修改hook函数为one_gadget发现所有给的execve都不行时,这时需要我们借用__libc_realloc去调整栈的参数,使execve的环境被满足才可以利用成功。__realloc_hook的地址是__malloc_hook-0x8。因为调用realloc会执行许多的push指令,可以把rsp调整到满足环境。要查看push操作,可以把__libc_realloc地址加上n,指向的都是push指令

操作:把one_gadget写到__realloc_hook地址上,因为在使用malloc函数后面会调用到__relloc_hook;在__malloc_hook地址写上__libc_realloc+n(就会先执行__libc_realloc+n调整,再执行one_gadget)

注:根据libc版本问题,不一定都是__libc_realloc ,也可以是 realloc

9、堆题libc都很重要,题目没给libc,如果存在double free,可以用其测试libc版本

  • 如果报fastbin double free 那就是2.23~2.26
    • 2.23下会检查free的fastbin chunk是不是fastbin链表中的第一个chunk
  • 如果没报错那就是2.27~2.28
    • 因为有tcahce,2.27的tcache没有任何检查
  • 如果报tcache double free那么就是2.29及其以上
    • 因为2.29以后tcache增加了key字段防止tcache double free

10、chunk的三个标志位:从高到低依次表示为:non_main_arena、is_mmap、prev_inuse

non_main_arena:0表示当前chunk是属于main_arena,1表示不属于

is_mmap:1表示当前chunk是使用mmap申请的,0表示不是

prev_inuse:1表示前一个chunk在被用户使用,0表示前一个chunk被释放

11、如果申请的chunk大小为0x60和0x68返回的是一样大小的chunk,而0x68多出的8个字节是使用了后一个chunk的presize

12、大小属于fastbinchunk被free的时候,不会改变next chunk的prev_inuse

13、想要让chunk进入small bins 或者large bins:

一、释放的chunk大小先达到 small bins 或者 large bins 的大小范围。该chunk先进入到 unsorted bin,然后再申请一次堆块,申请的大小大于之前释放的chunk,那么之前释放的chunk就会进入到 small bins 或者 large bins

二、释放的 chunk 大小先达到small bins 或者 large bins 的大小范围。该 chunk 进入到 unsorted bin 中,如果此时 unsorted bin 中存在其他的堆块,那么申请一个堆块(能够让其他堆块满足分配的)剩余的堆块就将会自动进入到相应的 bin 链中

14、如果从unsortedbin中分配出chunk,是从大chunk的头部开始分配的,分配完申请的大小之后把大chunk剩下的继续留在unsortedbin中

15、unsortedbin中的chunk重新分配出来时,里面的数据不会被系统清空(如果程序没动的话),所以里面还是存在着libc地址

16、修改free_hook内容为system地址,然后free一个chunk,chunk内容为”/bin/sh”,就相当于执行了system(“/bin/sh”),这种方法稳定getshell

17、在libc版本2.23及以下的时候,使用one_gadget稳定getshell,可以使用条件为[rsp+0x50] == NULL的one_gadget,然后对同一个chunk进行double free(指针没有置0)即可getshell

18、当把malloc_hook劫持为one_gadget失败时,除了可以去调整栈之外,还可以换思路去劫持free_hook,就是16中的办法,或者是劫持realloc(在malloc_hook-0x8的地方),往realloc_hook中写入one_gadget,malloc_hook中写入realloc函数的地址

19、因为free_hook前面一大段内容不存在像malloc_hook一样的0x7f的信息,所以要寻找的可以分配的堆头可能要往前找很大的偏移

  • 在2.23版本中可以使用unsortedbin attack(修改unsortedbin中的FD字段为0,BK字段为target addr - 0x10),在那边写入unsortedbin地址,从而拥有了0x7f(使用过之后如果fastbin里面没有chunk,那么申请堆块会到unsortedbin里面去找,找的话就会报错,此时需要到main_arena对应地址去修复unsortedbin的指针,改回原来的样子)
  • 劫持top chunk,生成一个fastchunk释放掉,接着把fd指针修改为fastchunk的大小(如写入0x81),然后再把刚才释放的堆块申请出来,此时会在main_arena生成出一个0x81,可以通过fastchunk的检查,而在这个下方,就是top chunk的指针,把指针内容修改到目标地址前面的地址(地址需要符合top size),然后再申请堆块即可(此时不可以存在被释放的堆块,要让堆块是从top chunk中分配的)

20、绕过calloc的清空数据:把堆的is_mmap位置改为1,calloc将不会清空属于mmap的堆块数据

顺序:释放==>修改堆头==>申请堆块

并且 calloc 函数不会分配 tcache bin 中的堆块,可以利用该函数直接绕过 tcache

21、tcache_perthread_struct这个结构体是可以释放的,并且可以将它释放到unsorted bin中去,然后分配这个unsorted bin chunk,可以控制任意地址分配堆内存

22、当堆题中没有show功能时,要泄露出libc地址,需要借助stdout吐出libc地址

需要把 _IO_2_1_stdout 上的值修改为p64(0xfbad1800) + p64(0) * 3 + '\x00'

23、当分配unsorted chunk时,如果剩下的块仍然大于0x80,并且还是唯一的,那这个新的unsorted chunkfd,bk指针仍然指向libc地址,并且分配出去的堆块,如果没有对堆块进行清零,那么上面将会存放着之前unsorted chunk的fd、bk指针

24、libc-2.23.so时的fd,bk指针,指向的是堆块的prev_size位置,libc-2.27.so时的fd,bk指针指向的是堆块的内容(就是存放fd,bk指针的位置)比libc-2.23.so多了0x10

25、利用exit_hook:劫持_dl_rtld_lock_recursive的值为one_gadget,当调用exit函数时可得到shell

image-20210903170451297

寻找exit_hook:

​ 在gdb中输入p _rtld_global,可以找到这两个被赋予的值。

image-20220721110209483

然后在输入 search "0x7ff75581d150" -t qword即可找到其对应的地址。

image-20220721110328165

libc-2.23.so:

  • rtld_lock = libc_base + 0x5F0F48
  • rtld_unlock = libc_base + 0x5F0F50

libc-2.27.so:

  • rtld_lock = libc_base + 0x619F60
  • rtld_unlock = libc_base + 0x619F68

注:上述只是2.23,2.27的libc版本,但是同2.23,2.27的libc其实还有小版本类型,但是不变的是都在_rtld_global+3840的地址

适用范围 glibc-2.23~glibc-2.34ubuntu3,直到 glibc-2.34ubuntu3.2 地址不可写了,才正式宣告失效。

26、libc-2.27.so的unlink要先把tcache bin填满才能用,unlink要保证unsorted bin是空的,所以有时候泄露信息用了unsorted bin,要申请回来

27、当我们申请一个大于 top chunk 的堆块时,程序会调用 mmap 进行内存分配,分配下来堆块的地址是libc中的地址

28、libc-2.27堆的orw:利用setcontext+53的指令,把setcontext+53的地址一般写在__free_hook上,而shellcode写在堆上,然后进行释放堆块从而执行orw

29、当劫持堆块到 tcache,释放 tcache 获得 libc 地址后, 由于 unsorted bin 的 fd 和 bk 会在 tcache struct 写一些数据,而这些数据会覆盖到 tcache struct 上的 counts,导致取出相应的 size 时发生错误,所以需要修复 tcache,将不必要的数据清为 0,有需要伪造并取出的部分赋值为 1(因为从 glibc-2.31 开始,不允许tcache的count小于0)

30、如果把 malloc_hook 劫持为 system 也是可以的,后面在调用malloc时传入参数,不传入size大小,而是变为 /bin/sh 的地址即可

31、realloc 函数原型:extern void *realloc(void *mem_address, unsigned int newsize);

  • mem_address 不变,newsize == 0,相当于释放原来的堆块;
  • mem_address == 0 && newsize > 0,相当于malloc;
  • mem_address 不变,newsize > originsize,如果与topchunk相邻则直接扩展到newsize,不相邻则先释放原堆块,然后再malloc一个更大的堆块,原堆块内容会被拷贝过去;返回新堆块的地址
  • mem_address 不变,newsize <= originsize,如果切割后剩下的堆块大于 2 * chunk_min_size则切割,剩下堆块被free,否则直接返回原堆块

32、在利用 offbynull 构造 overlap,遇到过多次:头尾两个要释放的堆块之间不能只间隔一个堆块,至少要两个才行,不然将无法成功利用 offbynull

33、unsorted bin 泄露出的 main_arena + 96 的 libc 地址和 __malloc_hook 的地址只相差一个字节的数据,所以如果拥有能在释放堆块修改的数据的权利时,可以修改 main_arena + 96 后一字节即可,可以避免要打印 libc 地址

34、large bin attack 利用:

  • 修改 TLS 中保存的 tcache 的地址,将其劫持为可控地址,等同于把 tcache bin 申请出来,可以直接进行任意地址分配。但是缺点是 TLS 可能存在第四、五位地址处出现偏差,需要爆破(但是是个定值。)

    image-20230201225501974

  • TCACHE_MAX_BINS 是 tcache bin 链的最大数量,宏定义为64,可以修改其值为 chunk 的地址,那么所有 chunk 块 free 之后都会进入tcache,位置如下图所示。(glibc-2.30后才可以适用)

    image-20220304094405889

  • global_max_fast记录着 fast bin 的最大 size,改大之后,可以让更大的size也算入 fast bin ,然后可以依据 main_arena 就算的偏移,把堆地址写入到 fast bin 之后的任一地址上,位置如下图所示。

image-20220310192041331

  • FSOP的利用,2.31及以上的主要用法。

35、小技巧:在使用 one_gadget 时,可以先用 0xcafebabedeadbeef 填充,然后直接 c 就会让程序 down 在调用的位置,可以直接查看寄存器的值。

36、临时关闭 ASLR 随机化,重启失效。

1
sudo sysctl -w kernel.randomize_va_space=0

37、glibc-2.34及以上开始删除 hook 后,寻求以 IO 中的函数调用作为替代品:

  1. house of kiwi 中劫持 _IO_file_jumps + 0x60 的 __IO_file_sync(高版本的 _IO_file_jumps 可写,但是也存在最新的小版本不可写的情况)
  2. house of emma
  3. house of apple
查看评论