Patching Win32 application binaries with Delphi 2
Posted: (EET/GMT+2)
If you are creating a large applications on tight schedule -- say, a HTML editor -- you will often find that your applications have bugs. To fix these bugs often require only a single source code line modification, and thus the original and fixed EXE files are almost identical. To send this often large EXE file to your customers can cost quite much. Of course, you can put the file for download via FTP, but someone still has to pay. In this kind of situations, "patching" can be a great solution.
A patch is technically a program, which updates the main application's EXE file and/or any support files, such as DLLs. Updating binary files directly can sound unsafe, but actually there are no risks is you do everything correctly.
In this tutorial we create a trivial sample application and another application, which patches the first -- called the patcher. Usually writing a simple patcher which must be run separately is sufficient, but in this How To we create an application which patches itself "on the fly". The sample application which is patched, contains a single typo, which the patcher fixes.
To patch an application, you need to have both the original EXE and the fixed EXE. Then you only need to know what are the differeces between the two files. Note that this How To assumes that the files are the same size. To compare the files, fire up DOS Prompt, and run the following command:
C:\> fc sample.old sample.new /b
Here, the FC (File Compare) utility is used to do the comprasion. There are two very important things to note. Firstly, the order of parameters. For our purposes, the "old" (original) file must be as the first parameter, and the name of the fixed ("new") application must be second. Also remember the "/b" parameter to do a binary compare.
The above command will display something like this:
Comparing files sample.old and sample.new 0002AA04: 52 84 0002AA34: 52 84 0002AA74: 52 84 0002AB1C: 52 84 0002AB34: 52 84 0002AB74: 52 84 0002AB8C: 52 84 0002ABA4: 52 84 ...
And so on. Next, save the output of FC to a file (using DOS output redirection). This information is used later when the patcher is created.
The first few code lines for the patcher are:
program Patch;
uses
Windows,
SysUtils;
Const
PatchCount = 39;
PatchedFile = 'sample.exe';
Type
TPatchRecord = Record
Ofs : Integer;
Old,New : Byte;
End;
Here, note the TPatchRecord type definition. This record holds three important things. The "Ofs" field specifies the position in the EXE file (Seek position) and "Old" and "New" contain the old and new values of the file, respectively.
Remember what FC output was like? Well, now it is the time to make its output into a Delphi readable array.
Const
PatchArray : Array[1..PatchCount] of TPatchRecord =
((Ofs : $0002AA04; Old : $52; New : $84),
(Ofs : $0002AA34; Old : $52; New : $84),
(Ofs : $0002AA74; Old : $52; New : $84),
(Ofs : $0002AB1C; Old : $52; New : $84),
(Ofs : $0002AB34; Old : $52; New : $84),
(Ofs : $0002AB74; Old : $52; New : $84),
(Ofs : $0002AB8C; Old : $52; New : $84),
(Ofs : $0002ABA4; Old : $52; New : $84),
...
You simply need to convert the FC output to an array. This can, of course be very tedious typing, but you can use for example macros to help you.
When you have the array created, the rest of the patcher code is pretty straightforward.
...
Var
AppMainWindow : Integer;
Procedure DoPatch;
Var
F : File of Byte;
B : Byte;
I : Integer;
Begin
Assign(F,PatchedFile);
Reset(F);
For I := 1 to PatchCount do Begin
With PatchArray[I] do Begin
Seek(F,Ofs);
Read(F,B);
If (B = Old) Then Begin
Seek(F,Ofs);
Write(F,New);
End;
End;
End;
Close(F);
End;
Procedure GetParameters;
Begin
If (ParamCount = 1) Then AppMainWindow := StrToInt(ParamStr(1))
Else Halt;
End;
Procedure WaitForAppShutdown;
Var Dummy : TMsg;
Begin
PeekMessage(Dummy,0,0,0,pm_NoRemove);
While IsWindow(AppMainWindow) do ;
Sleep(1000);
End;
Procedure RestartApp;
Begin
WinExec(PChar(PatchedFile),sw_ShowNormal);
End;
begin
GetParameters;
WaitForAppShutdown;
DoPatch;
RestartApp;
end.
Here, the patcher first gets parameters from the command line. Because the patcher is executed from the sample application (the sample application is "patch aware"), the handle of the sample application's main window is passed on the command line. This allows us to wait in the patcher until the sample application has finished. We must wait for the main aplication to end because otherwise the EXE file cannot be updated (it is locked).
The WaitForShutdown procedure does the actual wait. You might wonder what the call to PeekMessage is for. This is because the patcher is started inside the sample application using WinExec. As our patcher is a "GUI" application, WinExec will wait about 5 seconds for the patcher to create its first window. However, we don't create any windows, but using a dummy call like PeekMessage fools WinExec to think that we have infact created a window.
Then, WaitForShutdown simply sits in a loop and waits until the sample application has closed - and the waits again for a second. Note that a more elegant solution would be use the fact that processes "signal" when finished. Then WaitForSingleObject could be used, but for some reason I couldn't get this to work. Still, the implemented behaviour works for the purpose of this How To.
The actual patching is quite simple. We just open the EXE file for reading and writing, and seek to the appropriate positions specified in the patch data array. As a precaution, the old value found in the EXE is checked to make sure that no "invalid" patches will occur.
After the file has been succesfully patched, it is simply restarted.
The sample application is, as said, trivial. It simply displays a form, which has text and a button on it. The text contains a typo, and when you press the button, the application starts the patcher, and then closes. Recall that the patcher will eventually restart the sample application, so it looks like the form was simply hidden and then reshown. The code inside the sample application is only two lines:
procedure TForm1.Button1Click(Sender: TObject);
begin
WinExec(PChar('patch.exe '+IntToStr(Handle)),sw_ShowNormal);
Close;
end;
Note how the main form window handle is passed as a command line parameter.
Now you have learned how to patch applications. Of course, real-world patching requires more sophisticated behaviour, for example, changing the file size. (In fact, the given code can append the file because the Seek procedure allows you to seek one byte past the file size.) Also, it might be desirable to have the patcher as a "stand-alone" application so that it won't require co-operation with the main program.
You might also want to create more advanced features, such as checking the version of the EXE file. The current implementation simply ignores any modifications if the application doesn't confirm to the specifications.
However, this How To should give you an idea how patching can be done. Of course, the best thing is to write bugless code from the start, so that no patches are needed!