HCU Millenium strainer Stage 1: AESCUL.EXE
By Bouuu
published by Tsehp, June 2000.

Introduction:

Hi, it is my first tutorial, and for my first tutorial I choose the millenium strainer stage 1.
You can wonder why I write an other tutorial on this strainer insofar as DaFixer already did it. In fact when I started to reverse
it and I decided to write my first tutorial I didn't know that there already was a tut. But when I read the DaFixer's tut, I see I
used a very different way to do it, then I think this tut can be insteresting for some people.

My main tool for this tut will be IDA (I used IDA 4 for this tut).If you are a beginner don't worry, I'll explain as much as possible
what I do and how in some 'specials beginner parts'.Then Advanced Fravia can skip these interlude.
You'll see how powerful IDA is, and how you can (pratically) reverse all this strainer with IDA without any other tools. In this tut,
I'll show you how to use the IDC script under IDA, structure and many other.

Well , now let's reverse ;)

First Part : The Serial Checking Algo

well, start IDA and disassemble it.

[Beginner] don't forget to enable 'Load resource' and disable 'Rename DLL entries'

you will get this :

00401016 start: ; CODE XREF: CODE:00401000 j
00401016 push 0
00401018 call j_GetModuleHandleA
0040101D mov ds:dword_404187, eax
00401022 push 0
00401024 push offset sub_403184
00401029 push 0
0040102B push 65h
0040102D push ds:dword_404187
00401033 call j_DialogBoxParamA
00401038 nop
00401039 nop

Ok I rename dword_404187 to CurrentModuleHandleA and sub_403184 to DialogBoxFunction, and put some comment then we get this :

00401016 start: ; CODE XREF: CODE:00401000 j
00401016 push 0
00401018 call j_GetModuleHandleA
0040101D mov ds:CurrentModuleHandle, eax ; Save Module Handle
00401022 push 0
00401024 push offset DialogBoxFunction
00401029 push 0
0040102B push 65h
0040102D push ds:CurrentModuleHandle
00401033 call j_DialogBoxParamA ; During This Call DialogBoxFunction is called
00401038 nop ; After this function you have the Code and Data deciphered, the ProductID in the data, and
00401039 nop ; the User Name and Serial string too

It is so beautiful, isn't it ? ;)

[Beginner] just put your cursor on dword_404187 and use CTRL+N to rename it, and the same thing for sub_403184. Now you see why IDA is a lot
more powerfull than Wdasm ?

further, I will not say to rename it etc ... I will do it and just give you the code after the renaming.

Ok as my comment says, During the call to DialogBoxParamA the function DialogBoxFunction is executed, then we Jump into it

