expert
Patching Binaries to Improve Fuzzing¶
In this lesson, we'll walk you through how to modify binary applications when source code is not available in order to improve the fuzzing process.
Estimated Time: 20 minutes
By the end of this lesson, you will be able to:
- Define why patching binaries is useful.
- Walk through a binary patching example with
imagemagick
.
You will need the following:
- A working copy of Mayhem, with the Mayhem CLI installed.
-
Access to the tutorial examples by running the pre-built Docker image.
docker pull forallsecure/tutorial:2.10 docker run -ti --privileged --rm forallsecure/tutorial:2.10
-
Reverse-engineering expertise. This is an advanced tutorial, and you should be comfortable with assembly-level code.
- A disassembler with the ability to patch instructions. We will be using Binary Ninja.
-
This example requires online access, Docker installed on your localhost, and running the
debian:stretch
Docker image:docker pull debian:bullseye docker run -it debian:bullseye
Why Patch Binaries?¶
Binary modification is not required for fuzzing, but there are several reasons why vulnerability researchers sometimes modify binaries:
- Allow a program to cleanly exit after running a single test case.
- Remove a checksum or cryptographic primitive that is inhibiting analysis.
- Remove authentication checks.
- Target Mayhem directly on a specific function within the binary of interest.
Example: Checksums and imagemagick
¶
Suppose you had a program with a simple input format of:
data
: The data to transmitcksum
: a 32-bit checksum over thedata
field.
In order for a message to be valid, the checksum must be correct over data
, i.e., there is a data dependency between the data
and cksum
. Fuzzing is not aware of such data dependencies, and will not get past any code that checks for a valid checksum, thus missing defects.
There are two solutions:
- You can pass our inputs through a test driver, which will ensure proper checksums are in place before passing those inputs to the program under test.
- You can patch the binary so that it accepts all inputs, even those with an invalid checksum.
We will explore the second option in this tutorial, using convert
from the imagemagick
suite as an example.
convert
¶
First, we need to move the following files into our currently running debian:stretch
Docker container:
nsa-insignia-sm.png
, which is a valid PNG file.nsa-insignia-crc-error.png
, a PNG file with an invalid CRC checksum.
Run the docker ps
command to determine the container ID of the debian:stretch
image:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2261333873ac forallsecure/tutorial "bash" About a minute ago Up About a minute dazzling_ptolemy
c146984d637b debian:stretch "bash" 13 minutes ago Up 13 minutes infallible_shannon
Note
Your particular container ID for the debian:stretch
image may be different from what you see above.
Then, copy the /root/tutorial/binary-patching/testsuite
directory of the tutorial Docker container to your host machine, and then run the following to copy the files from your host machine to the debian:stretch
Docker container.
docker cp 2261333873ac:/root/tutorial/binary-patching/testsuite /path/on/host/machine
docker cp /path/on/host/machine c146984d637b:/mnt
If you navigate to the /mnt
directory within the debian:stretch
container, you should now see the following files:
root@c146984d637b:/mnt# ls -l
total 272
-rw-r--r-- 1 501 dialout 135939 Feb 13 00:34 nsa-insignia-crc-error.png
-rw-r--r-- 1 501 dialout 135629 Feb 13 00:34 nsa-insignia-sm.png
Next, we need to install the convert
command, which is a part of imagemagick
.
$ # Install imagemagick
$ apt-get update && apt-get install -y imagemagick
$ # Go to the /mnt directory, which should now contain the example files
$ cd /mnt
$ # run convert, and observe it works with a well-formed PNG.
$ convert nsa-insignia-sm.png /tmp/out.png
$ # Run convert, and observe it fails with a failing CRC PNG.
$ convert nsa-insignia-crc-error.png /tmp/out.png
convert-im6.q16: IDAT: CRC error `nsa-insignia-crc-error.png` @ error/png.c/MagickPNGErrorHandler/1628.
...
The fuzzer mutates bytes in the test suite input, e.g., if you provided nsa-insignia-sm.png
, a fuzzer such as Mayhem would create new test cases by mutating bytes. However, most mutations will fail since the checksum within the PNG is invalid, leading to wasted time. Mayhem's symbolic executor helps overcome this problem. However, for optimal performance you want both the fuzzer and symbolic executor running.
Let's patch out the checksum routine in convert
to speed up fuzzing by removing the CRC check. At the end of this, we will verify the patched version accepts the nsa-insignia-crc-error.png
file.
Identifying the checksum routine¶
There are several ways to identify the checksum routine in a binary:
- Search for magic numbers associated with the algorithm we are trying to modify, and work backwards to see where the output of that algorithm is verified against our input.
- Use the strings in the error message to find the relevant code.
- Reverse-engineer the binary until we find the correct place to patch.
In convert
, the checksum is checked in one of the libraries. You can get a list of libraries used by a program by running ldd
$ # Run ldd on convert to see all libraries.
$ ldd /usr/bin/convert
linux-vdso.so.1 (0x00007ffe24f4d000)
libMagickCore-6.Q16.so.3 => /usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so.3 (0x00007f7f4bae7000)
libMagickWand-6.Q16.so.3 => /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.so.3 (0x00007f7f4b7be000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7f4b5a1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7f4b202000)
...
For now, we tell you the library that computed the checksum has the string "CRC error", and you can find the library using the grep
command:
$ grep "CRC error" /usr/lib/x86_64-linux-gnu/*
...
Binary file /usr/lib/x86_64-linux-gnu/libpng16.so.16 matches
Binary file /usr/lib/x86_64-linux-gnu/libpng16.so.16.28.0 matches
...
We need to patch libpng16.so.16.28.0
, as libpng16.so.16
is simply a symbolic link. Copy the file to /mnt
, which is a shared directory between docker and your local system.
$ cp /usr/lib/x86_64-linux-gnu/libpng16.so.16.28.0 /mnt
Binary Editing¶
Next, open a terminal in your host OS and copy the libpng16.so.16.28.0
file from the debian:stretch
Docker image back to your host OS; this will copy the libpng16.so.16.28.0
file to the current directory on your host OS.
docker cp c146984d637b:/mnt/libpng16.so.16.28.0 .
Note
Again, the container ID shown above will differ from your particular container ID for the debian:stretch
image.
Now, open the libpng16.so.16.28.0
library in your favorite binary analysis platform. Here we use Binary Ninja:
We’ll search for the string “CRC error”.
The first entry finds the substring “CRC error” in the middle of another string. This is probably not what we want.
The next entry gives us the “CRC error” string alone. And we have a cross-reference to 0x1858a
! This looks promising.
A quick look at this function shows our "CRC error"
string being loaded immediately prior to a call to png_chunk_error
or png_chunk_warn
. We hope all we need to do is avoid the blocks which drive us over png_chunk_error
and png_chunk_warn
. If we can always take the branch at 0x18571
, hopefully we can avoid libpng
dying on checksum errors, and we will be able to more efficiently fuzz imagemagick.
Binary Ninja makes this process easy for us. We right-click the instruction at 0x18571, and select Patch -> Always Branch
.
When we’re done, the program should never execute the code taken to trigger an error or warning on a checksum mismatch. Our function now looks as follows:
In Binary Ninja, go to File -> Save Contents As
, and save your new file as libpng16.so.16.28.0.patched
.
Running the modified library¶
Finally, copy the libpng16.so.16.28.0.patched
file back to the debian:stretch
Docker image.
docker cp libpng16.so.16.28.0.patched c146984d637b:/mnt
You should now see libpng16.so.16.28.0.patched
under /mnt
within the debian:stretch
Docker image. To use this library, you can either:
- Overwrite the system version with:
$ cp libpng16.so.16.28.0.patched /usr/lib/x86_64-linux-gnu/libpng16.so.16.28.0
- Change your library load path
LD_LIBRARY_PATH
to find our modified version. From within/mnt
on the docker image:
$ # Change so the current directory is checked first for libraries.
$ export LD_LIBRARY_PATH=`pwd`
$ # Make a symbolic link so the library name is resolved to our
$ # modified version
$ ln -s libpng16.so.16.28.0.patched libpng16.so.16
$ # Verify convert shows we are using the modified version
$ ldd `which convert` | grep libpng
libpng16.so.16 => /mnt/libpng16.so.16 (0x00007fee99e75000)
$ # Verify that convert now accepts the png with an invalid CRC
$ convert nsa-insignia-crc-error.png /tmp/out.png
convert-im6.q16: PNG unsigned integer out of range `nsa-insignia-crc-error.png` @ error/png.c/MagickPNGErrorHandler/1628.
...
Info
Notice that before editing, convert
raised the error:
convert-im6.q16: IDAT: CRC error `nsa-insignia-crc-error.png' @ error/png.c/MagickPNGErrorHandler/1628.
With the modified library, convert
gets past that check. As an exercise, you can try removing the "integer out of range" error as well.
Running Mayhem¶
You can now package up convert
with the new library. From within the docker container, run:
$ # install Mayhem CLI
$ apt-get update && apt-get install -y curl
$ curl -o mayhem https://MAYHEM_HOST/images/mayhem && chmod +x mayhem && mv mayhem /usr/local/bin/
$ # package up convert.
$ mayhem package /usr/bin/convert -o /mt/convert-pkg
Packaging target: /usr/bin/convert
Packaging dependency: /usr/bin/convert-im6.q16 -> /mnt/convert-pkg/root/usr/bin/convert-im6.q16
Packaging dependency: /usr/bin/convert -> /mnt/convert-pkg/root/usr/bin/convert
Packaging dependency: /usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so.3.0.0 -> /mnt/convert-pkg/root/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so.3.0.0
...
# If you changed LD_LIBRARY_PATH earlier, copy over the modified patched binary. Note the Mayhem CLI does
# not follow LD_LIBRARY_PATH.
$ cp libpng16.so.16.28.0.patched \
/mnt/convert-pkg/root//usr/lib/x86_64-linux-gnu/libpng16.so.16.28.0
At this point, you should have on your local disk a directory convert-pkg
. We need to adjust the cmd
block for convert
to properly run the command:
cmd: /usr/bin/convert @@ /dev/null
All that’s left is uploading the package to Mayhem with mayhem run convert-pkg
!
✏️ Summary and Recap¶
In this lesson, you learned how to modify binary applications when source code is not available in order to improve the fuzzing process.
I learned how to...
1. Define why patching binaries is useful.
- Binary modification is not required for testing, but there are several reasons why vulnerability researchers sometimes modify binaries:
- Allow a program to cleanly exit after running a single test case.
- Remove a checksum or cryptographic primitive that is inhibiting analysis.
- Remove authentication checks.
- Target Mayhem directly on a specific function within the binary of interest.
2. Walk through a binary patching example with imagemagick
.
- You needed to perform the following steps:
- Install the
convert
command. - Identify the checksum routine.
- Edit the
imagemagick
binary (in Binary Ninja). - Run the modified
convert
libarary locally. - Run the modified
convert
library in Mayhem.
- Install the