TSCTF 2022 wp

baby_xor

1
2
3
4
5
enc = [18, 20, 7, 17, 4, 110, 10, 58, 25, 124, 32, 14, 122, 6, 123, 22, 100, 8, 6, 48, 4, 22, 34, 117, 27, 0, 36, 18, 40, 4, 105, 42, 57, 67, 43, 85, 13, 60, 5, 83, 19]

for i in range(0, len(enc)):
print(chr(enc[i] ^ i ^ 0x46),end='')
# TSCTF-J{W3lC0M3_2_ReVEr$E_xOr_1s_$O0o_e2}

baby_upx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 给定的 dword_140004480 数组
dword_140004480 = [
175, 172, 236, 175, 231, 345, 222, 508, 367, 493, 492, 478, 181, 367, 181, 238,
232, 238, 508, 181, 173, 174, 510, 181, 494, 494, 366, 382, 223, 364, 473, 509
]

# 反向推导 byte_140007750 数组的长度为 32
byte_140007750 = [0] * 32

# 逆向推导每个字节
for v5 in range(32):
target = dword_140004480[v5]

# 对于每个字节 v5,执行反向位运算,尝试找到与 target 匹配的 byte_140007750[v5]
for guess in range(32,129): # 假设字节范围从 0 到 255
# 计算位运算的结果
v3 = (guess >> 2) & 5 | (2 * (guess ^ 5 | (2 * (((~guess) | (2 * (guess & 0xF2))) & 0x5B))))

