Bakatsugi is a generic in-memory patching toolkit targeting x86-64 GNU/Linux. It can be used to modify code of running processes without needing to restart them. Currently support is implemented for replacing code of entire functions, either calls to dynamic libraries or functions present in the process itself, provided debug symbols are available.
This project is in prototype state and should not be used for critical applications.
Nightly Rust features are used, it is recommended to use the latest available
nightly version. The easiest way to obtain the toolchain is to use rustup.
A rust-toolchain.toml is provided, so rustup
should
automatically choose the nightly
toolchain when working on this project.
Building is only supported under Linux.
In addition, the following tools are required:
nasm
- The GNU C toolchain (
ld
andgcc
)
You then should be able to simply run cargo build
to obtain a self-contained
executable in target/debug/bakatsugi
.
A patch is just a shared object with some additional metadata in the bakatsugi
section. The library should contain functions which are to be used as replacements
and should have the same signature as the original counterparts. The patch library
can then be injected into the target process using bakatsugi
.
A helper C header is provided, which can be used to create the patch library. It
is present in the c/bakatsugi.h file. An example patch to replace the libc
time(2)
function can be found in the examples/simple-c/libpatch.c file.
You can compile it using the associated Makefile. An example target program is also provided.
This patch library can then be injected into the target process using bakatsugi
:
sudo ./target/debug/bakatsugi -p <PID> examples/simple-c/libpatch.so
Permissions for ptrace
-ing the target are required, that means either running as
root
or the target being owned by the same user and a properly configured kernel.yama.ptrace_scope
kernel parameter.
You should now see a bunch of debugging information dumped to the stderr
of the
target process and the patch getting applied.
Some things to keep in mind regarding the patching procedures. In all cases, the signature of the replacement function must be compatible with the signature of the original function which is being replaced. No thunks are inserted for translating arguments or return values. ABI compatibility is also required, please use a compiler which is ABI-compatible with the compiler that was used to build the target.
Currently, the payload injected into the target will produce debugging output,
writing it to file descriptor 2
(stderr). At this point, this cannot be
disabled. Watch out for patching processes whose stderr
is closed or the
output is processed by other software.
Only functions used directly by the target program are replaced. Library functions used transitively by shared libraries are untouched. The location where to perform the patch is determined from the relocation table of the taget. Currently, only the first matching relocation is used, so if multiple relocations pointing to the function are present, the patch will not be applied fully. The GNU C toolchain generally produces only one relocation for each function, in the GOT.
Currently this patch procedure uses the indirect 5-byte trampolines. This is not yet configurable. This means that no problems should occur, as long as the target function does not mind its first 5 bytes being overwritten, while it is potentially being executed. i.e. no jumps should be taken to somewhere in the first 5 bytes of the function's code. If the function is not on the call stack while the patch is performed, this is not an issue. In the future a mechanism to unwind the call stack and detect problematic functions might be implemented.
At the same time, there must be at least 5 bytes of space, before another function or data begins.
This is usually the case (since most functions are longer than 5 bytes) and gcc
aligns functions to 16-byte boundaries if optimizations are enabled.