RCTF 2025 Reverse Chaos2

分析

直接运行附件的 exe 打印出的 flag 是乱码,提示说找到所有的反调试就可以获取 flag
main 函数

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// positive sp value has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp-4h] [ebp-1A4h]
char v5; // [esp-4h] [ebp-1A4h]
_BYTE v6[260]; // [esp+14h] [ebp-18Ch] BYREF
int v7; // [esp+118h] [ebp-88h]
char v8[128]; // [esp+11Ch] [ebp-84h] BYREF

sub_5E1050(
"He said that if all the key modifications involved in anti-debugging are identified, the flag can be retrieved.\n",
v4);
sub_5E1510();
v7 = dword_5E4018;
EnumUILanguagesA(UILanguageEnumProc, 0, 0);
v8[0] = 15;
v8[1] = 26;
v8[2] = -118;
v8[3] = 90;
v8[4] = 34;
v8[5] = -85;
v8[6] = 30;
v8[7] = 99;
v8[8] = 25;
v8[9] = 90;
v8[10] = -121;
v8[11] = -14;
v8[12] = -26;
v8[13] = -23;
v8[14] = -41;
v8[15] = -47;
v8[16] = -105;
v8[17] = -7;
v8[18] = -8;
v8[19] = 50;
v8[20] = 91;
v8[21] = -34;
v8[22] = 45;
v8[23] = -42;
v8[24] = -93;
v8[25] = 79;
v8[26] = 126;
v8[27] = -53;
v8[28] = 97;
v8[29] = -78;
v8[30] = 63;
v8[31] = -65;
v8[32] = -73;
v8[33] = 27;
v8[34] = 10;
v8[35] = -124;
v8[36] = -77;
v8[37] = -76;
v8[38] = -34;
v8[39] = 3;
v8[40] = 70;
v8[41] = 123;
v8[42] = -125;
v8[43] = -16;
v8[44] = -60;
v8[45] = -77;
v8[46] = -85;
v8[47] = 123;
v8[48] = 41;
v8[49] = -68;
v8[50] = 31;
v8[51] = -2;
v8[52] = -118;
v8[53] = 121;
v8[54] = 38;
v8[55] = -38;
v8[56] = 8;
v8[57] = 1;
v8[58] = -123;
v8[59] = 102;
v8[60] = 125;
v8[61] = -69;
v8[62] = -18;
v8[63] = 15;
v8[64] = -119;
v8[65] = 89;
v8[66] = -44;
v8[67] = 95;
v8[68] = -84;
v8[69] = 24;
v8[70] = -82;
v8[71] = 11;
v8[72] = 78;
v8[73] = -16;
v8[74] = -73;
v8[75] = 5;
v8[76] = 92;
v8[77] = -127;
v8[78] = 4;
v8[79] = -97;
v8[80] = -92;
v8[81] = 28;
v8[82] = 93;
v8[83] = -96;
v8[84] = -71;
v8[85] = 7;
v8[86] = -110;
v8[87] = 92;
v8[88] = -118;
v8[89] = 83;
v8[90] = -13;
v8[91] = -1;
v8[92] = -9;
v8[93] = -89;
v8[94] = -35;
v8[95] = 46;
v8[96] = -26;
v8[97] = -19;
v8[98] = 15;
v8[99] = 119;
v8[100] = 44;
v8[101] = 74;
v8[102] = 34;
v8[103] = -15;
v8[104] = 54;
v8[105] = 79;
v8[106] = -89;
v8[107] = -18;
v8[108] = 13;
v8[109] = -42;
v8[110] = 4;
v8[111] = 115;
v8[112] = 85;
v8[113] = 94;
v8[114] = 62;
v8[115] = -109;
v8[116] = -92;
v8[117] = 52;
v8[118] = 41;
v8[119] = 103;
v8[120] = -4;
v8[121] = 35;
v8[122] = 121;
v8[123] = 25;
v8[124] = -40;
v8[125] = -55;
v8[126] = 43;
v8[127] = -49;
KSA(v6, dword_5E440C, 128);
PRGA(v6, v8, 128);
sub_5E1050("your flag is %s", (char)v8);
sub_5E1050((char *)&byte_5E321C, v5);
getchar();
return 0;
}

v8 是密文,然后下面这部分是假的密钥

程序有反调试,推测是反调试代码中对密钥做了修改导致打印出来的 flag 是乱码

反调试绕过

