Thursday September 28, 2000

A Study in Methology

 
 

Windows Commander 4.01 exposing protection routines without winice

 
  By +Indian_Trail  

Tools:
Win32Dasm 8.93
FileMonitor 4.0
WatcomSpy

The Purpose of this essay is to teach the art of deadlisting. Alot of crackers use  debuggers (which there's nothing wrong with) to study their target. In this essay we will  pretend that there's no such thing as a debugger, which is true in some cases. You might  not be at your computer, the  target use Anti Softice tricks or whatever. This is a Code  Reading & Puzzling tutorial. It's not about how to crack Windows Commander even  though we will expose the protection routines.  

Intro

I have chosen window commander 4.01 (from now on wc) for this essay because it contains four protections.

  1. Keyfile, this is the main protection.
  2. Checksum 1
  3. Checksum 2
  4. Nagscreen

In this essay I will try to focus on 'how to think' rather than 'where should I patch?'. This means that you should:

What is deadlisting? It's cracking by using a disassembler to get a source code without fancy variabel names and comments, this source code is usually called a deadlist and it is searched and examined carefully to find the protection routines. This method is more relaxed than the debugger method. You can sit and ponder and slowly put pieces together to get an understanding of the target. Usually when using a debugger you land inside a routine and you'll have to find out from where this routine was called by tracing backwards. Then you must find out how the caller was called and you'll end up building up a caller list. When looking at a deadlist you can quickly jump back and forward without starting all over again. You can save the deadlist to a textfile and edit it so 'call xxxxxx' becomes 'call NagScreen' which makes alot more sense.

I use here win32Dasm there are others like IDA which is alot more flexible than win32Dasm. You'll find both at your local wares dealer .

Section #1: NagScreen()

When I first started wc I noticed the spashscreen and the nagscreen begging for money. After clicking the correct button I started to look for a register option where I could register wc by typing in a serialnumber. I didn't find one so I proceeded by reading the helpfile where I read that the registered version will be shipped on a disk after payment. I also read that the only thing that's different between registered/unregistered is the NagScreen. I knew now (unless the helpfile was lying) that I had the full version of wc, all of it's functionality etc. After a while I started to get really irritated on the NagScreen so I decided to rip it out.

Since there's no register dialogbox I disassembled wc and examined the string reference which is something that's very nice in win32Dasm. I found some strings like "EVALUATION COPY", "Please register". These are seen in the splashscreen which means that wc has already determined that we are not registered. I did not find any reference to the text that's in the NagScreen, what I did find was the strings "wincmd.key" and "Your key is invalid please request a new one....". Very interesting. I ran filemon and restarted wc, from filemon I saw that wc tried to open wincmd.key which of course failed. I opened a texteditor put some text in it and saved it in wc's directory as wincmd.key. Again I ran filemon and restarted wc, now I noticed that wc tried to read 1024 bytes from the key file. 1024 bytes is a lot of bytes. If the keyfile contains the users name/company, adress, country, phone# then the rest is the serialnumber. This gives a serialnumber of at least 700 bytes. The helpfile says that either the name OR company will be shown in the titlelist. This probably means that only the name/company from the orderform is in the keyfile and the key is the rest of the bytes. I decided to fix the NagScreen so it never pops up on startup.


 

The wincmd.key is only found once in the listing and it's at 4BF77A. This function is called from two places 4BFA09 and 4C028F. 4BFA09 is a lot longer than 4C028F and 4C028F is called from 4BFAC6 and 4BFAD8 therefor I choosed to investigate 4BFA09 first.

 :004BF9FC 	mov bl, 01
 :004BF9FE 	lea eax, dword ptr [ebp+FFFFF95C]
 :004BFA04 	mov edx, 00000103
 :004BFA09 	call 004BF730	;Get keyfile path & configuration etc (1)
 :004BFA0E 	lea eax, dword ptr [ebp+FFFFF95C]
 :004BFA14 	xor edx, edx
 :004BFA16 	call 00442AA0	;Open the keyfile
 :004BFA1B 	mov esi, eax
 :004BFA1D 	call 00403508
 :004BFA22 	cmp dword ptr [eax+0000000C], 00000000	; Keyfile successfully opened?  (2)
 :004BFA29 	je 004BFA3D	; if so move on else close handle
 :004BFA2B 	lea eax, dword ptr [ebp+FFFFF95C]
 :004BFA31 	mov edx, 00000040
 :004BFA36 	call 00442AA0 
 :004BFA3B 	mov esi, eax
 :004BFA3D 	call 00403508
 :004BFA42 	cmp dword ptr [eax+0000000C], 00000000	; Check if the keyfile was opened  (4)
 :004BFA49 	jne 004BFA6C	; if not move on  


How do I know that the comments above are correct?

Comment 1:
If you look inside 004BF730 you'll see strings like "keypath" and "configuration". It also calls GetPrivateProfileStringA which is used to retrieving information from ini files. Looking inside the 442AA0 you'll see that it calls another routine which calls CreateFileA which is used for opening/reading/creating files. Therefor we know that 4BFA16 opens the file, even if it's done indirect. The call at 4BFA09 takes the parameter [ebp+FFFFF95C] which is also passed to 4BFA16. We can then assume that this parameter is used to hold the path to the keyfile.

Comment 2:
If you look at what happens when CreateFileA fails you'll see that it calls 00403508 and that zero is placed at eax-0c. Therefor the call 00403508 (which return with an adress in eax) followed by cmp dword ptr [eax+0000000C], 00000000 checks to see if the file was successfully opened! Notice how wc tries to open the keyfile two times. You can see this by examine filemons output.

Since I don't have a keyfile I take the jump to 4BFA6C.

:004BFA6C xor ebx, ebx
:004BFA6E mov word ptr [ebp+FFFFFEE2], 0000
:004BFA77 cmp word ptr [ebp+FFFFFEE2], 0080
:004BFA80 sete byte ptr [0051A7FD]
:004BFA87 mov byte ptr [ebp+FFFFFEE1], 01
:004BFA8E test bl, bl
:004BFA90 je 004BFD80



:004BFD80 push 00000103 :004BFD85 lea eax, dword ptr [ebp+FFFFF754] :004BFD8B push eax :004BFD8C mov eax, dword ptr [0051A80C] :004BFD91 call 00420A10 :004BFD96 push eax :004BFD97 Call 004058B8 :004BFD9C mov edx, 004C023C :004BFDA1 lea eax, dword ptr [ebp+FFFFF754] :004BFDA7 call 00406C18 :004BFDAC test bl, bl :004BFDAE je 004BFDDA :004BFDDA test bl, bl :004BFDDC je 004BFFDA :004C0057 test bl, bl :004C0059 je 004C00CF :004C00CF mov eax, dword ptr [ebp+FFFFFEFC] :004C00D5 call 00402CA4 :004C00DA mov eax, dword ptr [ebp+FFFFFEF8] :004C00E0 call 00402CA4 :004C00E5 mov eax, dword ptr [ebp+FFFFFEF4] :004C00EB call 00402CA4 :004C00F0 mov byte ptr [0051A7FC], bl
This just about everything that's done. there's no more conditional jumps before the end of the
routine

How do I know that bl remains zero?

So the value of bl is stored at [0051A7FC]. By searching the deadlist we find that this value is put in eax at location :004C03BC which is called from 4BDA79.
:004C03BC A0FCA75100              mov al, byte ptr [0051A7FC]
:004C03C1 C3                      ret




:004BDA79 E83E290000              call 004C03BC
:004BDA7E 84C0                    test al, al
:004BDA80 7426                    je 004BDAA8
:004BDA82 A1CC265100              mov eax, dword ptr [005126CC]
:004BDA87 8B4028                  mov eax, dword ptr [eax+28]
:004BDA8A B201                    mov dl, 01
:004BDA8C E833F8F5FF              call 0041D2C4
:004BDA91 C605CC0A510001          mov byte ptr [00510ACC], 01
:004BDA98 C605D00A510001          mov byte ptr [00510AD0], 01
:004BDA9F 8BC3                    mov eax, ebx
:004BDAA1 E8EEB0F5FF              call 00418B94		
:004BDAA6 EB3E                    jmp 004BDAE6

The flag [0051A7FC] is not used any more after this. So by just looking at the above code you should know that the jump at 4BDA80 is the trigger for 'show NagScreen'. If you wanna be 100% sure that the jump is the trigger, investigate the call at 4BDAA1. This call leads to PostMessage:

:00418C33 E85C030000              call 00418F94
      |
      v
:00418F94 53                      push ebx
:00418F95 8BD8                    mov ebx, eax
:00418F97 6A00                    push 00000000 ; lparam
:00418F99 6A00                    push 00000000 ; wparam 
:00418F9B 6821B00000              push 0000B021 ;message to send
:00418FA0 8BC3                    mov eax, ebx
:00418FA2 E8697A0000              call 00420A10
:00418FA7 50                      push eax     ;Hwnd

* Reference To: user32.PostMessageA, Ord:0000h
                                  |
:00418FA8 E823CAFEFF              Call 004059D0
:00418FAD 5B                      pop ebx
:00418FAE C3                      ret
I didn't find the 0x0B021 Message in my compilers headerfiles so it's a custom message used by windowscommander only.Lets use a messagespying Tool like Watcomspy to spy on the NagScreen. Press the correct button and you'll see from Watcomspy that the message 0000B021 is sent. This means that this routine kills the NagScreen. Now we can be 100% sure that our conclusion are correct. Patch it and try it. No NagScreen on startup, Yes!

What a relaxing cracking session this has been so far, there's no messy notes, no forgeting calling structure, no misstakes made. It's been clean and neat.

Section #2: Encryption

After you patched the NagScreen wc displays this messagebox after a few seconds.

Now the normal way to deal with this is to search the stringreferences in the disassembled listing (deadlisting) and look for a trigger. Scanning the stringreferences in win32Dasm doesn't give much, there's simply no strings that match the ones in the MessageBox. Examining the exe file doesn't turn up anything either. <br>
Conclusion?
The strings are encrypted along with some other strings like "NOT REGISTERED".
So, how do we find these strings then?

First we need a basic understanding of strings and encryption. Strings are nothing but a number of bytes in an array. This array must be terminated somehow (usually by putting a zero at the end or '$'), so the compiler knows where the string ends.

Ex
string1 = " Hello" looks like this if zerotermination is used:
'H' 'e' 'l' 'l' 'o' '0' equals the bytes -> 48h 65h 6Ch 6Ch 65h 00

This is also what win32Dasm will look for in the file. When encrypting a string the values of the bytes are changed by an algorithm called encryptor. When the program needs an encrypted string it is passed to a decryptor algorithm which changes the bytes values back. The encryptor is unlikely to be in a program since it's not needed, but the decryptor is. We must now find this decryptor algorithm to be able to find the string "WARNING: The WINCMD...".

One way of finding an encrypted string would be to count the number of characters inside the string and then look at the stringreference for a string which has equal many characters. This is however not a good idea in win32Dasm since it clips the strings. I'm sure you know a string like "This is an unregistered evaluation copy bla bla" and win32Dasm reference this string as "This is an unreg". I have come across this phenomena alot of times. A better way is to look for strings that makes no sense.

I found these strings that I thought were suspicious:

I probably missed some "strange" strings, but the above strings will be enough. Lets start off with the first. It's referenced at 446ECD.

* Possible StringData Ref from Code Obj ->"vUXBC"
                                  |
:00446ECD B8346F4400              mov eax, 00446F34
:00446ED2 E839FFFBFF              call 00406E10 ; calculate length of string etc
:00446ED7 E8282DFCFF              call 00409C04 ; decryption routine
:00446EDC A30C295100              mov dword ptr [0051290C], eax

Decryptor:
:00409C04 8BD0                    mov edx, eax
:00409C06 EB13                    jmp 00409C1B
:00409C08 80F90A                  cmp cl, 0A
:00409C0B 740D                    je 00409C1A
:00409C0D 8A0A                    mov cl, byte ptr [edx]
:00409C0F 80E920                  sub cl, 20
:00409C12 80F177                  xor cl, 77
:00409C15 80C120                  add cl, 20
:00409C18 880A                    mov byte ptr [edx], cl
:00409C1A 42                      inc edx
:00409C1B 8A0A                    mov cl, byte ptr [edx]
:00409C1D 84C9                    test cl, cl
:00409C1F 75E7                    jne 00409C08
:00409C21 C3                      ret

If you look at 446F34 you'll see the whole string:
If we put in "vUXBC" values in the decryptor we get:

((76h-20) xor 77) + 20 = 'A'
((75h-20) xor 77) + 20 = 'b'
((58h-20) xor 77) + 20 = 'o'
((42h-20) xor 77) + 20 = 'u'
((43h-20) xor 77) + 20 = 't'

Now that we have the decryption routine we can reverse it to see what an unencrypted string will look like encryptet. We need to know that so we can locate them in the deadlisting. Reversing the decryptor routine in this case is very simple. If you want to know what the word 'NOT' looks like you simply do the steps backwards, like this:

(('N'-20h) xor 77h) +20h = 79h = 'y'
(('O'-20h) xor 77h) +20h = 79h = 'x'
(('T'-20h) xor 77h) +20h = 79h = 'c'

You could easily write a small help program to show you what the strings look like encrypted. To get win32Dasm to reference the correct strings we change the encrypted strings in a copy of wincmd32.exe to unencrypted and use that copy to get a deadlisting with unencrypted string reference.

Aha the "`vey" reference IS the virus warning message.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004CA148(C)
|
:004CA1AF 55                      push ebp
:004CA1B0 685DAD4C00              push 004CAD5D
:004CA1B5 64FF30                  push dword ptr fs:[eax]
:004CA1B8 648920                  mov dword ptr fs:[eax], esp
:004CA1BB 8D85DBFDFFFF            lea eax, dword ptr [ebp+FFFFFDDB]

* Possible StringData Ref from Code Obj ->"`vey"
                                  |
:004CA1C1 8B158C0C5100            mov edx, dword ptr [00510C8C]


If you look at the code at 4CA148 you'll see that this is not CODE but DATA. A disassembler thinks that 7E65 is code but if you look at it you'll see that it's part of the encrypted string WARNING: The WINCMD...". So this jump is to be ignored. Notice if you DID decrypt the strings in the executable, and used it to get a deadlist, you'll see that the reference is:

* Possible StringData Ref from Code Obj ->"WARNING: The WINCMD executable "
                                        ->"file is corrupted, Possible VIRUS!"

Just below is a jump which sends us to the decryption routine and calls messagebeep and messagebox.

:004CA20A 83E809                  sub eax, 00000009
:004CA20D 0F84F7090000            je 004CAC0A
:004CA213 48                      dec eax
:004CA214 0F84850A0000            je 004CAC9F
:004CA21A 48                      dec eax
:004CA21B 0F84260A0000            je 004CAC47
:004CA221 2D36050000              sub eax, 00000536
:004CA226 0F8413030000            je 004CA53F	; Jump to show virus message
:004CA22C E9030B0000              jmp 004CAD34

Many of you might be tempted to patch some of these jumps that leads to the virus message. If you do that you'll patch the messagebox display and not the checksum routines. This whole routine where data is mixed with code is not referenced in win32Dasm. What we have here is a call table which works like this:

  1. Programmer/Compiler puts the adress of some calls in a table.
  2. A routine scout outs the correct adress to a specific call.
  3. A jump or call executes the function ie jmp Ecx or Call [ecx+35].
Back to the jumping business. As you can see we only jump if eax =536 this means than when we reach this code eax has the value of 536h +2h+9h=541h. The meaning of this value will remain unkown for now but it's clear that it is an important value since it triggers the show "virus warning". Follow the jump and you'll see:
:004CA53F 6841050000              push 00000541 ; Here is the evil value
:004CA544 8B45FC                  mov eax, dword ptr [ebp-04]
:004CA547 E8C464F5FF              call 00420A10
:004CA54C 50                      push eax

* Reference To: user32.KillTimer, Ord:0000h
                                  |
:004CA54D E8EEB3F3FF              Call 00405940

This means that 541h is a timer identifier, remember that the virus messagebox pops up after a while and not on the fly when windowscommander starts. Where is this timer set? If we search for push 00000541 and list the location where it's used as a parameter to SetTimer we find:

4CF0DE timeout = 5 sec 1388h  this one look interesting!!!
4CF109 timeout = 0.5 sec 1F4h
4E8BDB timeout = 1 sec 3e8h

The delay before the MessageBox is displayed, is about 5 seconds, so this means that the timer 541h is set at location 4CF0DE if the wc's executable file is changed.

:004CF0B1 8D4000                  lea eax, dword ptr [eax+00]
:004CF0B4 55                      push ebp
:004CF0B5 8BEC                    mov ebp, esp
:004CF0B7 53                      push ebx
:004CF0B8 8BD8                    mov ebx, eax
:004CF0BA 80BBB804000000          cmp byte ptr [ebx+000004B8], 00
:004CF0C1 7520                    jne 004CF0E3
:004CF0C3 C683B804000001          mov byte ptr [ebx+000004B8], 01
:004CF0CA 6A00                    push 00000000
:004CF0CC 6888130000              push 00001388
:004CF0D1 6841050000              push 00000541
:004CF0D6 8BC3                    mov eax, ebx
:004CF0D8 E83319F5FF              call 00420A10
:004CF0DD 50                      push eax
If you patch the above code so that the timer is never set, you'll get rid of the MessageBox. However as soon as you press any functionkey such as F1-F10 or Backspace wc exits. You can however work in wc by just using the mouse but that's not very satisfying. This means that the keyboard short-cuts is depending on the crc somehow (I didn't delve into this). Which for us means that the crc checks must be solved, anyway, it is quite good to have a virusdetector inside your favourite filemanager.



Section #3: Checksum (CRC)

In order to calculate a checksum, wc needs to read the file wincmd32.exe. Now where is it opened? First of all there's no need for a program to open it's own executable and read it from start to end, since the operating system handles the loading of the program into memory. The only thing that's in the executable file (not always though) are the resources, which need to be read into memory by calls to ie LoadString, LoadBitmap etc

So to find out where wincmd32 opens it self we use Filemonitor. Scanning filemonitors    output we find this: 
   
Wincmd32	Open	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS OPENEXISTING READONLY DENYNONE 	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 0 Length: 32768	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 32768 Length: 32743	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 65511 Length: 32743	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 98254 Length: 32743	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 130997 Length: 32743	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 163740 Length: 32743	
Wincmd32	Read	C:\APPZ\WINCMD\WINCMD32.EXE	SUCCESS	Offset: 196483 Length: 32743

So wincmd32 opens the executable file and reads it from offset 0 32768 (8000h) bytes and then it is read in 32743 (7FE7h) byte chunks through out the file. Hmmn very interesting. If you search for 8000 or 00008000 you'll get too many occurences, but if you search for 7FE7 you'll only get about 16 occurenses where only two of them are a value. The rest are either a part of an adress or a part of an instruction. The two places where actually 7FE7 are found as a value are:

:004504B5 66B9E77F mov cx, 7FE7
:00450E56 66B9E77F mov cx, 7FE7

If you look around these code areas you'll see that 450E56 is more or less a mirror to 4504B5. Investigation of 4504B5 leads to the same function that's used to open our keyfile, only this time the wincmd32.exe is opened. We know this because wc calls GetModuleFileNameA before it calls CreateFileA.

:00450463 	call 00442AA0  ;Open wincmd32.exe
:00450468 	mov dword ptr [ebp-10], eax
:0045046B 	call 00403508
:00450470 	cmp dword ptr [eax+0000000C], 00000000
:00450477 	je 0045048C ; if opened jump
:00450479 	lea eax, dword ptr [ebp+FFFFFED3]
:0045047F 	mov edx, 00000040
:00450484 	call 00442AA0
:00450489 	mov dword ptr [ebp-10], eax
:0045048C 	mov [ebp-29], 01
:00450490 	cmp byte ptr [ebp-29], 00
:00450494 	je 004504AB
:00450496 	lea eax, dword ptr [ebp-1A]
:00450499 	push eax
:0045049A 	mov edx, dword ptr [ebp-0C]
:0045049D 	mov cx, 8000
:004504A1 	mov eax, dword ptr [ebp-10]
:004504A4 	call 00442D3C
:004504A9 	jmp 004504C6
:004504AB 	lea eax, dword ptr [ebp-1A]
:004504AE 	push eax
:004504AF 	mov eax, dword ptr [ebp-0C]
:004504B2 	lea edx, dword ptr [eax+19]
:004504B5 	mov cx, 7FE7
:004504B9 	mov eax, dword ptr [ebp-10]
:004504BC 	call 00442D3C
:004504C1 	add word ptr [ebp-1A], 0019
:004504C6 	cmp word ptr [ebp-1A], 0004
:004504CB 	jb 00450507
:004504CD 	mov eax, dword ptr [ebp-24]
:004504D0 	cmp eax, dword ptr [ebp-28]
:004504D3 	jl 00450507
:004504D5 	movzx eax, word ptr [ebp-1A]
:004504D9 	add eax, dword ptr [ebp-28]
:004504DC 	cmp eax, dword ptr [ebp-24]
:004504DF 	jle 00450507
:004504E1 	mov eax, dword ptr [ebp-24]
:004504E4 	sub eax, dword ptr [ebp-28]
:004504E7 	mov dword ptr [ebp-18], eax
:004504EA 	mov [ebp-14], 00000005
:004504F1 	mov eax, dword ptr [ebp-18]
:004504F4 	add eax, dword ptr [ebp-14]
:004504F7 	mov edx, dword ptr [ebp-0C]
:004504FA 	mov byte ptr [edx+eax], 00
:004504FE 	inc [ebp-14]
:00450501 	cmp dword ptr [ebp-14], 0000001A
:00450505 	jne 004504F1
:00450507 	cmp byte ptr [ebp-29], 00
:0045050B 	je 0045051E
:0045050D 	lea ecx, dword ptr [ebp-04]
:00450510 	movzx edx, word ptr [ebp-1A]
:00450514 	mov eax, dword ptr [ebp-0C]
:00450517 	call 0044F14C ; calculate crc for one block
:0045051C 	jmp 00450533
:0045051E 	lea ecx, dword ptr [ebp-04]
:00450521 	movzx edx, word ptr [ebp-1A]
:00450525 	sub edx, 00000019
:00450528 	mov eax, dword ptr [ebp-0C]
:0045052B 	add eax, 00000019
:0045052E 	call 0044F14C ;calculate crc for one block
:00450533 	mov [ebp-29], 00
:00450537 	movzx eax, word ptr [ebp-1A]
:0045053B 	sub eax, 00000019
:0045053E 	add dword ptr [ebp-28], eax
:00450541 	movzx eax, word ptr [ebp-1A]
:00450545 	cmp eax, 00008000
:0045054A 	je 00450490
:00450550 	mov edx, 00008033
:00450555 	mov eax, dword ptr [ebp-0C]
:00450558 	call 00402674
:0045055D 	mov eax, dword ptr [ebp-10]
:00450560 	call 00442CCC
:00450565 	mov eax, dword ptr [ebp-20] ;  
:00450568 	mov eax, dword ptr [eax]    ; expected checksum in eax
:0045056A 	xor eax, 2A67BE65   	      ; xor it	
:0045056F 	mov dword ptr [ebp-08], eax ; store it at ebp-08
:00450572 	push ebp
:00450573 	call 00450388			

To find out where our checksum is stored, we look inside 0044F14C:
The last thing that's made before the ret instruction is:

:0044F1F0 mov dword ptr [esi], eax ; where eax is the checksum for the current block.

You can study this routine on your own to find out where and how the checksum is stored before it's moved into eax if you want. I choose to skip this code cause the essay should contain theoretical methods rather than code snippets.

Anyway what's the value of esi? In the beginning of this routine esi gets the value of ecx. Ecx value is moved before the call is made, like this:

lea ecx, dword ptr [ebp-04]
So our checksum is stored at ebp-04.
Inside 450388 our checksum us xored:

:004503B9 	xor dword ptr [eax-04], F5A3E289; eax-04 = our checksum
:004503C0 	push ebp
:004503C1 	call 00450280 


And inside 450280 comes the comparing code:


:00450292 3B42F8                  cmp eax, dword ptr [edx-08]  ; Checksum compare
:00450295 0F94C0                  sete al		       ; Set flag if correct	

:00450307 3B42F8                  cmp eax, dword ptr [edx-08]  ; Checksum compare
:0045030A 0F94C0                  sete al                      ; Set flag if correct

:00450364 3B42F8                  cmp eax, dword ptr [edx-08]  ; Checksum compare
:00450367 0F94C0                  sete al		       ; Set flag if correct

So the correct check sum is stored (before the xoring stuff) at ebp-20. Looking around you'll see that ebp-20 = [50FeB0]+5, done like this:

:00450409 	mov eax, dword ptr [0050FEB0]
:0045040E 	add eax, 00000005
:00450411 	mov dword ptr [ebp-20], eax

So what is it? Look at the data references at adress 0050FEB0 and you'll see it is an adress = 450264. Added with 5 we get 450269 :

00450269 0B74DC1A or esi, dword ptr [esp+8*ebx+1A]

So the expected checksum is 1ADC740B.
Expected CheckSum = 1ADC740B xor 2A67BE65 = 30BBCA6E <;br>
So we know the orginal checksum that's calculated from exe file and the expected checksum which is a constant.

[Newbie stuff]
I don't know the calculated checksum. Yes you do!
The expected checksum before xoring is 1ADC740B. It's xored with 2A67BE65 and we get 30BBCA6E. The unpatched file_checksum is xored with F5A3E289 and has to be equal to 30BBCA6E. This gives us:
F5A3E289 xor 30BBCA6E = C51828E7, which is the calculated unpatched checksum!!

So after we patched wc our checksum will not be C51828E7. To find out what it will be you could simply copy the checksumcalculation routine and put it in a small little program that you'll write in you favourite language. Mine is C. You could also use a debugger to fetch this value byplacing a breakpoint at 4503B9 and inspect EAX-04. It's up to you.

Section #4: Second Checksum (CRC)

So you replaced the expected crc with your wincmd32.exe's crc and everything works fine. There's no NagScreen, no mean MessageBox. You use the program for days, weeks, month and suddenly BANG! The evil MessageBox is there again. Informing that the executable file is corrupt.

This second crc check is clever, it snaps sometimes after just a few minutes, somtimes after several hours and so on. How can this be? Apparantly there's another checksum test that use a different calculation routine than the first one that runs on startup.

The problem with this crc check is that it's random. It's very difficult to catch.You'll need to have filemon or winice or whatever tools you wish to use running while using windowscommander and yet there's no guarantee that the message is displayed. The only tools that matter now is our brain and the deadlist.

Locating the second checksum routine:

Lets summerize what we know:

  1. The messagebox routine thats executed when timer 541h has reached its timeout value = 5 sec The first messagebox only pops up IF the CRC's doesn't match. If the crc's are equal timer 541 is never set to 5 sec.
  2. The timer 541h is only set three times with a timeout value of 5 (4CF0DE)sec, 0.5 sek (4CF0FC) and 1 sec (4E8BDB).
  3. The routine that calculates crc for one block.
  4. SetTimer is called 41 times (use count all in Uedit)
  5. The first timer is set at 4CF0DE

Some of you might have found the mirror to the first crc check, the mirror is located at 450D84. But this check we pass because it works exactly like the first one.

This second checksum test is a good example of where deadlisting beats the debugger method. By just examining the code in win32Dasm we should be able to find the second checksum routine. If you look inside the exefile you'll find no wincmd32.exe string. That explains why GetModuleFileName was called in the first checksum routine. The routine we are looking for must besides calling GetModuleFileNameA also call CreateFileA and ReadFileA. The bytes read by ReadFile must be Compared with some other fixed bytes. These are our demands.

GetModuleFilename is called from
:0040A813  
:00438A9C
:00438E9C
:00446A44
:0045041C    ; This is the start_up CRC
:00450DDA  ; This is the Mirror
:004510AA   ; Very Interesting check expected checksum value against a parameter pushed on the stack                        before entering. Call is unreferenced. It Reads a file first by 4096 bytes and then 4071 bytes.

The last routine is very interesting since it checks the value of [50FEB0] which is used to point the expected checksum in the first crc routine. It then takes the bytes that is 9 steps ahead from [50FEB0] instead of 5 that the first checksum routine did. This Must be the second check. Lets see how it works:

The 451015 routine first reads 1000h bytes and then it keeps reading the file in 0xFE7 bytes til end of file. First we read in 1000h bytes to ebp-FFFFEFA4 and then we store FE7h bytes in ebp-FFFFEFBD
On entry to the reading loops ebp-28 is zero. There are three questions that needs be answered.

1 Where is the final filechecksum stores
2 Whats done with the bytes that's read from the exe file
3 Where is the good/bad jump

If you examine this Reading_Loop you'll see that it basically does this:

Call 44FE88 is the manipulation call. You can know this because eax is loaded with ebp-20 and inside the call, one of the registers are loaded with eax+28 which is the buffer. Its then manipulated with bitoperations inside 44F4A0. The other call is simply a byte mover and is not that interesting for us.

The bytes indexed by [50FEB0+9] is placed in [ebp-1C] and it only used at the end of the routine. Here:

:0045122D mov eax, dword ptr [ebp-1C]
:00451230 mov eax, dword ptr [eax]
:00451232 mov edx, dword ptr [ebp-20]
:00451235 mov dword ptr [edx+78], eax
:00451238 mov eax, dword ptr [ebp-1C]
:0045123B mov eax, dword ptr [eax+04]
:0045123E mov edx, dword ptr [ebp-20]
:00451241 mov dword ptr [edx+7C], eax
:00451244 mov eax, dword ptr [ebp-1C]
:00451247 mov eax, dword ptr [eax+08]
:0045124A mov edx, dword ptr [ebp-20]
:0045124D mov dword ptr [edx+00000080], eax
:00451253 mov eax, dword ptr [ebp-1C]
:00451256 mov eax, dword ptr [eax+0C]
:00451259 mov edx, dword ptr [ebp-20]
:0045125C mov dword ptr [edx+00000084], eax
:00451262 lea eax, dword ptr [ebp+FFFFEE80]
:00451268 mov edx, dword ptr [ebp-20]
:0045126B mov dword ptr [edx+30], eax
:0045126E mov eax, dword ptr [ebp-20]
:00451271 call 0044FE88
:00451276 mov eax, dword ptr [ebp-20]
:00451279 call 0044FF90
:0045127E mov eax, dword ptr [ebp-08]
:00451281 call 00442CCC
:00451286 mov eax, dword ptr [ebp-20]
:00451289 call 00402CA4
:0045128E lea eax, dword ptr [ebp+FFFFEE90]
:00451294 lea edx, dword ptr [ebp+FFFFEE80]
:0045129A mov ecx, 00000010
:0045129F call 00402A0C
:004512A4 je 004512D7

From the above code you'll see that not only the bytes indexed by [50FEB0+9] is used but [50FEB0+9+4], [50FEB0+9+8] and [50FEB0+9+C] . These bytes are copied into some locations and then the calculation routine is called and the result is stored at [ebp+FFFFEE80] which is then compared in 402A0C. This gives us the the Good/Bad jump is at 4512A4. If you look inside the routine 402A0C you'll see that if [ebp+FFFFEE90] doesn't equal [ebp+FFFFEE80] we leave the routine with eax = nonezero. So we need to jump at 4512A4.

 

If you wanto keep the crc check intact (incase of virus) you'll need to examine the calculation routine and make sure that it equals the executable files crc by changing the values starting at [50FEB0+9] . If you don't need the virus protection then simply make this routine leave with eax=0. I think that one viruscheck/crc routine is enough.

Section #5: Getting the Second Checksum (CRC) to snap

For those of you who wants to keep the second crc routine, you might need to use a debugger. Since I assume that you don't feel like waiting for hours or days before getting a chance to debug this routine we need to make it snap as fast as we can. What's trigger this?

If you start wc, delete everything from the c:\windir\applog directory and change the system time you'll probably get lucky. I started by fiddling with the systemdate and saw that the crc check was triggered if systemdate wa changed and I executed a number of commands inside wc. That got me to examine the GetLocalTime API in wc. This function is only called from two locations.

4076F0 this is called from 8 locations
40771C this is called from three

Since 40771C is only called from three location I started to examine it first. This got me to a routine that calls both 4076F0 and 40771C. The routine is located at 407748. Going backwards frm 407748 and investigating the locations from where it's called, got me to 4CC960 and there I found:

 

:004CC960 call 00407748
:004CC965 fsub qword ptr [00512BA8]
:004CC96B fld tbyte ptr [004CD280]
:004CC971 fcompp
:004CC973 fstsw ax
:004CC975 sahf
:004CC976 jnb 004CC97D
:004CC978 call 004513AC


Changing the jump at 4CC976 will make the second crc routine execute as soon as enough commands has been executed. What you do is to switch back and forth for a while (30 seconds or so) by holding down the returnkey. Put the cursor on a directory first.

Now you can debug this!!

Hope you learned something from this.

/Indian_Trail

PS!
If anyone wants to delve inside the keyfile algorithms, please send me an essay and I'll put it on this site. I might delve into the wincmd.key algorithms one day, but not today.

1