
Практический прием для фаззинг-тестирования: извлечение внутреннего API проекта
Введение
Сегодня рассмотрим небольшой прием, который может облегчить вам жизнь при фаззинг-тестировании некоторых проектов.
Начнем с проблемы: представим, что вы выбрали в качестве цели внутренний метод проекта. Вы хотите собрать обертку для этого метода, однако сталкиваетесь с тем, что у проекта нет библиотеки, экспортирующей данный метод, а самостоятельное добавление в обертку всех необходимых зависимостей, осознание порядка, параметров сборки и линковки кажутся избыточно сложными или у вас просто нет времени настолько погружаться в исходники проекта. Подход, который поможет решить данную проблему возможно не является самым изящным, однако на практике показал свою эффективность. Чтобы найти проекты, в которых использовался рассматриваемый нами подход достаточно перейти на github.com и в строке поиска ввести:
repo:google/oss-fuzz path:*.sh /(?-i)\bar\b/ AND /(?-i)\bmain\b/
1. Репозиторий OSS-Fuzz
Репозиторий OSS-Fuzz, в котором мы осуществляем поиск — это инфраструктурный проект Google, содержащий коллекцию проектов с уже интегрированными фаззинг-обвязками. Многие проекты в этом репозитории используют нестандартные решения для сборки оберток, экспорта внутренних функций и подготовки тестовых окружений.
В результатах поиска мы увидим 7 проектов в составе репозитория OSS-Fuzz, среди которых: clib, binutils, postgresql, kamailio, haproxy, dnsmasq, dng_sdk.

Давайте ознакомимся с данным приемом на практике. Чтобы не повторяться, рассмотрим пример с всем известной утилитой file.
sh
git clone https://github.com/file/file.git
cd file
2. Определяем функцию для фаззинг-тестирования и пишем обертку
Предположим, что, в учебных целях, нас заинтересовал метод file_private void setparam(const char *p), который определен в файле с точкой входа main() (https://github.com/file/file/blob/master/src/file.c), и мы бы хотели выполнить фаззинг-тестирование именно этой функции.
При сборке проекта данный символ не попадает в библиотеку, являющуюся движком утилиты - libmagic.so, этот символ находится в локальной (неэкспортируемой) text-секции исполняемого файла file.
sh
autoreconf -vif
export AFL_USE_ASAN=1 AFL_USE_UBSAN=1
export CC=afl-clang-fast
./configure --enable-static --enable-shared
make
nm ./src/.libs/libmagic.so | grep setparam
nm ./src/.libs/file | grep setparam

Мы написали простейшую обертку для этого метода setparam_fuzzer.c:
sh
#include <stdint.h>
#include "file.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void setparam(const char *);
int main(int argc, char **argv) {
unsigned char input[1024];
ssize_t len;
len = read(STDIN_FILENO, input, sizeof(input) - 1);
if (len <= 0) {
return 0;
}
input[len] = '\0';
setparam((const char *)input);
return 0;
}
3. Проводим линковку и решаем возможные ошибки
И теперь сталкиваемся с задачей ее линковки с объектным файлом file.o, чтобы иметь возможность запускать фаззинг-тестирование.
sh
cd src
сp /path/to/setparam_fuzzer.c .
afl-clang-fast -c setparam_fuzzer.c -o setparam_fuzzer.o
afl-clang-fast setparam_fuzzer.o file.o -o fuzz_harness
Попробовав это сделать, мы столкнемся с ошибками:
1) multiple definition of main
2) undefined reference to setparam
3) undefined reference to ...

4. Решаем возможные ошибки
Для решения этих проблем без погружения в исходники нам необходимо:
1) Заменить символ main в файле file.c, поскольку он нам более не нужен (точка входа будет в обертке, а в бинаре не может быть двух точек входа).
2) Переместить символ из неэкспортируемой секции в экспортируемую, убрав макроопределение file_private, за которым скрывается static.
sh
sed 's/main(/main2(/g' -i ./file.c
sed -i 's/^file_private void/void/' ./file.c
3) Выполнить пересборку всего проекта.
sh
cd ..
make clean
make
При этом линковка завершится ошибкой, поскольку у нас отсутствует символ main, но в тоже время будут созданы все необходимые объектные файлы.

4) Собрать все объектные файлы в статическую библиотеку.
sh
cd src/
ar rcs libfilefuzz.a ./*.o
5) Собрать обертку.
sh
afl-clang-fast -c setparam_fuzzer.c -o setparam_fuzzer.o
afl-clang-fast setparam_fuzzer.o -L. -lfilefuzz -o fuzz_harness

Мы видим, что при сборке возникла ошибка, т.к. мы забыли про линковку с зависимостями file, исправим это:
sh
afl-clang-fast setparam_fuzzer.o -L. -lfilefuzz -lzstd -llzma -lbz2 -lz -o fuzz_harness
Попробуем запустить фаззинг-тестирование:
sh
mkdir in out
echo "1" > in/1
afl-fuzz -i ./in -o ./out -- ./fuzz_harness

Заключение
Таким образом описанный прием демонстрирует практичный способ извлечения и фаззинг-тестирования внутренних, неэкспортируемых функций проектов и позволяет обойти ограничения, возникающие при отсутствии соответствующих API, и получить рабочую обертку за счет минимального вмешательства в исходный код.
Авторы:
Козачок Александр Васильевич, доктор технических наук доцент, заведующий лабораторией 12.2 ИСП РАН
Николаев Дмитрий Александрович, кандидат технических наук, научный сотрудник лаборатории 12.2 ИСП РАН