第一处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL __stdcall UILanguageEnumProc(LPSTR a1, LONG_PTR a2)
{
HMODULE hModule; // [esp+0h] [ebp-28h]
FARPROC ProcAddress; // [esp+4h] [ebp-24h]
CHAR ProcName[28]; // [esp+8h] [ebp-20h] BYREF

hModule = LoadLibraryW(L"Ntdll.dll");
strcpy(ProcName, "NtQueryInformationProcess");
ProcAddress = GetProcAddress(hModule, ProcName);
sub_5E1200((int (__stdcall *)(HANDLE, int, int *, int, _DWORD))ProcAddress);
sub_5E12A0();
sub_5E13A0((int (__stdcall *)(HANDLE, int, int *, int, _DWORD))ProcAddress);
return 0;
}

其中的 sub_5E1200sub_5E13A0

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
int __cdecl sub_5E1200(int (__stdcall *a1)(HANDLE, int, int *, int, _DWORD))
{
int result; // eax
HANDLE CurrentProcess; // [esp+Ch] [ebp-10h]
int v3; // [esp+14h] [ebp-8h] BYREF

sub_5E123D();
CurrentProcess = GetCurrentProcess();
result = a1(CurrentProcess, 7, &v3, 4, 0);
dword_5E4408 = 14;
if ( v3 )
*(_BYTE *)(dword_5E4408 + dword_5E440C) = 73;
return result;
}


int __cdecl sub_5E13A0(int (__stdcall *a1)(HANDLE, int, int *, int, _DWORD))
{
int result; // eax
HANDLE CurrentProcess; // [esp+Ch] [ebp-10h]
int v3; // [esp+14h] [ebp-8h] BYREF

sub_5E13DD();
CurrentProcess = GetCurrentProcess();
result = a1(CurrentProcess, 31, &v3, 4, 0);
dword_5E4408 = 18;
if ( v3 != 1 )
*(_BYTE *)(dword_5E4408 + dword_5E440C) = 111;
return result;
}

a1NtQueryInformationProcess,CurrentProcess 是当前进程的句柄,7 是 ProcessInformationClass 的值,它对应于 ProcessDebugPort(或 ProcessDebugObjectHandle)。
检查原理: 如果一个进程正在被调试,操作系统会为其分配一个调试端口,此时查询结果 v3 将是该端口的句柄(一个非零值);如果未被调试,v3 为 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
.text:005E13EA loc_5E13EA:                             ; CODE XREF: sub_5E13A0+3B↑j
.text:005E13EA ; DATA XREF: sub_5E13DD+6↑o
.text:005E13EA mov ebx, 22222222h
.text:005E13EF call ds:GetCurrentProcess
.text:005E13F5 mov [ebp+var_10], eax
.text:005E13F8 push 0
.text:005E13FA push 4
.text:005E13FC lea eax, [ebp+var_8]
.text:005E13FF push eax
.text:005E1400 push 1Fh
.text:005E1402 mov ecx, [ebp+var_10]
.text:005E1405 push ecx
.text:005E1406 call [ebp+arg_0]
.text:005E1409 mov dword_5E4408, 12h
.text:005E1413 cmp [ebp+var_8], 1
.text:005E1417 jnz short loc_5E141B ; Keypatch modified this from:
.text:005E1417 ; jz short loc_40141B
.text:005E1419 jmp short loc_5E142A
.text:005E141B ; ---------------------------------------------------------------------------
.text:005E141B
.text:005E141B loc_5E141B: ; CODE XREF: sub_5E13A0+77↑j
.text:005E141B mov edx, dword_5E440C
.text:005E1421 add edx, dword_5E4408
.text:005E1427 mov byte ptr [edx], 6Fh ; 'o'
.text:005E142A
.text:005E142A loc_5E142A: ; CODE XREF: sub_5E13A0+79↑j
.text:005E142A pop edi
.text:005E142B pop esi
.text:005E142C pop ebx
.text:005E142D mov ecx, [ebp+var_4]
.text:005E1430 xor ecx, ebp ; StackCookie
.text:005E1432 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:005E1437 mov esp, ebp
.text:005E1439 pop ebp
.text:005E143A retn

如果检测到调试器,就会跳到 loc_5E141B,把密钥的第 18 个字节改成 ‘o’,所以这里把 jz short loc_5E141B 改成 jnz short loc_5E141B

第二处

