LockDown
2000
A Tutorial on Time Limit Cracking
The DrunkMan
So, I've been asked to write a tutorial about this target
(which is easy enough to understand and to kill).
This tut will just show you how I removed the time limit
protection (just 11 days!) from this file. We'll see that the
protectione scheme itself is trivial, but the main trouble is
patching the executable. You'll learn how a loader works and why
it is so useful.
Beginning
ŻŻŻŻŻŻŻŻŻ
After installation, launch the proggie and see what
happens: initialization, then the main window displaying that
your version is not registered and you have some days left.
What to do? As usually, when you have in front of you a time
limit protection, you should activate it... so change your system
date consequently. Run again the proggie and it will say
"Sorry, your time is over, hope you enjoyed this piece of
software, cause it won't run again..." and - BOOM - back to
Windoze.
So here it starts our journey!
Removing the
Protection
ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ
Time limit is not the best way to protect your
sw, mainly because you have not many ways to check the system
date; as long as I know, the only time API I've ever seen are
GetSystemTime and GetLocalTime.
So, the next step is place a breakpoint over these.
If you choose GetSystemTime you break on only one call, in the
very beginning of the execution, and nothing more - so I have
good reasons to believe this is not the good one.
Then try GetLocalTime; you will break into SoftIce - press F5 - you break again - press F5 - you break again. (Note: when you break, you should take a look at the code nearby to see if the call to GetLocalTime is significant - but I'll just skip this step to keep this tut short enough not to bore the reader :)
Now press F12 and you are in LockDown - you see there is some kind of manipolation using FPU registers:
0157:00409D7C E823CEFFFF CALL KERNEL32!GetLocalTime 0157:00409D81 668B4C240E MOV CX,[ESP+0E] <- after F12 you are here 0157:00409D86 668B54240A MOV DX,[ESP+0A] 0157:00409D8B 668B442408 MOV AX,[ESP+08] 0157:00409D90 E81FFEFFFF CALL 00409BB4 0157:00409D95 DD5C2418 FSTP REAL8 PTR [ESP+18] 0157:00409D99 9B WAIT 0157:00409D9A 668B442416 MOV AX,[ESP+16] 0157:00409D9F 50 PUSH EAX 0157:00409DA0 668B4C2418 MOV CX,[ESP+18] 0157:00409DA5 668B542416 MOV DX,[ESP+16] 0157:00409DAA 668B442414 MOV AX,[ESP+14] 0157:00409DAF E828FCFFFF CALL 004099DC 0157:00409DB4 DC442418 FADD REAL8 PTR [ESP+18] 0157:00409DB8 DD1C24 FSTP REAL8 PTR [ESP] 0157:00409DBB 9B WAIT 0157:00409DBC DD0424 FLD REAL8 PTR [ESP] 0157:00409DBF 83C420 ADD ESP,20 0157:00409DC2 C3 RET
Press F12 again and you are straight in the time limit
comparison code. Why?
Because you can see there is still FPU manipolation (a
subtraction - between two different dates maybe?) and, looking at
the result of the following call, you see a positive value in eax
meaning "OK, still evaluating" and a negative value
meaning "Your time has come" (you may try it yourself
by changing the value in eax after the call at 4A31F3).
0157:004A31E8 E8876BF6FF CALL 00409D74 0157:004A31ED DCADDCFDFFFF FSUBR REAL8 PTR [EBP-0224] <- after F12 you are here 0157:004A31F3 E808F9F5FF CALL 00402B00 <- time checking routine 0157:004A31F8 8BD8 MOV EBX,EAX <- eax>=0 : go on evaluating 0157:004A31FA 33C0 XOR EAX,EAX eax<0 : kick his ass! 0157:004A31FC 5A POP EDX 0157:004A31FD 59 POP ECX 0157:004A31FE 59 POP ECX 0157:004A31FF 648910 MOV FS:[EAX],EDX 0157:004A3202 682C324A00 PUSH 004A322C 0157:004A3207 8D85E4FDFFFF LEA EAX,[EBP-021C] 0157:004A320D BA02000000 MOV EDX,00000002 0157:004A3212 E8BD0AF6FF CALL 00403CD4 0157:004A3217 8D45F4 LEA EAX,[EBP-0C] 0157:004A321A BA02000000 MOV EDX,00000002 0157:004A321F E8B00AF6FF CALL 00403CD4 0157:004A3224 C3 RET
Instead of tracing through the call at 4A31F3, I just want to fool the app saying that I will always have some days to evaluate it (let's say 13 - 0Dh - my lucky number :).
I just need to patch some bytes:
0157:004A31F3 E808F9F5FF CALL 00402B00 -----> 0157:004A31F3 33C0 XOR EAX,EAX 0157:004A31F5 040D ADD AL,0D 0157:004A31F7 90 NOP
Doing this will lock eax to 0Dh, regardless of time!
Ok, by now you should start your favourite patcher looking for
E8,08,F9,F5,FF to replace with a nicer 33,C0,04,0D,90... but as
you can see there's no such string in your original
LockDown2000.exe !
Some
Troubles...
ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ
No panic, are you a cracker or what ?!?
This means that your executable is packed and/or encrypted (if
you are not sure, just try to disassemble the file using WDasm32
and have some fun :). This should prevent casual crackers from
completing their jobs. The question is, we are NOT casual
crackers :)
The first step I took (it's always the best way) was searching
over the net for some tuts about loaders. Well, I find many, but
the one that really helped me was R!SC's one; you can find it at
csir.cjb.net. There are many interesting docs even on Fravia
site, if you manage to find a working mirror...
How does a packer works?
Mainly, a packer compresses your original executable, decreasing
its size and writing this compressed version to a new executable;
this new executable begins with a decompression algorithm which
restores the original program. After the decompression, the
original file is in memory and the "real" execution
begins.
How can I crack packed programs?
Well, there's more than one way. In this tutorial we'll use a
loader, but you may also hard-code the patching bytes in the
packed program itself to make your crack (this second method is
very elegant indeed).
So how does a loader works?
Loaders
ŻŻŻŻŻŻŻ
You know that the packed file, sooner or later,
will be unpacked in memory and when it will be we can patch him
as described before. The only thing is, how can we modify a
program that is already running?
Windoze offers a couple of process API to help us: they are
ReadProcessMemory and WriteProcessMemory. The first one loads in
a buffer some bytes directly from a process' memory image, the
second one does exactly the opposite - writes a buffer over a
process' memory image.
But you can't access those functions unless you have some privileges over the process you want to patch. To gain the required privileges you should CREATE that process - I mean, you have to start that process from INSIDE your loader (by the way, that's the reason why you call it a loader :)
Mainly the step behind a loader are the following:
* Create the process
* Wait for it to decompress
* Read its memory
* Compare with original bytes
* If you find the original bytes, patch them
* Kill the loader, let the patched process run
Obviously, this sequence can be improved (for example, if the bytes in memory are not identical to the original bytes you may try to read again the memory again some times - maybe that part of code hasn't been unpacked yet).
So, here's an example of how a loader should look like (it is
taken directly from RISC tutorial - all my credits go to that guy
:)
I used Masm32.
;-=-Loader.asm-=-=-cut & paste me :)=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; based on original loader.exe by Hayras [tNO '98] used in ; Hayras's Neolite v1.22 memory patcher. ; ; Special thanks to TNO for graciously allowing me to use this ; source on behalf of Hayras, who has now retired from the scene.
; Yes, Hayras's loader reversed by R!SC, then totally re-written ; & released to the public, so everyone can learn this shit :)
; (c)1999 R!SC (see what i do instead of cracking...duh) yey Prophecy!
.386P
.Model Flat ,StdCall
include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib
;Define the needed external functions and constants here.
;-=-Normal data-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- .Data
LARGE dw 0F000h
CSiR_Tag db ' Lockdown 2000 Loader + Patcher',0 CSiR_Error db 'Error!!!',0 CSiR_Error1 db 'Something fucked up...',0 OpenERR_txt db 'CreateProcess Error :(',0 ReadERR_txt db 'ReadProcessMemory Error :(',0 WriteERR_txt db 'WriteProcessMemory Error :P',0 VersionERR_txt db 'Incorrect Version of application :(',0
CSiR_ProcessInfo dd 4 dup (0) ;process handles CSiR_StartupInfo db 48h dup (0) ;startup info for the process were opening CSiR_RPBuffer db 5 dup (0) ;read buffer, for checking data
;-=-Patch datas-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
CSiR_AppName db 'lockdown2000.EXE',0
fuck dd 04A31F3h ; address to read data from for version checking origsize dd 5 ; in the new process
checkbytes db 0E8h,8h,0F9h,0F5h,0FFh ; original bytes ; if they are not there,
;we have the wrong version??
patch_data_1 db 033h,0C0h,04h,0Dh,90h ; patch bytes patch_size_1 dd 5 patch_addr_1 dd 04A31F3h
.Code ;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Main: push offset CSiR_Tag mov dword ptr [CSiR_StartupInfo],44h ; (the size in bytes of the structure) push offset CSiR_ProcessInfo ; Typedef struct _PROCESS_INFORMATION push offset CSiR_StartupInfo ; Pointer to STARTUPINFO structure push 0 push 0 push 20h ; Creation flags push 0 push 0 push 0 push 0 push offset CSiR_AppName ; Pointer to name of executable mod call CreateProcessA ; starts packed executable test eax,eax jz OpenERR
Wait4Depack: push LARGE-1 ; Timeout (in milliseconds, -1 = infinite) push dword ptr [CSiR_ProcessInfo] call WaitForInputIdle ; wait for executable unpacking
Check_Data:
push 0 ; BytesRead push dword ptr [origsize] ; Length push offset CSiR_RPBuffer ; Destination (to read them to) push dword ptr [fuck] ; Source push dword ptr [CSiR_ProcessInfo] ; Process whose memory we are to read call ReadProcessMemory test eax,eax jz ReadERR ;... ;int 03 ;-) cld lea esi, CSiR_RPBuffer ; compares process' bytes lea edi, checkbytes ; and original ones mov ecx, dword ptr [origsize] repnz cmpsb jnz VersionERR ;... Patch_the_mother: push 0 ; Pointer to byteswritten (i like null though) push dword ptr [patch_size_1] ; Length push offset patch_data_1 ; Source push dword ptr [patch_addr_1] ; Destination push dword ptr [CSiR_ProcessInfo] ; Process whose memory we are to patch call WriteProcessMemory ; Call Kernel32!WriteProcessMenory test eax,eax jz WriteERR
Close_This_app: push dword ptr [CSiR_ProcessInfo] call CloseHandle push dword ptr [CSiR_ProcessInfo+4] call CloseHandle ; kills the loader
Exit_Proc: Push LARGE-1 Call ExitProcess
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
VersionERR: lea eax, VersionERR_txt jmp abort ReadERR: lea eax, ReadERR_txt jmp abort OpenERR: lea eax, OpenERR_txt jmp abort WriteERR: lea eax, WriteERR_txt abort: push 0 push offset CSiR_Error ; Title push eax ; Message push 0 call MessageBoxA
jmp Close_This_app
End Main ;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
The code is straightforward: it follows all the steps
mentioned above.
To see how does the CreateProcess, WaitForInputIdle,
ReadProcessMemory, WriteProcessMemory functions work just refer
to a Win32 API Reference... or simply use this piece of code as a
template (be careful: it does not always works - when you have
really small executables the decryption process may be too fast
to allow your patch).
As you can see, the offset where you read from and write to
inside de process image is the same you found during the cracking
session: 4A31F3.
There should be no more problems - just build the code, copy the
executable to the target directory and your little crack is done.
Hope you found this tut somewhat interesting...
Happy cracking,
The DrunkMan
=========================================================================================
Credits: if you still didn't catch that, without RISC this tut
would never have been possible.
GREETZ
All Armageddon members ;) ZeroC00l MartiX dvj
=========================================================================================