Windows reverseme – nothing tricky
July 25, 2008 1 Comment
Windows reverseme – nothing tricky, just a sort of complicated validation process. This was originally from crackmes.de, mirror of the executable here.
The easiest thing in the world is to get this thing to validate. Just run it and put a breakpoint at 00401288, and look at the value in 00406749. That’s it! You’re validated.
A lot more tricky was writing the keygen. To do it, I just stepped through the code very slowly, and duplicated the logic. ugh.
Ok, now here is my keygen of the executable. To simplify things, I just considered usernames that are 5 chacters long. Note all the mods. Longer usernames will work, but will require minor mods to the keygen (and I didn’t have the patience to step through the code again).
/************************************************************************** * keygen.c * * This crackme, while easy to break (just look at the end value) took * quite awhile to step through the key generating process, which includes * a lot of xoring with prestored constants and with itself. It's almost * like a small hash or something... * * To simplify things I only consider Usernames of 5 characters. Otherwise * the code needs to be modified slightly * * ************************************************************************/ #include <stdio.h> #include <string.h> #include <stdlib.h> void usage() { printf("Usage: keygen <5-letter-username>\n"); exit(0); } int main(int argc, char*argv[]) { if (argc != 2 || strlen(argv[1]) != 5) { usage(); } char* username = argv[1]; int i; /*initialize xorconst and xorop */ /*xorconst is stored at */ unsigned char xorconst[5]; xorconst[0] = 0xAA; xorconst[1] = 0x89; xorconst[2] = 0xC4; xorconst[3] = 0xFE; xorconst[4] = 0x46; unsigned char xorop[5]; for (i =0; i< 5; i++) { xorop[i] = 0; } /* calculate xorop, which is the value eventually stored at 0x40634a when the code finishes it's for loop at 0x401197 */ xorop[4] = 0x46; for (i = 0; i<strlen(username)-1; i++) { xorop[i%5] = (unsigned char)(xorconst[i%5] ^ username[i]); } /*start second loop */ /*initialize second xorconst */ unsigned char xorconst2[5]; xorconst2[0] = 0x78; xorconst2[1] = 0xF0; xorconst2[2] = 0xD0; xorconst2[3] = 0x03; xorconst2[4] = 0xE7; /* take xorop (and some more since we may need more than 5) and xor with the 40632D constant. To simplify, we'll only deal with 5 letter long unames for now */ unsigned char xorop2[5]; for (i = 0; i<strlen(username); i++) { xorop2[strlen(username)-i-1] = xorop[strlen(username)-i-1] ^ xorconst2[i]; } /*xorop2 now contains the string in 0x40634a at 0x004011BD though it does change more */ /*now do the third loop, starting at 0x4011d1 */ /*initialize the third xorconst */ unsigned char xorconst3[5]; xorconst3[0] = 0xF7; xorconst3[1] = 0xFD; xorconst3[2] = 0xF4; xorconst3[3] = 0xE7; xorconst3[4] = 0xB9; unsigned char xorop3[5]; for (i = 0; i<strlen(username); i++) { xorop3[i] = xorop2[i] ^ xorconst3[i]; } /* xorop3 now contains 0x40634a at the end of the third loop ~ 0x4011F0 */ /* and it appears xorop2 is stored at 0x406334 */ /*looks like there's one more loop, then some garbage */ /*the fourth loop xors xorop3[end to start] with xorconst4[starttoend] the xored result is stored in (reverse order) 40634a xorop3 is preserved at 406336 */ unsigned char xorconst4[5]; xorconst4[0] = 0xB5; xorconst4[1] = 0x1B; xorconst4[2] = 0xC9; xorconst4[3] = 0x50; xorconst4[4] = 0x73; unsigned char xorop4[5]; for (i = 0; i<5; i++) { xorop4[5-i-1] = xorop3[5-i-1] ^ xorconst4[i]; } /*xorop4 is now stored in 0040634a at the end of the fourth loop */ /*xorop3 (al) is now stored at 406336 */ /*loop 5 */ /*ecx = 406345 (which is initially 0) AND 3 bx = xorop4[i + ecx] + 00406345[i] final iteration: xorop5[0-3] = xorop4[allbutlast] xorop5[0] = xorop[0] + xorop[4] then xorop5 is stored in 406345 */ unsigned char xorop5[4]; /*first copy xorop4, but xorop5[0] = xorop4[0] + xorop4[4]*/ for (i = 0; i<4; i++) { xorop5[i] = xorop4[i]; } xorop5[0] = xorop4[0] + xorop4[4]; /*ends fifth loop xorop5 is in 406345 right before xorop4*/ unsigned int remainder; /*this could probably be done with an __asm__, but this is fine*/ unsigned int thisint = xorop5[3]*16*16*16*16*16*16 + xorop5[2]*16*16*16*16 + xorop5[1]*16*16 + xorop5[0]; char finalpass [20]; i = 0; while (thisint != 0) { remainder = (thisint % 10) + 0x30; thisint = thisint / 10; finalpass[i] = remainder; i++; } int length = i; /*finalpass is the final password, but in reverse order */ for(i=length-1; i>=0; i--) { printf("%c",finalpass[i]); } printf("\n"); return 0; }
Here is a dump of the applicable assembly instructions with comments.
0040117A > 8A0C16 MOV CL,BYTE PTR DS:[ESI+EDX] ; code to get 40634a stuff... has to do with length. 0040117D . 8AD9 MOV BL,CL 0040117F . 3298 28634000 XOR BL,BYTE PTR DS:[EAX+406328] ; xor with constant AA 89 C4 FE 46 00401185 . 40 INC EAX 00401186 . 83F8 05 CMP EAX,5 ; rehashes same 5 chars again and again 00401189 . 881C32 MOV BYTE PTR DS:[EDX+ESI],BL ; final compare value 0040118C . 8888 27634000 MOV BYTE PTR DS:[EAX+406327],CL 00401192 . 75 02 JNZ SHORT crackme.00401196 00401194 . 33C0 XOR EAX,EAX 00401196 > 46 INC ESI 00401197 . 3BF5 CMP ESI,EBP ; for i < len username 00401199 .^72 DF JB SHORT crackme.0040117A 0040119B . 33FF XOR EDI,EDI 0040119D . 33C9 XOR ECX,ECX 0040119F . 85ED TEST EBP,EBP 004011A1 . 76 26 JBE SHORT crackme.004011C9 004011A3 > 8A9F 2D634000 MOV BL,BYTE PTR DS:[EDI+40632D] 004011A9 . 8BF5 MOV ESI,EBP 004011AB . 2BF1 SUB ESI,ECX 004011AD . 4E DEC ESI 004011AE . 8A0432 MOV AL,BYTE PTR DS:[EDX+ESI] ; last character first in 40634a + srrlen? 004011B1 . 32D8 XOR BL,AL 004011B3 . 47 INC EDI 004011B4 . 881C32 MOV BYTE PTR DS:[EDX+ESI],BL 004011B7 . 8887 2C634000 MOV BYTE PTR DS:[EDI+40632C],AL 004011BD . 83FF 05 CMP EDI,5 004011C0 . 75 02 JNZ SHORT crackme.004011C4 004011C2 . 33FF XOR EDI,EDI 004011C4 > 41 INC ECX 004011C5 . 3BCD CMP ECX,EBP 004011C7 .^72 DA JB SHORT crackme.004011A3 ; end loop 004011C9 > 33F6 XOR ESI,ESI 004011CB . 33FF XOR EDI,EDI 004011CD . 85ED TEST EBP,EBP ; ebp begins as strlen? 004011CF . 76 21 JBE SHORT crackme.004011F2 004011D1 > 8A043A MOV AL,BYTE PTR DS:[EDX+EDI] ; 0040634a + i 004011D4 . 8A8E 32634000 MOV CL,BYTE PTR DS:[ESI+406332] ; 406332 constant??? 004011DA . 32C8 XOR CL,AL 004011DC . 46 INC ESI 004011DD . 880C3A MOV BYTE PTR DS:[EDX+EDI],CL 004011E0 . 8886 31634000 MOV BYTE PTR DS:[ESI+406331],AL 004011E6 . 83FE 05 CMP ESI,5 004011E9 . 75 02 JNZ SHORT crackme.004011ED 004011EB . 33F6 XOR ESI,ESI ; esi = esi % 5 004011ED > 47 INC EDI 004011EE . 3BFD CMP EDI,EBP 004011F0 .^72 DF JB SHORT crackme.004011D1 ; end loop 004011F2 > 33FF XOR EDI,EDI ; start fourth loop 004011F4 . 33C9 XOR ECX,ECX 004011F6 . 85ED TEST EBP,EBP 004011F8 . 76 26 JBE SHORT crackme.00401220 004011FA > 8A9F 37634000 MOV BL,BYTE PTR DS:[EDI+406337] 00401200 . 8BF5 MOV ESI,EBP 00401202 . 2BF1 SUB ESI,ECX 00401204 . 4E DEC ESI 00401205 . 8A0432 MOV AL,BYTE PTR DS:[EDX+ESI] 00401208 . 32D8 XOR BL,AL 0040120A . 47 INC EDI 0040120B . 881C32 MOV BYTE PTR DS:[EDX+ESI],BL 0040120E . 8887 36634000 MOV BYTE PTR DS:[EDI+406336],AL 00401214 . 83FF 05 CMP EDI,5 00401217 . 75 02 JNZ SHORT crackme.0040121B 00401219 . 33FF XOR EDI,EDI ; edi = edi%5 0040121B > 41 INC ECX 0040121C . 3BCD CMP ECX,EBP 0040121E .^72 DA JB SHORT crackme.004011FA ; end fourth loop 00401220 > 8D3D 45634000 LEA EDI,DWORD PTR DS:[406345] ; is 406345 a constant??? 00401226 . 33C0 XOR EAX,EAX ; start fifth loop 00401228 . 85ED TEST EBP,EBP 0040122A . C705 45634000 >MOV DWORD PTR DS:[406345],0 00401234 . 76 17 JBE SHORT crackme.0040124D 00401236 > 8BC8 MOV ECX,EAX 00401238 . 83E1 03 AND ECX,3 ; ecx = eax%3 0040123B . 8A1C0F MOV BL,BYTE PTR DS:[EDI+ECX] 0040123E . 8D340F LEA ESI,DWORD PTR DS:[EDI+ECX] 00401241 . 8A0C02 MOV CL,BYTE PTR DS:[EDX+EAX] 00401244 . 02D9 ADD BL,CL ; bl = bl + cl 00401246 . 40 INC EAX 00401247 . 3BC5 CMP EAX,EBP 00401249 . 881E MOV BYTE PTR DS:[ESI],BL ; eventually eax == this 0040124B .^72 E9 JB SHORT crackme.00401236 ; end fifth for loop 0040124D > 5D POP EBP 0040124E . B9 0A000000 MOV ECX,0A ; ecx = 10 00401253 . A1 45634000 MOV EAX,DWORD PTR DS:[406345] ; eax = ?? 00401258 . 33DB XOR EBX,EBX 0040125A > 33D2 XOR EDX,EDX 0040125C . F7F1 DIV ECX ; edx:eax = edx:eax/ecx 0040125E . 80C2 30 ADD DL,30 00401261 . 8893 49654000 MOV BYTE PTR DS:[EBX+406549],DL ; move char of serial here 00401267 . 43 INC EBX 00401268 . 85C0 TEST EAX,EAX 0040126A .^75 EE JNZ SHORT crackme.0040125A 0040126C . 68 49654000 PUSH crackme.00406549 ; /String = "" 00401271 . E8 86010000 CALL <JMP.&kernel32.lstrlenA> ; \lstrlenA 00401276 . 33DB XOR EBX,EBX 00401278 > 8A88 48654000 MOV CL,BYTE PTR DS:[EAX+406548] 0040127E . 888B 49674000 MOV BYTE PTR DS:[EBX+406749],CL 00401284 . 43 INC EBX 00401285 . 48 DEC EAX 00401286 .^75 F0 JNZ SHORT crackme.00401278 00401288 . 68 49674000 PUSH crackme.00406749 ; /String2 = "" 0040128D . 68 49654000 PUSH crackme.00406549 ; |String1 = crackme.00406549 00401292 . E8 5F010000 CALL <JMP.&kernel32.lstrcpyA> ; \lstrcpyA 00401297 . 68 00020000 PUSH 200 ; /Count = 200 (512.) 0040129C . 68 49694000 PUSH crackme.00406949 ; |Buffer = crackme.00406949 004012A1 . 6A 64 PUSH 64 ; |ControlID = 64 (100.) 004012A3 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd 004012A6 . E8 E5000000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA 004012AB . 68 49654000 PUSH crackme.00406549 ; /String2 = "" 004012B0 . 68 49694000 PUSH crackme.00406949 ; |String1 = "" 004012B5 . E8 36010000 CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA 004012BA . 0BC0 OR EAX,EAX 004012BC . 75 16 JNZ SHORT crackme.004012D4 004012BE . 6A 40 PUSH 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL 004012C0 . 68 DB624000 PUSH crackme.004062DB ; |Title = "Good boy..." 004012C5 . 68 AC624000 PUSH crackme.004062AC ; |Text = "Yep, thats the right code! Go write a keygen!" 004012CA . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner 004012CD . E8 CA000000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA 004012D2 . EB 14 JMP SHORT crackme.004012E8 004012D4 > 6A 10 PUSH 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL 004012D6 . 68 06634000 PUSH crackme.00406306 ; |Title = "Bad boy..." 004012DB . 68 E7624000 PUSH crackme.004062E7 ; |Text = "Nope, thats not it! Try again" 004012E0 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner 004012E3 . E8 B4000000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA
Well said, finally a good report on this stuff