in mid 99, read and enjoy, it's worth the trip.
"Aescul.exe, in particular is a very small protection, there is no overhead in it to "hide the sensitive locations"; it should be, therefore easier to grasp if compared with a 5 Mb executable using the same techniques, so don't forget to picture that when you're reversing it. The whole program (in case you still don't get it) is a protection, so one of the first steps in cracking (locate the protection) is not necessary, because the whole thing is a protection. Notice the performance impact that it exerts in your system. It was created using only assembly and a Resource Editor for the fancy presentation, nothing else. Some functions are repeated over the code (to confuse a little the wannabe's) and some API calls are not strictly necessary (I can't tell you which of them at this time because that would spoil the fun). In part, the idea behind these failures (which I'm portraying for you beforehand) is to see how far can you go to improve it. I suspect the Key algorithm will produce strange looking keys in a WinNT system (I can't explain why for obvious reason, at this time), I am not sure because the NT system where I test was not available when I coded it, however, this is not strictly necessary to solve the strainer if you understand how it works. Well, boring preambles are always important, now the objectives: To successfully approve this challenge, the participant must: 1. Decompile Aescul.exe and explain prolixly how it works. The better you explain the higher you qualify in this challenge. 2. Improve Aescul.exe or write something better than it. 3. Create a Key Generator for it (this final step is compulsory to approve)." +aesculapius The execution of aescul.exe differs on WinNT, Win98 and Win95 so it is possible some of my comments to not be exact. I haven't test it on Win98. If you have Win95 you may receive the following error: "The AESCUL.EXE file is linked to missing export KERNEL32.DLL:IsDebugerPresent" trying to execute aescul.exe. To correct this simply open the file with a Hex Editor and search for "IsDebuggerPresent" string. Change it for instance with "GetThreadContext"#0. Do not forget the last zero byte, because IsDebuggerPresent has one more letter that GetThreadContext. This will solve the problem with the missing export, cuz aescul.exe imports this function but do not call it.
Part1: Decryption and KeyGen Well, lets start and to try complete the first point of the requirements for this very interesting challenge. For the beginning lets take a look at the PE header of the executable: Object01: CODE RVA: 00001000 Offset: 00000600 Size: 00002600 Flags: E0000020 Object02: DATA RVA: 00004000 Offset: 00002C00 Size: 00000C00 Flags: C0000040 Object03: .idata RVA: 00005000 Offset: 00003800 Size: 00000400 Flags: C0000040 Object04: .reloc RVA: 00006000 Offset: 00003C00 Size: 00000200 Flags: 50000040 Object05: .rsrc RVA: 00007000 Offset: 00003E00 Size: 00001200 Flags: 50000040 Program Entry Point = 00401016 (aescul.exe File Offset:00003C16) So all here looks normal. All the flags of the sections are ok, the program entry point is in the CODE section (just a little shifted), there are no strange named sections. So let's disassemble it with W32DASM. Disassembling goes ok. When take a look at the source the first impressive thing is that very huge amount of NOPs from 401038 to 404037. These are exactly 2000 bytes!!! But according the +aesculapius description we can guess that on this place have been situated the sice detection and any other debugger detection code that he had been removed to make the aescul.exe more cracker friendly:) So lets take a look after this really big amount of NOPs: :00403038 33C0 xor eax, eax :0040303A 6893334000 push 00403393 :0040303F 64FF30 push dword ptr fs:[eax] :00403042 648920 mov dword ptr fs:[eax], esp :00403045 9C pushfd :00403046 9C pushfd :00403047 58 pop eax :00403048 0D00010000 or eax, 00000100 :0040304D 50 push eax :0040304E 9D popfd :0040304F 90 nop :00403050 CC int 03 <- Notice this INT 3 and the 100% wrong <- ASM instrucions after it :00403051 2133 and dword ptr [ebx], esi :00403053 00CC add ah, cl * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00403065(C) | :00403055 5A pop edx :00403056 D196DE96EFFF rcl dword ptr [esi+FFEF96DE], 1 Lets fire up our sice and take a closer look. I put a bpx on 403038 and this is what I saw: sice w32dasm asm source 00403038 xor eax, eax | xor eax, eax 0040303A push 00403393 | push 00403393 0040303F push dword ptr fs:[eax] | push dword ptr fs:[eax] 00403042 mov dword ptr fs:[eax], esp | mov dword ptr fs:[eax], esp 00403045 pushfd | pushfd 00403046 pushfd | pushfd 00403047 pop eax | pop eax 00403048 or eax, 00000100 | or eax, 00000100 0040304D push eax | popfd 0040304E popfd | popfd 0040304F nop | nop 00403050 xor esi, esi | int 03 00403052 xor edi, edi | and dword ptr [ebx], esi Another thing I'd like to note is that if you run the target without debugger and without any bpxs in sice when you write some #name and #reg nothing happens. The target just exits without any messages. But if there is a debugger or if you have a break in sice after putting something for #name and #reg you'll receive the dialog box message "Wrong Serial Number!" Note: This happens only under WinNT! I think we should go down to the OPCODE level and see what happens there. Lets start our WinHack (or any other real time memory editor) and have a look at the opcodes on 403038. You will probably see this shown below. If you are under Win95 you may not be able to use WinHack. When I tried it under Win95 I received a message "Integer Overflow". winhack CC C0 68 93 33 40 00 64 FF 30 64 89 20 9C 9C 58 sice 33 C0 68 93 33 40 00 64 FF 30 64 89 20 9C 9C 58 w32dasm 33 C0 68 93 33 40 00 64 FF 30 64 89 20 9C 9C 58 winhack 0D 00 01 00 00 50 9D 90 CC 21 33 00 CC 5A D1 96 sice 0D 00 01 00 00 50 9D 90 33 F6 33 FF 33 D2 8B 2D w32dasm 0D 00 01 00 00 50 9D 90 CC 21 33 00 CC 5A D1 96 Well, well ... It is very clear that the code is totally different from one point ahead. Note the really total difference! You also can make another experiment. Look at the opcodes when the aescul.exe is started without debugger and when is started with debugger. You'll also find a difference. For now I can only note that NEG(CC)=33 !! OK. Anyway we will need any listing of the asm code of the target, at this point we will not care if this is the right or most of it is wrong :). Here is one way to get some asm source. Disassemble eascul.exe with W32DASM and then start to debug the target with the W32DASM itself. Put a breakpoint at 403038 and when the debuggee stops there take a look at the right window where is listed the asm source that is executed. Just use that "copy" button and then past it in notepad. Do not step in the debuggee. Just change pages and copy each of the pages and past them in notepad till you have the full asm source code. When you have it you may improve it a little putting all the JMP and CALL references you can find by yourself and make the whole stuff to look little more friendly. Also you may comment all the API calls. So, lets start from the beginning and have a quick look at the code starting from our breakpoint after the huge NOP area at 403038: FRAGMENT 1: ---------- 00403045 pushfd ; pushes the flag register in the stack 00403046 pushfd ; pushes the flag register in the stack again 00403047 pop eax ; pops the flags in EAX from the stack 00403048 or eax, 00000100 ; sets the 9-th flag to 1 0040304D push eax ; pushes EAX back (with changed flag) 0040304E popfd ; loads the flags back 0040304F nop ; ........ 004030FA popfd ; loads the original flags (that have been at 403045) 004030FB xor eax, eax ; 004030FD pop dword ptr fs:[eax] ; 00403100 add esp, 00000004 ; 00403103 cmp dword ptr [004044F2], 00000001; this for me is some type of check, may be important one 0040310A jne 00403121 ; and this can be the good/bad cracker jump (note: Further it appears that the last 2 comments are not fully correct) Lets take a look which is this 9-th flag? It is the "Trap Flag". Well, we'll get back to this code fragment later. Let's now continue: 0040310C push 00000040 0040310E push 0040403D 00403113 push 004041AB 00403118 push 00000000 0040311A call aescul.004034C7 ; USER32.MessageBoxA 0040311F jmp aescul.00403134 00403121 push 00000030 ; will be here if [004044F2] doesn't contain 1 00403123 push 00404050 00403128 push 00404056 0040312D push 00000000 0040312F call aescul.004034C7 ; USER32.MessageBoxA 00403134 push 00000000 00403136 call aescul.00403503 ; KERNEL32.ExitProcess FRAGMENT 2: ----------- Lets try to break in the target other way for example with GetDlgItemTextA and lets trace a little with our sice. Just write something as UserName and as SrialNumber and when the sice pops press F12. This is what you should see: |* Referenced by jumps from (C)00403211 | 00403224 push 00000040 00403226 push 004041BA ; the User Name will be read in 004041BA 0040322B push 000003E8 ; this should be the resource ID of the UserName Edit 00403230 push [ebp+08] 00403233 call aescul.004034C1 ; USER32.GetDlgItemTextA, read the User Name 00403238 mov edi, 004041BA ; <- you should be here 0040323D xor eax, eax 0040323F or ecx, FFFFFFFF 00403242 repnz 00403243 scasb 00403244 not ecx 00403246 sub edi, ecx 00403248 mov dword ptr [00404B06], ecx ; moves in [00404B06] the length of the UserName +1 0040324E cmp byte ptr [004041BA], 00 ; is the UserName an empty string ? 00403255 push 00000040 00403257 push 004042BA ; the RegNumber will be read in 0042BA 0040325C push 000003E9 ; this should be the resource ID of the RegNum Edit 00403261 push [ebp+08] 00403264 call aescul.004034C1 ; USER32.GetDlgItemTextA, read the RegNumber 00403269 mov edi, 004042BA 0040326E xor eax, eax 00403270 or ecx, FFFFFFFF 00403273 repnz 00403274 scasb 00403275 not ecx 00403277 sub edi, ecx 00403279 mov dword ptr [00404B0A], ecx ; moves in [00404B0A] the length of #RegN+1 0040327F cmp byte ptr [004042BA], 00 ; is the RegNum an empty string ? 00403286 push 00404B0E 0040328B push 00000001 0040328D push 00000000 0040328F push 00404000 ; this is Software\Microsoft\Windows\CurrentVersion 00403294 push 80000002 ; this is HKEY_LOCAL_MACHINE 00403299 call aescul.0040350F ; ADVAPI32.RegOpenKeyExA ; open the key 0040329E push 00404B12 004032A3 push 004043BC ; here will be read the value ; if you type "d 4043BA" you should see "WS" 004032A8 push 00000000 004032AA push 00000000 004032AC push 00404033 ; this is ProductID 004032B1 push dword ptr [00404B0E] 004032B7 call aescul.00403509 ; ADVAPI32.RegQueryValueExA ; read the value 004032BC mov edi, 004043BA ; The ProductID string 004032C1 xor eax, eax 004032C3 or ecx, FFFFFFFF 004032C6 repnz 004032C7 scasb 004032C8 not ecx 004032CA sub edi, ecx 004032CC mov dword ptr [004044BC], ecx ; moves in [004044BC] the length of the value +1 004032D2 cmp dword ptr [004044FA], 00000001 ; Hm .... What is this? 004032D9 je 004032EC ; And what about this jump? 004032DB mov esi, 00403050 ; esi points 00403050 (this is in the CODE segment !) 004032E0 mov edi, esi ; 004032E2 mov ecx, 00000134 ; And what is this? Looks like some lenght 004032E7 call aescul.0040347A ; Well, and this interesting procedure ! Before taking a look at the next fragment I'd like to make the following note. In WindowsNT registry there is no ProductID value in the key HKLM\Software\Microsoft\Windows\CurrentVersion. And probably this should be the reason that registration numbers under NT could be strange - the note given by +aesculapius in the introduction of his aescul.exe challenge. So, lets continue and check this interesting procedure at 4032E7: FRAGMENT 3: ========== |* Referenced by call from 0040321F | 00403465 xor eax, eax ; 00403467 lodsb ; loads the next byte from edi to eax 00403468 rol al, cl ; rotate left AL, CL bytes 0040346A not al ; neg(AL) 0040346C stosb ; after this operation saves the byte back in edi 0040346D loop 00403465 ; loops until ecx=0 0040346F mov dword ptr [004044F6], 00000001 ; var_decoded_4044F6=True 00403479 ret |* Referenced by call from 004032E7 | 0040347A xor eax, eax ; 0040347C lodsb ; loads the next byte from edi to eax 0040347D rol al, cl ; rotate left AL, CL bytes 0040347F not al ; neg(AL) 00403481 stosb ; after this operation saves the byte back in edi 00403482 loop 00403465 ; loops until ecx=0 00403484 mov dword ptr [004044FA], 00000001 ; var_decoded_4044FA=True (??) 0040348E ret Well, well .... Lets take a closer look at the beginning of code - the difference between w32dasm and sice code. Here is what stays in the executable and what will happen to it: offset 403050: AL CC 21 33 00 CC 5A D1 96 (in the exe) CL 34 33 32 31 30 2F 2E 2D (CH=1) ROL AL,CL CC 09 CC 00 CC 2D 74 D2 NOT AL 33 F6 33 FF CC D2 8B 2D (in sice) Note that the decrypting code is repeated 2 times. Also note the LOOP instruction at 403482 loops to 403465 instead of looping to 40347A. Also notice that 40347A is referenced by call from 4032E7 and 403465 is referenced by a call from 40321F. All this means that instruction at 303484 is never executed and the var_decoded_4044FA is never set to one. This also means that the check at: 004032D2 cmp dword ptr [004044FA], 00000001 will never return true normally. May be this is the code that +aesculapius said in the introduction that is repeated twice just to confuse the cracker. Also my guess that there is a different code according to debugger present/ debugger not present was wrong. The truth for now is that the code that makes the reg number calculations is available during the small time after the user name and entered reg number have been read. So we have been lucky bpxing the 403038 and getting the asm code from W32DASM cuz this offset is reached after the code is decrypted. I think that this code we have is the whole and the correct asm code of aescul.exe ! Lets also take a look at the other place the decryption procedure is called. It is just before start reading the user name and reg number from the edit controls: |* Referenced by jumps from (C)004031F5 | 0040320A cmp dword ptr [004044F6], 00000001 ; if already decrypted 00403211 je 00403224 ; jumps the next part 00403213 mov esi, 00404000 ; the decryption will start at 404000 ; (this is in the DATA segment) 00403218 mov edi, esi ; 0040321A mov ecx, 00000B16 ; and B16 bytes will be decrypted 0040321F call aescul.00403465 ; this is the call to decription procedure If you bpx before and after this call you'll notice some string resources in the DATA segment. For instance "Software\Microsoft\Windows\CurrentVersion", "Wrong Serial Number", "WS" and also one string we would like to see but haven't seen yet : "Congratulations!". Well, now we know what will be the message for the GoodCrackers:) BTW during I tried to put my next bpx I noticed that sice not always pops at GetDlgItemTextA calls ... Finally I hacked in the code of aescul.exe enabling all the breakpoints :) And I again put a bpx on 403038 because of the following important reason. Relaying on the data for now the code starting on 403050 is decrypted after the user name and the regnumber have been read and this offset is reached before the beginning of the real key calculation procedures. When the sice poped on 403038 I immediately wrote "d 4041AB", "d 4042AB" and "d 4043AB" to check are the strings there. They were :) So we can start exploration of the keygen algorithm from here. So I choose "DaFixer" as a user name and "1234567890ABCDEF" as a reg number. Remember that the serial number is concated to "WS" - may be "Windows Serial".(note: This is needed in the case of WinNT, where the value had to be NULL if there is no "WS" before it, and this may cost problems). So after I broke at 403038 and checked the strings and started to trace down: FRAGMENT 4: ---------- 00403038 xor eax, eax 0040303A push 00403393 ; this 3 instructions set the exception handling 0040303F push dword ptr fs:[eax] ; procedure at 403393 (we'll check it later) 00403042 mov dword ptr fs:[eax], esp ; 00403045 pushfd ; this is clear (almost clear :) ) 00403046 pushfd ; 00403047 pop eax ; 00403048 or eax, 00000100 ; 0040304D push eax ; 0040304E popfd ; 0040304F nop 00403050 xor esi, esi ; clears esi 00403052 xor edi, edi ; clears edi 00403054 xor edx, edx ; clears edi 00403056 mov ebp, dword ptr [00404B12] ; moves in ebp 25h 0040305C mov edi, 00404502 ; edi point to 404502 - the address where the ; real reg num will be situated |* Referenced by jumps from (C)00403097 | 00403061 push ebp ; 00403062 push edi ; 00403063 push esi ; 00403064 mov ebp, 004044C0 ; ebp points to a hardcoded byte sequence 00403069 mov ebx, 004043BA ; moves in EBX the ProductID 0040306E mov al, byte ptr [ebx+esi] ; reads a symbol from the ProductID in AL 00403071 sar eax, 04 ; shifts al 00403074 and eax, 0000000F ; filters only the last digit (High digit of the byte) 00403077 call aescul.0040313B ; small procedure that calculates something 0040307C mov byte ptr [edi], al ; moves the result in EDI 0040307E mov cl, byte ptr [ebx+esi] ; loads the same byte in CL 00403081 and ecx, 0000000F ; and gets the Low digit in AL 00403084 mov eax, ecx ; moves it in EAX 00403086 call aescul.0040313B ; to call the calculation procedure 0040308B mov byte ptr [edi+01], al ; again moves the result EDI+1 0040308E pop esi ; 0040308F pop edi ; 00403090 pop ebp ; ebp is $25 again 00403091 inc esi ; increases the counter 00403092 add edi, 00000002 ; increases the pointer for next 2 symbols or the #reg 00403095 cmp ebp, esi ; is the counter equal to EBP = 25h 00403097 jne 00403061 ; if no then deals with the next symbol from ; the ProductID 00403099 xor esi, esi ; clears esi 0040309B mov eax, dword ptr [esi+004042BA] ; loads the first 4 chars of our #reg ; in eax as a hex values 004030A1 mov ebx, dword ptr [esi+00404502] ; loads the first 4 chars of correct #reg ; in ebx as hex values 004030A7 cmp eax, ebx ; compare them 004030A9 jne 004030F0 ; and jump away if a bad guy 004030AB add esi, 00000004 ; incrases the counter with 4 004030AE mov eax, dword ptr [esi+004042BA] ; to check the next 4 chars from our 004030B4 mov ebx, dword ptr [esi+00404502] ; and the correct #reg 004030BA cmp eax, ebx 004030BC jne 004030F0 ; and jump away if bad guy 004030BE add esi, 00000004 ; again increase with 4 004030C1 mov eax, dword ptr [esi+004042BA] ; for the next 4 chars 004030C7 mov ebx, dword ptr [esi+00404502] 004030CD cmp eax, ebx 004030CF jne 004030F0 ; 004030D1 add esi, 00000004 ; 004030D4 mov eax, dword ptr [esi+004042BA] ; and for the last 4 chars 004030DA mov ebx, dword ptr [esi+00404502] ; this means the correct #reg is from 16 chars 004030E0 cmp eax, ebx 004030E2 jne 004030F0 ; the last bad guy jump 004030E4 mov dword ptr [004044F2], 00000001 ; GoodCracker=True 004030EE jmp aescul.004030FA ; jumps the bad flag set If now you write "d 404502" you'll se the correct reg number "57U3PPPPPPPPPPPP" (note: If you are under Win9x only the first 4 chars will match) This number is only for WinNT or more exact for not existing ProductID value in the HKLM\Software\Microsoft\Windows\CurrentVersion key |* Referenced by jumps from (C)004030A9, (C)004030BC, (C)004030CF, (C)004030E2 | 004030F0 mov dword ptr [004044F2], 00000000 ; GoodCracker=False |* Referenced by jumps from (U)004030EE | 004030FA popfd ; pops the original flags from the stack ; these from before OR EAX, 00000100 004030FB xor eax, eax 004030FD pop dword ptr fs:[eax] ; sets the exception handling procedure to 00403100 add esp, 00000004 ; the default one (noone) 00403103 cmp dword ptr [004044F2], 00000001 ; Is a GoodCracker ? 0040310A jne 00403121 ; if no goes to 403121 0040310C push 00000040 ; prepares the good MessageBox 0040310E push 0040403D ; with caption "Congratulations!" 00403113 push 004041AB ; and message "registered to:" your name 00403118 push 00000000 ; parent window=null 0040311A call aescul.004034C7 ; USER32.MessageBoxA ; shows the message box 0040311F jmp aescul.00403134 ; and exits |* Referenced by jumps from (C)0040310A | 00403121 push 00000030 ; bad guys are here 00403123 push 00404050 ; with bad caption 00403128 push 00404056 ; and bad message 0040312D push 00000000 0040312F call aescul.004034C7 ; USER32.MessageBoxA ; shows the message box |* Referenced by jumps from (U)0040311F | 00403134 push 00000000 00403136 call aescul.00403503 ; KERNEL32.ExitProcess ; and halts the program So lets now take a look at that calculation procedure to find out how the digits of each char of the ProductID are turned to char of the correct registration number. Before this I wrote down the char sequence at 4044C0, that is used during the key generation and is stored in ebp. Here is it: 004044C0 30 49 35 4C 5A 37 47 31 32 33 52 58 43 56 39 4F 004044D0 50 41 53 36 54 42 4E 34 38 59 55 48 4A 4B 44 46 004044E0 30 51 57 45 4D 00 25 00 00 00 00 00 00 00 00 00 FRAGMENT 5: ---------- enters with: EAX - containing the digit that will be proceeded |* Referenced by Call from 00403077, 00403086 | 0040313B mov dword ptr [004044EE], esi ; saves esi in 4044EE 00403141 mov edx, dword ptr [004044EA] ; loads in edx a byte variable (B1) 00403147 mov ecx, dword ptr [004044E6] ; loads in ecx another byte variable (B1) (always $25) 0040314D cmp edx, ecx ; is B1<25h ? (25h is the size of the hardcoded sequence) 0040314F jb 00403153 ; if true then jumps 00403151 xor edx, edx ; else B1=0; |* Referenced by jumps from (C)0040314F, (C)0040316C ,(U)00403153 | 00403153 movsx esi, byte ptr [ebp+edx] ; S=EBP[B1] 00403158 and esi, 8000000F ; S=S and $8000000F 0040315E jns 00403165 ; is S>=0 ? 00403160 dec esi ; if no Dec(S) 00403161 or esi, FFFFFFF0 ; S=S or $FFFFFFF0 00403164 inc esi ; and again Inc(S) (S=-S) |* Referenced by jumps from (C)0040315E | 00403165 cmp esi, eax ; is S=A ? 00403167 je 00403172 ; if yes jumps to the end of the proc 00403169 inc edx ; else increases B1 0040316A cmp edx, ecx ; is B1<25h ? 0040316C jl 00403153 ; if starts again 0040316E xor edx, edx ; B1=0; 00403170 jmp aescul.00403153 ; starts again with B1=0 |* Referenced by jumps from (C)00403167 | 00403172 mov dword ptr [004044EA], edx ; stores B1 (global variable) 00403178 mov esi, dword ptr [004044EE] ; restored ESI 0040317E movsx eax, byte ptr [edx+ebp] ; rezult=EBP[B1] 00403182 inc edx ; 00403183 ret So here it is a small keygen written in Delphi:
Const Arr : Array [0..$24] of Byte = ($30, $49, $35, $4C, $5A, $37, $47, $31, $32, $33, $52, $58, $43, $56, $39, $4F, $50, $41, $53, $36, $54, $42, $4E, $34, $38, $59, $55, $48, $4A, $4B, $44, $46, $30, $51, $57, $45, $4D);
var i : Integer; s,ss : String; bKeyExists : Boolean; dakey : HKEY; sz,tp : DWORD;
function GetRegNumber(ProductID : String) : String; var b1,b2: DWORD; c,c1,c2 : Byte; s1 : String;
function GetRegChar(c : Byte) : Byte; asm push esi mov edx, B1 mov ecx, B2 mov ah, 0 mov al, C cmp edx, ecx jb @@1 xor edx, edx @@1: movsx esi, byte ptr [Offset(Arr)+edx] and esi, $8000000F jns @@2 dec esi or esi, $FFFFFFF0 inc esi @@2: cmp esi, eax je @@3 inc edx cmp edx, ecx jl @@1 xor edx, edx jmp @@1 @@3: mov B1, edx movsx eax, byte ptr [Offset(Arr)+edx] pop esi End;
begin s1:=''; b1:=0; b2:=$25; i:=-1; Repeat Inc(i); C:=0; If i<Length(ProductID)+1 Then C:=ORD(ProductID[i+1]); c1:=C div 16; c1:=GetRegChar(c1); c2:=C mod 16; c2:=GetRegChar(c2); s1:=s1+CHR(c1)+CHR(c2); Until i=7; Result:=s1; end;
begin sz:=100; SetLength(ss,sz); tp:=REG_SZ; If RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar('Software\Microsoft\Windows\CurrentVersion'),0,KEY_QUERY_VALUE,dakey) <>ERROR_SUCCESS Then Halt; Try bKeyExists:=RegQueryValueEx(dakey,PChar('ProductId'),nil,@tp,@ss,@sz)=ERROR_SUCCESS; Finally RegCloseKey(dakey); End;
ss:='WS'+ss; s:=GetRegNumber(ss); If bKeyExists Then s:=Copy(s,1,2*Length(ss)-2); s:=Copy(s,1,16); MessageBox(0,PChar('The key for this WindowsID is: '+s),PChar('Y2K +HCU aescul.exe - KeyGen by DaFixer'),0); end.
Part 2: Antidebugging techniques Until now we saw the decryption procedure and the key gen procedure. We were lucky we had this bpx on 403038 and got the asm code of aescul.exe. There is still a lot of code from aescul.exe that we didn't look up to now. Before all I'd like to note the following fact I found with experiment. If patch the exe : 00403048 or eax, 00000100 with 00403048 or eax, 00000000 you'll find that the reg numbers generated by our keygen pass successfully on WinNT. And this is not all. You will receive not only the good guy message but also the bad guy message. (note: With this little patch you can make aescul.exe to accept the #reg numbers generated by our keygen under NT. +Aesculapius have said he had not been test the program under NT. So the missing messages under NT is just a bug!) Other thing we haven't done is we haven't checked the exception handler at 403393. Before doing this let's make another experiment. Let's just patch the code and remove this exception handling procedure. Setting of this procedure is patched easy with 10 NOPs:) :0040303A push 00403393 -> NOP; NOP; NOP; NOP; NOP :0040303F push dword ptr fs:[eax] -> NOP; NOP; NOP :00403042 mov dword ptr fs:[eax], esp -> NOP; NOP; NOP I'd like to note that here NOP is 90! To patch the code that removes it we should patch this: :004030FD 648F00 pop dword ptr fs:[eax] and we should work a little. The reason is that this offset normally in the executable is encrypted. So if we want to explore the execution of aescul.exe without debuggers we should hard patch the exe. This will mean to perform the decryption algorithm back to find the write bytes. We should patch 3 bytes that starts at offset FD-50=AD from the beginning of the encrypted block.In the decrypting algorithm is used the lower byte of the offset from the end of the block instead from the beginning of the block. So the first byte at 004030FD will be at offset 134- AD=87 from the end of the block and will be RORed 87 times. But ROR operation is equal for 8, F,18, 1F , etc. So when encrypting it back we should ROR the first byte 7 times, the second byte at 4030FE - 6 times and the 3-th at 4030FF - byte 5 times. Before the ROR we should find the NEG() of the patch. NEG(90) = NOT 90 = 6F = 01101111. ROR 6F, 7 = ROR 01101111, 7 = 11011110 = DE ROR 6F, 6 = ROR 01101111, 6 = 10111101 = BD ROR 6F, 5 = ROR 01101111, 5 = 01111011 = 7B so finally the required patch that corresponds to NOP, NOP, NOP will be: :004030FD DEBD7B nop, nop, nop (encoded) When you patch the executable, without patching the OR EAX, 00000100 instruction, and running the patched aescul.exe you'll find this impressive result: aescul2.exe: Single Step Exception 0x80000004 at adress 0x403050 (WinNT) AESCUL95_EXC caused an exception 01H in module AESCUL95_EXC.EXE at 013f:00403050. Bytes at CS:EIP: 33 f6 33 ff 33 d2 8b 2d 12 4b 40 00 bf 02 45 40 (Win95) If we take a look at the description of the Trap Flag we'll find that when this flag is set the processor goes into Single Step Mode and after every step (executed instruction) an exception will be generated. This exception will be handled by the "exception handler" if any. Elsewhere the default windows exception handler will handle it and will show the above messages. But if there are other exception handlers, as SoftIce for example, the things will go rather complicated. The last thing I'd like to note is that the exception is raised after the execution of the instruction next to popfd. So this result shows the normal execution of aescul.exe without debugger. This means that in the original aescul.exe this exception is raised and the exception handling procedure handles it. So lets now have a look at this procedure. Just before this I'd like to note what stays at the beginning of the CODE section of aescul.exe: 00401000 EB14 jmp 00401016 00401002 58344000 DWORD 00403458 00401006 5C344000 DWORD 0040345C 0040100A 8F344000 DWORD 0040348F 0040100E 9E344000 DWORD 0040349E 00401012 A0344000 DWORD 004034A0 00401016 - Program Entry Point And here is the heaviest stuff of all the proggie !!! Before proceeding below you may take a look at the _CONTEXT structure, the GetThreadContext()/SetThreadContext() functions, the debug registers Dr0 to Dr7 description (MSDN is not enough). The best will be if you write your own debugger :) WOW ! But anyway it is not so awful ... FRAGMENT 6: ----------- 00403393 push ebp 00403394 mov ebp, esp ; saves the stack in ebp !!! 00403396 call aescul.004034F1 ; KERNEL32.GetCurrentThread 0040339B mov dword ptr [0040418F], eax ; the thread handle is in eax and it ; is moved to [0040418F] 004033A0 mov dword ptr [004040BB], 0000007C ; _CONTEXT.ContextFlags = 7C (?!) = ; CONTEXT_FLOATING_POINT ; OR CONTEXT_CONTROL ; OR CONTEXT_SEGMENTS ; OR CONTEXT_DEBUG_REGISTERS ; OR CONTEXT_EXTENDED_REGISTERS 004033AA push 004040BB ; the _CONTEXT structure will be received here 004033AF push dword ptr [0040418F] ; thread handle 004033B5 call aescul.004034E5 ; KERNEL32.GetThreadContext ; if eax=1 the function is succeeded ; under WinNT - 0 (?) !!! ; under Win95 - 1 004033BA mov dword ptr [004040BF], eax ; 004033BF mov dword ptr [004040C3], eax ; Dr1 = 0/1 004033C4 mov dword ptr [004040C7], eax ; Dr2 = 0/1 004033C9 mov dword ptr [004040CB], eax ; Dr3 = 0/1 004033CE mov dword ptr [004040D3], 000024FF ; Dr7 = 000024FF Lets have a look what means this 24FF. The Dr7 register is something like a flag register for debugging purposes. So this are the flags: Flags G RGL GLGLGLGL of Dr7: D sEE 33221100 24FF = 00100100 11111111 The most important think is setting of the GD flag. After this in any try of accessing the debug register from another application, like SoftIce, will generate a General-Detect Exception. The idea of this exception and this GD flag is to provide a full control over the debug registers when is necessary. This automatically means that after SetThreadContext() if you trace in sice this exception will be generated and you'll be again at the beginning of the exception handler at 403393. 004033D8 mov dword ptr [004040BB], 00000018 ; _CONTEXT.ContextFlags = 18 = ; CONTEXT_DEBUG_REGISTERS ; OR CONTEXT_FLOATING_POINT 004033E2 push 004040BB ; push the _CONTEXT structure 004033E7 push dword ptr [0040418F] ; thread handle 004033ED call aescul.004034F7 ; KERNEL32.SetThreadContext ; again unsuccessfull call under NT!!! ; and succesfull under Win95 If you set a bpx on 403393 and trace down under Win95 (with F10), immediately after the call to SetThreadContext() you'll be at 403393 again. The reason is that General-Detect Exception which is handled by the first registered exception handler - this one at 403393. If you bpx after the call to SetThreadContext(), when the SoftIce stops you'll see this: 004033ED Call SetThreadContext 004033F2 Int 3 <- You are here. After Ctrl+D <- your Windows (or exactly SoftIce) will freez your machine 004033F3 Inc ebp By the way, if you have a bpx anywhere after SetThreadContext() in the exception handler of aescul.exe the same windows "boom" will appear. The reason is this change of Dr7. The above calling of this Set/Get ThreadContext is not strictly needed for the normal flow of aescul.exe. So these are the APIs +Aesculpius refuses to note to not "spoil the fun". But they makes a big problems if we try to trace through the code. They also shows an advanced way to defeat debuggers, or may be lamers :) Lets continue with the code: 004033F2 mov eax, dword ptr [ebp+10] ; ebp point to the stack in the current ; call of the exception handler ; [ebp+10] point to a _CONTEXT structure (?) 004033F5 mov edi, eax ; _CONTEXT 004033F7 mov ebx, dword ptr [edi+000000B8] ; EBX points to the EIP, the program should ; contunue after exiting the exception ; handler 004033FD cmp byte ptr [ebx], CC ; is the first instruction INT3 00403400 je 0040341E ; if so jmp to 40341E 00403402 cmp byte ptr [ebx], 9D ; If the first instruction POPFD ; this shouldn't happen ! 00403405 je 00403411 ; this shouldn't jump 00403407 or dword ptr [edi+000000C0], 00000100 ; EFlags=EFlags OR SINGLE_STEP CPU Mode 00403411 mov eax, dword ptr [ebp+08] ; eax points to debug information structure 00403414 cmp dword ptr [eax], 80000003 ; Is the exception a break point 0040341A je 0040341E 0040341C jmp aescul.00403452 ; if no breakpoint jmp 403452 |* Referenced by jumps from (C)00403400, (C)0040341A ; handler for INT3 and for breakpoints | 0040341E xor eax, eax ; 00403420 mov al, byte ptr [ebx+01] ; bl contains the second byte from the ; offset that eaxception has been raised 00403423 mov dword ptr [004041A3], eax ; stores this byte byte in 4041A3 00403428 lea eax, dword ptr [edi+000000B8] ; eax contains the EIP in the time of the ; exception 0040342E push eax 0040342F push edx ; 00403430 mov eax, 00000005 ; eax=5 00403435 xor edx, edx ; edx=0 00403437 xor ebx, ebx ; ebx=0 00403439 mov bl, byte ptr [004041A3] ; ebx=BYTE2 0040343F xchg eax,ebx ; eax=BYTE2, ebx=5 00403440 div ebx ; eax=BYTE2 div 5; edx= BYTE2 mod 5 00403442 xchg edx, ebx ; edx=5; ebx=BYTE2 mod 5 00403444 pop edx ; 00403445 pop eax ; 00403446 mov edi, 00401002 ; mov [edi], 00403458 0040344B lea esi, dword ptr [edi+4*ebx] ; mov [esi], 0040345C This code just randomly (random_value mod 5) choose the address of a "bad procedure" from where the program will continue to execute after exiting all the exception handlers, but only if there has been a breakpoint exception in the exception handler of aescul.exe. If there were no breakpoint then the execution will continue on the next instruction. Note also that the processor is set again in Single Step Mode at 00403407 and this will generate new single step exception after execution of this next instruction. 0040344E mov ebx, dword ptr [esi] ; bpx points to "bad procedure" 00403450 mov dword ptr [eax], ebx ; changes the EIP |* Referenced by jumps (U)0040341C ; jmp here if exception is not a breakpoint | 00403452 xor eax, eax ; 00403454 mov esp, ebp ; this changes the stack that is equal to ; calling SetThreadContext() !!! 00403456 pop ebp ; restores ebp 00403457 ret ; Returns to next instruction, to the ; beginning of a "bad procedure" or to ; other exception handler if registered And here are the 5 "bad procedure"s ----------------------------------- |* This will cause an exception | |* The offset is contained at 00401002 + 0 x 4 | 00403458 xor eax, eax 0040345A mov eax, dword ptr [eax] ; Access Violation = CALL 00403393 |* This will cause an exception | |* The offset is contained at 00401002 + 1 x 4 | 0040345C mov eax, 0000000F ; 00403461 xor ecx, ecx 00403463 div ecx ; Division by Zero = CALL 00403393 ........ |* | |* The offset is contained at 00401002 + 2 x 4 | 0040348F mov ecx, 0000020D ; 00403494 xor eax, eax 00403496 mov edi, 000011F4 ; (?) 0040349B stosd 0040349C loop 0040349B |* | |* The offset is contained at 00401002 + 3 x 4 | 0040349E jmp aescul.0040349E ; FOREVER LOOP |* | |* The offset is contained at 00401002 + 4 x 4 | 004034A0 mov edi, 80000000 ; 004034A5 xor eax, eax 004034A7 mov ecx, EEEEFFFF 004034AC stosd 004034AD loop 004034AC And finally here is the code that first generates (raises) an exception: ------------------------------------------------------------------------ FRAGMENT 7: ----------- 00403038 xor eax, eax 0040303A push 00403393 ; 0040303F push dword ptr fs:[eax] ; 00403042 mov dword ptr fs:[eax], esp ; 00403045 pushfd ; pushes the flags 00403046 pushfd ; pushes the flags again 00403047 pop eax ; pops the flags into eax 00403048 or eax, 00000100 ; sets Trap Flag up (SINGLE STEP MODE !!!) 0040304D push eax ; pushes eax 0040304E popfd ; pops flags !!! 0040304F nop ; this is the first instruction after popfd ; the esxception handlig procedure wll be activated ; so this is reference to 00403393 !!! The last parts of the aescul.exe asm code from 403188 to 40321F is just the normal message loop for the windows messages. There all the messages are handled, also the pressing of "OK" button. Some ideas to improve aescul.exe -------------------------------- 1) To put the decryption procedure call in the exception handler and it to be called only if no debugger present, no breakpoint exceptions 2) To use in the decryption procedure a param that is changed in the exception handler if no debugging is found
If you find in the essay any wrong comments or descriptions, please feel free to mail me at d_Fixer@hotmail.com Thanks to xavier.