[Beginner] just double click on 'DialogBoxFunction' in the 'push offset DialogBoxFunction' (I say it because I saw this question in #c4n).
To go back, just press <ESC>.

You'll see in this function 'arg_4', this is an aother powerfull feature of IDA: it can recognize local variable, argument of a function ...

After some renaming we get this:

00403184 DialogBoxFunction proc near ; DATA XREF: CODE:00401024 o
00403184
00403184 uMsg = dword ptr 0Ch
00403184
00403184 enter 0, 0 ; uMsg=Message which creates this call
00403188 cmp [ebp+uMsg], 0Fh ; Message=WM_PAINT ?
0040318C jnz short loc_403195
0040318E call FunctionWMPAINT ; Nothing Interesting Here
00403193 jmp short loc_4031C8
00403195 ; ---------------------------------------------------------------------------
00403195
00403195 loc_403195: ; CODE XREF: DialogBoxFunction+8 j
00403195 cmp [ebp+uMsg], 111h ; Message=WM_COMMAND ?
0040319C jnz short loc_4031A5
0040319E call FunctionWMCOMMAND
004031A3 jmp short loc_4031C8
004031A5 ; ---------------------------------------------------------------------------
004031A5
004031A5 loc_4031A5: ; CODE XREF: DialogBoxFunction+18 j
004031A5 cmp [ebp+uMsg], 110h ; Message=WM_INITDIALOG ?
004031AC jnz short loc_4031B5
004031AE call FunctionWMINITDIALOG
004031B3 jmp short loc_4031C8
004031B5 ; ---------------------------------------------------------------------------
004031B5
004031B5 loc_4031B5: ; CODE XREF: DialogBoxFunction+28 j
004031B5 cmp [ebp+uMsg], 2 ; Message=WM_DESTROY ?
004031B9 jnz short loc_4031C2
004031BB call FunctionWMDESTROY ; Nothing Interesting here
004031C0 jmp short loc_4031C8
004031C2 ; ---------------------------------------------------------------------------
004031C2
004031C2 loc_4031C2: ; CODE XREF: DialogBoxFunction+35 j
004031C2 xor eax, eax
004031C4 leave
004031C5 retn 10h
004031C8 ; ---------------------------------------------------------------------------
004031C8
004031C8 loc_4031C8: ; CODE XREF: DialogBoxFunction+F j
004031C8 ; DialogBoxFunction+1F j
004031C8 ; DialogBoxFunction+2F j
004031C8 ; DialogBoxFunction+3C j
004031C8 xor eax, eax
004031CA leave
004031CB retn 10h
004031CB DialogBoxFunction endp

Well in both FunctionWMDESTROY and FunctionWMPAINT there arenothing interesting. In FunctionWMINITDIALOG there is nothing too,
but it is always a good idea to look into, because the coder may put some interesting init in,but here there is nothing.
Go to the FunctionWMCOMMAND.At the beginning you see 'cmp dword ptr [ebp+10h], 1' , it means that IDA have not detect that this function
is BP based frame then press ALT+P (or Edit=>Functions=>Edit function) and enable "BP based frame" option, then OK.
Now you see that arg_4 and arg_C were created :

004031F1 FunctionWMCOMMAND proc near ; CODE XREF: DialogBoxFunction+1A p
004031F1
004031F1 hDlg = dword ptr 8
004031F1 wParam = dword ptr 10h
004031F1
004031F1 cmp [ebp+wParam], 1 ; Ok
004031F5 jz short loc_40320A
004031F7 cmp [ebp+wParam], 2 ; Maybe mean Kill or something like that
004031FB jz FunctionWMDESTROY ; Nothing Interesting here
00403201 mov eax, 0
00403206 leave
00403207 retn 10h
0040320A ; ---------------------------------------------------------------------------
0040320A
0040320A loc_40320A: ; CODE XREF: FunctionWMCOMMAND+4 j
0040320A cmp ds:dword_4044F6, 1
00403211 jz short loc_403224
00403213 mov esi, offset unk_404000 ; mmm very insteresting ... it is the beginning of the DATA
00403218 mov edi, esi
0040321A mov ecx, 0B16h ; maybe the size of the data ...
0040321F call sub_403465 ; we MUST take a look of this function
00403224 loc_403224: ; CODE XREF: FunctionWMCOMMAND+20 j

Ok here is the sub_403465 function (I renamed it):

00403465 DecipherData proc near ; CODE XREF: FunctionWMCOMMAND+2E p
00403465 ;DecipherData+8 j
00403465 ; sub_40347A+8 j
00403465 xor eax, eax
00403467 lodsb ; get the BYTE
00403468 rol al, cl ; rotate the bit
0040346A not al ; and get the complement
0040346C stosb ; finally put the decyphered byte
0040346D loop DecipherData
0040346F mov ds:IsDataDecipher, 1 ; and set the flag to prevent from another deciphering
00403479 retn
00403479 DecipherData endp

We see that this function is a deciphering function.Well how can we continue to reverse with IDA if there is ciphered data ? You may think that
we have to dump data from memory with icedump during the execution and past it in the EXE and disassemble it ...
NO, we have the most powerfull tool, do you remember ? ;) It is an occasion to use another good feature under IDA: IDC script.
With the help of IDC script you can do everything you want (you have to know a little about C , and you have to take a look in the IDA help to see
the list of the function provided by IDA).

Create a file decryptdata.idc and put this in :

// IDA script to decipher data from HCU Millenium strainer stage 1 (AESCUL.EXE)
static decryptdata(from,size)
{
auto i,x,n;
for(i=size;i>0;i=i-1){ // loop for the all Byte
x=Byte(from); // get the Current Byte
n=(i&0xFF); // take the first 8bit of the counter
n=(n%8); // these two line is
x=(x<<n)+(x>>(8-n)); // equal to rol
x=(~x); // get the complement (equal to not)
PatchByte(from,x); // and patch the Byte in IDA
from=from+1; // increment the counter
}
}
// End of Script

Ok now, as we see at 00403213h we have to decipher data at 404000h with the size B16h.Press F2 to load the decipherdata.idc.
Go to 404000 (press G and enter 404000) you see this :

00404000 DATA segment para public 'DATA' use32
00404000 assume cs:DATA
00404000 ;org 404000h
00404000 unk_404000 db 0B2h ; ; DATA XREF: FunctionWMCOMMAND+22 o
00404000 ; FunctionWMCOMMAND+9E o
00404001 db 84h ;
00404002 db 99h ;
00404003 db 71h ; q
00404004 db 22h ; "
00404005 db 4Fh ; O
00404006 db 8Dh ;

Now press Shift-F2 and write this: decryptdata(0x404000,0xB16);
Then press OK, Hoooohhhhh It is magical !!! :) look this :

00404000 DATA segment para public 'DATA' use32
00404000 assume cs:DATA
00404000 ;org 404000h
00404000 unk_404000 db 53h ; S ; DATA XREF: FunctionWMCOMMAND+22 o
00404000 ; FunctionWMCOMMAND+9E o
00404001 db 6Fh ; o
00404002 db 66h ; f
00404003 db 74h ; t
00404004 db 77h ; w
00404005 db 61h ; a
00404006 db 72h ; r
00404007 db 65h ; e

