Writing a patcher - by Sunshine

Download Example Files: tutpatcher.zip (including this tut)

1. Introduction

After patching in memory in my first tutorial, we will learn this time how to patch a file. I think it's even easier than writing a loader. It's just a basic example to show you the code to change bytes in a file (exactly that is what a patcher does). Several checks are made if it's really the right file, but no backup of the target file will be made. I will describe 2 possibilities to write a patcher, one "delphi-style" patcher and one "WinApi" patcher. In the first one we will use functions from Delphi, in the other one just Windows functions which will decrease file size. So let's begin...

2. "Delphi-style" Patcher

program Patcher;

uses
  Windows, Messages,Classes,sysutils;

const
  filename = 'Test.exe';                                       // which file to patch
  offsets : array[1..2] of DWORD = ($813, $814);   // which offsets
  patchbytes : array[1..2] of byte = ($6F, $76);     // what to write
  oldbytes : array[1..2] of byte = ($69, $66);         // old bytes
  numberpatch : Integer = 2;                              // how many bytes to patch

var
  f : file;
  i : Integer;                                                        // loop variable
  buf : array[1..2] of Byte;                                   // temp var to store bytes from file
{$R *.RES}

begin
  if MessageBoxA(0,'This prog will patch Test.exe! File' +
                            'must be in the same folder!', 'Example',mb_OkCancel) = IDCANCEL then
  begin
    MessageboxA(0, 'Closing without patching', 'Bye', mb_OK);
    halt;
  end;

  AssignFile(f,filename);
  {$I-}
  reset(F,1);
  {$I+}
  if IOResult <> 0 then                                      // check if file is there
  begin
    MessageBoxA(0, 'File NOT Found!!', 'Error!', MB_OK);
    exit;
  end;

  If filesize(f) <> 2560 then                               // check correct file size
  begin
    if MessageBoxA(0,'Filesize different! Seems not to be the right file!' +
    #13 + 'Do you really want to go on?','Shit..', mb_okCancel) = IDCANCEL
    then
    begin
      Closefile(f);
      halt;
    end;
  end;

  for i := 1 to numberpatch do                        // check if correct bytes are there
  begin
    Seek(f,offsets[i]);
    BlockRead(f, buf[i],1);
    if buf[i] <> oldbytes[i] then
    begin
      if MessageBoxA(0,'Bytes found are not identical to original bytes!' +
      #13 + 'Do you really want to go on?','Shit..',
      mb_okCancel) = IDCANCEL
      then
      begin
        Closefile(f);
        halt;
      end;
    end;

  end;


  for i := 1 to numberpatch do                         // now patch it!
  begin
    Seek(f,offsets[i]);
    Blockwrite(f,patchbytes[i],1);
  end;
  Closefile(f);
  MessageBoxA(0, 'Everything seemed to work fine!', ':-)', MB_OK);

end.

Analysis:
The constants we define at the beginning are easy to understand so I will not describe them in detail.

procedure AssignFile(var F; FileName: string);
At first, we'll define a file variable called f. Now we link this variable with the name of our target file, in our case 'Test.exe' with Assignfile.

procedure Reset(var F : File);

The function Reset opens the file. To trap errors , for example if this file doesn't exist, we call IOResult. That this works it's important to switch off I/O-checking with {$I-} and switch it on after calling reset with {$I+}.

function FileSize(var F): Integer;
Well, with the filesize function we get the filesize of our file. Just for another check if it's the right file.

procedure Seek(var F; N: Longint);
With this function, we'll move the file pointer to the correct file offset N. In our example we have stored the correct file positions in our array offsets.

procedure BlockRead(var F: File; var Buf; Count: Integer);
When we are at the right offset, we read in one byte in our buffer to compare if it's really the right file. We make this in a loop to check every offset where we will patch the byte.

procedure BlockWrite(var f: File; var Buf; Count: Integer);
This function really does the patching. It writes the byte stored in buf at the current offset.

Well, it's not difficult to understand how everything works. Just have a look at the source and my describtion of the functions, it's quite self-explanatory.

2. "WinAPI" Patcher

program PatcherApi;

uses
  Windows, Messages;                                         // sysutils and classes units are not needed-> decreases file size

const
  filename = 'Test.exe';                                        // which file to patch
  offsets : array[1..2] of DWORD = ($813, $814);    // which offset
  patchbytes : array[1..2] of byte = ($6F, $76);      // what to write
  oldbytes : array[1..2] of byte = ($69, $66);          // old bytes
  numberpatch : Integer = 2;                               // how many patches


var
  f : THandle;
  i : Integer;
  Written, reada : DWord;
  buf : Byte;                                                         // temp var to store bytes from file

{$R *.RES}

begin
  if MessageBoxA(0,'This prog will patch Test.exe! File must be in the same folder!',
                           
'Example', mb_OkCancel) = IDCANCEL then
  begin
    MessageboxA(0, 'Closing without patching', 'Bye', mb_OK);
    halt;
  end;

  f := CreateFileA(filename,GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0);

  if f = INVALID_HANDLE_VALUE then                 // if file doesn't exist
  begin
    MessageboxA(0, 'File NOT found! Prog will quit...', 'Bye', mb_OK);
    halt;
  end;

  if GetFileSize(F, nil) <> 2560 then                    // different file size?
  begin
    IF MessageboxA(0, 'Different file size! Seems not to be the right file!' + #13 +
                               'Do you really want to go on?' , 'Bye', mb_OKCancel) = IDCANCEL
    then
    begin
      CloseHandle(f);
      halt;
    end;
  end;

  for i := 1 to numberpatch do                            // Check bytes
  begin
    SetFilePointer(f,offsets[i],nil,FILE_BEGIN);
    ReadFile(f,buf ,1,reada,nil);
    if buf <> oldbytes[i] then
    begin
      IF MessageboxA(0,pchar('Bytes found are not identical to original bytes!' +#13 +
                                 'Do you really want to go on?'),'Shit..', mb_okCancel)
      = IDCANCEL

      then
      begin
        CloseHandle(f);
        halt;
      end;
    end;
  end;

  for i := 1 to numberpatch do                           // now patch!
  begin
    SetFilePointer(f,offsets[i],nil,FILE_BEGIN);
    WriteFile(f,patchbytes[i],1,written,nil);
  end;

  CloseHandle(f);
  MessageBoxA(0, 'Everything seemed to work fine!', ':-)', MB_OK);

end.

Analysis:
HANDLE CreateFile(
                                 LPCTSTR lpFileName, // pointer to name of the file
                                 DWORD dwDesiredAccess, // access (read-write) mode
                                 DWORD dwShareMode, // share mode
                                 LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
                                 DWORD dwCreationDistribution, // how to create
                                 DWORD dwFlagsAndAttributes, // file attributes
                                 HANDLE hTemplateFile // handle to file with attributes to copy );

We open our target file with CreateFile. The variable f is now defined as a THandle. Because we want to read and to write from file, we set dwDesiredAccess to 'GENERIC_READ or GENERIC_WRITE'. DwCreationDistribution must be set to OPEN_EXISTING which makes sense cause we want to open an existing file and not create a new one. If this function returns INVALID_HANDLE_VALUE, our target file does not exist.

DWORD GetFileSize(
                                  HANDLE hFile, // handle of file to get size of
                                  LPDWORD lpFileSizeHigh // address of high-order word for file size);

Not much to say; returns file size of our file.

DWORD SetFilePointer(
                                      HANDLE hFile, // handle of file
                                      LONG lDistanceToMove, // number of bytes to move file pointer
                                      PLONG lpDistanceToMoveHigh, // address of high-order word of distance to move
                                      DWORD dwMoveMethod // how to move );

This moves the file pointer to the correct file offset which stands in lDistanceToMove. Important is to set dwMoveMethod to FILE_BEGIN.

BOOL ReadFile(
                          HANDLE hFile, // handle of file to read
                          LPVOID lpBuffer, // address of buffer that receives data
                          DWORD nNumberOfBytesToRead, // number of bytes to read
                          LPDWORD lpNumberOfBytesRead, // address of number of bytes read
                          LPOVERLAPPED lpOverlapped // address of structure for data );

We read in one byte in our lpbuffer buf. We must pass a lpNumberofByteRead variable which stores how many bytes are read. Because we always read only one byte in my example, it's useless and we don't use it again. But we must pass it to the function.

BOOL WriteFile(
                           HANDLE hFile, // handle to file to write to
                           LPCVOID lpBuffer, // pointer to data to write to file
                           DWORD nNumberOfBytesToWrite, // number of bytes to write
                           LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
                           LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O );

WriteFile is the function which writes our data to our file. It's quite similar to Readfile. We have also to pass a variable called lpNumberOfBytesWritten which counts how many bytes are really written. Again, here it's useless cause we always write only one byte to file at a time. As you see, we have a loop to write several bytes to file.

Well, it's really easy to understand. Nevertheless if you have problems, don't hesitate to mail me.
Sunshine, May 2002


This Site is part of Sunshine's Homepage