Skip to content

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:

  1. Define why patching binaries is useful.
  2. Walk through a binary patching example with imagemagick.

You will need the following:

  1. A working copy of Mayhem, with the Mayhem CLI installed.
  2. 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
    
  3. Reverse-engineering expertise. This is an advanced tutorial, and you should be comfortable with assembly-level code.

  4. A disassembler with the ability to patch instructions. We will be using Binary Ninja.
  5. 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:

  1. Allow a program to cleanly exit after running a single test case.
  2. Remove a checksum or cryptographic primitive that is inhibiting analysis.
  3. Remove authentication checks.
  4. 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 transmit
  • cksum: a 32-bit checksum over the data 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:

  1. 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.
  2. 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:

  1. nsa-insignia-sm.png, which is a valid PNG file.
  2. 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:

  1. 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.
  2. Use the strings in the error message to find the relevant code.
  3. 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:

imagemagick-binary-ninja-0

We’ll search for the string “CRC error”.

imagemagick-binary-ninja-1

The first entry finds the substring “CRC error” in the middle of another string. This is probably not what we want.

imagemagick-binary-ninja-2

The next entry gives us the “CRC error” string alone. And we have a cross-reference to 0x1858a! This looks promising.

imagemagick-binary-ninja-3

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.

imagemagick-binary-ninja-4

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:

imagemagick-binary-ninja-5

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:
    1. Allow a program to cleanly exit after running a single test case.
    2. Remove a checksum or cryptographic primitive that is inhibiting analysis.
    3. Remove authentication checks.
    4. 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:
    1. Install the convert command.
    2. Identify the checksum routine.
    3. Edit the imagemagick binary (in Binary Ninja).
    4. Run the modified convert libarary locally.
    5. Run the modified convert library in Mayhem.