You can read "Software" :))). Ok now we have to rebuild a little the data section:
When you see a string put your cursor at the beginning (on the first db ..) and press the key 'A'
You should get this :

00404000 STR->SoftwareMicrosoftWindowsCurrentversion db 'Software\Microsoft\Windows\CurrentVersion',0
00404000 ; DATA XREF: FunctionWMCOMMAND+22 o
00404000 ; FunctionWMCOMMAND+9E o
0040402A STR->Software db 'SOFTWARE',0
00404033 STR->Productid db 'ProductId',0 ; DATA XREF: FunctionWMCOMMAND+BB o
0040403D STR->Congratulations___ db 'Congratulations...',0
00404050 STR->Error db 'Error',0
00404056 STR->WrongSerialNumber db 'Wrong Serial Number!',0

[Beginner] You probably notice that I have STR-> before my string, it is because you can put a prefix before the string in the menu
Options=>General=>Strings and under the edit box prefix put your prefix, it is a good way to easily find string reference in the 'Names' Window
(menu View=>Names)

There is a lot of Null Byte after these strings, but scroll down there are three other string:

004041AB STR->RegisteredTo db 'Registered to: '
004043BA STR->Ws db 'WS',0

004044C0 STR->0i5lz7g123rxcv9opas6tbn48yuhjkdf0qwem db '0I5LZ7G123RXCV9OPAS6TBN48YUHJKDF0QWEM',0

well now go back to the FunctionWMCOMMAND

[Beginner] just press <ESC> until you are in FunctionWMCOMMAND or you can double click on this function in the menu View=>Names window

let's continue with this function :

004031F1 FunctionWMCOMMAND proc near ; CODE XREF: DialogBoxFunction+1A p
004031F1
004031F1 hDlg = dword ptr 8
004031F1 wParam = dword ptr 10h
004031F1
004031F1 cmp [ebp+wParam], 1 ; Ok
004031F5 jz short loc_40320A
004031F7 cmp [ebp+wParam], 2 ; Maybe mean Kill or something like that
004031FB jz FunctionWMDESTROY ; Nothing Interesting here
00403201 mov eax, 0
00403206 leave
00403207 retn 10h
0040320A ; ---------------------------------------------------------------------------
0040320A
0040320A loc_40320A: ; CODE XREF: FunctionWMCOMMAND+4 j
0040320A cmp ds:IsDataDecipher, 1
00403211 jz short loc_403224
00403213 mov esi, offset STR->SoftwareMicrosoftWindowsCurrentversion ; "Software\\Microsoft\\Windows\\CurrentVersi"... Note that this has changed, because
00403213 ; we have 'virtualy' deciphered the data section ;)
00403218 mov edi, esi
0040321A mov ecx, 0B16h
0040321F call DecipherData ; esi=src,edi=dest,ecx=size
00403224
00403224 loc_403224: ; CODE XREF: FunctionWMCOMMAND+20 j
00403224 push 40h
00403226 push offset UserNameString
0040322B push 3E8h ; UserName, I use quickly softice to know this
00403230 push [ebp+hDlg]
00403233 call j_GetDlgItemTextA ; Get The User Name Field
00403238 mov edi, offset UserNameString
0040323D xor eax, eax
0040323F or ecx, 0FFFFFFFFh
00403242 repne scasb
00403244 not ecx ; Get the size of the User Name string
00403246 sub edi, ecx
00403248 mov ds:UserNameStringSize, ecx ; And save it
0040324E cmp ds:UserNameString, 0
00403255 push 40h
00403257 push offset SerialString
0040325C push 3E9h ; Serial
00403261 push [ebp+hDlg]
00403264 call j_GetDlgItemTextA ; Get the Serial Field
00403269 mov edi, offset SerialString
0040326E xor eax, eax
00403270 or ecx, 0FFFFFFFFh
00403273 repne scasb
00403275 not ecx ; Get the size of Serial string
00403277 sub edi, ecx
00403279 mov ds:SerialStringSize, ecx ; And save it
0040327F cmp ds:SerialString, 0
00403286 push offset KeyHandle_SoftwareMicrosoftWindowsCurrentversion
0040328B push 1 ; KEY_QUERY_VALUE
0040328D push 0
0040328F push offset STR->SoftwareMicrosoftWindowsCurrentversion ; "Software\\Microsoft\\Windows\\CurrentVersi"...
00403294 push 80000002h ; HKEY_LOCAL_MACHINE
00403299 call j_RegOpenKeyExA ; Open the RegKey
0040329E push offset ProductIDSizeOfBuffer
004032A3 push offset ProductID
004032A8 push 0
004032AA push 0
004032AC push offset STR->Productid ; "ProductId"
004032B1 push ds:KeyHandle_SoftwareMicrosoftWindowsCurrentversion
004032B7 call j_RegQueryValueExA ; Get The ProductId Key value
004032BC mov edi, offset ProductID_WithHeader ; ProductID With Header "WS"
004032BC ; just before ProductId string
004032C1 xor eax, eax
004032C3 or ecx, 0FFFFFFFFh
004032C6 repne scasb
004032C8 not ecx ; Get the size of the ProductID_WithHeader
004032CA sub edi, ecx
004032CC mov ds:ProductID_WithHeader_Size, ecx ; And save it

