分析
直接运行附件的 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
| int __cdecl main(int argc, const char **argv, const char **envp) { char v4; char v5; _BYTE v6[260]; int v7; char v8[128];
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; FARPROC ProcAddress; CHAR ProcName[28];
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_5E1200 和 sub_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; HANDLE CurrentProcess; int v3;
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; HANDLE CurrentProcess; int v3;
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; }
|
a1 是 NtQueryInformationProcess,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;
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
| int sub_5E1090() { uint8_t BeingDebugged;
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 了
