Tool to help rebuild and patch 32-bit Windows applications.
dump
- dump information about section of executablegenlds
- generate GNU ld script for re-linking executablepe2obj
- convert PE executable into win32 object filepatch
- apply a patch set from the .patch sectionsetdd
- set any DataDirectory in PE headersetvs
- set VirtualSize for a sectionsetsc
- set Characteristics for a sectionsetts
- set TimeDateStamp in FileHeaderexport
- export section data as raw binaryimport
- dump the import table as assemblyre2obj
- convert the resource section into COFF objectgenmak
- generate project Makefilegensym
- generate full sym.cpp with all importsgenprj
- generate full project directory (Supports drag and drop of 1 Executable)genpatch
- compare 2 executables and generate patch macros (Supports drag and drop of 2 Executables)genproxy
- generate proxy dll project directory (Supports drag and drop of 1 dll)
- GNU make
- GNU cc
Type 'make' to build the tools on Linux. On Windows you might need to adjust some environment variables to get a successful build. The developers test with GCC on Linux and Windows (via MinGW), but the code aims to be strictly C99-compliant so any C99-supporting platform should suffice.
petool
uses fixed-with numeric types wherever possible so building on both 64-
and 32-bit architectures is supported. Note however that the code currently
supports working with 32-bit portable executable.
To begin, generate a project by giving a path to an executable file as the only
argument. Dragging and dropping an executable on petool.exe
also does this
on Windows and the project is generated next to the original executable in a
subdirectory.
You should be able to re-link the executable now by executing make
without any
modifications.
Note:
You can port old byte patches to your new repo by dragging and dropping
the original executable and the patched one on petool.exe
, this will generate
the needed macros (Or use genpatch
).
Generating the patch set can be done with macros. There are macros available for
C/C++ in inc/macros/patch.h
, several macros for NASM
in inc/macros/*.inc
and
macros for GNU as
in inc/macros/*.s
. Runtime patching is supported as well
via inc/patch.h
(Not recommended, unless you have no other choice).
Below are some C/C++ examples for how these macros are used in practice.
Note: All labels passed to the macros must be prefixed with _ and C++ functions must be defined with EXTERN_C
/* example.cpp */
/* This does NOT work - undefined reference to `doMagic' */
CALL(0x410000, doMagic);
void doMagic()
{
something();
}
/* Working example - prefixed with _ and defined with EXTERN_C */
CALL(0x410000, _doMagic);
EXTERN_C void doMagic()
{
something();
}
/* Working example - prefixed with _ and using asm labels instead of EXTERN_C */
extern void doMagic() asm("_doMagic");
CALL(0x410000, _doMagic);
void doMagic()
{
something();
}
The CALL
macro writes a CALL instruction at from to to. It is commonly used
to replace an existing function call with a call to your own function. This is
the most simple and cleanest way to create a patch (No assembly required).
CALL
can be used to replace a 5byte call instruction, CALL_NOP
can replace a
6byte instruction.
Note: For functions using the watcom register calling convention you will need to pass the additional <arg_count> arg (C/C++ only).
CALL(<from>, <to>);
CALL(<from>, <to>, <arg_count>);
CALL_NOP(<from>, <to>);
CALL_NOP(<from>, <to>, <arg_count>);
Example:
/* Replace function call at 0x410000 with a call to doMagic */
CALL(0x410000, _doMagic);
EXTERN_C void doMagic(int arg1, int arg2, int arg3)
{
/* insert your own code here */
something();
/* call the orginal function that was replaced by the patch (optional) */
original(arg1, arg2, arg3);
/* Note: you will have to insert "original" into sym.cpp and app.h to be able to call it */
}
/* Same as above, but for functions using the watcom register calling convention */
CALL(0x410000, _doMagic, 3);
EXTERN_C void doMagic(int arg1, int arg2, int arg3)
{
/* insert your own code here */
something();
/* call the orginal function that was replaced by the patch (optional) */
original(arg1, arg2, arg3);
/* Note: you will have to insert "original" into sym.cpp and app.h to be able to call it */
}
Note: the CALL
macro is also available for NASM
and GNU as
under the name @CALL
The DETOUR
macro redirects all calls to an existing function to your own
replacement function. It does also clear (INT3) the original function.
Note: For functions using the watcom register calling convention you will need to pass the additional <arg_count> arg.
DETOUR(<from>, <end>, <to>);
DETOUR(<from>, <end>, <to>, <arg_count>);
Example:
/*
Clear all bytes from the start of the function (0x410000) up to the end of the function (0x410200)
AND do a long jump from 0x410000 to label doMagic
*/`
DETOUR(0x410000, 0x410200, _doMagic);
EXTERN_C void doMagic(int arg1, int arg2, int arg3)
{
/* insert your own code here */
something();
}
/* Same as above, but for functions using the watcom register calling convention */
DETOUR(0x410000, 0x410200, _doMagic, 3);
EXTERN_C void doMagic(int arg1, int arg2, int arg3)
{
/* insert your own code here */
something();
}
You can also keep the original function intact by clearing only the first instructions (just to make enough space for the 5 byte jump to fit) and afterwards create a trampoline in sym.cpp to restore the instructions that were removed.
/* Clear first two instructions AND do a long jump from 0x410000 to label doMagic */`
DETOUR(0x410000, 0x410007, _doMagic);
EXTERN_C void doMagic(int arg1, int arg2, int arg3)
{
/* insert your own code here */
something();
/* call the orginal function that was replaced by the patch (optional) */
original(arg1, arg2, arg3);
}
/* sym.cpp - Create trampoline to restore the two instructions removed by DETOUR */
TRAMPOLINE(0x410007, original, "sub esp, 8; mov eax, [esp+0x1C]");
/* Same as above, but for functions using the watcom register calling convention */
TRAMPOLINE(0x410007, original, "sub esp, 8; mov eax, [esp+0x1C]", 3);
Note: the DETOUR
macro is NOT available for NASM
and GNU as
but the same can be done via @HOOK
instead
The HOOK
macro writes a JMP instruction at addr. You can use HOOK
in case
there is no Call instruction nearby that could be hooked via the CALL
macro.
HOOK
creates a naked function, so be sure you save and restore the values of the registers if needed.
The HOOK
macro with 2 args can do a additional CLEAR
, make sure you use it in case the
replaced instructions don't have a size of 5 bytes (leftover bytes could break your disassembler).
HOOK(<addr>);
HOOK(<addr>, <end>);
Example:
/* Insert a JMP at 0x410000 to your own code */
HOOK(0x410000)
{
/* insert original instructions that were replaced by the patch (5 byte jump) */
__asm("mov ecx, 0xFFFFFFFF");
/* save the values of the registers */
__asm("pushad");
/* insert your own code here */
something();
/* restore the values of the registers and jump back to the original location */
__asm("popad; jmp 0x410000 + 5");
}
Note: the HOOK
macro is also available for NASM
and GNU as
under the name @HOOK
Inserts the given instruction at the chosen address.
SETINST(<addr>, <inst>);
Example:
SETINST(0x410000, "mov eax, 1");
Note: the SETINST
macro is also available for NASM
and GNU as
under the name @SET
Both short and long variants are included. No overflow checks are done so do pre-calculate which one you need.
LJMP(<from>, <to>);
SJMP(<from>, <to>);
Example:
/* Do a long jump from 0x410000 to label doMagic */`
LJMP(0x410000, _doMagic);
/* Do a short jump from 0x410000 to 0x41000F */`
SJMP(0x410000, 0x41000F);
Note: the LJMP
macro is also available for NASM
and GNU as
under the name @LJMP
Note: the SJMP
macro is also available for NASM
and GNU as
under the name @SJMP
Sets all bytes between from and to (not inclusive) to the 8-bit argument byte.
When you make a LJMP
or anything else over the original code that would
leave some instructions broken or a dead code block, consider clearing the area
before writing the jump. It ensures when you or someone else is following the
code in a disassembler or a debugger that they will not get confused by sudden
long jumps which have broken instructions just after them.
CLEAR(<from>, <byte>, <to>);
CLEAR_NOP(<from>, <to>);
CLEAR_INT(<from>, <to>);
Example:
/* NOP 5 bytes starting from 0x410000 */`
CLEAR(0x410000, 0x90, 0x410005);
/* Does the same as the one above, NOP 5 bytes starting from 0x410000 */`
CLEAR_NOP(0x410000, 0x410005);
/* Same as CLEAR_NOP, just that it clears with INT3 instead of NOP */`
CLEAR_INT(0x410000, 0x410005);
Note: the CLEAR
macro is also available for NASM
and GNU as
under the name @CLEAR
Change values at a given location.
SETDWORD(<addr>, <value>);
SETWORD(<addr>, <value>);
SETBYTE(<addr>, <value>);
SETBYTES(<addr>, <value>);
SETFLOAT(<addr>, <value>);
SETDOUBLE(<addr>, <value>);
Example:
/* Change dword value at 0x410000 to 250000 */`
SETDWORD(0x410000, 250000);
/* Change word value at 0x410000 to 30000 */`
SETWORD(0x410000, 30000);
/* Change byte value at 0x410000 to 150 */`
SETBYTE(0x410000, 150);
/* Change bytes at 0x410000 to 0x146878185001 */`
SETBYTES(0x410000, "\x14\x68\x78\x1B\x50\x01");
/* Change string at 0x410000 to HelloWorld */`
SETBYTES(0x410000, "HelloWorld\0");
/* Change float value at 0x410000 to 1.2 */`
SETFLOAT(0x410000, 1.2);
/* Change double value at 0x410000 to 1.2 */`
SETDOUBLE(0x410000, 1.2);
Note: These macros are NOT available for NASM
and GNU as
but the same can be done via @SET
instead
SETCGLOB(0x004D2A80, WinMain);
When you need to refer to existing symbols inside the executable, you can export
global symbols from assembly source via the SETCGLOB
macro. Symbols can be any named
memory address: function, data, uninitialized variable. As long as you define
them in sym.cpp, you can use them anywhere.
Note: For functions using the watcom register calling convention you will need to pass the additional <arg_count> arg.
SETCGLOB(<addr>, <name>, <arg_count>);
SETCGLOB(0x004D500, doMagic, 4);
You can use any compilable language that can produce an COFF or ELF object or
whatever your build of GNU binutils
supports. GNU as
, GNU cc
, GNU g++
and NASM
are the most compatible tools and supported by w64devkit
out of the box.
When mixing already compiled executable with new code, you need to make sure of the calling convention of your functions and compiler can be made compatible with everything else.
The patch
command reads a PE image section for patch data. The format of the
patch data is as follows:
<dword absolute address> <dword patch length> <patch data>
These binary blobs are right after each other to form a full patch set. The patch command simply looks up the file offset in the PE image file based on given absolute memory address and writes over the blob at that point.
After the patch is applied, you should remove the patch section with GNU strip as it is not needed in the final product. The default project template includes this additional step.