Nothing really hard here: gets the User Name, gets the Serial string, gets the size of these string, gets the ProductID of Windows and Add 'WS' just
before this ProductID string, and finally gets the size of the 'New' ProductID string.

[Beginner] I have forgotten to say how you can put comment: just right click far at the end of a line, and you'll get a popup menu with 'comment' menu.
Now with this last snipset, do you see the power of IDA ? It is as you've got the source code ! You can definitly forget WDasm now ...

Ok now there is an interesting part :

004032D2 cmp ds:IsCodeDecipher, 1 ; Is the code already deciphered ? (it never happen if you see the DecipherCode function)
004032D9 jz short loc_4032EC
004032DB mov esi, offset CodeCipheredBegin
004032E0 mov edi, esi
004032E2 mov ecx, 134h
004032E7 call DecipherCode ; esi=src,edi=dest,ecx=size
004032EC
004032EC loc_4032EC: ; CODE XREF: FunctionWMCOMMAND+E8 j
004032EC jmp FunctionWMDESTROY ; End of Dialog Box
004032EC FunctionWMCOMMAND endp

And here the DecipherCode function :

0040347A DecipherCode proc near ; CODE XREF: FunctionWMCOMMAND+F6 p
0040347A xor eax, eax
0040347C lodsb ; get the BYTE
0040347D rol al, cl ; rotate the bit
0040347F not al ; and get the complement
00403481 stosb ; finally put the decyphered byte
00403482 loop DecipherData ; LOOK, it doesn't loop at DecipherCode
00403484 mov ds:IsDataDecipher, 1 ; Then this line is never executed !
0040348E retn
0040348E DecipherData endp

Well, we see that there is the same thing than DecipherData, but it loop at DecipherData instead of DecipherCode ... A bug ?
Certainly a useless repeted function as +aesculapius said.

Mm, we have to decipher the code section, but since it is the same deciphered algo than for data we can use the same IDC script.
Then: decipher at 0x00403050 with the size of 0x134: decipherdata(0x00403050,0x134);
Ok now go to 0x00403050 , you see that there is not really better :o)) It is normal : we have to rebuild a little :
Undefined from 0x00403050 to 0x00403183 and rebuild the code from 0x00403050.

[Beginner] Select from 0x00403050 to 0x00403183 and press the key 'U', go to 0x00403050 and press the key 'C'

Now go back to WMCOMMAND.We see that it exits from DialogBox just after the code was deciphered (jmp FunctionWMDESTROY).
Then we have to return to 0x00401038 (just after the 'call j_DialogBoxParamA' ). Scroll down until the end of the 'nop'.
It should be at 0x00403038 (a little before the deciphered code).

Since it doesn't used for Serial checking algo ,we skip the code from 0x00403038 to 0x00403050, after reversing (not really hard) we get this :

