🍗 Wiki

patchelf

patchelf

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.

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!