WinHex 9.53
 
Intro


Tools used:

IDA
Soft Ice for Windows (SIW)
Masm32 (for the KeyGen)

About the program


WinHex 9.53 hex editor. A nice little app with some interesting features.
In the description in the help file we can read:

Author: Stefan Fleischmann
E-Mail: mail@sf-soft.de

First released in 1995, last updated August 2000.

The following operating systems are supported:
Windows 95/98
Windows NT 4.0
Windows 2000

Homepage: http://www.winhex.com and http://www.sf-soft.com

On feature of the app is that you can load memory of a process into the program. In the help file we can read:

RAM Editor

The RAM Editor is part of the Tools menu. It allows examining the virtual memory of a process
(i.e. a program that is being executed). All memory pages committed by this process are presented
in a continuous block. Unused (free or reserved) pages are ignored by WinHex.

Select one of the listed processes. You may access either the so-called primary memory or the entire
memory of this process or one of the loaded modules. The primary memory is used by programs for nearly all purposes.
Usually it also contains the main module of a process (the EXE file), the stack, and the heap.
The "entire memory" contains the whole virtual memory of a process, including the part of memory that is
share among all processes, except system modules. Under Windows 95/98, system modules are listed optionally
in the process tree. System modules are defined as modules that are loaded above the 2 GB barrier
(such as kernel32.dll, gdi32.dll). They are shared among all running processes.

Please note the following limitations:

Caution: Only keyboard input can be undone!
Virtual memory of 16-bit processes is partially accessible under Windows 95/98 only.
Editing is possible in in-place mode only.
System modules of Windows 95/98 can only be examined in view mode, not manipulated.
The unregistered version only supports view mode. Please register!

The options relevant for the RAM editor are "Check for virtual memory alteration" (security options)
and "Virtual addresses" (general options).

 

 

The crack
When we open the register form in WinHex we can see that 2 registration codes is used,
Code1 and Code2. The first thing to do is to find out where the app gets the codes and
any clue of what type of code it is.

Press Ctrl+D to break into SIW and put a bpx on GetDlgItemText and press F5 to resume to Windows.
When we enter the codes and press the OK button nothing happens. Maybe the app don't use API's
to get the codes? Let's try another one first. Press Ctrl+D again to break into SIW.
Enter a bpx on GetDlgItemInt and resume to Windows with F5.

Again enter the codes and press the OK button. SIW breaks in two times for this bpx, this is nice.
Now we know that the codes are numbers. To find out where to start looking, when SIW breaks in,
press F12 and we end up in WinHex right after the call to GetDlgItemInt.

We end up here:





004247DB                 call    j_GetDlgItemInt 		;Get code 1
004247E0 and eax, 7FFFFFFFh
004247E5 mov [ebp+Code1_var_4], eax ;Save code 1
004247E8 push 0
004247EA push 457570h
004247EF push 30Bh
004247F4 mov eax, [ebx]
004247F6 push eax
004247F7 call j_GetDlgItemInt ;Get code 2
004247FC and eax, 7FFFFFFFh
00424801 mov [ebp+Code2_var_8], eax ; Save code 2 00424804
00424804 loc_424804: ;Nothing more done with the codes here
00424804 cmp byte ptr [ebx+5Ch], 35h
00424808 jnz loc_424964
0042480E mov eax, ebx
00424810 mov dx, 0Ch
00424814 call sub_423F2C
00424819 mov ds:456E5Dh, al
0042481E mov eax, ebx
00424820 mov dx, 10h
00424824 call sub_423F2C
00424829 mov ds:456E5Eh, al
0042482E mov eax, ebx

To avoid long tracing in SIW we put a memory breakpoint, bpm, on [ebp-4] and [ebp-8], which is where our codes goes.
We then break in to SIW here:

00425F47        loc_425F47: 
00425F47                  mov al, [ebx+5Ch]
00425F4A                  cmp al, 24h
00425F4C                  jnz short loc_425F60
00425F4E                  mov edx, [ebp+Code1_var_4]
00425F51                  mov ds:455208h, edx     ; store code 1
00425F57                  mov edx, [ebp+Code2_var_8]
00425F5A                  mov ds:45520Ch, edx     ; store code 2
       

Now we can clear the two previous memory breaks and put two new ones on 455208h and 45520Ch. This is where our codes are moved. Start again with F5 and after a delay
(delay timer used) we break in here:
               