00403050 CodeCipheredBegin: ; DATA XREF: FunctionWMCOMMAND+EA o
00403050 xor esi, esi ; these Data was deciphered by DialogBox function (WM_COMMAND Message)
00403052 xor edi, edi
00403054 xor edx, edx
00403056 mov ebp, ds:ProductIDSizeOfBuffer
0040305C mov edi, offset GoodSerial ; Get the offset of futur Good serial string
00403061
00403061 CreateSerialLoop: ; CODE XREF: CODE:00403097 j
00403061 push ebp
00403062 push edi
00403063 push esi
00403064 mov ebp, offset STR->0i5lz7g123rxcv9opas6tbn48yuhjkdf0qwem ; "0I5LZ7G123RXCV9OPAS6TBN48YUHJKDF0QWEM"
00403064 ; this is a hardcoded SerialKey to create the good serial
00403069 mov ebx, offset ProductID_WithHeader
0040306E mov al, [ebx+esi] ; Get The Current ProductID_WithHeader Byte
00403071 sar eax, 4
00403074 and eax, 0Fh ; get the four LAST bit as a Number
00403077 call GetCharSerialFromNumber ; ebp=SerialKey string , eax=Number
0040307C mov [edi], al ; Get the Serial char from this Number and Store it
0040307E mov cl, [ebx+esi] ; Get The Current ProductID_WithHeader Byte
00403081 and ecx, 0Fh
00403084 mov eax, ecx ; get the four FIRST bit as a Number
00403086 call GetCharSerialFromNumber ; ebp=SerialKey string
00403086 ; eax=Number
0040308B mov [edi+1], al ; Get the Serial char from this Number and Store it
0040308E pop esi
0040308F pop edi
00403090 pop ebp
00403091 inc esi ; increment the ProductID_WithHeader Index
00403092 add edi, 2 ; Increment GoodSerial Index by two because one char from ProductID_WithHeader generate two Serial char
00403095 cmp ebp, esi ; are we at the end of ProductID_WithHeader string ?
00403097 jnz short CreateSerialLoop ; no ? => we loop
00403099 xor esi, esi
0040309B mov eax, dword ptr ds:SerialString[esi]
004030A1 mov ebx, dword ptr ds:GoodSerial[esi]
004030A7 cmp eax, ebx ; compare 4 Bytes
004030A9 jnz short InvalidSerial
004030AB add esi, 4 ; go to the Next 4 Byte
004030AE mov eax, dword ptr ds:SerialString[esi]
004030B4 mov ebx, dword ptr ds:GoodSerial[esi]
004030BA cmp eax, ebx ; compare 4 Bytes
004030BC jnz short InvalidSerial
004030BE add esi, 4 ; go to the Next 4 Byte
004030C1 mov eax, dword ptr ds:SerialString[esi]
004030C7 mov ebx, dword ptr ds:GoodSerial[esi]
004030CD cmp eax, ebx ; compare 4 Bytes
004030CF jnz short InvalidSerial
004030D1 add esi, 4 ; go to the Next 4 Byte
004030D4 mov eax, dword ptr ds:SerialString[esi]
004030DA mov ebx, dword ptr ds:GoodSerial[esi]
004030E0 cmp eax, ebx ; compare 4 Bytes
004030E2 jnz short InvalidSerial
004030E4 mov ds:IsAValidSerial, 1 ; Ok The serial is valid
004030EE jmp short loc_4030FA ; there is 4 cmp of 4 Bytes, then the serial is 16 Bytes long, then there are a lot of useless Byte
004030EE ; generated by the CreateSerial Loop
004030F0 ; ---------------------------------------------------------------------------
004030F0
004030F0 InvalidSerial: ; CODE XREF: CODE:004030A9 j
004030F0 ; CODE:004030BC j
004030F0 ; CODE:004030CF j
004030F0 ; CODE:004030E2 j
004030F0 mov ds:IsAValidSerial, 0
004030FA
004030FA loc_4030FA: ; CODE XREF: CODE:004030EE j
004030FA popf ; restore flag then now the TF flag is now longer set
004030FB xor eax, eax
004030FD pop dword ptr fs:[eax] ; And restore default exception handling
00403100 add esp, 4
00403103 cmp ds:IsAValidSerial, 1 ; Is a good serial ?
0040310A jnz short BadSerial
0040310C push 40h
0040310E push offset STR->Congratulations___ ; "Congratulations..."
00403113 push offset STR->RegisteredTo ; "Registered to: "
00403118 push 0
0040311A call j_MessageBoxA ; I love this message box :)
0040311F jmp short ProcessEnd
00403121 ; ---------------------------------------------------------------------------
00403121
00403121 BadSerial: ; CODE XREF: CODE:0040310A j
00403121 push 30h
00403123 push offset STR->Error ; "Error"
00403128 push offset STR->WrongSerialNumber ; "Wrong Serial Number!"
0040312D push 0
0040312F call j_MessageBoxA
00403134
00403134 ProcessEnd: ; CODE XREF: CODE:0040311F j
00403134 push 0
00403136 call j_ExitProcess

I think with the comment you should understand this part.Now the GetCharSerialFromNumber function :

0040313B GetCharSerialFromNumber proc near ; CODE XREF: CODE:00403077 p
0040313B ; CODE:00403086 p
0040313B mov ds:CurrentCounter, esi ; Save the current ProductID_WithHeader Index
00403141 mov edx, ds:LastPosition ; Get the Last position where the serial Key Byte was found.
00403141 ; The serial key hardcoded is used as 'a loop' :
00403141 ; when we reach the end of the serialkey Hardcoded we
00403141 ; come back at the beginning
00403147 mov ecx, ds:MaxSize
0040314D cmp edx, ecx ; is it out of the serial key ?
0040314F jb short FindSerialKeyByteLoop
00403151 xor edx, edx ; then come back to the beginning
00403153
00403153 FindSerialKeyByteLoop: ; CODE XREF: GetCharSerialFromNumber+14 j
00403153 ; GetCharSerialFromNumber+31 j
00403153 ; GetCharSerialFromNumber+35 j
00403153 movsx esi, byte ptr [ebp+edx+0] ; Get the Current SerialKey Byte
00403158 and esi, 8000000Fh ; get the first 4 bit
0040315E jns short loc_403165
00403160 dec esi
00403161 or esi, 0FFFFFFF0h
00403164 inc esi
00403165
00403165 loc_403165: ; CODE XREF: GetCharSerialFromNumber+23 j
00403165 cmp esi, eax ; Number=the first 4 bit of the current Serial Key Byte ?
00403167 jz short GetSerialKeyByte ; yes => Get this Serial Key Byte
00403169 inc edx ; increment SerialKey Index
0040316A cmp edx, ecx ; and if we reach the end
0040316C jl short FindSerialKeyByteLoop
0040316E xor edx, edx ; we come back to the beginning
00403170 jmp short FindSerialKeyByteLoop ; Continue to search
00403172 ; ---------------------------------------------------------------------------
00403172
00403172 GetSerialKeyByte: ; CODE XREF: GetCharSerialFromNumber+2C j
00403172 mov ds:LastPosition, edx ; save the index where the Serial Key was found
00403178 mov esi, ds:CurrentCounter ; restore the ProductID_With Header Index
0040317E movsx eax, byte ptr [edx+ebp] ; Get The Serial Key Byte
00403182 inc edx
00403183 retn
00403183 GetCharSerialFromNumber endp

