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 32.Cracking Tutorial (18.12.1999) XX My solution of the 3nd Kraecker mAG Project I. Introduction I.1 The tools II. The essay II.1 The CrackMe II.2 DocClear III. BTW I. Introduction I participated (again) at the Cracking project of the German Scene EZine Kraecker. The targets to crack were FireWorx' CrackMe 16 and the shareware app DocClear 2.0.0.1 - The better you crack them, the more points you achieve. I have to explain the solutions shortly, but as I don't like short solutions, I can write a tut about it. I.1 The tools For the CrackMe: SoftIce (I have v3.25) MASM (to compile the KeyGen) For DocClear: SoftIce MASM (to compile the KeyGen) "Intellectual" support: The CD "Krebskolonie" from Eisregen II. The essay II.1 The CrackMe When you start this CrackMe you see that you gotta enter a name, a company and a serial. Hit OK and you see that there is no MessageBox that says "Wrong serial" but nothing happens instead. So, the decision for a breakpoint is easy: hmemcpy Enter name/company/serial and set a "bpx hmemcpy" in SICE. Leave SICE and hit the OK button. When SICE breaks enter "bc *" to clear all breakpoints and hit F12 until you reach the line 441742. Here is a listing of the important part: Note that this listing is created using a memory dump and then disassembled, so the offsets of the Instructions and the calls/jumps will differ: The first line is 441737 :00000000 8B45D4 mov eax, dword ptr [ebp-2C] ;; EAX = NAME :00000003 E8EA22FCFF call FFFC22F2 ;; THIS CALL GETS THE LENGTH OF THE NAME :00000008 83E802 sub eax, 00000002 ;; SUB 2 FROM LENGTH :0000000B 0F8C02010000 jl 00000113 ;; IF 0 OR BELOW: JUMP :00000011 40 inc eax :00000012 8945DC mov dword ptr [ebp-24], eax ;; USELESS SAVING OF LENGTH :00000015 C745F002000000 mov [ebp-10], 00000002 ;; MOVES 2 TO DWORD PTR [EBP-10] :0000001C 8B45F0 mov eax, dword ptr [ebp-10] ;; EAX = 2 :0000001F F76DF0 imul [ebp-10] ;; EAX = EAX * EAX = 2*2 = 4 :00000022 83C038 add eax, 00000038 ;; EAX = 3Ch :00000025 3534640000 xor eax, 00006434 ;; EAX = 6408 :0000002A 8945EC mov dword ptr [ebp-14], eax ;; SAVE THIS VALUE :0000002D 8B7DEC mov edi, dword ptr [ebp-14] ;; EDI = 6408 :00000030 0FAF7DEC imul edi, dword ptr [ebp-14] ;; EDI = EDI * 6408 =27164040 :00000034 81C749030000 add edi, 00000349 ;; EDI = 27164389 :0000003A 81F772020000 xor edi, 00000272 ;; EDI = 271641FB :00000040 8D55D4 lea edx, dword ptr [ebp-2C] :00000043 8B45FC mov eax, dword ptr [ebp-04] :00000046 8B80C8020000 mov eax, dword ptr [eax+000002C8] :0000004C E8111AFEFF call FFFE1A62 :00000051 8B45D4 mov eax, dword ptr [ebp-2C] ;; EAX POINTS TO SERIAL :00000054 E89922FCFF call FFFC22F2 ;; THIS ONE GETS LENGTH OF NAME :00000059 83E802 sub eax, 00000002 ;; SAME CHECK :0000005C 0F8CA5000000 jl 00000107 ;; AS ABOVE :00000062 40 inc eax :00000063 8945D8 mov dword ptr [ebp-28], eax ;; USELESS :00000066 BB02000000 mov ebx, 00000002 ;; EBX = 2 :0000006B 8BC3 mov eax, ebx ;; EAX = 2 :0000006D F7EB imul ebx ;; EAX = EAX * EBX = 2*2 = 4 :0000006F B938240000 mov ecx, 00002438 ;; ECX = 2438h :00000074 99 cdq ;; NEEDED FOR DIVISION :00000075 F7F9 idiv ecx ;; EAX = EAX / ECX = 0 ; EDX = EAX MOD ECX =2438 :00000077 8BC8 mov ecx, eax ;; ECX = 0 :00000079 81F173640000 xor ecx, 00006473 ;; ECX = 6473h :0000007F 83E918 sub ecx, 00000018 ;; ECX = 645B :00000082 8D3419 lea esi, dword ptr [ecx+ebx] ;; ESI=ECX+EBX=645B+2 = 645D :00000085 8BC7 mov eax, edi ;; EAX = 271641FB :00000087 F76DEC imul [ebp-14] ;; EAX=EAX*[ebp-14]=645D*271641FB=EA781BD8 :0000008A 03F0 add esi, eax ;; ESI = ESI + EAX = EA788035 :0000008C 8BC7 mov eax, edi ;; EAX = EA788035 :0000008E 99 cdq ;; NEEDED FOR DISIVSION :0000008F F77DEC idiv [ebp-14] ;; EAX=EAX/[ebp-14] = 6408 :00000092 8BD6 mov edx, esi ;; EDX = EA788035 :00000094 0FAFD1 imul edx, ecx ;; EDX = EDX * ECX = 6AEA46D7 :00000097 03C2 add eax, edx ;; EAX = 6AEAAADF :00000099 8945E8 mov dword ptr [ebp-18], eax :0000009C 8B45E8 mov eax, dword ptr [ebp-18] :0000009F 99 cdq :000000A0 F7FE idiv esi ;; EAX = EAX / ESI = FFFFFFFC :000000A2 03C7 add eax, edi ;; EAX = 271641F7 :000000A4 05B40C0000 add eax, 00000CB4 ;; EAX = 27164EAB :000000A9 8945E4 mov dword ptr [ebp-1C], eax :000000AC C745E0FFE0F505 mov [ebp-20], 05F5E0FF :000000B3 03F1 add esi, ecx ;; ESI = EA78E490 :000000B5 0375E8 add esi, dword ptr [ebp-18] ;; ESI = 55638F6F :000000B8 8B45E4 mov eax, dword ptr [ebp-1C] ;; EAX DOESN'T CHANGE HERE :000000BB F76DE0 imul [ebp-20] ;; EAX = EAX * [ebp-20] = 14B4FC55 :000000BE 03F0 add esi, eax ;; ESI = 6A188BC4 ------------- HERE HAS ESI THE CORRECT SERIAL - CONVERT TO DEC AND YOU GET 1779993540 ------------- THE FOLLOWING CALLS ARE ONLY FOR CONVERTING AND COMPARING THE SERIALS :000000C0 8D55F8 lea edx, dword ptr [ebp-08] :000000C3 8BC6 mov eax, esi :000000C5 E8245EFCFF call FFFC5EEE As you see the serial does NOT depend on name or company but is always 1779993540. Actually there is no need to keygen this one, but 1) FireWorx says to do it in his NFO 2) You can - perhaps - learn something from the plenty sources I included and 3) I get points at the Kraecker project ;) Now we can code a loader for this CrackMe, so that it shows always the "Good Cracker" message. If you trace farther below, you will find this check pretty fast: :000000E6 8B45D4 mov eax, dword ptr [ebp-2C] :000000E9 8B55F4 mov edx, dword ptr [ebp-0C] :000000EC E81123FCFF call FFFC2402 :000000F1 750A jne 000000FD ;; THIS IS OFFSET 441833 And exactly this is the "Wrong serial" jump. Now we gotta make it *never* jump. Some of you might say: "Let's NOP it", some other "INC EAX, DEC EAX", but that's all shit ;) The best way to make a jump never jump is to make it jump 0 Bytes. So we gotta change 750A to 7500. This is - IMHO - much better style and we need only 50% of the bytes we would have to patch using NOP or INC/DEC ;) The source for a loader is included in the ZIP, too. btw: This patch makes the "Good Cracker" message appear as often as the company you entered is long - 1. II.2 DocClear Ok, now the next one and to say it right now: This one was quite more difficult than the Crackme, but nevertheless a good target ;) At first - as always - the keygen ;) The helpfile of the program says that there are *no* limitations in the program when it is unregistered and in fact: If there were one or two limits the program would have no function left ;) 10$ for a prog that clears your start menu? Never! Ok, when you start the program it gets started as a icon in your system tray. Here you can choose a menu which makes the register-window appear. When you enter a wrong serial, a messagebox appears telling you that you entered a wrong serial. Neither bpx on MessageBox nor MessageBoxA work but if you disassemble DocClear.exe with W32Dasm you can find the string "Incorrect or incomplete information was entered". You will find it here: :0046862D 8D95FCFDFFFF lea edx, dword ptr [ebp+FFFFFDFC] :00468633 8B83CC020000 mov eax, dword ptr [ebx+000002CC] :00468639 59 pop ecx :0046863A E84959FEFF call 0044DF88 :0046863F 85C0 test eax, eax :00468641 7529 jne 0046866C :00468643 8B83CC020000 mov eax, dword ptr [ebx+000002CC] :00468649 E80A5AFEFF call 0044E058 :0046864E 6A00 push 00000000 :00468650 668B0DC0864600 mov cx, word ptr [004686C0] :00468657 B202 mov dl, 02 * Possible StringData Ref from Code Obj ->"DocClear was registered successfully." | :00468659 B8CC864600 mov eax, 004686CC :0046865E E8CDFDFDFF call 00448430 :00468663 8BC3 mov eax, ebx :00468665 E87A92FDFF call 004418E4 :0046866A EB23 jmp 0046868F * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00468641(C) | :0046866C 6A00 push 00000000 :0046866E 668B0DC0864600 mov cx, word ptr [004686C0] :00468675 B202 mov dl, 02 * Possible StringData Ref from Code Obj ->"Incorrect or incomplete information " ->"was entered." | :00468677 B8FC864600 mov eax, 004686FC :0046867C E8AFFDFDFF call 00448430 OK, let's analyze: The jump at :00468641 decides whether the "Correct" or the "Incorrect" messagebox appears. In front of it there is a call and some pushes. At first glance I'd have bet 10$ that the correct serial is one of the pushes, but I was wrong. This is just the beginning of the calculation and comparison routines and your name and serial you entered are pushed. When you enter a correct serial, the first call after the "DocClear was..." string shows the messagebox and the second one writes - as we will see later - the registration information to a file. I thought that this call had this function because what else should a call do that only appears when you entered the correct serial? If you enter a wrong serial, the "Incorrect..." message appears and the program just goes on. Prepare for much tracing into calls as this prog tries to hide the important routines deep in the "dark codewoods" ;) So, trace in the call and you find yourself at :0044DF88. Now trace on until you reach the following section (Remark: In serial calculations, try to find loops. They are mostly important) :0044DFBF 8A54240A mov dl, byte ptr [esp+0A] ;; DL = LENGTH OF NAME :0044DFC3 42 inc edx ;; EDX = LENGTH OF NAME + 1 :0044DFC4 83FA32 cmp edx, 00000032 ;; COMPARE EDX WITH 32h :0044DFC7 7F0E jg 0044DFD7 ;; IF EDX IS GREATER, THEN JUMP :0044DFC9 8D44140A lea eax, dword ptr [esp+edx+0A] ;; EAX POINTS TO FIRST ;; BYTE AFTER THE NAME * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0044DFD5(C) | :0044DFCD C60019 mov byte ptr [eax], 19 ;; THIS LOOP FILLS :0044DFD0 42 inc edx ;; A REGION :0044DFD1 40 inc eax ;; OF 32h BYTES (INCLUDING YOUR :0044DFD2 83FA33 cmp edx, 00000033 ;; NAME) WITH 19h :0044DFD5 75F6 jne 0044DFCD ;; BYTES Now trace on until you reach the next call. It is at :0044DFDF. Enter and trace a little until you find this (only few bytes away from the start of the call): :0044DEED 8D442440 lea eax, dword ptr [esp+40] :0044DEF1 50 push eax :0044DEF2 8BCB mov ecx, ebx :0044DEF4 8D54240E lea edx, dword ptr [esp+0E] ;; EDX = NAME :0044DEF8 8BC6 mov eax, esi :0044DEFA E805FEFFFF call 0044DD04 :0044DEFF 8D442440 lea eax, dword ptr [esp+40] ;; EAX POINTS TO A SUSPICIOUS ;; STRING :0044DF03 8BD4 mov edx, esp ;; EDX = SERIAL YOU ENTERED Well, to say it directly: The string eax points to is the correct serial for the name you have entered. For example for "LaZaRuS" it will be "$716B5842". Now we gotta trace into the call at :0044DEFA to see how the serial gets calculated. When you trace a while you will come to another loop which just adds 19h bytes to your name until you have a 32h bytes long segment of name and 19h bytes. This loop is from :0044DD3C to :0044DD44. Now the interesting part begins: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0044DD36(C) | :0044DD46 8D8D6CFFFFFF lea ecx, dword ptr [ebp+FFFFFF6C] :0044DD4C 8B9324020000 mov edx, dword ptr [ebx+00000224] ;; EDX = 9BFD62F25 :0044DD52 8BC3 mov eax, ebx :0044DD54 E853FEFFFF call 0044DBAC ;; THIS CALL CONVERTS EDX TO "$9BFD62F25" :0044DD59 8D956CFFFFFF lea edx, dword ptr [ebp+FFFFFF6C] ;; HERE EDX POINTS TO IT This call converts the integer value of edx to a string and moves it in front of the name in the memory. A byte which contains the length of the following string is in front of every string DocClear uses. So the memory part we deal with looks like this (bytes = []): [9 = Length of "$9BFD62F25"]$9BFD62F25[7=Length of LaZaRuS (Name)]LaZaRuS[19][19]... The following calls and instructions do nothing but move strings like "$9BFD62F25" or your name to different memory adresses. The next important call is at :0044DD94 8D8D6CFFFFFF lea ecx, dword ptr [ebp+FFFFFF6C] :0044DD9A 8B9328020000 mov edx, dword ptr [ebx+00000228] ;; EDX = E40FB826 :0044DDA0 8BC3 mov eax, ebx :0044DDA2 E805FEFFFF call 0044DBAC This call converts the integer value of edx to a string and moves it at the end of the FirstString/Name/19hBytes block. The block now looks like this: [9]$9BFD62F25[7]LaZaRuS[19][19]...[9]$E40FB826 You gotta think of so many 19h Bytes that the following equation is correct: 3 Bytes for the length of the three strings + 18 Bytes for the two $-Strings + Name + X 19h Bytes = 47h And again: The next operations do only move strings in the memory. But now we get to the core of the algo that calculates the serial: Look at this part: :0044DDE8 8B9334030000 mov edx, dword ptr [ebx+00000334] :0044DDEE 8D8D7BFFFFFF lea ecx, dword ptr [ebp+FFFFFF7B] :0044DDF4 8BC3 mov eax, ebx :0044DDF6 E869FDFFFF call 0044DB64 Look what edx contains after we trace *over* the call: 716B5842 (or more general: The correct serial w/o $). Well, I strongly believe that this call is the end of our journey through the code :) Enter it and you will see the following: * Referenced by a CALL at Addresses: |:0044DDF6 , :0044DE5B , :0044DEA0 | :0044DB64 53 push ebx :0044DB65 56 push esi :0044DB66 57 push edi :0044DB67 83C4B8 add esp, FFFFFFB8 :0044DB6A 8BF1 mov esi, ecx ;; ESI POINTS TO "$9BFD62F25" :0044DB6C 8D3C24 lea edi, dword ptr [esp] :0044DB6F B911000000 mov ecx, 00000011 :0044DB74 F3 repz :0044DB75 A5 movsd :0044DB76 66A5 movsw :0044DB78 A4 movsb :0044DB79 B147 mov cl, 47 ;; CL = 47 = LOOP VARIABLE :0044DB7B 8BC4 mov eax, esp ;; EAX POINTS TO "$9BFD62F25" * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0044DBA1(C) ;; THIS LOOP CALCULATES THE SERIAL :) | :0044DB7D 8BDA mov ebx, edx ;; EBX = EDX :0044DB7F C1EB08 shr ebx, 08 ;; EBX = EBX * 2^8 = EBX * 256 :0044DB82 81E3FFFFFF00 and ebx, 00FFFFFF ;; EBX = EBX AND 00FFFFFF :0044DB88 0FB630 movzx esi, byte ptr [eax] ;; LOOK BELOW (1) :0044DB8B 33D6 xor edx, esi ;; EDX = EDX XOR ESI :0044DB8D 81E2FF000000 and edx, 000000FF ;; EDX=CL (all higher bytes are erased) :0044DB93 8B1495B4AB4600 mov edx, dword ptr [4*edx+0046ABB4] ;;GET DWORD FROM TABLE :0044DB9A 33DA xor ebx, edx ;; EBX = EBX + EDX :0044DB9C 8BD3 mov edx, ebx ;; EDX = EBX :0044DB9E 40 inc eax :0044DB9F FEC9 dec cl :0044DBA1 75DA jne 0044DB7D :0044DBA3 8BC2 mov eax, edx ;; EAX CONTAINS CORRECT SERIAL :0044DBA5 83C448 add esp, 00000048 :0044DBA8 5F pop edi :0044DBA9 5E pop esi :0044DBAA 5B pop ebx :0044DBAB C3 ret (1) Here, ESI gets the i. byte of the [9]$9BFD62F25[7]LaZaRuS[19][19]...[9]$E40FB826 block. This loop is run until every byte of this 47h block was in esi once. :0044DB8D 81E2FF000000 and edx, 000000FF ;; EDX=CL (all higher bytes are erased) :0044DB93 8B1495B4AB4600 mov edx, dword ptr [4*edx+0046ABB4] ;;GET DWORD FROM TABLE Those two lines get a value from a "table". That is a block of bytes that is used to calculate serials. The length of the table is easy to get: In line :0044DB8D you see that edx can get a max value of FFh (at least if you know how the AND instruction works ;) I tell you just this: AND a byte with 00 and it will be 00; AND a byte with FFh and its value will remain. In the next line you see that the dword is received from [4*edx+0046ABB4]. 0046ABB4 is prolly the beginning of the table and 4*edx is the position of the dword which is read. So we calculate 4*FF = 3FCh - This could be the length of the table, but you have to watch out that you add 4 Bytes which will be read if edx has the value FFh. So, the length of the table is 3FCh+4h = 400h = 1024 = 1KB The table is easy to get, too. Just trace through the serial calculation and look for the first 8 bytes at :0046ABB4. Search for them in a hexeditor and copy/paste the following 1024 Bytes in a new file. You will find the file table.hex in the keygen sourcecode. So, let's head for the patch. I believe the following part of the code is the best for a patch: :0044DEFF 8D442440 lea eax, dword ptr [esp+40] ;; EAX POINTS TO A SUSPICIOUS ;; STRING :0044DF03 8BD4 mov edx, esp ;; EDX = SERIAL YOU ENTERED I hope you remember this section ;) The best way to patch this one is to change it the following way: :0044DEFF 8D442440 lea eax, dword ptr [esp+40] ;; EAX = CORRECT SERIAL :0044DF03 8BD0 mov edx, eax ;; EDX = CORRECT SERIAL I just gave the register that should point to the serial you entered the value of the serial that was calculated. So the prog believes that you entered the correct serial and you get the "Correct Serial" message and the name you entered appears in the About-Box. And best of it all: This will stay so when you restart the program, as this section is used at the beginning, too ;) III. BTW Credits go to: Shadow - for the skeleton of the TASM source CrackZ - for the HexToChar routine I used for the DOS-ASM source NaTzGUL - for the essay where I have 90% of my loader source from Greetings go to: +Sandman, Acid Burn, alpine, Blind Angel, Borna Janes, Carpathia, CrazyKnight, DEATH, DEZM, dimwitz, DnNuke, duelist, Eternal Bliss, Fravia+, Iczelion, Jordan, KnowledgeIsPower, Knotty, Lucifer48, MisterE, Neural Noise, noos, Prof.X, R!SC, rubor, Shadow, SiG, tC, The AntiXryst, The Hobgoblin, TORN@DO, ultraschall, viny, Volatility, wAj, _y and all the guys I forget and I'll add next time.