国城杯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();
}

关键点分析:

  1. 编码过程与标准Base64类似,但进行了位置置换
  2. 特殊处理在于i6的计算:i5 != 1 ? i5 == 2 ? 1 : i5 : 2
  3. 需要逆向这个置换过程才能正确解码

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;
}

解题方法:

  1. 使用Frida进行动态分析
  2. 采用DFS搜索可能的字符组合
  3. 关键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(自修改代码)题目,关键点:

  1. 补全环境后直接运行并附加调试
  2. 识别出XXTEA加密算法
  3. 解密脚本:
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 魔方变换部分

  1. 输入格式:使用R, U, F, L, D, B控制操作,'代表反向操作
  2. 魔方状态存储在26个class中
  3. 迷宫地图大小为27×36,每个小方格由9个int组成

3.2 迷宫行走部分

解题步骤:

  1. 不进行任何魔方操作,保持原始状态
  2. 输入UUDDRRLLFFBB得到迷宫
  3. 起点:(11,1),终点:(13,31)
  4. 手动获取路径并转换为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. 总结与技巧

  1. 逆向分析流程

    • 静态分析:IDA Pro反编译,理解程序结构
    • 动态分析:Frida/Xposed/调试器动态跟踪
    • 算法识别:常见加密算法特征识别
  2. 常见加密算法特征

    • XXTEA:使用0x61C88647作为魔数
    • Base64变种:关注字符映射表和填充方式
    • 虚拟机保护:识别指令分发器和处理函数
  3. 工具使用技巧

    • Frida动态Hook:适用于Java/Android/Native代码
    • IDA Python:自动化分析复杂逻辑
    • 调试器:OllyDbg/x64dbg/WinDbg根据场景选择
  4. 特殊题型处理

    • SMC题目:补全环境后动态调试
    • 驱动题目:关注IRP处理流程和回调函数
    • 游戏/图形题目:关注渲染逻辑和状态存储
CTF逆向题目解析与解题技巧 1. "国城杯Re方向"题目解析 1.1 用户检测部分 用户检测采用了一种变形的Base64编码方式,关键逻辑如下: 关键点分析: 编码过程与标准Base64类似,但进行了位置置换 特殊处理在于 i6 的计算: i5 != 1 ? i5 == 2 ? 1 : i5 : 2 需要逆向这个置换过程才能正确解码 1.2 密码检测部分 密码检测实现了一个虚拟机风格的验证机制: 解题方法: 使用Frida进行动态分析 采用DFS搜索可能的字符组合 关键Frida脚本部分: 2. "crush's secret"题目解析 这是一个SMC(自修改代码)题目,关键点: 补全环境后直接运行并附加调试 识别出XXTEA加密算法 解密脚本: 3. "FunMz"题目解析 这是一个魔方相关的逆向题目,分为两部分: 3.1 魔方变换部分 输入格式:使用R, U, F, L, D, B控制操作,'代表反向操作 魔方状态存储在26个class中 迷宫地图大小为27×36,每个小方格由9个int组成 3.2 迷宫行走部分 解题步骤: 不进行任何魔方操作,保持原始状态 输入 UUDDRRLLFFBB 得到迷宫 起点:(11,1),终点:(13,31) 手动获取路径并转换为hjkl指令 迷宫可视化代码: 4. "eazy_ key"驱动题目解析 这是一个键盘驱动相关的题目,关键点: 4.1 驱动入口分析 4.2 回调函数分析 关键回调函数 func_callback 中: 这些值对应键盘扫描码,需要转换为实际按键。 4.3 键盘扫描码转换 5. 总结与技巧 逆向分析流程 : 静态分析:IDA Pro反编译,理解程序结构 动态分析:Frida/Xposed/调试器动态跟踪 算法识别:常见加密算法特征识别 常见加密算法特征 : XXTEA:使用0x61C88647作为魔数 Base64变种:关注字符映射表和填充方式 虚拟机保护:识别指令分发器和处理函数 工具使用技巧 : Frida动态Hook:适用于Java/Android/Native代码 IDA Python:自动化分析复杂逻辑 调试器:OllyDbg/x64dbg/WinDbg根据场景选择 特殊题型处理 : SMC题目:补全环境后动态调试 驱动题目:关注IRP处理流程和回调函数 游戏/图形题目:关注渲染逻辑和状态存储