Этот вопрос чертовски обширен и связан с моей магистерской диссертацией, поэтому я смиренно прошу вашего терпения. Около полугода назад я столкнулся с проблемой, о которой буду рассказывать дальше, и проблема требовала внешнего взгляда, потому что в тот момент я действительно застрял, и мне некому было помочь. В конце концов я махнул рукой на проблему, но теперь снова в деле (второе дыхание, скажем так).
ВВЕДЕНИЕ
Важнейшие технологии, использованные в проекте:
C++, llvm/clang 13.0.1, ASAN, libFuzzer
Основная идея проекта, который я писал это:
- Написать парсер проектов C-code для поиска функций, которые предположительно уязвимы (в рамках текущего вопроса не имеет значения, как я решу, что они уязвимы)
- Когда я нахожу уязвимую функцию, я начинаю писать код фаззера с помощью libFuzzer для этой функции.
- На этом этапе у меня есть IR-файл с моей уязвимой функцией, IR-файл с мой код фаззера, так что пришло время
выполнить отдельную компиляцию двух файлов. В процессе компиляции я снабжаю их ASAN и libFuzzer с помощью компилятора clang. - Таким образом, два файла объединяются, и у меня есть исполняемый файл, называемый, например, «fuzzer». Теоретически я могу выполнить этот исполняемый файл, и libFuzzer собирается фаззить мою уязвимую функцию.
ASAN как-то плохо инструктирует мой код. Это дает мне неправильный результат.
Откуда я это знаю?
Я нашел и воспользовался уязвимой функцией. Эта функция взята из старой версии libcurl и называется sanitize_cookie_path. Я воспроизвел ошибку с помощью AFL++, и это дало мне то, что я хотел. Если вы передадите в функцию одинарную кавычку, она «взорвется». Нечто подобное я хотел сделать с libFuzzer и ASAN, но, как я уже говорил ранее, эти два не дали мне ожидаемого результата. Потратив некоторое время на проблему, могу сказать, что с ASAN что-то есть.
ВОСПРОИЗВЕДЕНИЕ ПРОБЛЕМЫ
- Код (см. ниже) у меня есть в файле sanitize_cookie_path.c:
Код: Выделить всё
#include #include #include #include #include static char* sanitize_cookie_path(const char* cookie_path) { size_t len; char* new_path = strdup(cookie_path); if (!new_path) { return NULL; } if (new_path[0] == '\"') { memmove((void *)new_path, (const void*)(new_path + 1), strlen(new_path)); } if (new_path[strlen(new_path) - 1] == '\"') { new_path[strlen(new_path) - 1] = 0x0; } if (new_path[0] !='/') { free(new_path); new_path = strdup("/"); return new_path; } len = strlen(new_path); if (1 < len && new_path[len - 1] == '/') { new_path[len - 1] = 0x0; } return new_path; } int main(int argc, char** argv) { if (argc != 2) { exit(1); } sanitize_cookie_path('\"'); return 0; } - Мой код C++ компилирует его с помощью команды:
Код: Выделить всё
clang -O0 -emit-llvm path/to/sanitize_cookie_path.c -S -o path/to/sanitize_cookie_path.ll > /dev/null 2>&1 - На уровне IR приведенного выше кода я избавляюсь от «main», поэтому присутствует только функция «sanitize_cookie_path».
- Я генерирую простой код фаззера (см. ниже) для этой функции:
Код: Выделить всё
#include #include static char* sanitize_cookie_path(const char* cookie_path) ; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { (void) sanitize_cookie_path((char*) data); return 0; } - Затем я компилирую его командой:
Код: Выделить всё
clang -O0 -emit-llvm path/to/fuzz_sanitize_cookie_path.cc -S -o path/to/fuzz_sanitize_cookie_path.ll > /dev/null 2>&1 - Два IR-файла компилируются отдельной компиляцией. ЗАМЕТЬТЕ, что перед отдельной компиляцией я выполняю некоторые действия, чтобы они соответствовали друг другу. Например, я отказываюсь от ключевого слова static и разрешаю искажение имен из кода C++ в код C.
- Я компилирую их оба вместе с помощью команды:
Код: Выделить всё
clang++ -O0 -g -fno-omit-frame-pointer -fsanitize=address,fuzzer -fsanitize-coverage=trace-cmp,trace-gep,trace-div path/to/sanitize_cookie_path.ll path/to/fuzz_sanitize_cookie_path.ll -o path-to/fuzzer > /dev/null 2>&1 - Окончательный исполняемый файл фаззера готов.
Если вы запустите программу фаззера, она не даст вам таких же результатов, как AFL++. Мой фаззер падает на функцию __interceptor_strdup из какой-то стандартной библиотеки (см. фрагмент ошибки ниже). Отчет о сбое, сделанный libFuzzer, буквально пустой (0 байт), но в идеале он должен был обнаружить, что ошибка связана с кавычкой ("). Проведя собственное исследование, я обнаружил, что ASAN неправильно инструментировал код, и это дает мне результат ложной позиции. Честно говоря, я могу фаззить функцию printf из stdio.h и найти ту же ошибку.
Код: Выделить всё
[sanitize_cookie_path]$ ./fuzzer
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1016408680
INFO: Loaded 1 modules (11 inline 8-bit counters): 11 [0x5626d4c64c40, 0x5626d4c64c4b),
INFO: Loaded 1 PC tables (11 PCs): 11 [0x5626d4c64c50,0x5626d4c64d00),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
=================================================================
==2804==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000011 at pc 0x5626d4ba7671 bp 0x7ffe43152df0 sp 0x7ffe431525a0
READ of size 2 at 0x602000000011 thread T0
#0 0x5626d4ba7670 in __interceptor_strdup (/path/to/fuzzer+0xdd670)
#1 0x5626d4c20127 in sanitize_cookie_path (/path/to/fuzzer+0x156127)
#2 0x5626d4c20490 in LLVMFuzzerTestOneInput (/path/to/fuzzer+0x156490)
#3 0x5626d4b18940 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/path/to/fuzzer+0x4e940)
#4 0x5626d4b1bae6 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::vector&) (/path/to/fuzzer+0x51ae6)
#5 0x5626d4b1c052 in fuzzer::Fuzzer::Loop(std::vector&) (/path/to/fuzzer+0x52052)
#6 0x5626d4b0100b in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/path/to/fuzzer+0x3700b)
#7 0x5626d4af0297 in main (/path/to/fuzzer+0x26297)
#8 0x7f8e6442928f (/usr/lib/libc.so.6+0x2928f)
#9 0x7f8e64429349 in __libc_start_main (/usr/lib/libc.so.6+0x29349)
#10 0x5626d4af02e4 in _start /build/glibc/src/glibc/csu/../sysdeps/x86_64/start.S:115
Код: Выделить всё
0x0000555555631684 : mov %rbp,%rsi
0x0000555555631687 : addr32 call 0x555555674100
0x000055555563168d : pop %rax
- Я пытался инструментировать свои sanitize_cookie_path.c и fuzz_sanitize_cookie_path.cc с помощью ASAN в самом начале, не на уровне IR, но что бы я ни делал, ничего не получалось.
- Я перешел к так называемому «фаззеру» каталог corpus с предварительно подготовленными данными для передачи в фаззер. Я даже явно передал цитату «фаззеру», но ничего. Пример (с тем же каталогом, что и фаззер):
Код: Выделить всё
$ mkdir corpus/; echo "\"" > corpus/input; hexdump corpus/input 0000000 0a22 0000002 $ ./fuzzer corpus/ - Я также погуглил все, что мог, о libFuzzer и ASAN, но ничего не дало результатов.
- Изменил команду компиляции. Я избавился от '-fno-omit-frame-pointer' и '-fsanitize-coverage=trace-cmp,trace-gep,trace-div'.
Что еще есть? сайты/форумы, где меня могут услышать? В идеале я бы хотел связаться с разработчиками ASAN.
Буду более чем рад любой помощи.
ОБНОВЛЕНИЕ 10.04.2022
llvm/clang был обновлен с 13.0.1 до последней доступной версии в репозитории Arch - 14.0.6. Проблема по-прежнему сохраняется.
Открыл проблему в репозитории google/sanitizers.
Подробнее здесь: https://stackoverflow.com/questions/739 ... ive-result
Мобильная версия