国城杯Re方向题解
字数 1000 2025-08-22 12:22:15
CTF逆向题目解析与解题技巧
1. "国城杯Re方向"题目解析
1.1 用户检测部分
用户检测采用了一种变形的Base64编码方式,关键逻辑如下:
public String encodeToBase64(String str) {
StringBuilder sb = new StringBuilder();
byte[] bytes = str.getBytes();
int length = (3 - (bytes.length % 3)) % 3;
int length2 = bytes.length + length;
for (int i = 0; i < length2; i += 3) {
int i2 = 0;
for (int i3 = 0; i3 < 3; i3++) {
i2 <<= 8;
int i4 = i + i3;
if (i4 < bytes.length) {
i2 |= bytes[i4] & UByte.MAX_VALUE;
}
}
int i5 = 0;
while (i5 < 4) {
int i6 = i5 != 1 ? i5 == 2 ? 1 : i5 : 2;
if (((i * 8) / 6) + i5 < ((bytes.length * 8) / 6) + length) {
sb.append(BASE64_CHARS[(i2 >> ((3 - i6) * 6)) & 63]);
} else {
sb.append('=');
}
i5++;
}
}
return sb.toString();
}
关键点分析:
- 编码过程与标准Base64类似,但进行了位置置换
- 特殊处理在于
i6的计算:i5 != 1 ? i5 == 2 ? 1 : i5 : 2 - 需要逆向这个置换过程才能正确解码
1.2 密码检测部分
密码检测实现了一个虚拟机风格的验证机制:
public boolean round(int[] iArr, String str) {
Result add;
int length = str.length();
int[] iArr2 = new int[length];
int[] iArr3 = {352, 646, 752, 882, 65, 0, 122, 0, 0, 7, 350, 360};
int i = 33;
for (int i2 = 0; i2 < str.length(); i2++) {
int charAt = str.charAt(i2);
for (int i3 = 0; i3 < 32; i3++) {
int i4 = (((iArr[i] ^ charAt) % 5) + 5) % 5;
if (i4 == 0) {
add = add(iArr, charAt, i);
} else if (i4 == 1) {
add = sub(iArr, charAt, i);
} else if (i4 == 2) {
add = xor(iArr, charAt, i);
} else if (i4 == 3) {
add = shl(charAt, i);
} else if (i4 == 4) {
add = shr(charAt, i);
} else {
add = new Result(charAt, i);
}
charAt = add.getNum();
i = add.getRip();
}
iArr2[i2] = charAt;
}
if (length != 12) {
return false;
}
for (int i5 = 0; i5 < length; i5++) {
if (iArr2[i5] != iArr3[i5]) {
return false;
}
}
return true;
}
解题方法:
- 使用Frida进行动态分析
- 采用DFS搜索可能的字符组合
- 关键Frida脚本部分:
Java.perform(function() {
var RoundClass = Java.use('com.example.demo.Round');
var roundInstance = RoundClass.$new();
const ResultClass = Java.use("com.example.demo.Round$Result");
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
var r = [352, 646, 752, 882, 65, 0, 122, 0, 0, 7, 350, 360];
function dfs(len, newFlag) {
if (len == 12) {
console.log("flag: " + newFlag);
return true;
}
var choice = [];
for (var i = 0; i < charset.length; i++) {
var test = newFlag + charset[i] + "*".repeat(12 - newFlag.length - 1);
var result = roundInstance.round(box, test);
if (r[len] == result) {
choice.push(charset[i]);
}
}
for (var i = 0; i < choice.length; i++) {
if (dfs(len + 1, newFlag + choice[i])) {
return true;
}
}
return false;
}
dfs(0, "");
});
2. "crush's secret"题目解析
这是一个SMC(自修改代码)题目,关键点:
- 补全环境后直接运行并附加调试
- 识别出XXTEA加密算法
- 解密脚本:
v10 = [0] * 12
v10[0] = 0x5A764F8A
v10[1] = 0x5B0DF77
v10[2] = 0xF101DF69
v10[3] = 0xF9C14EF4
v10[4] = 0x27F03590
v10[5] = 0x7DF3324F
v10[6] = 0x2E322D74
v10[7] = 0x8F2A09BC
v10[8] = 0xABE2A0D7
v10[9] = 0xC2A09FE
v10[10] = 0x35892BB2
v10[11] = 0x53ABBA12
v5 = [0] * 4
v5[0] = 0x5201314
v5[1] = 0x52013140
v5[2] = 0x5201314
v5[3] = 0x52013140
def dec(c, k):
num = (0 - 32 * 0x61C88647) & 0xFFFFFFFF
for i in range(32):
c[1] -= ((c[0] ^ k[(((num >> 2) & 3) ^ 1 & 3)]) + (c[0] ^ num)) ^ (((c[0] << 4) ^ (c[0] >> 3)) + ((c[0] << 2) ^ (c[0] >> 5)))
c[1] &= 0xFFFFFFFF
c[0] -= ((c[1] ^ k[(((num >> 2) & 3) ^ 0 & 3)]) + (c[1] ^ num)) ^ (((c[1] << 4) ^ (c[1] >> 3)) + ((c[1] << 2) ^ (c[1] >> 5)))
c[0] &= 0xFFFFFFFF
num = (num + 0x61C88647) & 0xFFFFFFFF
return c
flag = []
for i in range(0, len(v10), 2):
p = v10[i:i+2]
p = dec(p, v5)
flag += p
import struct
print(struct.pack("I"*12, *flag))
3. "FunMz"题目解析
这是一个魔方相关的逆向题目,分为两部分:
3.1 魔方变换部分
- 输入格式:使用R, U, F, L, D, B控制操作,'代表反向操作
- 魔方状态存储在26个class中
- 迷宫地图大小为27×36,每个小方格由9个int组成
3.2 迷宫行走部分
解题步骤:
- 不进行任何魔方操作,保持原始状态
- 输入
UUDDRRLLFFBB得到迷宫 - 起点:(11,1),终点:(13,31)
- 手动获取路径并转换为hjkl指令
迷宫可视化代码:
with open("maze", 'rb') as f:
data = f.read()
import struct
from colorama import Fore, Style
maze = struct.unpack('i'*(len(data)//4), data)
length = 36
width = 27
color_map = {
0: Fore.WHITE,
1: Fore.GREEN,
2: Fore.YELLOW,
3: Fore.BLUE,
4: Fore.MAGENTA,
5: Fore.CYAN,
6: Fore.RED,
7: Fore.BLACK,
}
for i in range(width):
for j in range(length):
value = maze[i*length + j]
color = color_map.get(value % len(color_map), Fore.RESET)
print(f"{color}{value:2X}{Style.RESET_ALL}", end=' ')
print()
4. "eazy_key"驱动题目解析
这是一个键盘驱动相关的题目,关键点:
4.1 驱动入口分析
NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = (PDRIVER_UNLOAD)unload;
DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)callback_read; // IRP_MJ_READ
// ...其他初始化...
}
4.2 回调函数分析
关键回调函数func_callback中:
v14 = [0]*50
v14[0] = 0x20
v14[1] = 0x2A
// ...其他值...
这些值对应键盘扫描码,需要转换为实际按键。
4.3 键盘扫描码转换
keyboard_scan_codes = {
'1': {'down': '2', 'up': '82'},
'2': {'down': '3', 'up': '83'},
# ...其他键位映射...
}
v14 = [0x20, 0x2A, 0xB, 0x22, 0x4, 0x2D, ...] # 原始扫描码
keyboard_dict = {}
for k, v in keyboard_scan_codes.items():
keyboard_dict[int(v['down'], 16)] = k
flag = ""
for i in range(50):
flag += keyboard_dict[v14[i]]
# 处理Shift键
while flag.find("Lshift") != -1:
idx = flag.find("Lshift")
ch = flag[idx-1]
if ch.isalpha():
flag = flag[:idx-1] + flag[idx-1].upper() + flag[idx+6:]
elif ch == "[":
flag = flag[:idx-1] + "{" + flag[idx+6:]
elif ch == "]":
flag = flag[:idx-1] + "}" + flag[idx+6:]
print(flag)
5. 总结与技巧
-
逆向分析流程:
- 静态分析:IDA Pro反编译,理解程序结构
- 动态分析:Frida/Xposed/调试器动态跟踪
- 算法识别:常见加密算法特征识别
-
常见加密算法特征:
- XXTEA:使用0x61C88647作为魔数
- Base64变种:关注字符映射表和填充方式
- 虚拟机保护:识别指令分发器和处理函数
-
工具使用技巧:
- Frida动态Hook:适用于Java/Android/Native代码
- IDA Python:自动化分析复杂逻辑
- 调试器:OllyDbg/x64dbg/WinDbg根据场景选择
-
特殊题型处理:
- SMC题目:补全环境后动态调试
- 驱动题目:关注IRP处理流程和回调函数
- 游戏/图形题目:关注渲染逻辑和状态存储