Featured image of post Geekchalleng2025 Her

Geekchalleng2025 Her

这题的核心是手动构建一个SEH链,自己触发异常,导致伪代码无法正确查看程序执行流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.text:00401DBD loc_401DBD:                             ; CODE XREF: sub_401D30+82j
.text:00401DBD                 mov     eax, [ebp+var_20]
.text:00401DC0                 cmp     eax, [ebp+var_8]
.text:00401DC3                 jnb     short loc_401E3E
.text:00401DC5                 mov     eax, [ebp+var_20]
.text:00401DC8                 mov     dword_40B1DC, eax
.text:00401DCD                 push    offset j_enc3
.text:00401DD2                 push    large dword ptr fs:0
.text:00401DD9                 mov     large fs:0, esp
.text:00401DE0                 push    offset j_enc2
.text:00401DE5                 push    large dword ptr fs:0
.text:00401DEC                 mov     large fs:0, esp
.text:00401DF3                 push    offset j_enc1
.text:00401DF8                 push    large dword ptr fs:0
.text:00401DFF                 mov     large fs:0, esp
.text:00401E06                 ud2

这里手动写了一个SEH链,通过ud2这个异常来触发异常处理。

这里有enc1–>enc2–>enc3

查看enc1的汇编可知

1
2
3
.text:00401F60                 push    offset j_enc2
.text:00401F65                 push    large dword ptr fs:0
.text:00401F6C                 mov     large fs:0, esp

调用enc1之后还会调用enc2

查看enc2

1
2
3
.text:004020DD                 push    offset j_enc3
.text:004020E2                 push    large dword ptr fs:0
.text:004020E9                 mov     large fs:0, esp

调用enc2之后还会调用enc3

enc3中无SEH链的构造

所有最终的加密链条是enc1–>enc2–>enc3–enc2–>enc3–>enc3

enc1中对数据的处理byte_40B2E0[dword_40B1DC] = ++dword_40B1D8 ^ Str[dword_40B1DC] ^ 0xAA;

enc2中对数据的处理byte_40B2E0[dword_40B1DC] += ++dword_40B1D8 % 0x100u + dword_40B1DC * dword_40B1DC;

enc3中队数据的处理byte_40B260[v5] = ((int)(unsigned __int8)byte_40B2E0[v5] » 5) | (8 * byte_40B2E0[v5]);

dword_40B1DC为下标,dword_40B1D8 为一个参与加密的中间量,byte_40B260是最终密文数组

唯一难搞的就是dword_40B1D8的变化,在enc1和enc2会有+1,在每次动用反调试时会有dword_40B1D8 ^= 0x1A373u,每次enc会有反调试,循环加密最开始也有一个反调试

