03.18.2000
by DaFixer
 
Courtesy of Fravia's page of reverse engineering
slightly edited by
+tsehp
fra_00xx
98xxxx
handle
1100
NA
PC

This is the first and (I hope) not the last solution to the strainer published
in mid 99, read and enjoy, it's worth the trip.

There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner (X)Intermediate (X)Advanced ( )Expert

This essay is about the first challenge of Y2K +HCU strainer and shows a particial solution of the challenge, explanation of how the program work, decryption and keygen algorithms. The essay also shows some antidebugging techniques related to raising and handling exceptions, manage with debug registers, etc. Knowledge about thread contexts, _CONEXT structure, debug registers and exception handling are required to fully understand the stuff.
AESCUL.EXE - The First Y2K +HCU Strainer Challenge
Antidebugging Techniques
Written by DaFixer


Introduction
        "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.



Tools required
SoftIce, W32DASM, WinHack, Hex Editor, Compiler

Target's URL/FTP
Your_target's_url_or_ftp_locations

Essay
   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:



program aescul_kgen;
uses Windows;
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[1],@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



Final Notes

   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.




Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell, don't come back.

You are deep inside Fravia's page of reverse engineering, choose your way out:


redhomepage redlinks redsearch_forms red+ORC redhow to protect redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_Fravia
redIs reverse engineering legal?