# 如果计算结果与 target 相等,则找到匹配的字节
if v3 == target:
print(chr(guess),end='')
print()

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
T
QS
C
T
F
-
HJ
y{
$4
u
cqs
hj
_
$4
_
@B
A
@B
y{
_
U
PR
xz
_
`bpr
`bpr
"02€
8:
L
#13
m
}

多试几次:TSCTF-J{$uch_4_BABy_UPx_pr08L3m}

或者记录官解方法dfs提交,还是手动比较快

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
import subprocess
import sys

enflag = ['T', 'S', 'C', 'T', 'F', '-', 'J', '{', '$4', 'u', 'cqs', 'hj', '_', '$4', '_', '@B', 'A', '@B', 'y', '_', 'U', 'P', 'x', '_', '`bpr', '`bpr', '"02', '8:', 'L', '#13', 'm', '}']

def dfs(dep, flag):
if dep == 32:

file = "F:\\Desktop\\CTF\\Problems\\出题\\TSCTF-J 2022\\Reverse\\iPlayForSG - baby_upx\\baby_upx_upx_version.exe"

p = subprocess.Popen([file], stdin = subprocess.PIPE, stdout = subprocess.PIPE)

p.stdin.write(flag.encode())
p.stdin.close()

result = p.stdout.read()
p.stdout.close()

if b'YOU ARE GENIUS!!!' in result:
print(flag)
sys.exit(0)

else:
for i in enflag[dep]:
dfs(dep + 1, flag + i)

dfs(0, '')

byte_code

挺简单的,ai直接缩了bytecode里面的逻辑也挺清晰的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
12         326 LOAD_NAME                0 (a)
328 LOAD_NAME 8 (i)
330 BINARY_SUBSCR
332 LOAD_NAME 4 (d)
334 LOAD_NAME 8 (i)
336 BINARY_SUBSCR
338 BINARY_ADD
340 LOAD_NAME 1 (b)
342 LOAD_NAME 3 (pos)
344 LOAD_NAME 8 (i)
346 BINARY_SUBSCR
348 BINARY_SUBSCR
350 BINARY_XOR
352 LOAD_NAME 0 (a)
354 LOAD_NAME 8 (i)
356 STORE_SUBSCR
358 EXTENDED_ARG 1
360 JUMP_ABSOLUTE 322

比如这段,就是a[i]=(a[i]+d[i]) ^ pos[i],STORE_SUBSCR就是给容器对象(list / dict / bytearray 等)里某个下标(key/index)存值。

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
def main():
# a = list of ASCII codes for "reverse_the_byte"
a = [114,101,118,101,114,115,101,95,116,104,101,95,98,121,116,101]

# b = list of ASCII codes for "code_to_get_flag"
b = [99,111,100,101,95,116,111,95,103,101,116,95,102,108,97,103]

# e: 32-entry XOR table (从 bytecode 常量拷贝)
e = [
80, 115, 193, 24, 226, 237, 202, 212,
126, 46, 205, 208, 215, 135, 228, 199,
63, 159, 117, 52, 254, 247, 0, 133,
163, 248, 47, 115, 109, 248, 236, 68
]

# pos: 16-entry permutation (从 bytecode 常量拷贝)
pos = [9,6,15,10,1,0,11,7,4,12,5,3,8,2,14,13]

# d: 32-entry integer multipliers (从 bytecode 常量拷贝)
d = [
335833164, 1155265242, 627920619, 1951749419,
1931742276, 856821608, 489891514, 366025591,
1256805508, 1106091325, 128288025, 234430359,
314915121, 249627427, 207058976, 1573143998,
1443233295, 245654538, 1628003955, 220633541,
1412601456, 1029130440, 1556565611, 1644777223,
853364248, 58316711, 734735924, 1745226113,
1441619500, 1426836945, 500084794, 1534413607
]

# 首先打印提示信息(bytecode 中先拼接并打印)
c = a + b
# 按照原程序, 先输出前 31 个字符(没有换行),再输出最后一个(带换行)
for i in range(31):
print(chr(c[i]), end='')
print(chr(c[31]))

# 第一轮变换:对 a[0..15] 做 (a[i] + d[i]) ^ b[pos[i]]
for i in range(16):
a[i] = (a[i] + d[i]) ^ b[pos[i]]

# 第二轮变换:对 b[0..15] 做 b[i] = b[i] ^ a[pos[i]]
for i in range(16):
b[i] = b[i] ^ a[pos[i]]

# 重新拼接 c = a + b,长度 32
c = a + b

# 对每个元素做 (c[i] * d[i]) % 256,然后异或 e[i],再输出对应字符
out_chars = []
for i in range(32):
val = (c[i] * d[i]) % 256
val ^= e[i]
out_chars.append(chr(val))

# 打印 flag(或最终字符串)
print(''.join(out_chars))


if __name__ == '__main__':
main()

TSCTF-J{bY7ecoDe_I$_nOT_so_HArd}

baby_key

先输入密码,有个类似虚拟机的

image-20250921231847005

image-20250921231628340

tea然后端绪转化

一开始的密码就是key要满足相应要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
key = 'flag{VM1sSo3asy}'

for i in range(len(key)):
print(ord(key[i]),end=',')

print()

e = [0x4F,0x2D,0x90,0x70,0xB3,0x2B,0x64,0x2A,0x48,0x14,0x75,0x1C,0x5A,0x7A,0x62,0x3E]
for i in range(len(key)):
print(e[i],end=',')

print()

pos = [11, 12, 10, 8, 3, 5, 2, 0, 7, 6, 13, 15, 9, 14, 4, 1]
print(pos)
1
2
3
102,108,97,103,123,86,77,49,115,83,111,51,97,115,121,125,
79,45,144,112,179,43,100,42,72,20,117,28,90,122,98,62,
[11, 12, 10, 8, 3, 5, 2, 0, 7, 6, 13, 15, 9, 14, 4, 1]

好在不多自己稍微看看就知道了

比如102-79=23那么要输入s才能这样便

可得

1
sO*h4hdsOm3!!sg!
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>
#include <iostream>
#include <cstdio>

using namespace std;

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x9e3779b9;
uint32_t sum = (32) * delta;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (uint32_t i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}

int main() {
unsigned char enc[] = { 0x2f, 0x33, 0x20, 0x70, 0xac, 0x7e, 0x89, 0x4, 0xca, 0xd2, 0xfb, 0x3, 0x51, 0x8c, 0x80, 0x23, 0x69, 0xe0, 0xc0, 0xe5, 0x41, 0x62, 0xf2, 0x26, 0xb8, 0x87, 0xa4, 0x33, 0xfb, 0x7a, 0x29, 0xe4, 0x45, 0x20, 0x3c, 0x2a, 0xfe, 0x2c, 0xec, 0x18, 0xf3, 0x2, 0x1, 0xe, 0x99, 0x3b, 0x7, 0x21};
unsigned int* key = (uint32_t*)"sO*h4hdsOm3!!sg!";
for (int i = 0; i < 48; i += 4) swap(enc[i], enc[i + 3]), swap(enc[i + 1], enc[i + 2]);
for (int i = 0; i < 48; i++) enc[i] ^= 0x27;
for (int i = 10; i >= 0; i--) {
decrypt((uint32_t*)&enc[4 * i], key);
}
cout << enc;

}

加密出来是乱码,发现,init做了xor

image-20250922095657847

TSCTF-J{T1ny_eNcryPtIoN_4LgOrIthm_Is_so_FUn}

19260817分才能拿到flag,用CE没找到…

只能逆向发现是个C#程序

看了下动调改分数也不太行,看来只能手动分析算法了

image-20250922194640593

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
enc =  [53, 71, 22, 108, 73, 97, 59, 107, 63, 126,
103, 125, 106, 80, 98, 66, 83, 93, 75, 2,
94, 96, 91, 48]

k = [71, 65, 77, 51]

Aim = [0] * 20

for i in range(21):
t = k[0]
k[0] = k[1]
k[1] = k[2]
k[2] = k[3]
k[3] = t

for i in range(20,-1,-1):
t = k[3]
k[3] = k[2]
k[2] = k[1]
k[1] = k[0]
k[0] = t
num3 = enc[i + 3] ^ k[3]
num2 = enc[i + 2] ^ k[2]
num1 = enc[i + 1] ^ ((k[1] + (num3 >> 4)) & 103)
num0 = enc[i] ^ ((k[0] + (num2 >> 3)) & 69)
enc[i] = num2
enc[i + 1] = num3
enc[i + 2] = num0
enc[i + 3] = num1
print('TSCTF-J{',end='')
for i in range(20):
print(chr(enc[i]),end='')
print('}',end='')

TSCTF-J{Y0u_@r3_1inKgAm3_M@2}

ez_maze

image-20250922215914630

这个迷宫不是+1-1要结合地图来猜

关键在于:current_position 指向的是「走廊里的空间字符」,而不是墙、也不是格点。这个 ASCII 迷宫是用典型的 +–+ / | | 结构画出来的,每个逻辑格子的左右两边都有竖墙(|),中间是一整条空格。结果就是「可走的位置」在同一行里呈现出这样的周期:

|···|···|···|
^ ^ ^

(· 表示空格)。可以看到,相邻两个可走中心之间隔了 3 个字符:从左侧中心到右侧中心,要跨过两个空格和一列墙(这一列墙在 ASCII 图里是 |,但一旦没有墙,那里就会是空格)。如果这里有墙,那么 < 对应的字符会是 ‘|’,于是程序就会直接报 “Wrong Path”。因为 ASCII 图里竖墙就是占据那一列。

所以虽然表面看是 “减 3” 有点怪,但结合地图的具体排版就很自然了:我们其实是在 3 列宽的格子之间跳跃,pos-1 / pos+2 只是用来扫描两格之间的那条「通道」,确认有没有墙挡住。

bfs

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
import hashlib
from collections import deque

MAZE_FILE = 'maze.txt'
WIDTH = 91
START = 0x5C
GOAL = 0x1551
MAX_LEN = 0xC4
MOVES = {
'A': (-1, -3),
'D': (2, 3),
'W': (-WIDTH, -2 * WIDTH),
'S': (WIDTH, 2 * WIDTH),
}

def load_maze(path: str) -> str:
with open(path, 'r') as f:
return f.read()

def bfs_path(maze: str) -> str:
limit = len(maze)
queue = deque([START])
parents = {START: (None, '')}

while queue:
pos = queue.popleft()
if pos == GOAL:
# reconstruct command string
commands = []
while parents[pos][0] is not None:
pos, cmd = parents[pos]
commands.append(cmd)
return ''.join(reversed(commands))

path_len = 0
# recover path length without storing full string for each node
node = pos
while parents[node][0] is not None:
path_len += 1
node = parents[node][0]
if path_len >= MAX_LEN:
continue

for cmd, (check_offset, move_offset) in MOVES.items():
check_idx = pos + check_offset
new_pos = pos + move_offset
if not (0 <= check_idx < limit and 0 <= new_pos < limit):
continue
if maze[check_idx] != ' ':
continue
if new_pos not in parents:
parents[new_pos] = (pos, cmd)
queue.append(new_pos)
raise RuntimeError('No valid path found within constraints')

def main():
maze = load_maze(MAZE_FILE)
path = bfs_path(maze)
if len(path) > MAX_LEN:
raise RuntimeError('Path length exceeds maze constraint')
flag = hashlib.md5(path.encode()).hexdigest().upper()
print('Commands:', path)
print('Flag: TSCTF-J{' + flag + '}')

if __name__ == '__main__':
main()

DSDWDDSDWDDDSASSSSDDSSDSSDWDWAWWWWASAAWDWDWDDSDSDDWWDDSASSSDDDWAWWWDWDDSSDSSDSDSDSSAAAAAWWASASASDSDWDDSDDDWDDSASDSSDWDWWDSSSSSSSAWAAWAAAAASAAASASDDDDWDWDSSSDDWWDSSSDSASSAAWAWWAASSDSDSSDSDDSDDSASDD

Flag: TSCTF-J{1C34207F7C0B2F2C79A28A13B16907C6}

本题用angr可以去混淆,记录下,下次学一下

官解的dfs版本:

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
#include <bits/stdc++.h>
using namespace std;
char mp[233][233] =
{
"+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
"|SS | | | | | |",
"+ + + +--+ + +--+--+ + +--+--+--+--+ +--+--+--+--+ +--+ + +--+ + +--+--+ + +",
"| | | | | | | | | | | | | | | | | |",
"+--+--+--+ +--+--+ + + +--+ +--+ +--+ + +--+ + +--+ + +--+ +--+ +--+ +--+ +",
"| | | | | | | | | | | | | | | | | | |",
"+ + +--+ +--+--+ + +--+ +--+ +--+ +--+ + + + + +--+--+ + + +--+ + +--+ +",
"| | | | | | | | | | | | | | | | | | |",
"+ +--+ +--+--+ + + + +--+ + + +--+--+--+ + + +--+ +--+ +--+--+ +--+ + +--+",
"| | | | | | | | | | | | | | | | | | |",
"+--+ + +--+ +--+ + +--+--+--+ + + + + + +--+--+ +--+ +--+ +--+ + +--+ + +",
"| | | | | | | | | | | | | | | | | |",
"+ +--+--+ + + + +--+--+ + + + + + +--+--+--+ +--+--+ + +--+ +--+ + + + +",
"| | | | | | | | | | | | | | | | | | | |",
"+--+ + + +--+--+--+--+ + +--+ + +--+--+ +--+ +--+ + + + + +--+ +--+ +--+ +",
"| | | | | | | | | | | | | | |",
"+ +--+--+--+ + +--+--+--+--+ +--+ + +--+--+ +--+ +--+ +--+--+--+--+ + +--+ +--+",
"| | | | | | | | | | | | | |",
"+--+ + +--+--+--+--+--+ + + + +--+ + +--+ + +--+--+--+--+--+ +--+--+--+ +--+ +",
"| | | | | | | | | | | | |",
"+ +--+ + +--+ + +--+--+ +--+--+ +--+ +--+--+--+ + +--+ +--+--+ +--+ + +--+--+",
"| | | | | | | | | | | | | | | | | |",
"+ + +--+ + + + + +--+--+ + +--+ +--+ + + + +--+ +--+--+ +--+ +--+ + + +",
"| | | | | | | | | | | | | | | | | | | | | |",
"+ +--+ + + + +--+ + +--+--+ + +--+ +--+--+ + +--+ + + +--+--+--+ +--+ + +",
"| | | | | | | | | | | | | | | | | | |",
"+ + + + + +--+--+--+--+ + +--+--+ + + + + +--+--+--+ +--+--+--+ + + +--+ +",
"| | | | | | | | | | | | | | | |",
"+ +--+ +--+--+--+ + + +--+--+--+--+--+ + + +--+ + + + +--+ + +--+--+--+--+ +",
"| | | | | | | | | | | | | |",
"+ + +--+--+--+--+ + + + +--+ +--+ +--+--+ + +--+--+--+--+ +--+--+--+--+--+--+ +",
"| | | | | | | | | | | | | | |",
"+ +--+--+ + + +--+ +--+ + +--+ + + + +--+ +--+--+--+ +--+--+--+--+ +--+--+ +",
"| | | | | | | | | | | | | | |",
"+ +--+ +--+--+ + +--+--+--+--+ +--+--+ +--+ +--+ +--+--+--+ + + + +--+--+ + +",
"| | | | | | | | | | | | | | | |",
"+--+--+ + + +--+ + + +--+--+--+ + +--+ + + +--+--+--+ +--+ + + + + +--+--+",
"| | | | | | | | | | | | | | | | | | |",
"+ + +--+ +--+ +--+ + +--+ +--+ + +--+--+--+--+--+--+--+--+ + +--+ + + + + +",
"| | | | | | | | | | | | | | |",
"+ +--+ +--+ +--+--+--+--+ +--+ + + + +--+--+--+ + + +--+--+--+--+--+ +--+ + +",
"| | | | | | | | | | | | | | | |",
"+ +--+ + +--+--+--+--+ +--+ +--+--+ + + +--+--+ +--+--+ +--+ + + +--+ +--+ +",
"| | | | | | | | | | | | | | | | |",
"+ +--+--+--+--+--+--+--+--+ + +--+ + + + + + +--+ + + + + +--+ + +--+ +--+",
"| | | | | | | | | | | | | | |",
"+ + +--+--+--+--+--+ +--+--+--+--+ +--+ + + +--+--+--+ +--+ +--+ +--+ + +--+ +",
"| | | | | | | | | | | | | | |",
"+ +--+--+--+ + + +--+ +--+--+ +--+ +--+--+ +--+ + +--+ +--+ +--+--+--+ + +--+",
"| | | | | | | | | | | | | | | | |",
"+--+--+ + + +--+--+ +--+ + +--+ + + + + +--+--+--+ +--+ + +--+ + +--+--+ +",
"| | | | | | | | | | | | | | | | |",
"+ +--+--+ +--+ + +--+--+ +--+--+--+ + +--+--+ + + +--+ + +--+ +--+--+ + +--+",
"| | | | | | | | | | | | | | | | | |",
"+ + + + + + + +--+--+--+--+ + +--+ + +--+--+ +--+ +--+ + +--+--+ +--+--+ +",
"| | | | | | | | | | | | | | | | |",
"+ + +--+--+ +--+--+--+ +--+--+--+--+ + +--+ + +--+ +--+ +--+ +--+ +--+--+ + +",
"| | | | | | | | | | | | | | | |",
"+ + +--+--+ +--+--+ + + +--+ +--+--+--+ +--+ +--+--+ +--+ +--+ + +--+ +--+ +",
"| | | | | | EE|",
"+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
};
int minsiz = 0x7f7f7f7f;
char rd[2333];
void dfs(int nx, int ny, int lx, int ly, int len)
{
if (nx >= 61 || nx < 0 || ny >= 91 || ny < 0) return;
if (mp[nx][ny] == 'E')
{
if (len <= minsiz)
{
minsiz = len;
cout << len << ": ";
for (int i = 0; i < len; ++i) putchar(rd[i]);
putchar('\n');
}
return;
}
if ((nx - 2 != lx) && mp[nx - 1][ny] == ' ') rd[len] = 'W', dfs(nx - 2, ny, nx, ny, len + 1);
if ((nx + 2 != lx) && mp[nx + 1][ny] == ' ') rd[len] = 'S', dfs(nx + 2, ny, nx, ny, len + 1);
if ((ny - 3 != ly) && mp[nx][ny - 1] == ' ') rd[len] = 'A', dfs(nx, ny - 3, nx, ny, len + 1);
if ((ny + 3 != ly) && mp[nx][ny + 2] == ' ') rd[len] = 'D', dfs(nx, ny + 3, nx, ny, len + 1);
}
int main()
{
dfs(1, 1, -10, -10, 0);
return 0;
}

记录下:

这里给出混淆脚本(-199):

BogusControlFlow.cpp

Utils.cpp

Utils.h

SplitBasicBlock.h

可以考虑用 angr 去混淆,建议的参考学习链接:https://bbs.pediy.com/thread-266005.htm

Thunder_air

中间的调用非常深怀疑是不是混淆,还加了很多反调试,好像还有花指令

image-20250923092915075

先对0EB…这一段u c可以看到生成了一段代码

再把jmp near ptr 0Fxx改成

image-20250923100451948

再把上面的jz两句nop掉

对方法ucp即可F5

image-20250923100529441

前面还有个花指令

image-20250923110904310

image-20250923110941818

这么改,把sleep那两句汇编nop掉,下面的test改成xor

image-20250923114736472

image-20250923120620859

就是把这几个checkesp和sleep和isdebugggerpresen,nop掉

提出来就行,我这里没记录了

image-20250923130100445

TSCTF-J{3nj0y_P1@Ylng_ThuNd3r_41r_B4tTle_g@m3!}

upx_revenge

image-20250923210324954

image-20250923210914379

全部改回UPX

image-20250923211914483

控制流平坦化

D810实测无法去

试试angr大法

也不太行

动调是唯一出路

tsctf-j_2022/writeup.md at main · MakeMerakGreatAgain/tsctf-j_2022

做不出来,好难…