How
to write a loader
-
by
Sunshine |
Download Example Prog: tut_loader.zip
1. Introduction
This tutorial will
explain how to write a loader if you don't want to patch the program itself
or if it's not possible because it's packed or something else. As you know I'm
a beginner and it took me long to find an tutorial concerning this topic and
when I found one it was using MASM which I don't really understand (yet!). That's
why I decided to write one for Delphi!
I've written a simple target prog which we'll crack using our own loader. But
keep in mind that patching a packed prog can be more difficult because you have
to wait until the program is unpacked in memory. This is not as easy as our
VERY SIMPLE target prog.
But you'll see writing a loader is quite easy so let's begin!
(Oh, sorry for my bad English, it's not my mother tongue!)
2. Cracking our target
Ok, let's start Example.exe. We enter a password (choose what you like) and it shows us a nice Messagebox : "Wrong! Try again!". Start WDasm, open Example.exe, click on String References and click on the string from our message. You'll see this:
* Reference
To: user32.GetWindowTextA, Ord:0000h
|
:00403CBF E8CCFEFFFF Call 00403B90
:00403CC4 8D85F8FDFFFF lea eax, dword ptr [ebp+FFFFFDF8]
:00403CCA 8D95FFFDFFFF lea edx, dword ptr [ebp+FFFFFDFF]
:00403CD0 B901020000 mov ecx, 00000201
:00403CD5 E836F1FFFF call 00402E10
:00403CDA 8B85F8FDFFFF mov eax, dword ptr [ebp+FFFFFDF8]
* Possible
StringData Ref from Code Obj ->"Sunshine"
|
:00403CE0 BA703D4000 mov edx, 00403D70
:00403CE5 E83EF1FFFF call 00402E28
:00403CEA 7519 jne
00403D05 <-
JUST NOP IT OUT! SO WE NEVER JUMP!
:00403CEC 6A40 push
00000040
* Possible
StringData Ref from Code Obj ->"Success"
|
:00403CEE 687C3D4000 push 00403D7C
* Possible
StringData Ref from Code Obj ->"Perfect! All right!"
|
:00403CF3 68843D4000 push 00403D84
:00403CF8 A100654000 mov eax, dword ptr
[00406500]
:00403CFD 50 push
eax
* Reference
To: user32.MessageBoxA, Ord:0000h
|
:00403CFE E89DFEFFFF Call 00403BA0
:00403D03 EB17 jmp
00403D1C
* Referenced
by a (U)nconditional or (C)onditional Jump at Address:
|:00403CEA(C)
|
:00403D05 6A40 push
00000040
* Possible
StringData Ref from Code Obj ->"Error"
|
:00403D07 68983D4000 push 00403D98
* Possible
StringData Ref from Code Obj ->"Wrong! Try again!"
|
:00403D0C 68A03D4000 push 00403DA0
:00403D11 A100654000 mov eax, dword ptr
[00406500]
:00403D16 50 push
eax
* Reference
To: user32.MessageBoxA, Ord:0000h
|
:00403D17 E884FEFFFF Call 00403BA0
So we have everything we need. At Adress 403CEA we must replace 7519 with 9090.
3. Writing our loader
program loader;
uses
Windows, Messages;
{$R Loader.RES}
var
si : Startupinfo;
pi : Process_Information;
NewData : array[0..1] of byte = ($90,$90);
NewDataSize : DWORD;
Bytesread : DWORD;
Olddata : array[0..1] of byte;
begin
NewDataSize := sizeof(newdata);
IF CreateProcess(nil,'Example.exe',nil,nil,FALSE,
Create_Suspended,nil,nil,si,pi)
= true then
begin
ReadProcessMemory(pi.hprocess,Pointer($403CEA),@olddata,2,bytesread);
if (olddata[0] = $75) and (olddata[1] = $19) then
begin
WriteProcessMemory(pi.hProcess, Pointer($403CEA),
@NewData, NewDataSize, bytesread);
ResumeThread(pi.hThread);
end else
begin
Messagebox(0,pchar('Bytes not found! Wrong version?...'),pchar('Error'),mb_iconinformation);
TerminateProcess(PI.hProcess,0);
end;
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
end;
end.
Analysis:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // pointer to name of executable module
LPTSTR
lpCommandLine, // pointer to command line string
LPSECURITY_ATTRIBUTES
lpProcessAttributes, // pointer to process security attributes
LPSECURITY_ATTRIBUTES
lpThreadAttributes, // pointer to thread security attributes
BOOL
bInheritHandles, // handle inheritance flag
DWORD
dwCreationFlags, // creation flags
LPVOID
lpEnvironment, // pointer to new environment block
LPCTSTR
lpCurrentDirectory, // pointer to current directory name
LPSTARTUPINFO
lpStartupInfo, // pointer to STARTUPINFO
LPPROCESS_INFORMATION
lpProcessInformation // pointer to PROCESS_INFORMATION );
At first, we create
our Process we want to patch; in our case Example.exe. It's important
to set the creation flag to CREATE_SUSPENDEND. So the process won't be executed
immediately; it does not start until we call ResumeThread. So we have time to
patch in memory.
Now we call ReadProcessMemory.
BOOL ReadProcessMemory(
HANDLE
hProcess, // handle of the process whose memory is read
LPCVOID
lpBaseAddress, // address to start reading
LPVOID
lpBuffer, // address of buffer to place read data
DWORD
nSize, // number of bytes to read
LPDWORD
lpNumberOfBytesRead // address of number of bytes read);
At Address 403CEA we read 2 bytes in our buffer olddata
and check if these are $75 and $19. If so we're right and now we can patch the
memory.
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID
lpBaseAddress, // address to start writing to
LPVOID
lpBuffer, // pointer to buffer to write data to
DWORD
nSize, // number of bytes to write
LPDWORD
lpNumberOfBytesWritten // actual number of bytes written );
With WriteProcessMemory we can easily write our
buffer newdata at address 403CEA. We make our Prog count the number of bytes
to write with sizeof(newdata) and store the length in NewDataSize.
Then we return to our process and let it run with
ResumeThread(pi.hThread).
Now we call CloseHandle(pi.hProcess)
and CloseHandle(PI.hThread)
to make sure not
creating memory leaks.
Well, I hope you could understand the basics of writing
a loader. Questions, ideas? Mail
me!
Sunshine,
December 2001
This Site is part of Sunshine's Homepage