Ok now we have all we need to make a keygen. Here the two main function in C of a Keygen:

void GenerateSerial()
{
BYTE i;
HKEY hCurrentVersion;
DWORD ProductIDSize;
BYTE ProductID[0x27];
char GoodSerial[16+1];
// some init
memset(GoodSerial,0,17);
ProductIDSize=0x25;
ProductID[0]='W';
ProductID[1]='S';
// Get the product ID of Windows
RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion", 0,KEY_QUERY_VALUE,&hCurrentVersion);
RegQueryValueEx(hCurrentVersion,"ProductId",0,0,ProductID+2,&ProductIDSize);
// Create the good serial
for(i=0;i<8;i++){
GoodSerial[i*2]=Getchar((ProductID[i]>>4)&0x0F);
GoodSerial[i*2+1]=Getchar(ProductID[i]&0x0F);
}
// Here Good serial is in GoodSerial variable
}

char Getchar(BYTE Number)
{
static char SerialKey[]="0I5LZ7G123RXCV9OPAS6TBN48YUHJKDF0QWEM";
static LastPosition=0;
BYTE i,Nb;
i=LastPosition-1;
Nb=-1;
while(Nb!=Number){
i++;
if(i==0x25) i=0;
Nb=SerialKey[i]&0x0F;
}
LastPosition=i;
return SerialKey[LastPosition];
}

Second Part : Exception Handling

Now we come back to the part of the code we skiped:

00403038 xor eax, eax
0040303A push offset ExceptionHandlingFunction
0040303F push dword ptr fs:[eax] ; Save The pointer of Exception Handling
00403042 mov fs:[eax], esp ; And put the new with ExceptionHandlingFunction
00403045 pushf
00403046 pushf ; these line
00403047 pop eax ; get the flag register
00403048 or eax, 100h ; and put the TF Trap Flag (Single Step)
0040304D push eax ; And then
0040304E popf ; Put it in the register flag
0040304F nop ; Single Step Exception (0x01) is fired Here due to the TF flag set

[Beginner] It is a little hard to explain all from now.But I'll try. If you don't understand, you should read the Pietrek's Book (in fact even if you understand
you should read the Pietrek's Book ;) ). fs contain a selector which point to the TIB (Thread Information Block). the first DWORD of this table
is a pointer to the head of the structured exception handling chain (then fs:[0] is this pointer).

what we see is that the program first change the Exception handling function , after it enables the TF trap falg, then after 0x0040304E all the instruction
create and Exception 0x01 (single step). It means that after all the instruction the ExceptionHandlingFunction is called.Then take a look of this function should
be a good idea :) But first create theses 2 structures:

FLOATING_SAVE_AREA struc ; (sizeof=0x70)
0000 ControlWord dd ?
0004 StatusWord dd ?
0008 TagWord dd ?
000C ErrorOffset dd ?
0010 ErrorSelector dd ?
0014 DataOffset dd ?
0018 DataSelector dd ?
001C RegisterArea db 80 dup(?)
006C Cr0NpxState dd ?
0070 FLOATING_SAVE_AREA ends
0000 ; ---------------------------------------------------------------------------
0000
0000 CONTEXT struc ; (sizeof=0xcc)
0000 ContextFlags dd ?
0004 iDr0 dd ?
0008 iDr1 dd ?
000C iDr2 dd ?
0010 iDr3 dd ?
0014 iDr6 dd ?
0018 iDr7 dd ?
001C FloatSave FLOATING_SAVE_AREA ?
008C regGs dd ?
0090 regFs dd ?
0094 regEs dd ?
0098 regDs dd ?
009C regEdi dd ?
00A0 regEsi dd ?
00A4 regEbx dd ?
00A8 regEdx dd ?
00AC regEcx dd ?
00B0 regEax dd ?
00B4 regEbp dd ?
00B8 regEip dd ?
00BC regCs dd ?
00C0 regFlag dd ?
00C4 regEsp dd ?
00C8 regSs dd ?
00CC CONTEXT ends
00CC
0000