0042BAB0 sub_42BAB0      proc near
0042BAB0
0042BAB0 Code1_var_1E54 = dword ptr -1E54h
0042BAB0 Code2_var_1E50 = dword ptr -1E50h
0042BAB0 var_1E4C = byte ptr -1E4Ch
0042BAB0
0042BAB0 push ebx
0042BAB1 push esi
0042BAB2 push edi
0042BAB3 add esp, 0FFFFF004h
0042BAB9 push eax
0042BABA add esp, 0FFFFF0D4h
0042BAC0 mov ebx, 45763Ch
0042BAC5 mov esi, 45516Ch
0042BACA mov edi, esp
0042BACC mov ecx, 7CBh ; 1995 bytes
0042BAD1 repe movsd
 
We can see that 7CBh bytes is moved from 455116Ch, which is before our codes, to the stack.
We can also see that 45516Ch+7CBh = 455937h which are after our codes, so our codes is moved.

0040126F C9 leave

0042BAD3                 lea     eax, [esp+1F2Ch+var_1E4C]
0042BADA sub eax, 8
0042BADD mov eax, [eax]
0042BADF mov [ebx], eax ;Code 1 in ebx

If we look at ebx in SIW we see that our Code1 is in ebx.

0042BAE1                 cmp     dword ptr [ebx], 0D9038h 	; 888 888
0042BAE7 jg ret_42BBE5
0042BAED cmp dword ptr [ebx], 15B38h ; 88 888
0042BAF3 jl ret_42BBE5
0042BAF9 mov eax, [ebx] ; Code1
0042BAFB mov ecx, 3E8h ; 1000
0042BB00 cdq
0042BB01 idiv ecx
0042BB03 cmp edx, 247h ; 583
0042BB09 jz ret_42BBE5
0042BB0F mov eax, [ebx] ; Code1
0042BB11 mov ecx, 3E8h ; 1000
0042BB16 cdq
0042BB17 idiv ecx
0042BB19 cmp edx, 25Fh ; 607
0042BB1F jz ret_42BBE5
0042BB25 mov eax, [ebx] ; Code1
0042BB27 mov ecx, 3E8h ; 1000d
0042BB2C cdq
0042BB2D idiv ecx
0042BB2F cmp edx, 315h ; 789
0042BB35 jz ret_42BBE5
0042BB3B mov eax, [ebx]
0042BB3D mov ecx, 3E8h ; 1000
0042BB42 cdq
0042BB43 idiv ecx
0042BB45 cmp edx, 7Bh ; 123
0042BB48 jz ret_42BBE5
0042BB4E mov eax, [ebx] ; Code1
0042BB50 mov ecx, 64h ; 100
0042BB55 cdq
0042BB56 idiv ecx
0042BB58 cmp edx, 54h ; 84
0042BB5B jnz ret_42BBE5

Now, what can we make out of this then? This is the validation for Code 1.
If we read it carefully we can reduce it to this:

15B38h (88 888) < Code1 < D9038h (888 888)
Code1 mod 1000 != 247h (583)
Code1 mod 1000 != 25Fh (607)
Code1 mod 1000 != 315h (789)
Code1 mod 1000 != 7Bh (123)
Code1 mod 100 == 54h (84)

If we can find a number that pass all this checks we have a valid number for Code 1.
A mathematician would probably put up a nice algo for this.
But if you are lazy like me you do it the quick and dirty way.
Why not, we have the code above, why not use it. My masm32 KeyGen code for Code1 looks like this.

       
KeyGen.asm
;****************************************************************************
;
; FILE: KeyGen.asm
;
; PURPOSE: Key generator for Code1 in WinHex registration.
;
; COMMENTS: Quick and dirty...:-)
;
; FUNCTIONS: start (Cracking WinHex)
;
; HISTORY:
;
; CREATED: 2001-12-29 21:48 Basse
;
;****************************************************************************
.386
         .model flat, stdcall
         option casemap:none
         include e:\masm32\include\windows.inc
         include e:\masm32\include\kernel32.inc
         include e:\masm32\include\advapi32.inc
         include e:\masm32\include\user32.inc
         include e:\masm32\include\masm32.inc
includelib e:\masm32\lib\user32.lib
         includelib e:\masm32\lib\kernel32.lib
         includelib e:\masm32\lib\advapi32.lib
         includelib e:\masm32\lib\masm32.lib
