【SWPUCTF2019】 easyRE 详细分析过程及解题脚本
字数 427 2025-08-29 08:30:36

SWPUCTF2019 easyRE 逆向分析详解

1. 程序概述

这是一个包含反调试保护和复杂加密逻辑的逆向工程挑战题,要求通过逆向分析找到正确的flag格式和内容。

2. 主要函数分析

2.1 主函数结构

int __cdecl main(int argc, const char **argv, const char **envp) {
    _DWORD v4[28]; 
    _DWORD *v5; 
    _DWORD *v6; 
    int v7; 
    char v8[108]; 
    int v9; 
    
    if (sub_40EF90())  // 反调试检测
        return 1;
    
    sub_4026C0(0x6Cu);
    sub_401FE0(v4[27], v5);  // 初始化虚函数表和关键数据
    v9 = 0;
    v6 = v4;
    sub_40F360(v8);
    sub_40F080(v4[0], v4[1]);
    v5 = v4;
    sub_40F360(v8);
    sub_40F150(argc, (int)argv);  // 主要处理函数
    v7 = 0;
    v9 = -1;
    sub_4021C0(v8);
    return v7;
}

2.2 反调试检测函数

BOOL sub_40EF90() {
    HANDLE v0; 
    NTSTATUS (__stdcall *NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
    HMODULE hModule; 
    int v4; 
    
    v4 = 0;
    hModule = LoadLibraryA("Ntdll.dll");
    NtQueryInformationProcess = (NTSTATUS (__stdcall *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG))GetProcAddress(hModule, "NtQueryInformationProcess");
    v0 = GetCurrentProcess();
    NtQueryInformationProcess(v0, ProcessDebugPort, &v4, 4, 0);
    return v4 != 0;
}

绕过方法

  1. 直接nop掉检测后的跳转指令
  2. 先运行程序,再用调试器附加

2.3 初始化函数

_DWORD *__thiscall sub_401FE0(_DWORD *this) {
    int i; 
    
    *this = &EASYRE::`vftable';  // 设置虚函数表
    this[1] = 0;
    
    // 初始化第一组比较数据(40字节)
    *((_BYTE *)this + 52) = 8;
    *((_BYTE *)this + 53) = 0xEA;
    *((_BYTE *)this + 54) = 0x58;
    *((_BYTE *)this + 55) = 0xDE;
    *((_BYTE *)this + 56) = 0x94;
    *((_BYTE *)this + 57) = 0xD0;
    *((_BYTE *)this + 58) = 0x3B;
    *((_BYTE *)this + 59) = 0xBE;
    *((_BYTE *)this + 60) = 0x88;
    *((_BYTE *)this + 61) = 0xD4;
    *((_BYTE *)this + 62) = 0x32;
    *((_BYTE *)this + 63) = 0xB6;
    *((_BYTE *)this + 64) = 0x14;
    *((_BYTE *)this + 65) = 0x82;
    *((_BYTE *)this + 66) = 0xB7;
    *((_BYTE *)this + 67) = 0xAF;
    *((_BYTE *)this + 68) = 0x14;
    *((_BYTE *)this + 69) = 0x54;
    *((_BYTE *)this + 70) = 0x7F;
    *((_BYTE *)this + 71) = 0xCF;
    
    // 初始化第二组比较数据(20字节)
    qmemcpy(this + 18, "03\"3 0 203\"$", 20);
    
    sub_4030A0(this + 23);
    sub_402DE0(this + 26);
    
    for (i = 0; i < 40; ++i)
        *((_BYTE *)this + i + 12) = 0;
    
    return this;
}

2.4 主要处理函数

int sub_40F150(int a1, int a2, ...) {
    va_list va;
    va_start(va, a2);
    
    sub_40F5B0(std::cout, "Please input your flag : ");
    std::ostream::operator<<(v2, sub_40F8F0);
    sub_40F930(std::cin, flag);
    
    if (sub_4024B0(flag)) {  // 关键校验函数
        sub_40F5B0(std::cout, &unk_4122F0);  // "Congratulations"
        result = 1;
    } else {
        sub_40F5B0(std::cout, &unk_41231C);  // "I'm sorry"
        result = 0;
    }
    
    return result;
}

2.5 关键校验函数

BOOL __thiscall sub_4024B0(_DWORD *this, int flag) {
    this[2] = flag;
    result = 0;
    
    if (sub_402500(flag)) {  // 校验flag格式
        sub_4026E0();        // 处理flag
        if (sub_402A00())    // 比较结果
            result = 1;
    }
    
    return result;
}

3. Flag格式校验

通过分析sub_402500函数,发现flag格式为:

^swpuctf\{\w{4}\-\w{4}\-\w{4}\-\w{4}\-\w{4}\}$

即:swpuctf{xxxx-xxxx-xxxx-xxxx-xxxx}

4. 核心加密逻辑

4.1 字符处理函数

int __thiscall sub_402730(_DWORD *this, int a2) {
    // 获取flag的4个字符部分
    v10 = this[2] + 5 * a2 + 8;
    
    // 对每个字符进行处理
    for (i = 0; i < 4; ++i) {
        // 计算左移进位次数
        v4 = *((_BYTE *)&v13 + v2);
        _DL = v4;
        __asm { rcl dl, 1 }  // 循环左移直到进位
        
        // 计算右边0的个数
        do {
            v6 = v4 & 1;
            v4 = (v4 >> 1) | v8;
            ++v7;
        } while (v6);
        
        // 计算结果
        res1 = 右边0的个数 + 左移进位次数;
        res2 = (flag << 左移进位次数) & 0xff >> res1;
        res3 = (flag << (8 - 0的个数)) | ((flag >> (8 - 左移次数)) << 左移次数);
    }
    
    return sub_402F80(&v13);
}

4.2 关键比较函数

int __thiscall sub_402A00(unsigned __int8 *this) {
    for (i = 0; i < 40; ++i) {
        if (this[i + 52] != this[i + 12])
            return 0;
    }
    return 1;
}

5. 解题思路

  1. 分析加密过程

    • 每个字符独立处理,不受前后字符影响
    • 对每个字符进行位移运算,生成3个结果
    • 通过5次循环处理,每次处理4个字符
  2. 逆向算法

    • 根据已知的比较数据逆向推导字符
    • 使用暴力破解方法尝试可能的字符组合

6. 解题脚本

import string

# 计算左移进位次数
def check_1(c):
    num = 0
    while True:
        c = c << 1
        num += 1
        if c & 0x100:
            return num

# 计算右边0的个数
def check_0(c):
    num = 0
    while True:
        if c & 1:
            return num
        num += 1
        c = c >> 1

# 生成三个结果
def generate_0(c):
    res1 = check_0(c) + check_1(c)
    res2 = ((c << check_1(c)) & 0xff) >> res1
    res3 = ((c >> (8 - check_1(c))) << check_1(c)) | ((c << (8 - check_0(c))) & 0xff) >> res1
    return [res1, res2, res3]

# 分类可能字符
def classify():
    for_each = string.ascii_lowercase + string.ascii_uppercase + string.digits
    second_part_res = '03"3 0 203"$'
    res = {}
    d = dict.fromkeys(list(set(second_part_res)))
    
    for i in list(set(second_part_res)):
        d[i] = []
    
    for i in for_each:
        tmp = check_part(ord(i))
        if tmp:
            d[tmp].append(i)
    
    return d

# 检查字符是否符合第二部分
def check_part(c):
    tmp = list(set('03"3 0 203"$'))
    tmp2 = check_0(c) | (16 * check_1(c))
    for i in tmp:
        if tmp2 == ord(i):
            return i
    return ''

# 测试第一部分
def test_1(c, v14):
    exam = {c: generate_0(ord(c))}
    v14 = v14 - (8 - exam[c][0])
    tmp = exam[c][1] << v14
    return tmp, v14

# 测试第二部分
def test_2(c, v14):
    exam = {c: generate_0(ord(c))}
    v14 = v14 - exam[c][0]
    tmp = exam[c][2] << v14
    return tmp, v14

# 检查第一部分
def check_first_part(second_part, first_part):
    for i in d[second_part[0]]:
        for j in d[second_part[1]]:
            for k in d[second_part[2]]:
                for m in d[second_part[3]]:
                    v14 = 0x20
                    tmp, v14 = test_1(i, v14)
                    tmp2, v14 = test_1(j, v14)
                    tmp3, v14 = test_1(k, v14)
                    tmp4, v14 = test_1(m, v14)
                    tmp5, v14 = test_2(i, v14)
                    tmp6, v14 = test_2(j, v14)
                    tmp7, v14 = test_2(k, v14)
                    tmp8, v14 = test_2(m, v14)
                    tmp = tmp | tmp2 | tmp3 | tmp4 | tmp5 | tmp6 | tmp7 | tmp8
                    if tmp == first_part:
                        return i + j + k + m

d = classify()
s2 = '03"3 0 203"$'
s = ['08', 'EA', '58', 'DE', '94', 'D0', '3B', 'BE', '88', 'D4', '32', 'B6', '14', '82', 'B7', 'AF', '14', '54', '7F', 'CF']

flag = 'swpuctf{'
for i in range(0, 5):
    first_part = int(s[3 + 4 * i] + s[2 + 4 * i] + s[1 + 4 * i] + s[4 * i], 16)
    second_part = s2[i * 4 : i * 4 + 4]
    res = check_first_part(second_part, first_part)
    if i == 4:
        flag += res
        break
    flag += res + '-'
flag += '}'

print("Flag:", flag)

7. 最终Flag

swpuctf{we18-l8co-m1e4-58to-swpu}
SWPUCTF2019 easyRE 逆向分析详解 1. 程序概述 这是一个包含反调试保护和复杂加密逻辑的逆向工程挑战题,要求通过逆向分析找到正确的flag格式和内容。 2. 主要函数分析 2.1 主函数结构 2.2 反调试检测函数 绕过方法 : 直接nop掉检测后的跳转指令 先运行程序,再用调试器附加 2.3 初始化函数 2.4 主要处理函数 2.5 关键校验函数 3. Flag格式校验 通过分析 sub_402500 函数,发现flag格式为: 即: swpuctf{xxxx-xxxx-xxxx-xxxx-xxxx} 4. 核心加密逻辑 4.1 字符处理函数 4.2 关键比较函数 5. 解题思路 分析加密过程 : 每个字符独立处理,不受前后字符影响 对每个字符进行位移运算,生成3个结果 通过5次循环处理,每次处理4个字符 逆向算法 : 根据已知的比较数据逆向推导字符 使用暴力破解方法尝试可能的字符组合 6. 解题脚本 7. 最终Flag