已知是单字节加密,就可以通过前四位SYC{,来验证同构

同构如下

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

#define ANTI_DEBUG_KEY 0x1A373u  // sub_4010CD的异或值
uint32_t dword_40B1D8 = 0;       // 核心计数变量,初始为0
uint8_t byte_40B2E0[10] = {0};  // 加密中间缓冲区
uint8_t byte_40B260[10] = {0};  // 最终密文缓冲区


void sub_4010CD(void) {
    dword_40B1D8 ^= ANTI_DEBUG_KEY;
}

uint8_t encrypt_single_byte(uint8_t plain_byte, uint32_t dw) {
    // ==================== 1. enc1 ====================
    byte_40B2E0[dw] = ++dword_40B1D8 ^ plain_byte ^ 0xAA;

    // ==================== 2. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 3. enc2 ====================
    byte_40B2E0[dw] += ++dword_40B1D8 % 0x100u + (dw * dw);
    // 强制截断为8位无符号字节(匹配C语言unsigned __int8规则)
    byte_40B2E0[dw] &= 0xFF;

    // ==================== 4. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 5. enc3:仅++dword_40B1D8 ====================
    ++dword_40B1D8;

    // ==================== 6. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 7. enc3:移位运算 ====================
    byte_40B260[dw] = ((uint8_t)byte_40B2E0[dw] >> 5) | (8 * byte_40B2E0[dw]);
    byte_40B260[dw] &= 0xFF;  // 截断为8位

    // ==================== 8. 第二次enc2 ====================
    byte_40B2E0[dw] += ++dword_40B1D8 % 0x100u + (dw * dw);
    byte_40B2E0[dw] &= 0xFF;

    // ==================== 9. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 10. 第二次enc3:仅++dword_40B1D8 ====================
    ++dword_40B1D8;

    // ==================== 11. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 12. 第二次enc3:移位运算 ====================
    byte_40B260[dw] = ((uint8_t)byte_40B2E0[dw] >> 5) | (8 * byte_40B2E0[dw]);
    byte_40B260[dw] &= 0xFF;

    // ==================== 13. 第三次enc3:仅++dword_40B1D8 ====================
    ++dword_40B1D8;

    // ==================== 14. 调用sub_4010CD() ====================
    sub_4010CD();

    // ==================== 15. 第三次enc3:移位运算(最终密文) ====================
    byte_40B260[dw] = ((uint8_t)byte_40B2E0[dw] >> 5) | (8 * byte_40B2E0[dw]);
    byte_40B260[dw] &= 0xFF;

    // 返回最终密文
    return byte_40B260[dw];
}

// 测试:验证前4位明文 SYC{ → 目标密文 0x2D/0x4F/0x69/0x3D
int main() {
    // 前4位明文:S(0x53)、Y(0x59)、C(0x43)、{(0x7B)
    uint8_t plain_4[] = {0x53, 0x59, 0x43, 0x7B};
    // 目标密文(题目给出的前4位)
    uint8_t target_cipher_4[] = {0x2D, 0x4F, 0x69, 0x3D};
	sub_4010CD();
    printf("===== 单字节加密验证(SYC{ → 目标密文)=====\n");
    for (uint32_t dw = 0; dw < 4; dw++) {
        uint8_t cipher = encrypt_single_byte(plain_4[dw], dw);
        printf("下标%d | 明文:0x%02X(%c) | 加密结果:0x%02X | 目标密文:0x%02X | %s\n",
               dw, plain_4[dw], plain_4[dw], cipher, target_cipher_4[dw],
               (cipher == target_cipher_4[dw]) ? "? 匹配" : "? 不匹配");
    }

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

#define ANTI_DEBUG_KEY 0x1A373u

// 解密函数:根据密文字节数组和长度,恢复明文
void decrypt(const uint8_t* cipher, int len, uint8_t* plain) {
    uint32_t state = ANTI_DEBUG_KEY; // 初始状态,与加密前一致
    for (int dw = 0; dw < len; dw++) {
        // 记录中间状态 S1, S3, S8
        uint32_t S1, S3, S8;

        // 步骤1
        state += 1;
        S1 = state;

        // 步骤2
        state ^= ANTI_DEBUG_KEY;

        // 步骤3
        state += 1;
        S3 = state;

        // 步骤4
        state ^= ANTI_DEBUG_KEY;

        // 步骤5
        state += 1;

        // 步骤6
        state ^= ANTI_DEBUG_KEY;

        // 步骤7不改变 state

        // 步骤8
        state += 1;
        S8 = state;

        // 从密文恢复 b3(循环右移3位)
        uint8_t c = cipher[dw];
        uint8_t b3 = ((c >> 3) | ((c & 0x07) << 5)) & 0xFF;

        // 计算 b2
        uint8_t b2 = (b3 - (S8 & 0xFF) - (dw * dw)) & 0xFF;

        // 计算 b1
        uint8_t b1 = (b2 - (S3 & 0xFF) - (dw * dw)) & 0xFF;

        // 计算明文
        plain[dw] = (b1 ^ (S1 & 0xFF) ^ 0xAA) & 0xFF;

        // 继续完成当前字节的剩余状态更新(步骤9到步骤14)
        state ^= ANTI_DEBUG_KEY; // 步骤9
        state += 1;              // 步骤10
        state ^= ANTI_DEBUG_KEY; // 步骤11
        // 步骤12不改变 state
        state += 1;              // 步骤13
        state ^= ANTI_DEBUG_KEY; // 步骤14
        // 步骤15不改变 state
    }
}

int main() {
    // 已知密文(30字节)
    uint8_t cipher[31] = {
        0x2D, 0x4F, 0x69, 0x3D, 0x5F, 0x01, 0xBD, 0x9F,
        0xA4, 0x6D, 0x89, 0xAE, 0x2A, 0xEA, 0xD1, 0x9C,
        0x71, 0x6D, 0xE1, 0x1E, 0x38, 0x7E, 0x8C, 0x0A,
        0xCE, 0x6B, 0xE0, 0xF7, 0x36, 0x72, 0x99
    };

    uint8_t plain[30] = {0};

    decrypt(cipher, 31, plain);

    printf("解密结果(字符串):\n");
    for (int i = 0; i < 31; i++) {
        printf("%c", plain[i]);
    }
    printf("\n");

    printf("解密结果(十六进制):\n");
    for (int i = 0; i < 30; i++) {
        printf("0x%02X ", plain[i]);
    }
    printf("\n");

    return 0;
}

SYC{M@y_bE_m3et_HeR_1s_A_Err0R}