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 34.Cracking Tutorial (27.12.1999) XX Cracking the CiA Trial CrackMe #5 I. Introduction I.1 The tools II. The essay II.1 First look at the target II.2 The serial II.3 The keygen III. BTW I. Introduction Well, two days ago I saw that the new trial CrackMe from CiA was released and as I really enjoyed the earlier versions from tKC and BuLLeT I wanted to try this one (coded by Northpole). Well, I also heard that it should be harder as the last one, I am of a different opinion, but maybe it's only my skill that got better compared to the last one ;) We'll see. I.1 The tools W32Dasm (I have v8.9) SoftIce (I have 3.25) ProcDump (I have 1.4) HexWorkshop (I have 3.00) Interrupter (I have 1.01 which has a major bug; grrr, I'm lame :( ) II. The essay II.1 First look at the target When you start the CrackMe and look at the NFO file, you see that it's all pretty straight forward. You gotta patch a nagscreen, get a unlock code for your name and create a valid keyfile. Furthermore you get a special "serial", in my case it was "8915-AD" which - as we see later - is used to calculate the unlock code. Whatever...let's start :) II.2 The nagscreen The first thing I did was unpacking the file using Procdump. Don't worry, I will patch the packed file, the unpacked version is only to get a deadlisting and information about the EXE file. As I found this information in the packed file... 000002D0 0000 0000 0000 0A00 2449 6E66 6F3A 2054 ........$Info: T 000002E0 6869 7320 6669 6C65 2069 7320 7061 636B his file is pack 000002F0 6564 2077 6974 6820 7468 6520 5550 5820 ed with the UPX 00000300 6578 6563 7574 6162 6C65 2070 6163 6B65 executable packe 00000310 7220 240A 0024 4964 3A20 5550 5820 302E r $..$Id: UPX 0. 00000320 3839 2E36 2043 6F70 7972 6967 6874 2028 89.6 Copyright ( 00000330 4329 2031 3939 362D 3139 3939 204C 6173 C) 1996-1999 Las 00000340 7A6C 6F20 4D6F 6C6E 6172 2026 204D 6172 zlo Molnar & Mar 00000350 6B75 7320 4F62 6572 6875 6D65 7220 240A kus Oberhumer $. 00000360 0024 4964 3A20 4E52 5620 302E 3631 2043 .$Id: NRV 0.61 C 00000370 6F70 7972 6967 6874 2028 4329 2031 3939 opyright (C) 199 00000380 362D 3139 3939 204D 6172 6B75 7320 462E 6-1999 Markus F. 00000390 582E 4A2E 204F 6265 7268 756D 6572 2024 X.J. Oberhumer $ 000003A0 0A00 244C 6963 656E 7365 3A20 4E52 5620 ..$License: NRV 000003B0 666F 7220 5550 5820 6973 2064 6973 7472 for UPX is distr 000003C0 6962 7574 6564 2075 6E64 6572 2073 7065 ibuted under spe 000003D0 6369 616C 206C 6963 656E 7365 2024 0A00 cial license $.. ... I was pretty sure that it is packed with UPX, so I have chosen the Unpack/UPX function of Procdump which worked pretty fine. The unpacked file did run without fixing imports or other stuff. OK, we go for the nagscreen. The size and the appearance of the CrackMe made me sure that the language used to code this one is Borland Delphi. Opening the unpacked file I searched for "TForm" and found it several times. Now it could be Delphi or C++ Builder, but that doesn't matter as I have the info I needed. Now I will tell you a nice trick how to deal with Delphi files. I searched for Form1 and found this: 00042BC0 4400 1062 746E 5265 6769 7374 6572 436C D..btnRegisterCl 00042BD0 6963 6B0F 0020 5244 0008 466F 726D 5368 ick.. RD..FormSh 00042BE0 6F77 1300 3052 4400 0C46 6F72 6D41 6374 ow..0RD..FormAct 00042BF0 6976 6174 6513 0050 5244 000C 4275 7474 ivate..PRD..Butt 00042C00 6F6E 3143 6C69 636B 0654 466F 726D 3106 on1Click.TForm1. You see FormShow and FormActivate? These are two events you can define in Delphi. The nag- screen that appears is most probably showed at the FormShow event and not at FormActivate, because - I believe - if it was defined in FormActivate the main window would be displayed *before* the nagscreen appears, but the nagscreen appears first. So, what's the deal? Look at this line: 00042BD0 6963 6B0F 0020 5244 0008 466F 726D 5368 ick.. RD..FormSh You see "20 5244 0008 46". The 08 is the length of the string "FormShow", the 46h is the "F" of FormShow and 20524400, what's this? That's one of the things I love most about Delphi progs. If you swap the bytes you get 00445220 and that is the RVA of the FormShow event, that is: At this address the FormShow event starts. Isn't that cool? Getting all this information about the nagscreen only using the HexEditor. You could even kick the complete Nagscreen using only the hexeditor. Just convert the RVA 445220 to the file offset manually. I used W32Dasm for this. Disassemble the unpacked file and goto 445220. You will see that the file offset for this RVA is 44820h. :00445220 A1FC6C4400 mov eax, dword ptr [00446CFC] :00445225 8B00 mov eax, dword ptr [eax] Now make a copy of the packed file and call it patched.exe. Use Interrupter to replace the byte at the entrypoint to CCh, because SICE loader doesn't break at the entry point. Now set a "bpint 03". Load the packed file into a hexeditor and scroll to the end of it. Here we will add the code which will patch the packed file during runtime. I have chosen offset 33EF0h. Why? Doesn't matter. You can choose the bytes 33EE0 until ~33FF0. If you choose the same offset as I did, you will have the same opcodes for the instrucions, so you better do so ;) Write something you easily remember and that can't appear in your memory once again. I have chosen the nice word "Trallala" (as always ;) Now disassemble the *packed* file and go to the entry point. Then scroll down to the very end until the last "jmp" appears (it is at offset 4ABC4Ch). Remember this address, because your next bpx will be placed here. So, start the program - if you have bpint 03 set - and SICE breaks, now enter "e eip 60" and set a "bpx 4ABC4C". Leave SICE and it will immediately break again. Now we are at the very end of the UPX unpacking routine. Now we gotta search for "Trallala" or whatever you have chosen. This can be done with "s 0 l ffffffff 'Trallala' SICE finds it at offset 4ACCF0. So we have to patch the jump :004ABC4C E97797F9FF jmp 004453C8 to :004ABC4C E99F100000 jmp 004ACCF0 You can get the opcodes when you choose the "a eip" function. When you are at offset 4ACCF0 you should be completely sure what to add there. I have chosen the following instructions: mov byte ptr [445220], C3 // PLACE A RET AT THE BEGINNING OF THE FormShow FUNCTION jmp 4453C8 // JUMP TO "NORMAL" ENTRY POINT The opcodes for these instructions are - as revealed with SICE and the "a eip" function: C60520524400C3 E9CC86F9FF Place these opcodes with your hexeditor at offset 33EF0 and replace the CCh Interrupter has set at the entry point (32ED0) with 60h and start the program. If the nag is gone everything's alright. If it is still there, check the opcodes of the MOV instruction. If the program crashes check the two jump instructions. Then you should find the mistake. II.3 The keyfile So, the nagscreen is eliminated, now the keyfile. This time we will deal with the OnActivate event of Form1 which starts at 445230 which is revealed by the following hex-codes. 00042BE0 6F77 1300 3052 4400 0C46 6F72 6D41 6374 ow..0RD..FormAct 00042BF0 6976 6174 6513 0050 5244 000C 4275 7474 ivate..PRD..Butt Additionaly set a breakpoint at on "CreateFileA" as this is a very important API function to create Keyfiles. Now start the program and every time SICE breaks on CreateFileA hit F12 and look where you are. If the address you are looks like this: XXXX:XX4XXXXX you are at the right place, if not you are still in Windows code which starts the CrackMe. The correct call is at 4057D7. You will see this: :004057C7 6A00 push 00000000 // THE FOLLOWING PUSHES ARE ONLY OPTIONS :004057C9 6880000000 push 00000080 // FOR THE FOLLOWING CREATEFILEA FUNCTION :004057CE 51 push ecx // IF YOU WANT TO KNOW MORE, CONSULT YOUR :004057CF 6A00 push 00000000 // API REFERENCE :004057D1 52 push edx :004057D2 50 push eax :004057D3 8D4348 lea eax, dword ptr [ebx+48] // NAME OF THE KEYFILE :004057D6 50 push eax :004057D7 E804BAFFFF call 004011E0 // CreateFileA * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00405801(U) | :004057DC 83F8FF cmp eax, FFFFFFFF // DOES FILE NOT EXIST? :004057DF 7429 je 0040580A // IF IT DOES NOT EXIST, JUMP :004057E1 8903 mov dword ptr [ebx], eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040581B(U) | :004057E3 5F pop edi :004057E4 5E pop esi :004057E5 5B pop ebx :004057E6 C3 ret If you look at [ebx+48] right after the call, you can see the string "RegKey.CiA", so let's create such a file. I filled it initially with the string "1234567890". Now restart the CrackMe and trace off the subroutine and a little on until you reach... :00443B11 8D854CFEFFFF lea eax, dword ptr [ebp+FFFFFE4C] :00443B17 E85C1AFCFF call 00405578 // THIS CALL GETS THE SIZE OF THE KEYFILE :00443B1C 8BD8 mov ebx, eax :00443B1E 8BD3 mov edx, ebx :00443B20 8D854CFEFFFF lea eax, dword ptr [ebp+FFFFFE4C] :00443B26 E8491DFCFF call 00405874 :00443B2B 83FB32 cmp ebx, 00000032 // COMPARES SIZE WITH 32h = 50d :00443B2E 7D15 jge 00443B45 // IF GREATER OR EQUAL, JUMP * Possible StringData Ref from Data Obj ->"INVALID KEYFILE" | :00443B30 BABC444400 mov edx, 004444BC // IF NOT: INVALID KEYFILE So we fill the keyfile with - at least - 50 bytes and restart. I will fill it with.. QWERTZUIOPASDFGHJKLYXCVBNMqwertzuiopasdfghjklyxcvbnm which has 52 bytes. If you start the CrackMe now it will crash at offset 443C0C. I searched quite some time for the reason, but was unable to find out the reason by debugging it. So, I got through the string references hoping to find something useful. I found these: "*CiA Trial Crackme #5 Keyfile*" "*End Of KeyFile*" "[Code]" "[Name]" The two strings that come at first have obviously something to do with the keyfile (unless Northpole tried to fool us, but he didn't). The two others remembered me of INI files, so I had a closer look at them. Additionaly they come only short time after the code snippet we are currently working with. So, my next Keyfile looked like this: *CiA Trial Crackme #5 Keyfile* [Code] 666999 [Name] LaZaRuS *End Of KeyFile* Still crashes, to the next keyfile I tried was this one.. --- BEGINNING OF FILE --- *CiA Trial Crackme #5 Keyfile* [Code] 666999 [Name] LaZaRuS *End Of KeyFile* --- END OF FILE --- Whoop, now it works (at least it doesn't crash) :) I believe we are at the right track. So, let's go through the next code snippet. --- SNIP, SNIP --- * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00443B2E(C) | :00443B45 8D854CFEFFFF lea eax, dword ptr [ebp+FFFFFE4C] :00443B4B E8EC19FCFF call 0040553C * Possible StringData Ref from Data Obj ->"RegKey.CiA" | :00443B50 BAA8444400 mov edx, 004444A8 :00443B55 8D8580FCFFFF lea eax, dword ptr [ebp+FFFFFC80] :00443B5B E83A19FCFF call 0040549A :00443B60 8D8580FCFFFF lea eax, dword ptr [ebp+FFFFFC80] :00443B66 E8BC1BFCFF call 00405727 :00443B6B E830ECFBFF call 004027A0 :00443B70 85C0 test eax, eax // THIS ONE WAS *ALWAYS* TAKEN :00443B72 7415 je 00443B89 * Possible StringData Ref from Data Obj ->"INVALID KEYFILE" | :00443B74 BABC444400 mov edx, 004444BC --- SNIP, SNIP --- Now there's a long time with many calls. The next jump is here: :00443E65 83F806 cmp eax, 00000006 :00443E68 7C1E jl 00443E88 :00443E6A 8B45DC mov eax, dword ptr [ebp-24] ;; POINTS TO SECOND LINE OF ;; KEYFILE * Possible StringData Ref from Data Obj ->"[Name]" | :00443E6D BAD4444400 mov edx, 004444D4 :00443E72 E89DFDFBFF call 00403C14 ;; COMPARES SECOND LINE WITH "[Name]" :00443E77 750F jne 00443E88 ;; IF NOT EQUAL, THEN "INVALID" :00443E79 8B45D8 mov eax, dword ptr [ebp-28] ;; THIRD LINE OF KEYFILE * Possible StringData Ref from Data Obj ->"[Code]" | :00443E7C BAE4444400 mov edx, 004444E4 :00443E81 E88EFDFBFF call 00403C14 ;; COMPARES SECOND LINE WITH "[Code]" :00443E86 7415 je 00443E9D ;; IF EQUAL, THEN JUMP * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:00443E68(C), :00443E77(C) | * Possible StringData Ref from Data Obj ->"INVALID KEYFILE" | :00443E88 BABC444400 mov edx, 004444BC So we got to flip the lines 2 and three of our keyfile. The new keyfile is... --- BEGINNING OF FILE --- *CiA Trial Crackme #5 Keyfile* [Name] LaZaRuS [Code] 666999 *End Of KeyFile* --- END OF FILE --- Then - again - there are dozens of calls and manipulating of offsets, but I didn't care at all. The only thing that interested me was the next jump-instruction. It was here... :00444157 8D9534FCFFFF lea edx, dword ptr [ebp+FFFFFC34] :0044415D 8D45E8 lea eax, dword ptr [ebp-18] :00444160 E843F9FBFF call 00403AA8 :00444165 8B45CC mov eax, dword ptr [ebp-34] ;; LAST LINE OF KEYFILE * Possible StringData Ref from Data Obj ->"*End Of KeyFile*" | :00444168 BAF4444400 mov edx, 004444F4 :0044416D E8A2FAFBFF call 00403C14 ;; COMPARE LAST LINE WITH "*End Of KeyFile*" :00444172 750F jne 00444183 ;; IF NOT EQUAL, THEN JUMP :00444174 8B45E0 mov eax, dword ptr [ebp-20] ;; FIRST LINE WITH KEYFILE * Possible StringData Ref from Data Obj ->"*CiA Trial Crackme #5 Keyfile*" | :00444177 BA10454400 mov edx, 00444510 :0044417C E893FAFBFF call 00403C14 ;; COMPARE FIRST LINE WITH "*CiA Trial..." :00444181 7415 je 00444198 ;; JUMP, IF EQUAL - IF NOT: INVALID * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444172(C) | * Possible StringData Ref from Data Obj ->"INVALID KEYFILE" | :00444183 BABC444400 mov edx, 004444BC Then - once again - I searched for the next code snippet that looked like this: mov SOMETHING mov SOMETHING_ELSE call SOMETHING jne SOMEWHERE The next snippet where this appears is: :0044437E 8B45E8 mov eax, dword ptr [ebp-18] :00444381 8B55D0 mov edx, dword ptr [ebp-30] :00444384 E88BF8FBFF call 00403C14 :00444389 7558 jne 004443E3 When I looked at EAX, I saw " 666999" (watch out: it starts with a space) in my data-window, and EDX showed "A605C214". Should this be our correct serial? Try it and you'll see: VALID KEYFILE. The problem is: The serial is valid for the name " LaZaRuS", but I want "LaZaRuS" (w/o space). OK, easy. Just change the keyfile and read the value EDX points to again. Now it is "15C614A6". So the valid serial for the name LaZaRuS is --- BEGINNING OF FILE --- *CiA Trial Crackme #5 Keyfile* [Name]LaZaRuS [Code]15C614A6 *End Of KeyFile* --- END OF FILE --- If you want to know *how* the keyfile is created I wish you much fun in tracing and understanding about 100 calls ;) II.4 The serial Now we go for the most difficult part of the CrackMe. As always, I entered my name and serial and set a breakpoint on hmemcpy and it worked at once. I traced out of any calls hitting F12 several times (until you are at :00444805). You'll see this: :00444800 E8B7F2FDFF call 00423ABC ;; GET SERIAL YOU ENTERED :00444805 8B45A0 mov eax, dword ptr [ebp-60] ;; SERIAL :00444808 E8F7F2FBFF call 00403B04 ;; GET LENGTH OF SERIAL :0044480D 83F818 cmp eax, 00000018 ;; COMPARE LENGTH WITH 18h (=24d) :00444810 7456 je 00444868 ;; IF LENGTH=18h, THEN JUMP :00444812 8D55A0 lea edx, dword ptr [ebp-60] ;; USELESS... :00444815 8B87EC020000 mov eax, dword ptr [edi+000002EC] :0044481B E89CF2FDFF call 00423ABC :00444820 8B45A0 mov eax, dword ptr [ebp-60] :00444823 E8DCF2FBFF call 00403B04 :00444828 8BF0 mov esi, eax :0044482A 85F6 test esi, esi :0044482C 0F8E05070000 jle 00444F37 ;; 444F37 IS THE "BAD SERIAL" ADDRESS Here we can see that the serial must be 24 chars long. Current serial (all info we have, yet): XXXXXXXXXXXXXXXXXXXXXXXX When we enter a 24-chars-long serial, we come to this code snippet: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444810(C) | :00444868 8D55A0 lea edx, dword ptr [ebp-60] :0044486B 8B87EC020000 mov eax, dword ptr [edi+000002EC] :00444871 E846F2FDFF call 00423ABC :00444876 8B45A0 mov eax, dword ptr [ebp-60] ;; SERIAL :00444879 80780A2D cmp byte ptr [eax+0A], 2D ;; COMPARES 11. CHAR WITH "-" :0044487D 7517 jne 00444896 ;; IF NOT EQUAL, THEN "WRONG SERIAL" :0044487F 8D5598 lea edx, dword ptr [ebp-68] :00444882 8B87EC020000 mov eax, dword ptr [edi+000002EC] :00444888 E82FF2FDFF call 00423ABC :0044488D 8B4598 mov eax, dword ptr [ebp-68] ;; SERIAL :00444890 8078152D cmp byte ptr [eax+15], 2D ;; COMPARE 22. CHAR WITH "-" :00444894 746B je 00444901 ;; IF EQUAL, THEN "GOOD SERIAL" So, we have some more information about the serial: Current serial (all info we have, yet): XXXXXXXXXX-XXXXXXXXXX-XX If we use this serial, we will come here: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444894(C) | :00444901 8D45F4 lea eax, dword ptr [ebp-0C] :00444904 50 push eax :00444905 8D55A0 lea edx, dword ptr [ebp-60] :00444908 8B87EC020000 mov eax, dword ptr [edi+000002EC] :0044490E E8A9F1FDFF call 00423ABC :00444913 8B45A0 mov eax, dword ptr [ebp-60] ;; SERIAL :00444916 B90A000000 mov ecx, 0000000A :0044491B BA01000000 mov edx, 00000001 :00444920 E8E3F3FBFF call 00403D08 ;; THIS CALL EXTRACTS THE FIRST 10 CHARS :00444925 BB01000000 mov ebx, 00000001 :0044492A B001 mov al, 01 ;; AN IMPORTANT FLAG * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0044494D(C) | :0044492C 8B45F4 mov eax, dword ptr [ebp-0C] ;; FIRST 10 CHARS OF SERIAL :0044492F 8A4418FF mov al, byte ptr [eax+ebx-01] ;; i. CHAR OF SERIAL :00444933 3C30 cmp al, 30 ;; IS CHAR :00444935 7207 jb 0044493E ;; LESS THAN 30h = "0", THEN JUMP :00444937 8B55F4 mov edx, dword ptr [ebp-0C] ;; FIRST 10 CHARS OF SERIAL :0044493A 3C39 cmp al, 39 ;; IS CHAR :0044493C 7609 jbe 00444947 ;; LESS THAN 39h = "9" THEN JUMP * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444935(C) | :0044493E B001 mov al, 01 ;; SET FLAG TO 1 :00444940 BB0B000000 mov ebx, 0000000B :00444945 EB02 jmp 00444949 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0044493C(C) | :00444947 33C0 xor eax, eax ;; SET FLAG TO 0 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444945(U) | :00444949 43 inc ebx ;; NEXT CHAR :0044494A 83FB0B cmp ebx, 0000000B ;; IF NOT ALREADY TESTED ALL TEN CHARS :0044494D 7CDD jl 0044492C ;; THEN JUMP :0044494F 3C01 cmp al, 01 ;; COMPARE FLAG WITH 01 :00444951 7562 jne 004449B5 ;; IF NOT EQUAL, THEN JUMP The above loop tests, if all of the first ten chars are digits. If not, then the flag is set to 01 and the important jump at 444951 is not taken. If all digits are chars, then the flag is set to 00 and the jump is taken. Current serial (all info we have, yet): 1234567890-XXXXXXXXXX-XX If we use this serial, the jump is taken, but when we go on tracing we get an error message saying "X is no valid integer value". So, we know that - at least - one of the remaining X must be a digit. The passage where we get this message is here: :00444A5A 8B45A0 mov eax, dword ptr [ebp-60] ;; EAX IS SERIAL :00444A5D 8A5016 mov dl, byte ptr [eax+16] ;; 23. CHAR OF SERIAL :00444A60 8D459C lea eax, dword ptr [ebp-64] :00444A63 E8C4EFFBFF call 00403A2C :00444A68 8B459C mov eax, dword ptr [ebp-64] ;; 23. CHAR OF SERIAL :00444A6B E80033FCFF call 00407D70 ;; HERE WE GET THE ERROR MESSAGE So we have to change the serial in this way: Current serial (all info we have, yet): 1234567890-XXXXXXXXXX-0X :00444A84 894594 mov dword ptr [ebp-6C], eax ;; GETS "5" FROM "8915-DA" ;; WHICH IS MY GIVEN SERIAL :00444A87 DB4594 fild dword ptr [ebp-6C] ;; LOADS 5 in FPU REGISTER :00444A8A 83C4F4 add esp, FFFFFFF4 :00444A8D DB3C24 fstp tbyte ptr [esp] :00444A90 9B wait :00444A91 6802400000 push 00004002 :00444A96 68000000A0 push A0000000 :00444A9B 6A00 push 00000000 :00444A9D 6802C00000 push 0000C002 :00444AA2 68000000A0 push A0000000 :00444AA7 6A00 push 00000000 :00444AA9 E8FE71FDFF call 0041BCAC ;; THIS CALL LOADS 1.e-10 INTO ST0 :00444AAE DB7D88 fstp tbyte ptr [ebp-78] :00444AB1 9B wait :00444AB2 8B45F4 mov eax, dword ptr [ebp-0C] ;;GETS CHARS 12-21 FROM SERIAL :00444AB5 E82E33FCFF call 00407DE8 :00444ABA 894580 mov dword ptr [ebp-80], eax :00444ABD 895584 mov dword ptr [ebp-7C], edx :00444AC0 DF6D80 fild qword ptr [ebp-80] ;; LOADS THE CHARS INTO ST0 :00444AC3 DB6D88 fld tbyte ptr [ebp-78] ;; LOADS 1.e-10 INTO FPU-REGISTER :00444AC6 DEC9 fmulp st(1), st(0) ;; MULTIPLYS BOTH VALUES :00444AC8 89B57CFFFFFF mov dword ptr [ebp+FFFFFF7C], esi :00444ACE DB857CFFFFFF fild dword ptr [ebp+FFFFFF7C] ;; LOADS 23.CHAR INTO ST0 :00444AD4 DEC1 faddp st(1), st(0) ;; THE NEXT OPERATIONS PERFORM :00444AD6 83C4F4 add esp, FFFFFFF4 ;; SOME MATHMATICAL STUFF WHICH GOES :00444AD9 DB3C24 fstp tbyte ptr [esp] ;; QUITE BEYOND MY MATHMATICAL :00444ADC 9B wait ;; SKILL, ESPECIALLY WHEN YOU TRACE INTO THE :00444ADD E8CA71FDFF call 0041BCAC ;; CALLS - YOU CAN REVERSE IT, IF YOU :00444AE2 DB7DEA fstp tbyte ptr [ebp-16] ;; ARE SKILLED ENOUGH - I FOUND :00444AE5 9B wait ;; ANOTHER WAY TO GET THIS PART OF THE SERIAL :00444AE6 895D94 mov dword ptr [ebp-6C], ebx :00444AE9 DB4594 fild dword ptr [ebp-6C] ;; LOAD 35093 (WHICH IS 8915h) :00444AEC DB6DEA fld tbyte ptr [ebp-16] ;; LOADS CALCULATED VALUE :00444AEF DEE1 fsubrp st(1), st(0) ;; SUBTRACTS 35093 FROM VALUE :00444AF1 D9E1 fabs ;; MAKE NEW VALUE POSITIVE :00444AF3 DB2D64504400 fld tbyte ptr [00445064] ;; LOADS 5.e-6 :00444AF9 DED9 fcompp ;; COMPARES VALUE WITH 5.e-6 :00444AFB DFE0 fstsw ax ;; WRITE FPU FLAGS IN AX :00444AFD 9E sahf ;; WRITE AX INTO FLAG REGISTER :00444AFE 736B jnb 00444B6B ;; IF CARRY FLAG SET (VALUE1 <= 5.e-6) Ok, I was not able to reverse the mathmatical functions performed here, as they contain some nice LOG and POW functions which go beyond my mathematical horizon ;) The lines 444AE9-444AFE reveal that the value that needs to be calculated must be around 35093 (which is the dez-value of my given serial 8915-DA) as this value - 35093 must be smaller than 5.e-6. Luckily the calculation is completely linear although it contains so many nice functions. We see above that the input-parameters for the calculation are the chars 12-21 (444AC0) and the 23.char (444ACE). So, I just tried to get a valid serial by trying several possibilities. This was my row. 0000000000-XXXXXXXXXX-0X 0000000000-XXXXXXXXXX-5X 0000000000-XXXXXXXXXX-6X 5000000000-XXXXXXXXXX-6X 5500000000-XXXXXXXXXX-6X 5020000000-XXXXXXXXXX-6X ... 5027404155-XXXXXXXXXX-6X Although this looks quite complicated and time expensive, I easily did it in 2 or 3 mins and you cannot tell me that reversing the maths stuff is easier and faster ;) btw: 5027404154-XXXXXXXXXX-6X would work, too. If the jnb... is taken, you'll come here: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444AFE(C) | :00444B6B 8D55E4 lea edx, dword ptr [ebp-1C] :00444B6E 8B87E8020000 mov eax, dword ptr [edi+000002E8] :00444B74 E843EFFDFF call 00423ABC :00444B79 33C0 xor eax, eax :00444B7B 8945E0 mov dword ptr [ebp-20], eax :00444B7E 8D55A0 lea edx, dword ptr [ebp-60] :00444B81 8B87E8020000 mov eax, dword ptr [edi+000002E8] :00444B87 E830EFFDFF call 00423ABC :00444B8C 8B45A0 mov eax, dword ptr [ebp-60];; NAME :00444B8F E870EFFBFF call 00403B04 ;; GETS LENGTH OF NAME :00444B94 8BF0 mov esi, eax ;; LENGTH :00444B96 83EE05 sub esi, 00000005 ;; LENGTH - 5 :00444B99 85F6 test esi, esi :00444B9B 7E26 jle 00444BC3 ;; IF LENGTH OF NAME <= 5 THEN "WRONG SERIAL" :00444B9D BB01000000 mov ebx, 00000001 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444BC1(C) | :00444BA2 8D55A0 lea edx, dword ptr [ebp-60] :00444BA5 8B87E8020000 mov eax, dword ptr [edi+000002E8] :00444BAB E80CEFFDFF call 00423ABC :00444BB0 8B45A0 mov eax, dword ptr [ebp-60] ;; NAME :00444BB3 8A441804 mov al, byte ptr [eax+ebx+04] ;; GET CHAR FROM NAME :00444BB7 25FF000000 and eax, 000000FF :00444BBC 0145E0 add dword ptr [ebp-20], eax ;; ADD ALL CHARS FROM CHAR4 TO ;; END AND SAVE SUM IN ebp-20 :00444BBF 43 inc ebx :00444BC0 4E dec esi ;; NEXT CHAR :00444BC1 75DF jne 00444BA2 ;; IF NOT LAST CHAR, THEN JUMP --- SNIP, SNIP (SOME CALCULATION) --- :00444C23 8B4598 mov eax, dword ptr [ebp-68] ;; YOUR SERIAL :00444C26 B901000000 mov ecx, 00000001 :00444C2B BA18000000 mov edx, 00000018 :00444C30 E8D3F0FBFF call 00403D08 ;; GETS 18h (LAST) CHAR OF SERIAL :00444C35 8B45A0 mov eax, dword ptr [ebp-60] ;; LAST CHAR :00444C38 8B55DC mov edx, dword ptr [ebp-24] ;; "1" :00444C3B E8D4EFFBFF call 00403C14 ;; COMPARES LAST CHAR TO "1" :00444C40 0F85F1020000 jne 00444F37 ;; IF NOT EQUAL, THEN "WRONG SERIAL" So, here the last char of our serial is compared with "1" So our new serial is "5027404155-XXXXXXXXXX-61" The next important things can be found a few lines later: :00444C58 8B45A0 mov eax, dword ptr [ebp-60] ;; SERIAL :00444C5B B90A000000 mov ecx, 0000000A :00444C60 BA0C000000 mov edx, 0000000C :00444C65 E89EF0FBFF call 00403D08 ;; THIS CALL GETS THE CHARS 12-21 :00444C6A B001 mov al, 01 ;; FLAG :00444C6C BB01000000 mov ebx, 00000001 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444C8B(C) ;; THIS LOOP TESTS IF ALL OF THE CHARS 12-21 ARE LETTERS BETWEEN A AND Z | :00444C71 8B55D4 mov edx, dword ptr [ebp-2C] :00444C74 8A541AFF mov dl, byte ptr [edx+ebx-01] :00444C78 80FA41 cmp dl, 41 :00444C7B 7208 jb 00444C85 :00444C7D 8B4DD4 mov ecx, dword ptr [ebp-2C] :00444C80 80FA5A cmp dl, 5A :00444C83 7602 jbe 00444C87 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444C7B(C) | :00444C85 33C0 xor eax, eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00444C83(C) | :00444C87 43 inc ebx :00444C88 83FB0B cmp ebx, 0000000B :00444C8B 75E4 jne 00444C71 :00444C8D 84C0 test al, al :00444C8F 0F84A2020000 je 00444F37 ;; IF NOT ALL CHARS ARE LETTERS, THEN JUMP Now there are - once again - lots of calculations, the next important things are here: :00444DA9 DB6DAC fld tbyte ptr [ebp-54] ;; LOADS 0 INTO FPU REGISTER :00444DAC E86FDAFBFF call 00402820 :00444DB1 897594 mov dword ptr [ebp-6C], esi :00444DB4 DB4594 fild dword ptr [ebp-6C] ;; LOADS CALCULATED VALUE :00444DB7 DED9 fcompp ;; COMPARES THEM :00444DB9 DFE0 fstsw ax :00444DBB 9E sahf :00444DBC 7448 je 00444E06 ;; IF THEY ARE EQUAL, THEN "GOOD SERIAL" :00444DBE DB6DAC fld tbyte ptr [ebp-54] ;; YOU GET ANOTHER CHANGE NOW :00444DC1 E85ADAFBFF call 00402820 :00444DC6 D825D4504400 fsub dword ptr [004450D4] :00444DCC 89B57CFFFFFF mov dword ptr [ebp+FFFFFF7C], esi :00444DD2 DB857CFFFFFF fild dword ptr [ebp+FFFFFF7C] :00444DD8 DED9 fcompp ;; COMPARES NEW VALUES :00444DDA DFE0 fstsw ax :00444DDC 9E sahf :00444DDD 7427 je 00444E06 ;; IF EQUAL, THEN "GOOD SERIAL" :00444DDF DB6DAC fld tbyte ptr [ebp-54] ;; IF NOT, YOU GET A 3rd CHANCE :00444DE2 E839DAFBFF call 00402820 :00444DE7 B841000000 mov eax, 00000041 :00444DEC 2BC6 sub eax, esi :00444DEE BA5A000000 mov edx, 0000005A :00444DF3 2BD0 sub edx, eax :00444DF5 895594 mov dword ptr [ebp-6C], edx :00444DF8 DB4594 fild dword ptr [ebp-6C] :00444DFB DED9 fcompp ;; COMPARE NEW VALUES :00444DFD DFE0 fstsw ax :00444DFF 9E sahf :00444E00 0F8531010000 jne 00444F37 ;; IF NOT EQUAL, THEN "BAD SERIAL" * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:00444DBC(C), :00444DDD(C) | :00444E06 43 inc ebx ;; NEXT CHAR :00444E07 83FB06 cmp ebx, 00000006 :00444E0A 0F85C4FEFFFF jne 00444CD4 Now we have to find out how this value that gets compared with 0 is calculated. The only thing that can be used to calculate this serial are the chars 12 to 21. And - as you can see, if you change them - those values are used. The first time I came to this offset the value was -12. Then I changed the char 12 from X to A and the value was 11. So, I came to the conclusion that the correct char lies somewhere between A and X. If you go on trying it is "L". If you go on for the next 4 chars, you will see that the serial will look like this: So our new serial is "5027404155-LAZARXXXXX-61" So the chars 12-16 are the first 5 chars of our name. But this seems to be a nice incident, as the serial for "Totenmond" is "5027404155-PDPEIXXXXX-65" at this part. If you can't use the first "Good serial" comparison for your name (for example, for the name "Eisregen" then you just have to go to the next fcompp and look for the value there. For the name "LAZARUS" the following values are "good serials". POS:1 2 3 4 5 CMP 1 L A Z A R 2 M B S 3 Z Z So the following strings are possible: 01. LAZAR 02. MAZAR 03. LBZAR 04. MBZAR 05. LZZAR 06. MZZAR 07. LAZZR 08. MBZZR 09. MZZAR 10. MZZZR 11. MAZZR 12. LBZZR 13. LAZAS 14. MAZAS 15. LBZAS 16. MBZAS 17. LZZAS 18. MZZAS 19. LAZZS 20. MBZZS 21. MZZAS 22. MZZZS 23. MAZZS 24. LBZZS If you remember that for the first 10 digits both, 5027404154 and 5027404155 are valid numbers, then you see that for the name "LAZARUS" there are 48 different correct serials. This should make clear that it was only luck that LAZAR was the first string I found. If you keep on tracing over dozens of codes you will come to this part: :00444EDE 8B45C0 mov eax, dword ptr [ebp-40] ;; CHARS 17-21 :00444EE1 8B55A8 mov edx, dword ptr [ebp-58] ;; "FRUIT" :00444EE4 E82BEDFBFF call 00403C14 ;; COMPARES CHARS WITH "FRUIT" :00444EE9 754C jne 00444F37 ;; IF THEY ARE NOT THE SAME, THEN "WRONG" Now we try this serial: 5027404155-LAZARFRUIT-61 ...and "Congratz! You succeeded in the CrackMe" - way kewl :] Now I could be a member of CiA ;) btw: I cannot be bothered to write a keygen or a keyfile-creator as I don't have time to waste ;) III. BTW In the end I can say that I liked Northpole'c CiA CrackMe more than tKC's, but less than BuLLeT's. But nevertheless, Northpole: good work :) 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, 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.