.data
         Tmp dd 88888
         hFile dd 0
         pBuffer db 255 dup(0)
         pBytesWritten dd 0
         pCrLf db 0Dh,0Ah
         pEndTxt db "Number of Code1's: %i",0
         pFileName db "c:\windows\desktop\cracker\hacks\winhex\Code1.txt",0
         .code
         xor ebx, ebx ;Match counter
         start:
         mov eax, Tmp ;Code1 to eax
         .if eax > 888888
         jmp done
         .endif
 ;========================================
 ; Here's the selection algo for Code1
 ; See sub_42BAB0 in WinHex
 ;========================================
 ;Idiv with 1000
         mov ecx, 3E8h
         cdq
         idiv ecx
         cmp edx, 25Fh
         jz inc_Tmp
         cmp edx, 315h
         jz inc_Tmp
         cmp edx, 7Bh
         jz inc_Tmp
 ;Idiv with 100
         mov eax, Tmp
         mov ecx, 64h
         cdq
         idiv ecx
         cmp edx, 54h
         jnz inc_Tmp
 ;If we get to here we have a valid code in Tmp.
 ;Save it to file.
         convert:
 ;Convert to ascii string
         mov edx,Tmp
         invoke dwtoa ,edx,addr pBuffer
 ;Create the file
         .if hFile==0
         invoke CreateFileA ,addr pFileName,\
         GENERIC_WRITE,\
         0,\
         0,\
         CREATE_ALWAYS,\
         FILE_ATTRIBUTE_ARCHIVE,\
         NULL
         .if eax==INVALID_HANDLE_VALUE
         invoke GetLastError ;Get last error in eax
         jmp done
         .endif
         mov hFile, eax
         .endif
 ;Save to file
         invoke lstrlenA ,addr pBuffer
         invoke WriteFile ,hFile,addr pBuffer,eax,addr pBytesWritten,NULL
         invoke WriteFile ,hFile,addr pCrLf,2,addr pBytesWritten,NULL
 ;Zero out memory (max 6 bytes "888888")
         mov dword ptr[pBuffer],0
         mov dword ptr[pBuffer+4],0
 ;Increment match counter
         inc ebx
 inc_Tmp:
         ;Next code to try
         inc dword ptr [Tmp]
         jmp start
 done:
 ;Write number of code1's to the end of the file
         invoke wsprintf ,addr pBuffer, addr pEndTxt,ebx
         invoke lstrlenA ,addr pBuffer
         invoke WriteFile ,hFile,addr pBuffer,eax,addr pBytesWritten,NULL
 ;Close file
         .if hFile!=0
         invoke CloseHandle ,hFile
         .endif
 invoke ExitProcess ,0
end start
------------------------------------------------------------------------------------------------------------
       

Now we have a file with 8000 valid Code1's. Now what? If we look down a few lines we see this:

0042BB7F loc_42BB7F:                             
0042BB7F mov eax, [esp+1F2Ch+Code1_var_1E54]
0042BB86 call sub_42F78C ; Check code2?
0042BB8B cmp eax, [esp+1F2Ch+Code2_var_1E50]
0042BB92 jnz short ret_42BBE5
0042BB94 mov eax, 45759Ch
0042BB99 inc eax
0042BB9A mov byte ptr [eax], 1
0042BB9D push 2
0042BB9F mov eax, ds:4584A4h
0042BBA4 push eax
0042BBA5 call j_KillTimer ;Kill the delay timer
We can see here that Code1 is an argument to sub_42F78C and that the return value is compared with Code2.
We can assume that Code1 is manipulated in some way to equal Code2.
If we trace in to this sub we quickly realize that is it not as easy as with Code1.
It is messy, to say the least. Jumps and calls all over the place.

Lets try our valid Code1's and see what happens. If we try the first code from our list we can see in
SIW that the return value from sub_42F78C is -1 and we jump out at 42BB92.
This means that a valid Code1 don't automatically get mangled to a valid Code2.
I randomly picked another Code1 from the list (luck?) and this time I was luckier.
The return value In eax was code 2 (not my standard 1234567890 :-).

Now we got both code1 and code2 so we can register WinHex and the message "Codes OK!" comes up.

 

At 42BBAC after the call to sub_42F78C we find one of Dettens favorites:

0042BBAA                 mov     al, 0Ah	          ;string length
0042BBAC mov edx, 451B2Ch ; ptr to text "iENOY",0Ah,'ea',0Bh,'*'
0042BBB1 loc_42BBB1: ;The xor loop
0042BBB1 xor byte ptr [edx], 2Ah ;xor all chars with this
0042BBB4 inc edx ;next char
0042BBB5 dec al
0042BBB7 jnz short loc_42BBB1 ;loop on?
0042BBB9 mov eax, 451B2Ch ; string at 451B2C is now "Codes OK!"
Final notes


If my random pick of Code1 from the list was luck, or how many are valid I don't know.
If anyone out there want to find out, let me know ;-)

As you have noticed I haven't given out any codes :-).
Well you got the code for the KeyGen, use it and start trying! Maybe you learn something :-)

Basse

Remember, If you like this program, and tested it long enough, buy it!

www.biw-reversing.cjb.net