和第一处同理,把原来的 jz short loc_5E127B 改成 jnz short loc_5E127B

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
.text:005E123D sub_5E123D      proc near               ; CODE XREF: sub_5E1200:loc_5E1231↑p
.text:005E123D mov ebx, 11111111h
.text:005E1242 pop ebx
.text:005E1243 mov ebx, offset loc_5E124A
.text:005E1248 push ebx
.text:005E1249 retn
.text:005E1249 sub_5E123D endp
.text:005E1249
.text:005E124A ; ---------------------------------------------------------------------------
.text:005E124A ; START OF FUNCTION CHUNK FOR sub_5E1200
.text:005E124A
.text:005E124A loc_5E124A: ; CODE XREF: sub_5E1200+3B↑j
.text:005E124A ; DATA XREF: sub_5E123D+6↑o
.text:005E124A mov ebx, 22222222h
.text:005E124F call ds:GetCurrentProcess
.text:005E1255 mov [ebp+var_10], eax
.text:005E1258 push 0
.text:005E125A push 4
.text:005E125C lea eax, [ebp+var_8]
.text:005E125F push eax
.text:005E1260 push 7
.text:005E1262 mov ecx, [ebp+var_10]
.text:005E1265 push ecx
.text:005E1266 call [ebp+arg_0]
.text:005E1269 mov dword_5E4408, 0Eh
.text:005E1273 cmp [ebp+var_8], 0
.text:005E1277 jnz short loc_5E127B ; Keypatch modified this from:
.text:005E1277 ; jz short loc_40127B
.text:005E1279 jmp short loc_5E128A
.text:005E127B ; ---------------------------------------------------------------------------
.text:005E127B
.text:005E127B loc_5E127B: ; CODE XREF: sub_5E1200+77↑j
.text:005E127B mov edx, dword_5E440C
.text:005E1281 add edx, dword_5E4408
.text:005E1287 mov byte ptr [edx], 49h ; 'I'
.text:005E128A
.text:005E128A loc_5E128A: ; CODE XREF: sub_5E1200+79↑j
.text:005E128A pop edi
.text:005E128B pop esi
.text:005E128C pop ebx
.text:005E128D mov ecx, [ebp+var_4]
.text:005E1290 xor ecx, ebp ; StackCookie
.text:005E1292 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:005E1297 mov esp, ebp
.text:005E1299 pop ebp
.text:005E129A retn
.text:005E129A ; END OF FUNCTION CHUNK FOR sub_5E1200

第三处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HMODULE sub_5E12A0()
{
HMODULE result; // eax

sub_5E1300();
result = LoadLibraryW(L"Ntdll.dll");
if ( result )
{
dword_5E4408 = 17;
result = (HMODULE)GetProcAddress(result, "NtClose");
if ( result )
{
result = (HMODULE)((int (__stdcall *)(unsigned int))result)(0x99999999);
*(_BYTE *)(dword_5E4408 + dword_5E440C) = 111;
}
}
return result;
}

对应汇编

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

User
ub_401300 proc near ; CODE XREF: sub_4012A0:loc_4012F4↑p
.text:00401300 mov ebx, 11111111h
.text:00401305 pop ebx
.text:00401306 mov ebx, offset loc_40130D
.text:0040130B push ebx
.text:0040130C retn
.text:0040130C sub_401300 endp
.text:0040130C
.text:0040130D ; ---------------------------------------------------------------------------
.text:0040130D ; START OF FUNCTION CHUNK FOR sub_4012A0
.text:0040130D
.text:0040130D loc_40130D: ; CODE XREF: sub_4012A0+5E↑j
.text:0040130D ; DATA XREF: sub_401300+6↑o
.text:0040130D ; __unwind { // __except_handler4
.text:0040130D mov ebx, 22222222h
.text:00401312 push offset aNtdllDll_0 ; "Ntdll.dll"
.text:00401317 call ds:LoadLibraryW
.text:0040131D mov [ebp+hModule], eax
.text:00401320 cmp [ebp+hModule], 0
.text:00401324 jnz short loc_401328
.text:00401326 jmp short loc_401383
.text:00401328 ; ---------------------------------------------------------------------------
.text:00401328
.text:00401328 loc_401328: ; CODE XREF: sub_4012A0+84↑j
.text:00401328 mov dword_404408, 11h
.text:00401332 push offset ProcName ; "NtClose"
.text:00401337 mov eax, [ebp+hModule]
.text:0040133A push eax ; hModule
.text:0040133B call ds:GetProcAddress
.text:00401341 mov [ebp+var_28], eax
.text:00401344 cmp [ebp+var_28], 0
.text:00401348 jnz short loc_40134C
.text:0040134A jmp short loc_401383
.text:0040134C ; ---------------------------------------------------------------------------
.text:0040134C
.text:0040134C loc_40134C: ; CODE XREF: sub_4012A0+A8↑j
.text:0040134C ; __try { // __except at loc_40136A
.text:0040134C mov [ebp+ms_exc.registration.TryLevel], 0
.text:00401353 push 99999999h
.text:00401358 call [ebp+var_28]
.text:00401358 ; } // starts at 40134C
.text:0040135B mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00401362 jmp short loc_401383
.text:00401364 ; ---------------------------------------------------------------------------
.text:00401364
.text:00401364 loc_401364: ; DATA XREF: .rdata:stru_403680↓o
.text:00401364 ; __except filter // owned by 40134C
.text:00401364 mov eax, 1
.text:00401369 retn
.text:0040136A ; ---------------------------------------------------------------------------
.text:0040136A
.text:0040136A loc_40136A: ; DATA XREF: .rdata:stru_403680↓o
.text:0040136A ; __except(loc_401364) // owned by 40134C
.text:0040136A mov esp, [ebp+ms_exc.old_esp]
.text:0040136D mov ecx, dword_40440C
.text:00401373 add ecx, dword_404408
.text:00401379 mov byte ptr [ecx], 6Fh ; 'o'
.text:0040137C mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00401383
.text:00401383 loc_401383: ; CODE XREF: sub_4012A0+86↑j
.text:00401383 ; sub_4012A0+AA↑j ...
.text:00401383 mov ecx, [ebp+ms_exc.registration.Next]
.text:00401386 mov large fs:0, ecx
.text:0040138D pop ecx
.text:0040138E pop edi
.text:0040138F pop esi
.text:00401390 pop ebx
.text:00401391 mov ecx, [ebp+var_1C]
.text:00401394 xor ecx, ebp ; StackCookie
.text:00401396 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:0040139B mov esp, ebp
.text:0040139D pop ebp
.text:0040139E retn
.text:0040139E ; } // starts at 40130D