[Beginner] You may wonder how I can do these sort of structure 'mov ds:lpContext.ContextFlags, 7Ch' (in the next snipset)? It's another feature of IDA :).
Go to View=>Structures. With Ins/Del key you can create or delete structure. With D/A/* key you can create Data field,String field, or Array field, or
if your cursor is already on a field you can switch his type (for instance if you put your cursor on a data field and you press 'D' key, it switches beetween
db,dw,dd). To create an array, create a data field first, put your cursor on this field and press '*' key, then you can choose the size of the array and some
other thing. To rename the field name, use the key 'N'.
You see in the 2nd structure there is a field which has the type of the first structure : FLOATING_SAVE_AREA. To create this field, create a data field first
, put your cursor on this field and press ALT+Q to choose the FLOATING_SAVE_AREA structure.

As you can see in my next snipset the data at 0x004040BB (lpContext) is a CONTEXT structure type. To do it you have to Undefined the region
from 0x004040BB (included) to 0x00404186 (included) (as before, select this region and press the key 'U'). then put you cursor at 0x004040BB
and press ALT+Q to choose the type 'CONTEXT'.
Another thing: at 0x004033F7 you can see this : 'mov ebx, [edi+0B8h]' put your cursor on 'edi' , right-click, and choose the menu 'edi+CONTEXT.regEip'
Yes I know, IDA is so good ... ;)

Now after these comments about the use of the structure feature under IDA,the ExceptionHandlingFunction :

00403393 ExceptionHandlingFunction proc near ; DATA XREF: CODE:0040303A o
00403393
00403393 pExceptionRecord= dword ptr 8
00403393 pContextRecord = dword ptr 10h
00403393
00403393 push ebp
00403394 mov ebp, esp
00403396 call j_GetCurrentThread ; Get Current Thread Handle (dummy Handle :) )
0040339B mov ds:CurrentThreadHandle, eax ; And Save It
004033A0 mov ds:lpContext.ContextFlags, 7Ch
004033AA push offset lpContext
004033AF push ds:CurrentThreadHandle
004033B5 call j_GetThreadContext ; Get The Context Struct
004033BA mov ds:lpContext.iDr0, eax ; Change The debug register
004033BF mov ds:lpContext.iDr1, eax
004033C4 mov ds:lpContext.iDr2, eax
004033C9 mov ds:lpContext.iDr3, eax
004033CE mov ds:lpContext.iDr7, 24FFh ; Enable All the HardBreakPoint (from DRx register) and active GD flag
004033D8 mov ds:lpContext.ContextFlags, 18h ; CONTEXT_DEBUG_REGISTERS | CONTEXT_FLOATING_POINT
004033E2 push offset lpContext
004033E7 push ds:CurrentThreadHandle
004033ED call j_SetThreadContext ; And Set this new context
004033F2 mov eax, [ebp+pContextRecord]
004033F5 mov edi, eax
004033F7 mov ebx, [edi+CONTEXT.regEip] ; Get The EIP where the Exception happen
004033FD cmp byte ptr [ebx], 0CCh ; OPCODE Byte=int 3 ?
00403400 jz short loc_40341E
00403402 cmp byte ptr [ebx], 9Dh ; OPCODE Byte=popf ?
00403405 jz short loc_403411
00403407 or [edi+CONTEXT.regFlag], 100h ; set TF Flag (Single Step)
00403411
00403411 loc_403411: ; CODE XREF: ExceptionHandlingFunction+72 j
00403411 mov eax, [ebp+pExceptionRecord]
00403414 cmp dword ptr [eax], 80000003h ; ExceptionCode=STATUS_BREAKPOINT ?
0040341A jz short loc_40341E
0040341C jmp short loc_403452
0040341E ; ---------------------------------------------------------------------------
0040341E
0040341E loc_40341E: ; CODE XREF: ExceptionHandlingFunction+6D j
0040341E ; ExceptionHandlingFunction+87 j
0040341E xor eax, eax
00403420 mov al, [ebx+1] ; Next OpCode BYTE
00403423 mov ds:NextOPCodeBYTE, eax
00403428 lea eax, [edi+CONTEXT.regEip] ; Get the offset of regEip member
0040342E push eax
0040342F push edx
00403430 mov eax, 5
00403435 xor edx, edx
00403437 xor ebx, ebx
00403439 mov bl, byte ptr ds:NextOPCodeBYTE
0040343F xchg eax, ebx
00403440 div ebx ; ebx=5, eax=NextOPCodeByte/5, edx=NextOPCodeByte%5
00403442 xchg ebx, edx ; now ebx=NextOPCodeByte%5
00403444 pop edx
00403445 pop eax
00403446 mov edi, offset FunctionArray ; Get Offset Of Function Pointer Array
0040344B lea esi, [edi+ebx*4] ; Get Offset of the Function Index (NextOPCodeBYTE%5)
0040344E mov ebx, [esi] ; Get The Function Address
00403450 mov [eax], ebx ; CONTEXT.regEip=Function Address
00403452
00403452 loc_403452: ; CODE XREF: ExceptionHandlingFunction+89 j
00403452 xor eax, eax
00403454 mov esp, ebp
00403456 pop ebp
00403457 retn
00403457 ExceptionHandlingFunction endp

An interesting thing is this line : mov ds:lpContext.iDr7, 24FFh. It set the GD flag. When the GD flag is set modify a debug register create an exception.
Then if you put an hardware break point with Softice it will create an exception, and execute this Exception Handling function.
After we see that the exception handling function check if the exception was created by a break point or a popf, if not, the function return.Else
there is a amazing trick to crash the software ;) : it gets the next byte opcode where the exception happen, divide by 5 and get the mod, to have
a number beetween 0 and 4. with this number it gets a 'crash function pointer' from a 'crash function Array' and put this pointer in the Eip register in the
CONTEXT structure (then when the exception handling function return, the program continue at this crash function).
Here the crash Function Array :

00401002 FunctionArray dd offset AccesViolationException
00401002 ; DATA XREF: ExceptionHandlingFunction+B3 o
00401006 dd offset DivideByZeroException
0040100A dd offset EraseMemory1
0040100E dd offset InfiniteLoop
00401012 dd offset EraseMemory2

And here these functions:

00403458 AccesViolationException: ; DATA XREF: CODE:00401002 o
00403458 xor eax, eax
0040345A mov eax, [eax] ; Acces Violation Exception (it reads from Address 0)

0040345C DivideByZeroException: ; DATA XREF: CODE:00401006 o
0040345C mov eax, 0Fh
00403461 xor ecx, ecx
00403463 div ecx ; Divide By Zero Exception

0040348F EraseMemory1: ; DATA XREF: CODE:0040100A o
0040348F mov ecx, 20Dh
00403494 xor eax, eax
00403496 mov edi, 11F4h
0040349B
0040349B loc_40349B: ; CODE XREF: CODE:0040349C j
0040349B stosd
0040349C loop loc_40349B

0040349E InfiniteLoop: ; CODE XREF: CODE:0040349E j
0040349E ; DATA XREF: CODE:0040100E o
0040349E jmp short InfiniteLoop

004034A0 EraseMemory2: ; DATA XREF: CODE:00401012 o
004034A0 mov edi, 80000000h
004034A5 xor eax, eax
004034A7 mov ecx, 0EEEEFFFFh
004034AC
004034AC loc_4034AC: ; CODE XREF: CODE:004034AD j
004034AC stosd
004034AD loop loc_4034AC

As we already see, This exception handling function should be executed after all instruction after this exception handling was been initialized.
But in fact i have noticed that if you put load the aescul.exe with the symbol loader, put a bpx on 0x403030, execute the program (enter a serial and
user name) and when the softice popup at 0x403030 trace step by step, this exception handling seems to not give a problem to softice (I think that it isn't
the same thing under WinNT insofar as Win9x is a lot lesser protected and then Softice under Win9x 'may' be more powerfull).
But if after the popup at 0x403030 you put a breakpoint on 0x00403099 for instance and execute (G command) the trick of crash function seems to work.
Why ? I don't know ...

An other thing: bpx on 0x403030, after the popup bpx on 0x00403099 for instance and bpx on the exception handling function before execute: bpx on
0x00403393, normaly softice should popup at 0x00403393, now try to trace step by step ... you see the problem ? :) yes, when the SetThreadContext is
executed you come back to the beginning of the exception handling function (0x00403393), and if you remove this bpx the program crash ...

We see that the Exception Handling is a very good way to prevent from tracing after SetThreadContext, and to do 'hiding' thing (the exception handling is
executed after every instruction).

3th Part: Improving the protection

Here is my idea to improve the protection :
- Make some CRC check to prevent from patching.
- Init the exception Handling far from the Serial checking algo (for instance at the beginning in the WM_INITDIALOG function)
- Maybe you can put a lot of exception Handling function
- Encrypt the code some time after the code was executed (you can also put a encrypt function in a exception handling function with the help of
the flag trick I explain just after this)

- put the Serial checking algo in the exception handling after the SetThreadContext to prevent from tracing.More, here, you can put a flag like
ShouldCheckSerial and with this flag the serial checking ,under the exception handling function, check the serial only if the flag is set. And return
the result in the same flag. Then with this trick the main code will be :

[...]
call GetdlgItemTextA
[...]
mov ShouldCheckSerial,1 ; if 1 is the Code which says 'check the Serial'
[...] ; you can put some code here , but it isn't necessary
cmp ShouldCheckSerial,0BBh ; if 0BBh is the caode which says 'OK good serial'
je GoodGuys
[...]

When the cracker trace this code he may wonder : "Hey ?!! where this Flag is modified ?". But in fact if the cracker is under IDA, he will see the XREF
And then quickly find the Serial Checking algo, but i think that this trick will defeat a lot of cracker. (Don't forget to crypt this code ...)

Conclusion

As a conclusion I just would say that this tut can seem to be long, but in fact with a good understanding of IDA you can crack this strainer very quickly
(I have done the keygen in less than 1 hour).
I hope that my first tut learn you something, and I hope I hadn't make too much error ...
I hope I have completely proved that IDA is THE disassembler and beginner who use Wdasm should forget this disassembler :)

Well, Good reversing !

By Bouuu