patchelf is a tool to patch Linux binaries. You can change dynamically linked interpreter and library of the target program(ELF).
At the beginning, it was started as a small patch utility for nixpkgs
. Because the project goals of Nix are to provide users a reproducible environment, and a imperative way to describe their own environment, like a Dockerfile.
Patchelf is a part of NixOS GitHub Repository.
1. Installation
Currently in April 2024, I’m a big fan of NixOS and nixpkgs. So I can simply use nix
to try and install it.
nix-shell -p patchelf
Unless, most of the case, you installed nix
in your system, you would better build it manually.
$ git clone https://github.com/NixOS/patchelf
Cloning into 'patchelf'...
remote: Enumerating objects: 3209, done.
remote: Counting objects: 100% (855/855), done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 3209 (delta 800), reused 791 (delta 774), pack-reused 2354
Receiving objects: 100% (3209/3209), 1.16 MiB | 808.00 KiB/s, done.
Resolving deltas: 100% (1965/1965), done.
$
Enter to the directory patchelf
, and run commands that was mentioned in README.
$ cd patchelf
$ ./bootstrap.sh
autoreconf: export WARNINGS=all
autoreconf: Entering directory '.'
...
autoreconf: Leaving directory '.'
$ ./configure
...
$ make
Making all in src
make[1]: Entering directory '/root/patchelf/src'
...
make[1]: Leaving directory '/root/patchelf'
$ make check
...
============================================================================
Testsuite summary for patchelf 0.18.0
============================================================================
# TOTAL: 56
# PASS: 55
# SKIP: 1
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
============================================================================
...
$ sudo make install
Making all in src
make[1]: Entering directory '/root/patchelf/src'
...
make[1]: Leaving directory '/root/patchelf'
...
Then, probably you can use the patchelf
comamnd.
$ patchelf
syntax: patchelf
[--set-interpreter FILENAME]
[--page-size SIZE]
[--print-interpreter]
[--print-os-abi] Prints 'EI_OSABI' field of ELF header
[--set-os-abi ABI] Sets 'EI_OSABI' field of ELF header to ABI.
[--print-soname] Prints 'DT_SONAME' entry of .dynamic section. Raises an error if DT_SONAME doesn't exist
[--set-soname SONAME] Sets 'DT_SONAME' entry to SONAME.
[--set-rpath RPATH]
[--add-rpath RPATH]
[--remove-rpath]
[--shrink-rpath]
[--allowed-rpath-prefixes PREFIXES] With '--shrink-rpath', reject rpath entries not starting with the allowed prefix
[--print-rpath]
[--force-rpath]
[--add-needed LIBRARY]
[--remove-needed LIBRARY]
[--replace-needed LIBRARY NEW_LIBRARY]
[--print-needed]
[--no-default-lib]
[--no-sort] Do not sort program+section headers; useful for debugging patchelf.
[--clear-symbol-version SYMBOL]
[--add-debug-tag]
[--print-execstack] Prints whether the object requests an executable stack
[--clear-execstack]
[--set-execstack]
[--rename-dynamic-symbols NAME_MAP_FILE] Renames dynamic symbols. The map file should contain two symbols (old_name new_name) per line
[--no-clobber-old-sections] Do not clobber old section values - only use when the binary expects to find section info at the old location.
[--output FILE]
[--debug]
[--version]
FILENAME...
2. Usage
Sometimes you want to patch a ELF binary while playing a CTF challenge. For example, for an old heap exploitation challenge like house of force attack, you must patch an old linked library libc
. Because it was patched, thus that attack technique won’t work unless you apply a patch.
With this context, let’s assume there is a target executable ELF binary, which is basic_heap_overflow
. And there is also a linked libary, which is libc-2.23.so
.
2.1. --print-needed
and --print-interpreter
$ ls
basic_heap_overflow libc-2.23.so
To list up necessary librarys, you can use the patchelf
with --print-necessary
option.
$ patchelf --print-needed basic_heap_overflow
libc.so.6
It is a simple CTF challenge, only the 'libc.so.6' was printed.
And to see which interpreter is used, you can use --print-interpreter
option.
$ patchelf --print-interpreter basic_heap_overflow
/lib/ld-linux.so.2
It is same as the output of file
command.
$ file basic_heap_overflow
basic_heap_overflow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=70e759543ed70178a77a4f34ca7878565affc64a, not stripped
2.2. Patch the binary
You can use --replace-needed
option to patch and replace the linked library. Previously checked necessary `needed' thing first, in this case `libc.so.6', and path to the library you want to replace follows.
$ patchelf --replace-needed libc.so.6 ./libc-2.23.so basic_heap_overflow
$ patchelf --print-needed basic_heap_overflow
./libc-2.23.so
If succesfully patched, you can see the result of --print-needed
option has changed.
Similar as above, you can use --set-interpreter
to replace the ld
interpreter.
$ patchelf --set-interpreter ./ld-linux.so.2 ./basic_heap_overflow
$ patchelf --print-interpreter basic_heap_overflow
./ld-linux.so.2
Let’s do cross-checks that has really changed.
$ file basic_heap_overflow
basic_heap_overflow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter ./ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=70e759543ed70178a77a4f34ca7878565affc64a, not stripped
$ ldd basic_heap_overflow
linux-gate.so.1 (0xf7f10000)
./libc-2.23.so (0xf7d54000)
./ld-linux.so.2 => /lib/ld-linux.so.2 (0xf7f12000)
Cool!