I am currently taking a closer look at a certain range of I²C devices
that promise an easy way to enhance embedded systems security. My main goals
here are to check the SDK and drivers, which are partially closed source, for
potential security problems and maybe to reverse engineer the proprietary
protocol that is used to talk to the devices.
Since these devices are not yet publicly available at the time
of writing I can not yet publish any real world code. That also means
that the screenshots and examples you are going to see are not based on
production code but were made to resemble the real thing as closely
as possible.
Another consequence of the early stage of development of the devices is
that the software support package is currently only available for ARM which
brings me to the actual topic of this post.
Since AFL has been my go-to
fuzzing framework for quite a while but I
have only used it for Linux x86 code until now this was the perfect opportunity
for me to explore AFL's capabilities for fuzzing ARM binaries.
For working with ARM binaries in AFL there are basically two options:
Note: This guide was written for Raspberry Pi OS 2020-02-13 but should apply to most distributions based on Debian 10 (Buster).
With Raspberry Pi OS you have two options; build AFL from source or just install it as a package.
Using the existing packages is pretty straight forward:
# sudo apt-get install afl afl-clangThis should be everything you need to get going.
If on the other hand you decide to build AFL from source, e.g. because
you want to use some additional patches there is a small obstacle to
overcome.
AFL 2.52b currently only supports Clang 6, the default version for Debian 10
is Clang 7, though. Therefore to build AFL you will first have to install
Clang 6:
# sudo apt-get install clang-6.0Additionally you need to supply some additional flags when compiling AFL:
# cd afl-2.52bThat should do the trick.
# AFL_NO_X86=1 make
# make -C llvm_mode LLVM_CONFIG=llvm-config-6.0 CC=clang-6.0
If you are fuzzing an application for which you have the source code you
can now recompile it with instrumentation by using afl-clang-fast
as a drop in replacement for gcc or clang.
Depending on your build system one of the following is probably all you need:
# make CC=/usr/bin/afl-clang-fast CXX=/usr/bin/afl-clang-fast++Once your target binary is ready you should be able to run afl-fuzz just as you would on x86, e.g. as:
... or ...
# CC=/usr/bin/afl-clang-fast CXX=/usr/bin/afl-clang-fast++ cmake ...
# afl-fuzz -i /tmp/in -o /tmp/out -- ./foo @@
The second option when working with ARM binaries is to not use an actual ARM target, but to run afl-fuzz on an x86 host in QEMU mode.
Building AFL with QEMU support should in theory also be straight forward:
# cd afl-2.52bUnfortunately I ran into several issues with the last build step on Ubuntu 20.04 LTS.
# make
# cd qemu_mode
# ./build_qemu_support.sh
The first error I encountered was this one:
CC util/memfd.o
util/memfd.c:40:12: error: static declaration of ‘memfd_create’ follows non-static declaration
40 | static int memfd_create(const char *name, unsigned int flags)
| ^~~~~~~~~~~~
In file included from /usr/include/x86_64-linux-gnu/bits/mman-linux.h:113,
from /usr/include/x86_64-linux-gnu/bits/mman.h:34,
from /usr/include/x86_64-linux-gnu/sys/mman.h:41,
from /media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/include/sysemu/os-posix.h:29,
from /media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/include/qemu/osdep.h:104,
from util/memfd.c:28:
/usr/include/x86_64-linux-gnu/bits/mman-shared.h:50:5: note: previous declaration of ‘memfd_create’ was here
50 | int memfd_create (const char *__name, unsigned int __flags) __THROW;
| ^~~~~~~~~~~~
make: *** [/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/rules.mak:66: util/memfd.o] Error 1
You can find my patch to solve this problem in memfd.diff.--- a/build_qemu_support.sh 2020-08-07 18:44:29.794033735 +0200
+++ b/build_qemu_support.sh 2020-08-07 18:44:42.801908141 +0200
@@ -131,6 +131,7 @@
patch -p1 <../patches/elfload.diff || exit 1
patch -p1 <../patches/cpu-exec.diff || exit 1
patch -p1 <../patches/syscall.diff || exit 1
+patch -p1 <../patches/memfd.diff || exit 1
With this problem out of the way I ran into the next error:
CC x86_64-linux-user/linux-user/syscall.o
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/syscall.c:261:16: error: static declaration of ‘gettid’ follows non-static declaration
261 | _syscall0(int, gettid)
| ^~~~~~
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/syscall.c:191:13: note: in definition of macro ‘_syscall0’
191 | static type name (void) \
| ^~~~
In file included from /usr/include/unistd.h:1170,
from /media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/include/qemu/osdep.h:75,
from /media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/syscall.c:20:
/usr/include/x86_64-linux-gnu/bits/unistd_ext.h:34:16: note: previous declaration of ‘gettid’ was here
34 | extern __pid_t gettid (void) __THROW;
| ^~~~~~
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/ioctls.h:173:9: error: ‘SIOCGSTAMP’ undeclared here (not in a function); did you mean ‘SIOCSRARP’?
173 | IOCTL(SIOCGSTAMP, IOC_R, MK_PTR(MK_STRUCT(STRUCT_timeval)))
| ^~~~~~~~~~
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/syscall.c:5597:23: note: in definition of macro ‘IOCTL’
5597 | { TARGET_ ## cmd, cmd, #cmd, access, 0, { __VA_ARGS__ } },
| ^~~
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/ioctls.h:174:9: error: ‘SIOCGSTAMPNS’ undeclared here (not in a function); did you mean ‘SIOCGSTAMP_OLD’?
174 | IOCTL(SIOCGSTAMPNS, IOC_R, MK_PTR(MK_STRUCT(STRUCT_timespec)))
| ^~~~~~~~~~~~
/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/linux-user/syscall.c:5597:23: note: in definition of macro ‘IOCTL’
5597 | { TARGET_ ## cmd, cmd, #cmd, access, 0, { __VA_ARGS__ } },
| ^~~
make[1]: *** [/media/ramdisk/afl-2.52b/qemu_mode/qemu-2.10.0/rules.mak:66: linux-user/syscall.o] Error 1
make: *** [Makefile:326: subdir-x86_64-linux-user] Error 2
This problem is solved by syscall.diff which replaces
the existing syscall patch in afl-2.52b/qemu_mode/patches/syscall.diff.
With these patches you should now have afl-fuzz with working QEMU mode.
Unless your target binary was statically linked you will probably have to set a library path in your QEMU environment so that QEMU finds the library dependencies of your binary:
# export QEMU_SET_ENV=LD_LIBRARY_PATH=/path/to/your/arm/libs/
You can then run AFL in QEMU mode with the -Q flag, e.g:
# afl-fuzz -Q -i /tmp/in -o /tmp/out ./foo.arm @@
Doing so I encountered the following message:
[-] Hmm, looks like the target binary terminated before we could complete a
handshake with the injected code. There are two probable explanations:
- The current memory limit (200 MB) is too restrictive, causing an OOM
fault in the dynamic linker. This can be fixed with the -m option. A
simple way to confirm the diagnosis may be:
( ulimit -Sv $[199 << 10]; /path/to/fuzzed_app )
Tip: you can use http://jwilk.net/software/recidivm to quickly
estimate the required amount of virtual memory for the binary.
- Less likely, there is a horrible bug in the fuzzer. If other options
fail, poke for troubleshooting tips.
[-] PROGRAM ABORT : Fork server handshake failed
Location : init_forkserver(), afl-fuzz.c:2253
While the documentation says that this is to be expected since QEMU uses quite
a bit of memory, for me the error was not solved by just raising the memory limit.[-] Hmm, looks like the target binary terminated before we could complete a
handshake with the injected code. There are two probable explanations:
- The current memory limit (3.91 GB) is too restrictive, causing an OOM
fault in the dynamic linker. This can be fixed with the -m option. A
simple way to confirm the diagnosis may be:
( ulimit -Sv $[3999 << 10]; /path/to/fuzzed_app )
Tip: you can use http://jwilk.net/software/recidivm to quickly
estimate the required amount of virtual memory for the binary.
- Less likely, there is a horrible bug in the fuzzer. If other options
fail, poke for troubleshooting tips.
[-] PROGRAM ABORT : Fork server handshake failed
Location : init_forkserver(), afl-fuzz.c:2253
The solution here was set the following environment variable:
# export QEMU_RESERVED_VA=0xf00000
Now that you know two ways to use afl-fuzz with ARM binaries you will probably ask:
"Which one should I choose?"
And the answer to that clearly is: "It depends."
Personally I will continue using both since they cover different use cases.
As long as I have not reverse engineered the communication protocol of the above
mentioned devices far enough to be able to build a stub driver for fuzzing I need
to run a test loop that talks to the actual hardware to get sufficient code
coverage.
While this is painfully slow it is much easier done on the Raspberry Pi since I have
easy access to the I²C bus there.
The tests for all the functions that do not need to talk to hardware on the other hand
I run on my x86 Workstation in QEMU mode since that is a lot faster than running
the same tests on the Raspberry Pi.
Finally to give you a rough idea of the speed difference, here are
to screenshots of alf-fuzz running the same binary, first on the Raspberry Pi:
And here on an i3-3320 in QEMU: