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