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