AFL++: Finding several heap overflows in GNU Barcode 0.99
AFL++
AFL++ is an improved version of AFL, a popular and successful fuzzer. In this article we will use it to discover a couple heap-overflow bugs in GNU Barcode 0.99.
Compiling
It is quite simple to use AFL++, especially if you have access to the source code of the program you are trying to fuzz.
# Install some deps on Ubuntu
apt install git clang make build-essential gcc-9-plugin-dev
git clone https://github.com/AFLplusplus/AFLplusplus.git
cd AFLplusplus
# Set LLVM_CONFIG to point to your llvm-config binary
LLVM_CONFIG=llvm-config-10 make -j
Building a Target
AFL++ makes it rather easy here - all you have to do is replace CC
/CXX
with the afl-clang-fast
/afl-clang-fast++
file in your AFL++ build folder and it handles the rest.
For example with GNU Barcode 0.99:
wget -qO - https://mirrors.sarata.com/gnu/barcode/barcode-0.99.tar.gz | tar vxz
cd barcode-0.99
CC=../AFLplusplus/afl-cc ./configure
make -j
Fuzzing With AFL++
mkdir /tmp/{in,out}
# Our example input, modify accordingly
# (ex: PNG parser -> copy .png here). Can have multiple files
echo hello > /tmp/in/1
# Run fuzzer
AFLplusplus/afl-fuzz -i /tmp/in/ -o /tmp/out/ -- barcode-0.99/barcode
Crash-causing inputs will be found in the /tmp/out/default/crashes
folder where you can try and reproduce the bug.
Heap Overflows
Overflow 1
The first overflow I found occured when GNU Barcode tried to parse the following input:
[email protected] /tmp/poc % xxd < heapoverflow1
00000000: 3109 8000 0a20 3f39 0010 3952 0a47 6847 1.... ?9..9R.GhG
00000010: 6cff 262b 0a l.&+.
Compiling GNU Barcode with ASAN helps give a hint as to what is going on when the crash occurs.
Examining this further in GDB, it appears as if the following snippet in code128.c:443
is responsible for the crash here.
partial = malloc( 6 * len + 4); // 40 bytes allocated
//...
// Far more than 40 bytes are copied into the heap-allocated buffer
for (i=0; i<len; i++)
strcat(partial, codeset[codes[i]]);
Running the program in GDB while checking the ability to free()
the buffer partial
inside of the for loop succeeds several times until it is overflowed and the following output can be seen from PwnDBG:
`
pwndbg> try_free partial
General checks
Tcache checks
Using tcache_put
Fastbin checks
free(): invalid next size (fast) -> next chunk's size not in [2*size_sz; av->system_mem]
next chunk's size is 0x3131323234, 2*size_sz is 0x10, system_mem is 0x21000
----------
Free should succeed!
The full debugging session demo is shown below.
Overflow 2
The second one occurs with the following input:
[email protected] /tmp/poc % xxd < heapoverflow2
00000000: 3109 3100 1000 0039 0010 3944 0a47 6865 1.1....9..9D.Ghe
00000010: 6c80 262b 0a l.&+.
Here, ASAN gives us an even better hint that a heap overflow is occuring:
Digging through the source code and running the target in GDB, the source appears to be a different location inside of code128.c:557
.
// Buffer allocated
textinfo = malloc(12 * (1+strlen(text)/2) + 2);
...
textptr = textinfo;
for (i=0, count = 0; i < strlen(text); count++) {
if (sscanf(text + i, "%u%n", &code, &n) < 1) {
bc->error = EINVAL;
free(partial);
free(textinfo);
return -1;
}
strcat(partial, codeset[code]);
// ...
// Overflow occurs here!
sprintf(textptr, "%g:9:%c %g:9:%c ", (double)textpos,
code >= 100 ? 'A' : code/10 + '0',
textpos + (double)SYMBOL_WID/2, code%10 + '0');
textptr += strlen(textptr);
textpos += SYMBOL_WID; /* width of each code */
i += n;
}
The loop above continues to copy into the heap-allocated buffer without checking any sort of boundaries, eventually overflowing it.
Fixes & Conclusion
Over 90 crashing inputs were found after fuzzing GNU Barcode 0.99 for an hour. Of the 2 analyzed above, both are caused by an out-of-bounds write corrupting the heap which causes the subsequent free()
in the program to operate on a smashed heap causing a crash.
In both instances, a safer function could have been used (sprintf
-> snprintf
and strcpy
-> strncpy
) which would only copy within the specified bounds and prevented both of these vulnerabilities.