L ZZZZZZ RRRRR SSSSS L Z R R S L aaa Z aaa R R u u S L a Z a RRRRR u u SSSSS XX L aaaa Z aaaa R R u u S XXXX L a a Z a a R R u u S XXXXXX LLLLLLL aaaaa ZZZZZZZ aaaaa R R uuuuu SSSSSS XXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXX XXXXXX XXXX proudly presents his 38.Cracking Tutorial (23.03.2000) XX Unicorn's Reversing Contest I I. Introduction II. The essay III. BTW I. Introduction So, one more essay from me. This time I deal with Unicorn's Reversing Contest I which can be found at http://www.unicorn.f2s.com - This is the solution to the first stage. There are only few tools we need to crack this one. As always SoftIce is our first choice debugger (mine at least ;). Additionaly you should have FrogSice to find debugger detections because there are not only the standard CreateFile ones, but two more which ain't that easy to detect using just SICE. I'll show you a way later, but if you use FrogSice you shouldn't have much problems. All tools can be found at protools.cjb.net. MASM and Borland C++ Builder could be of some use, too, because we have to brute-force a little at the end of the essay. To create a good atmosphere in your room you should hear some good metal. For today's essay I switched from my standard bands I listen to when I write tuts (Eisregen/Blind Guardian) to Mystic Circle and their song "Dragonslayer" from the CD "Drachenblut" which deals with the German national epic ("Die Nibelungensage"). Ask me on IRC if you want to know more (I'm happy for everyone that's interested in nordic legends) about this epic as it's really interesting. (When you are German, *don't* ask me about more, but feel ashamed that you don't know it) So, let's start with the essay now as that's the reason you read those lines (you really should read the German national epic, too ;) II. The essay When I first started this crackme I found out that it's quiet simple. A small window with an edit field and a "Register" button. Very nice, no useless crap that are used to hide that the key algo is lame like in other crackmes. Actually, the key algo ain't that lame, though :] - OK, the EXE is 95KB big. Too big for ASM/Win32C and VB, too small for Delphi and BCB - It must be packed then ;) Using Procdump I unpacked it ("Full Dump" option) and disassembled it using W32Dasm. I found the nice strings "\\.\NTICE", "\\.\SICE" and "\\.\SIVWID" which are used for SICE detection. So I closed the crackme, fired up FrogSice and found that there was one more check for a debugger: The int68 trick which was (with several other ones) described by stone once. Alright, to find out what happens when the debugger was found you should allow the crackme to find the debugger. Enter a serial - I choose 666999 - and enter SICE to set a bpx on hmemcpy. When SICE breaks hit F12 until you reach the address :00429980. This is the line where we start tracing from. :00429980 8B9530FEFFFF mov edx, dword ptr [ebp+FFFFFE30] ; EDX POINTS TO SERIAL :00429986 B83CB74200 mov eax, 0042B73C :0042998B E8089CFDFF call 00403598 :00429990 EB04 jmp 00429996 :00429992 203B and byte ptr [ebx], bh :00429994 2920 sub dword ptr [eax], esp * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00429990(U) | :00429996 BB50C30000 mov ebx, 0000C350 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004299B1(C) | :0042999B 8B07 mov eax, dword ptr [edi] ;; ------------ :0042999D E87AACFFFF call 0042461C ;; | THIS LOOP IS EXECUTED OFTEN :004299A2 8B07 mov eax, dword ptr [edi] ;; | :004299A4 E873ACFFFF call 0042461C ;; | :004299A9 8B07 mov eax, dword ptr [edi] ;; | :004299AB E86CACFFFF call 0042461C ;; | :004299B0 4B dec ebx ;; | BUT I NEVER RESEARCHED WHAT :004299B1 75E8 jne 0042999B ;; | IT DOES :004299B3 803D32B7420000 cmp byte ptr [0042B732], 00 ;; THIS IS A DEBUGGER?-FLAG :004299BA 7416 je 004299D2 ;; IF NO DEBUGGER PRESENT, THEN JUMP :004299BC B83CB74200 mov eax, 0042B73C ;; IF A DEBUGGER IS PRESENT :004299C1 E8CA9FFDFF call 00403990 ;; THE FIFTH CHAR OF THE SERIAL :004299C6 8B153CB74200 mov eax, 0042B73C ;; WILL BE SET TO THE VALUE OF THE :004299CC 8A5201 mov dl, byte ptr [edx+01] ;; SECOND CHAR :004299CF 885005 mov byte ptr [eax+05], dl ;; EXAMPLE: 123456 -> 123452 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004299BA(C) | :004299D2 A13CB74200 mov eax, dword ptr [0042B73C] ;; EAX POINTS TO SERIAL :004299D7 E8E49DFDFF call 004037C0 ;; THIS CALL GETS THE LENGTH OF THE SERIAL :004299DC 83F80B cmp eax, 0000000B ;; IS IT 0B? Here I changed my serial to "66699966699" :004299DF 0F85B5010000 jne 00429B9A ;; IF NOT, THEN JUMP TO "BAD SERIAL" :004299E5 A13CB74200 mov eax, dword ptr [0042B73C] ;; EAX POINTS TO SERIAL Here I changed my serial to "6-699666699" because I did not yet know that it is a result of the debugger check that sets the 6th char to the same value as the 2nd one. If I'd known the serial would be "66699-66699" and the debugger-flag would be set to 0 (no debugger found). Let's go on with "6-699666699", though. :004299EA 8078052D cmp byte ptr [eax+05], 2D ;; 6th CHAR = "-" ? :004299EE 0F85A6010000 jne 00429B9A ;; IF NOT, THEN JUMP TO "BAD SERIAL" :004299F4 A13CB74200 mov eax, dword ptr [0042B73C] ;; EAX POINTS TO SERIAL :004299F9 E8C29DFDFF call 004037C0 ;; RETURNS THE LENGTH OF THE SERIAL :004299FE 8B153CB74200 mov edx, dword ptr [0042B73C] ;; POINTS TO SERIAL :00429A04 8A4402FE mov al, byte ptr [edx+eax-02] ;; AL = 2ND LAST CHAR :00429A08 8B153CB74200 mov edx, dword ptr [0042B73C] ;; POINTS TO SERIAL :00429A0E 3A4202 cmp al, byte ptr [edx+02] ;; COMPARES WITH 3RD CHAR :00429A11 0F8583010000 jne 00429B9A ;; IF NOT SAME, JUMP TO "BAD SERIAL" Here I changed my serial to "6-699666669" to make the 3rd and the 10th char the same value. :00429A17 8D952CFEFFFF lea edx, dword ptr [ebp+FFFFFE2C] :00429A1D 8B86E8010000 mov eax, dword ptr [esi+000001E8] :00429A23 E8F8F0FFFF call 00428B20 :00429A28 8B952CFEFFFF mov edx, dword ptr [ebp+FFFFFE2C] ; EDX POINTS TO A FILE ; IN THE WIN\TEMP DIR :00429A2E 8D8534FEFFFF lea eax, dword ptr [ebp+FFFFFE34] :00429A34 E8F1ADFDFF call 0040482A :00429A39 8D8534FEFFFF lea eax, dword ptr [ebp+FFFFFE34] :00429A3F E867B0FDFF call 00404AAB :00429A44 BA40B74200 mov edx, 0042B740 :00429A49 8D8534FEFFFF lea eax, dword ptr [ebp+FFFFFE34] :00429A4F E8CCA0FDFF call 00403B20 :00429A54 8D8534FEFFFF lea eax, dword ptr [ebp+FFFFFE34] :00429A5A E86DAEFDFF call 004048CC :00429A5F B83CB74200 mov eax, 0042B73C :00429A64 B906000000 mov ecx, 00000006 :00429A69 BA06000000 mov edx, 00000006 :00429A6E E8919FFDFF call 00403A40 :00429A73 6A1D push 1D :00429A75 8D852CFEFFFF lea eax, dword ptr [ebp+FFFFFE2C] :00429A7B 50 push eax :00429A7C A13CB74200 mov eax, dword ptr [0042B73C];PTS TO 1ST 5 CHARS OF SERIAL :00429A81 E8B6CCFDFF call 0040673C ;; WITH MY SERIAL I GET "INVALID INTEGER" Here I found out that the jump at :004299BA is in fact a debugger check. The serial must have "-" as fifth char, but the fifth char is overwritten with the second one which must be "-" though. But this call converts the first five chars of the serial to an integer and prolly fails when trying to convert "-". So I restarted the crackme and failed all debugger detections with FrogSice. In fact it would be better to change the flag at [0042B732] because the crackme might use SICE detections that are not recognized by FrogSice. In fact it seems to detect SICE in a way FrogSice can't detect. A "bpm 42B732 rw" should solve the riddle, but I just changed the flag to 0. I changed my serial to "12345-33333", too. Now the call is taken without any "Invalid Integer value" msgbox and EAX will hold the value of the first 5 chars, in my case 3039h. Be prepared, the fun part starts now ;) :00429A86 8BD0 mov edx, eax ;; EDX = EAX (=3039h) :00429A88 B904000000 mov ecx, 00000004 :00429A8D A140B74200 mov eax, dword ptr [0042B740] ;; POINTS TO ENCRYPTED DATA :00429A92 E895F5FFFF call 0042902C ;; THIS CALL DECRYPTS THE DATA Here is the complete call and what it does: Data that is going to be encrpyted: 00000000 578C ABB9 AAD2 35E8 77D7 1E7B 8A62 87D8 W.....5.w..{.b.. 00000010 A7F9 331F 91F1 326C 04C3 FA56 6F6D 7D1C ..3...2l...Vom}. ------------------------------------------------------------------------------------- * Referenced by a CALL at Address: |:00429A92 | :0042902C 55 push ebp :0042902D 8BEC mov ebp, esp :0042902F 83C4F4 add esp, FFFFFFF4 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00428FCB(C) | :00429032 53 push ebx :00429033 56 push esi :00429034 57 push edi :00429035 33DB xor ebx, ebx :00429037 895DF4 mov dword ptr [ebp-0C], ebx :0042903A 894DFC mov dword ptr [ebp-04], ecx :0042903D 8BF2 mov esi, edx :0042903F 8BF8 mov edi, eax ;; DATA THAT IS GOING TO BE ENCRYPTED :00429041 33C0 xor eax, eax :00429043 55 push ebp :00429044 68BA904200 push 004290BA :00429049 64FF30 push dword ptr fs:[eax] :0042904C 648920 mov dword ptr fs:[eax], esp :0042904F 8B4508 mov eax, dword ptr [ebp+08] :00429052 E8EDA4FDFF call 00403544 :00429057 8BC7 mov eax, edi ;; DATA THAT IS GOING TO BE ENCRYPTED :00429059 E862A7FDFF call 004037C0 ;; GET LENGTH OF DATA :0042905E 84C0 test al, al ;; IS LENGTH 0? :00429060 7642 jbe 004290A4 ;; IF BELOW OR EQUAL, THEN JUMP :00429062 8845FB mov byte ptr [ebp-05], al :00429065 B301 mov bl, 01 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004290A2(C) ;; THIS IS THE LOOP WHICH ENCRYPTS THE DATA (USED 20 TIMES (=FOR EACH BYTE)) | ;; THERE IS A BIT OF CALCULATION, BUT THE IMPORTANT XOR IS AT 429077 :00429067 8D45F4 lea eax, dword ptr [ebp-0C] :0042906A 33D2 xor edx, edx :0042906C 8AD3 mov dl, bl :0042906E 8A5417FF mov dl, byte ptr [edi+edx-01] :00429072 8BCE mov ecx, esi :00429074 C1E908 shr ecx, 08 :00429077 32D1 xor dl, cl ;; THIS IS THE IMPORTANT PART :00429079 E86AA6FDFF call 004036E8 ;; THOSE CALLS JUST WRITE :0042907E 8B55F4 mov edx, dword ptr [ebp-0C] ;; THE XORED BYTE :00429081 8B4508 mov eax, dword ptr [ebp+08] ;; TO A :00429084 E83FA7FDFF call 004037C8 ;; BUFFER :00429089 8B4508 mov eax, dword ptr [ebp+08] :0042908C 33C0 xor eax, eax :0042908E 8AC3 mov al, bl :00429090 0FB64407FF movzx eax, byte ptr [edi+eax-01] ;; GET CHAR FROM ENC.DATA :00429095 03F0 add esi, eax ;; SOME CALCULATIONS :00429097 0FAF75FC imul esi, dword ptr [ebp-04] ;; THAT ARE :0042909B 03750C add esi, dword ptr [ebp+0C] ;; USED FOR THE NEXT CHAR :0042909E 43 inc ebx :0042909F FE4DFB dec [ebp-05] ;; DECREASE LOOP VARIABLE :004290A2 75C3 jne 00429067 ;; IF STILL BYTES TO COME, JUMP * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00429060(C) | :004290A4 33C0 xor eax, eax :004290A6 5A pop edx :004290A7 59 pop ecx :004290A8 59 pop ecx :004290A9 648910 mov dword ptr fs:[eax], edx * Possible StringData Ref from Code Obj ->"_^[" | :004290AC 68C1904200 push 004290C1 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004290BF(U) | :004290B1 8D45F4 lea eax, dword ptr [ebp-0C] :004290B4 E88BA4FDFF call 00403544 :004290B9 C3 ret ------------------------------------------------------------------------------------- So, after the decryption was finished, we return to the main prog. :00429A97 8B952CFEFFFF mov edx, dword ptr [ebp+FFFFFE2C] ;; POINTS TO DECR. DATA :00429A9D B840B74200 mov eax, 0042B740 :00429AA2 E8F19AFDFF call 00403598 :00429AA7 A140B74200 mov eax, dword ptr [0042B740] ;; POINTS TO DECRYPT. DATA :00429AAC 8A4001 mov al, byte ptr [eax+01] ;; GET SECOND BYTE OF DATA :00429AAF 8B153CB74200 mov edx, dword ptr [0042B73C] ;POINT TO 1ST PART OF SERIAL :00429AB5 3A02 cmp al, byte ptr [edx] ;; CMP 1ST CHAR OF SERIAL WITH ;; 2ND CHAR OF DECRYPTED DATA :00429AB7 0F85CE000000 jne 00429B8B ;; IF NOT THE SAME, "BAD SERIAL" Oh, oh - The decryption is serial dependent (better: depends on the first 5 chars of the serial you entered). We have to find a serial where the 1st char of it equals the 2nd char of the encrypted data. The 2nd char must be 0-9 (=30h-39h) then. That should ease process of finding it. I have to admit that I coded a small brute forcer for this. I'm sure that you can find a serial using only maths, but don't expect any mathmathical tricks from me ;) The (main part of the) source is here (MASM): .code start: .WHILE counter>0 mov esi, counter mov counter2, 32 xor ebx, ebx mov bl, 01 mov edi, OFFSET UnEncrypted mov ebp, OFFSET buffer LOOP_IT: xor edx, edx mov dl, bl mov dl, byte ptr [edx+edi-01] mov ecx, esi shr ecx, 8 xor dl, cl mov byte ptr [ebx+ebp-1], dl xor eax, eax mov al, bl movzx eax, byte ptr [eax+edi-1] add esi, eax imul esi, multi add esi, addi inc ebx dec counter2 jne LOOP_IT dec counter mov dl, [ebp+2] mov edi, OFFSET hope add edi, counter mov byte ptr [edi], dl .IF byte ptr [ebp+1] == 31h invoke wsprintf, ADDR buffer2, ADDR sprint, counter .IF byte ptr [buffer2]==31h int 03 .ENDIF .ENDIF .ENDW Set a "bpint 03" in SICE and SICE will break when it found a serial which decrypts the data so that the 2nd char will be a "1". The serial which was used to decrypt the data can be found in [buffer] or counter. With the brute force attack you can get the following - 384 - valid serials for this check: 1st Digit Serial 0 - 1 12002-12065 2 28450-28513 3 - 4 44450-44513 5 - 6 60962-61025 7 77410-77473 8 - 9 93410-93473 Let's go on with 28508-55555: :00429ABD 8D8528FEFFFF lea eax, dword ptr [ebp+FFFFFE28] :00429AC3 8B1540B74200 mov edx, dword ptr [0042B740] ;; DECRYPTED DATA :00429AC9 8A5205 mov dl, byte ptr [edx+05] ;; DL = 6TH CHAR OF ENC. DATA :00429ACC E8179CFDFF call 004036E8 :00429AD1 8B8528FEFFFF mov eax, dword ptr [ebp+FFFFFE28] :00429AD7 8D952CFEFFFF lea edx, dword ptr [ebp+FFFFFE2C] :00429ADD E8BECAFDFF call 004065A0 :00429AE2 8B852CFEFFFF mov eax, dword ptr [ebp+FFFFFE2C] ;; EAX = 69h :00429AE8 BAE89B4200 mov edx, 00429BE8 ;; EDX POINTS TO 6TH CHAR OF ENC. DATA :00429AED E8DE9DFDFF call 004038D0 ;; COMPARE THEM :00429AF2 0F8593000000 jne 00429B8B ;; IF THEY ARE NOT THE SAME, "WRONG SERIAL" Here we see that the 6th char of the decrypted data must be 49h (20h is added in the calls). After I changed my brute forcer a little I found that only 6 serials pass this test succesfully: 12053 - 28501 - 44501 - 61013 - 77461 - 93461 The changes I made to the brute forcer were: mov byte ptr [edi], dl .IF byte ptr [ebp+1] == 39h .IF byte ptr [ebp+5] == 49h invoke wsprintf, ADDR buffer2, ADDR sprint, counter .IF byte ptr [buffer2]==39h int 03 .ENDIF .ENDIF .ENDIF .ENDW So, let's go on. :00429AF8 803D32B7420000 cmp byte ptr [0042B732], 00 ;; THE DEBUGGER :00429AFF 0F8586000000 jne 00429B8B ;; CHECK AGAIN :00429B05 A140B74200 mov eax, dword ptr [0042B740] ;; POINTS TO DECRYPT. DATA :00429B0A 80780256 cmp byte ptr [eax+02], 56 ;; IS 3RD CHAR = 56h? :00429B0E 757B jne 00429B8B IF NOT, "WRONG SERIAL" Next change to the bruteforcer: .IF byte ptr [ebp+2] == 56h invoke wsprintf, ADDR buffer2, ADDR sprint, counter .IF byte ptr [buffer2]==38h int 03 .ENDIF .ENDIF Only one valid serial passed this check: 28501 - This must be the first part of the valid serial: We remember that the only criterion the second part of the serial must have is that the 4th char of the second part must equal the 3rd char of the first serial. So, now we have a valid serial: 28501-55555. 28501-99959 is another valid serial, as well as 28501-12354 and so on. :00429B10 A140B74200 mov eax, dword ptr [0042B740] ;; DECRYPED DATA :00429B15 8A00 mov al, byte ptr [eax] ;; 1ST BYTE OF ENCRYPED DATA :00429B17 8B153CB74200 mov edx, dword ptr [0042B73C] ;; EDX IS SERIAL :00429B1D 3A4201 cmp al, byte ptr [edx+01] ;; CMP WITH 2ND CHAR OF SERIAL :00429B20 7569 jne 00429B8B ;; IF NOT SAME, "WRONG SERIAL" So, luck was on my side this time: The serial 28501-55555 can be used to pass this validation check succesfully, too. To be honest: This is already a valid serial :] The rest is just some more code to validate the serial. :00429B22 B840B74200 mov eax, 0042B740 :00429B27 B902000000 mov ecx, 00000002 :00429B2C BA01000000 mov edx, 00000001 :00429B31 E8CE9EFDFF call 00403A04 :00429B36 E84DF8FFFF call 00429388 :00429B3B 25FF000000 and eax, 000000FF :00429B40 48 dec eax :00429B41 48 dec eax :00429B42 7438 je 00429B7C :00429B44 33D2 xor edx, edx :00429B46 8B86E0010000 mov eax, dword ptr [esi+000001E0] :00429B4C E8AFC8FEFF call 00416400 :00429B51 803D32B7420000 cmp byte ptr [0042B732], 00 ;; ANOTHER DEBUGGER CHECK :00429B58 7513 jne 00429B6D :00429B5A 8B1540B74200 mov edx, dword ptr [0042B740] :00429B60 8B86E4010000 mov eax, dword ptr [esi+000001E4] :00429B66 E82DC9FEFF call 00416498 ;; "VALID SERIAL - CONGRATULATIONS" :00429B6B EB3A jmp 00429BA7 ;; GO ON I additionaly coded a small prog which displays *all* valid serials. You can find it in the ZIP file. III. BTW Greetings go to: +Sandman, Acid Burn, alpine, BlackB, Blind Angel, Borna Janes, Carpathia, CrazyKnight, DEATH, DEZM, dimwitz, DnNuke, duelist, Eternal Bliss, Fravia+, Iczelion, Jordan, KnowledgeIsPower, Knotty, Kwazy Webbit, Lucifer48, MisterE, Neural Noise, noos, Prof.X, R!SC, rubor, Shadow, SiG, tC, The AntiXryst, The Hobgoblin, TORN@DO, ultraschall, viny, Volatility, wAj WarezPup, _y and all the guys I forget and I'll add next time.