NtClose 是一个 NT API,用于关闭内核对象句柄,当程序尝试关闭一个无效、未被分配或已被关闭的句柄时(比如这里的 99999999h),NtClose 函数会抛出一个异常(通常是 STATUS_INVALID_HANDLE 或类似的错误码。
在非调试环境下: 这个异常会被程序自身的 SEH 机制捕获,程序流跳转到 __except 块(loc_40136A)。
在调试环境下: 调试器会首先捕获这个异常。如果调试器配置为“中断”而不是“忽略”该特定异常,程序将暂停,程序流不会进入 __except 块。
我这里是直接把 jmp short loc_401383 改成 jmp short loc_40136A

第四处

1
2
3
4
5
6
7
8
9
10
11
12
13
// positive sp value has been detected, the output may be wrong!
int sub_5E1090()
{
uint8_t BeingDebugged; // [esp+13h] [ebp-5h]

sub_5E10CD();
BeingDebugged = NtCurrentPeb()->BeingDebugged;
sub_5E1440();
dword_5E4408 = 8;
if ( BeingDebugged )
*(_BYTE *)(dword_5E4408 + dword_5E440C) = 105;
return 6164652;
}

对应汇编

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
.text:005E10DA loc_5E10DA:                             ; CODE XREF: sub_5E1090+3B↑j
.text:005E10DA ; DATA XREF: sub_5E10CD+6↑o
.text:005E10DA mov ebx, 22222222h
.text:005E10DF mov [ebp+var_5], 1
.text:005E10E3 push eax
.text:005E10E4 mov eax, large fs:30h
.text:005E10EA mov al, [eax+2]
.text:005E10ED mov [ebp+var_5], al
.text:005E10F0 pop eax
.text:005E10F1 call sub_5E1440
.text:005E10F6 mov dword_5E4408, 8
.text:005E1100 movzx eax, [ebp+var_5]
.text:005E1104 test eax, eax
.text:005E1106 jnz short loc_5E110A ; Keypatch modified this from:
.text:005E1106 ; jz short loc_40110A
.text:005E1108 jmp short loc_5E1119
.text:005E110A ; ---------------------------------------------------------------------------
.text:005E110A
.text:005E110A loc_5E110A: ; CODE XREF: sub_5E1090+76↑j
.text:005E110A mov ecx, dword_5E440C
.text:005E1110 add ecx, dword_5E4408
.text:005E1116 mov byte ptr [ecx], 69h ; 'i'
.text:005E1119
.text:005E1119 loc_5E1119: ; CODE XREF: sub_5E1090+78↑j
.text:005E1119 mov eax, [ebp+var_C]
.text:005E111C pop edi
.text:005E111D pop esi
.text:005E111E pop ebx
.text:005E111F mov ecx, [ebp+var_4]
.text:005E1122 xor ecx, ebp ; StackCookie
.text:005E1124 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:005E1129 mov esp, ebp
.text:005E112B pop ebp
.text:005E112C retn

BeingDebugged = NtCurrentPeb()->BeingDebugged; 是核心操作,
当一个进程被调试器附加或启动时,操作系统内核会修改当前进程 PEB 结构中的 BeingDebugged 字段,如果进程正在被调试,这个字段的值通常是 1(True)。如果进程没有被调试,这个字段的值是 0(False)。

所以这里就把 jz short loc_5E110A 改成 jnz short loc_5E110A

获取 flag

把上面四处全部 patch 之后再动调就可以直接出 flag 了


RCTF 2025 Reverse Chaos2
http://example.com/2025/11/19/RCTF2025/
作者
Eleven
发布于
2025年11月19日
许可协议