Thursday September 28, 2000 |
A Study in Methology |
|
Windows Commander 4.01 exposing protection routines without winice |
||
By +Indian_Trail | ||
Tools: |
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.
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
: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 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 004BFD80This just about everything that's done. there's no more conditional jumps before the end of the
: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
: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 retI 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:
: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 eaxIf 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.
Lets summerize what we know:
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.