Фаззинг, как ты, наверное, знаешь, — это техника, которая позволяет автоматизировать поиск уязвимостей в софте. Бывает фаззинг методом «черного ящика», когда у нас нет исходников программы, а бывает основанный на покрытии — то есть применяемый к исходникам. В этой статье сосредоточимся на втором виде и на примере AFL++ разберем, какую роль здесь играют санитайзеры и как их применять.
Суть фаззинга заключается в том, чтобы автоматически подавать программе неправильные или испорченные данные и отлавливать потенциальные уязвимости.
На свете существует множество фаззеров: AFL++, libfuzz, WinAFL, CI Fuzz, PeachTech Peach Fuzzer, FuzzDB, go-fuzz и прочие, в том числе самописные. Фаззеры применимы не только для программ, но и для сайтов. Например, wfuzz — это веб‑фаззер, который нужен, чтобы вводить любые данные в любое поле HTTP-запроса.
В этой статье идет речь о фаззинге программ. Нашим инструментом будет AFL++ — фаззер, ориентированный на покрытие. Это значит, что он собирает информацию о покрытии для каждого измененного входа, чтобы обнаружить новые пути выполнения и потенциальные ошибки. Если у тебя есть исходник программы, AFL может вставлять вызовы функций в начало каждого базового блока, например в функции и циклы.
О том, как при помощи AFL тестировать блекбоксы, написано множество статей, в том числе о том, как его распараллеливать. Но этот фаззер умеет компилировать программы и делать свои вставки в код, что облегчает фаззинг. Такие вставки называются санитайзерами.
В AFL++ доступны разные санитайзеры, которые помогают обнаруживать ошибки в коде. Вот краткое описание каждого из них.
Например, санитайзер ASAN может выявить следующие проблемы:
Например, в программе определяется какой‑то указатель:
Санитайзер меняет его использование на такое:
Санитайзер помечает любую высвобождаемую память как «отравленную», а затем следит за доступом к ней. Если произошло переполнение буфера, который выделялся через malloc, то получается, что кто‑то пишет в отравленную область, а в эту область писать нельзя. Так фаззер обнаруживает баг.
Подобная же ситуация и с переполнением буфера на стеке. Предположим, у нас есть такая программа:
Санитайзер ее изменит на вот такую:
Тут искусственно добавляется отравленная область, и, если произойдет запись в нее, фаззер сработает и сообщит о переполнении.
Для тестирования два раза скомпилируем программу. Первый раз обычным GCC, второй раз компилятором afl-clang-fast.
Компиляция при помощи afl-clang-fast выглядит точно так же, как и с GCC:
Если скомпилировать не получается, стоит пробовать варианты afl-clang или afl-gcc.
В результате мы получаем инструментированный бинарный файл. При компиляции ты увидишь сообщение о том, что AFL нашел определенное количество места для инструментации и вставил свое логирование в места ветвления.
Для проверки работы скомпилированного таким образом бинарного файла можно просто его запустить. Если программа падает, значит, фаззер будет врать:
Теперь загружу два бинарных файла в IDA Pro, чтобы показать, как это все выглядит внутри.
Разница очевидна: AFL добавил функцию afl_maybe_log() для логирования сообщения. Эта функция принимает два аргумента:
Функция afl_maybe_log() используется для логирования событий, происходящих во время работы AFL++: ошибок, предупреждений, информации о покрытии кода и так далее.
Вот пример ее использования:
Здесь сообщение об ошибке будет выведено в стандартный вывод, WARNING уйдет туда же, а INFO — нет, потому что уровень логирования не установлен на значение, которое не ниже типа этих сообщений.
Точно так же можно увидеть те самые семь инструментариев, которые вставил компилятор.
Ragg2 — это утилита из набора Radare2, с помощью которой можно генерировать мусорные строки. Например, это удобно, когда нужно вычислять данные для переполнения буфера.
Запускаем фаззер:
Давай посмотрим, что делает каждый из параметров:
Внизу можно увидеть состояние фаззера, в правом верхнем углу — количество циклов и количество крашей. Через пять минут поле state из состояния started перейдет в состояние finished. Значит, фаззинг закончился!
Все результаты мутирования входного файла находятся по пути $PWD/out/default/queue. Давай посмотрим последний файл.
Можно увидеть разницу между входным файлом и тем, что выдал фаззер.
Удобно, что фаззер сам сообщает, когда все закончилось.
Здесь в явном виде есть уязвимость Use-After-Free, но программа не падает и ошибки никакой не выдает. Наша задача — продемонстрировать работу санитайзера AFL++. Он должен обнаружить эту уязвимость, в то время как при обычном запуске фаззера, без санитайзеров, ничего произойти не должно.
Компилировать будем с флагом -fsanitize и в явном виде указываем, какой тип ошибки интересует:
Далее повторяем те же шаги, что и раньше:
И запускаем фаззер:
Результат говорит о том, что была обнаружена уязвимость Use-After-Free.
Теперь проделаем такой же фокус, только без санитайзеров:
И запустим фаззер:
Как видно, без санитайзера фаззер работает намного дольше.
Пример, который мы рассмотрели, показал достоинства санитайзера: фаззер адаптировался под код и заранее узнал, как ему надо двигаться, чтобы дойти до конца программы. Благодаря санитайзерам AFL++ смог грамотно подобрать тесткейс.
Автор Life-Hack
Источник xakep.ru
Суть фаззинга заключается в том, чтобы автоматически подавать программе неправильные или испорченные данные и отлавливать потенциальные уязвимости.
На свете существует множество фаззеров: AFL++, libfuzz, WinAFL, CI Fuzz, PeachTech Peach Fuzzer, FuzzDB, go-fuzz и прочие, в том числе самописные. Фаззеры применимы не только для программ, но и для сайтов. Например, wfuzz — это веб‑фаззер, который нужен, чтобы вводить любые данные в любое поле HTTP-запроса.
В этой статье идет речь о фаззинге программ. Нашим инструментом будет AFL++ — фаззер, ориентированный на покрытие. Это значит, что он собирает информацию о покрытии для каждого измененного входа, чтобы обнаружить новые пути выполнения и потенциальные ошибки. Если у тебя есть исходник программы, AFL может вставлять вызовы функций в начало каждого базового блока, например в функции и циклы.
О том, как при помощи AFL тестировать блекбоксы, написано множество статей, в том числе о том, как его распараллеливать. Но этот фаззер умеет компилировать программы и делать свои вставки в код, что облегчает фаззинг. Такие вставки называются санитайзерами.
ЧТО ТАКОЕ САНИТАЙЗЕРЫ?
Санитайзеры — это утилиты, которые обычно поставляются вместе с компиляторами. Они помогают искать ошибки в коде во время исполнения. Если обобщить, можно выделить три разновидности санитайзеров:- address — ищет ошибки работы с памятью;
- thread — ищет проблемы в многопоточных вычислениях (состояние гонки);
- undefined — ищет неопределенное поведение в программе.
В AFL++ доступны разные санитайзеры, которые помогают обнаруживать ошибки в коде. Вот краткое описание каждого из них.
- AddressSanitizer (ASAN) предназначен для обнаружения ошибок чтения или записи в память, таких как переполнение буфера или использование освобожденной памяти. Он обеспечивает детальную информацию об ошибках и помогает в их отладке.
- MemorySanitizer (MSAN) призван обнаружить неинициализированную память. Он помогает выявить ошибки, связанные с использованием неинициализированных данных и способные привести к непредсказуемому поведению программы.
- ThreadSanitizer (TSAN) предназначен для поиска гонок данных в многопоточных программах. Он помогает выявить ситуации, когда несколько потоков одновременно обращаются к одним и тем же данным, что иногда приводит к непредсказуемым результатам
- UndefinedBehaviorSanitizer (UBSAN) помогает обнаружить неопределенное поведение в программе, такое как деление на ноль, переполнение целочисленных типов или неправильное использование указателей. Он предупреждает о таких проблемах и помогает предотвратить возможные ошибки.
- Control Flow Integrity (CFI) обеспечивает защиту от атак, связанных с изменением потока управления программы. Он проверяет целостность вызовов функций и предотвращает нежелательные изменения потока выполнения.
- Safe-stack предназначен для защиты от переполнения стека. Он обеспечивает дополнительные механизмы безопасности, чтобы предотвратить возможные атаки, связанные с переполнением стека.
Например, санитайзер ASAN может выявить следующие проблемы:
- Use-After-Free — использование чанка кучи после его освобождения;
- HeapOverflow — переполнение одного чанка и добавление данных в другой чанк;
- StackOverflow — классическое переполнение буфера.
ТЕНЕВАЯ ПАМЯТЬ
Одна из техник, которую используют санитайзеры, — это теневая память. По сути, они поддерживают параллельную структуру данных наряду с фактической памятью программы, при этом каждый бит в теневой памяти соответствует 8 битам в фактической памяти. Такая схема позволяет хранить метаданные о каждом выделении памяти и обнаруживать нарушения при записи в нее.Например, в программе определяется какой‑то указатель:
Код:
*address = ...; // or: ... = *address;
Код:
if (IsPoisoned(address)) {
ReportError(address, kAccessSize, kIsWrite);
}
*address = ...; // or: ... = *address;
Подобная же ситуация и с переполнением буфера на стеке. Предположим, у нас есть такая программа:
Код:
void foo() {
char a[8];
...
return;
}
Код:
void foo() {
char redzone1[32]; // 32-byte aligned
char a[8]; // 32-byte aligned
char redzone2[24];
char redzone3[32]; // 32-byte aligned
int *shadow_base = MemToShadow(redzone1);
shadow_base[0] = 0xffffffff; // poison redzone1
shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'
shadow_base[2] = 0xffffffff; // poison redzone3
...
shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison all
return;
}
КОМПИЛИРУЕМ ТЕСТОВУЮ ПРОГРАММУ С AFL-CLANG-FAST
В качестве подопытного для испытаний используем вот такую незамысловатую программу:
Код:
#include <stdio.h>
int main()
{
char buf[256] = {0};
char *a = 0;
unsigned int size = read(0, buf, 256);
if(size > 0 && buf[0] == 'H'){
if(size > 1 && buf[1] == 'I'){
if(size > 2 && buf[2] == '!'){
a = 1;
}
}
}
if(a == 1){
printf("g00d pwn\n");
}
return 0;
}
Для тестирования два раза скомпилируем программу. Первый раз обычным GCC, второй раз компилятором afl-clang-fast.
Компиляция при помощи afl-clang-fast выглядит точно так же, как и с GCC:
Код:
$ afl-clang-fast main.c -o san.elf
В результате мы получаем инструментированный бинарный файл. При компиляции ты увидишь сообщение о том, что AFL нашел определенное количество места для инструментации и вставил свое логирование в места ветвления.
Для проверки работы скомпилированного таким образом бинарного файла можно просто его запустить. Если программа падает, значит, фаззер будет врать:
Код:
$ ./san.elf
HI!
g00d pwn
Теперь загружу два бинарных файла в IDA Pro, чтобы показать, как это все выглядит внутри.
ИЩЕМ ОТЛИЧИЯ БИНАРНЫХ ФАЙЛОВ С ПОМОЩЬЮ IDA PRO
Слева загружена оригинальная программа, скомпилированная с помощью GCC. Справа — программа, скомпиленная при помощи afl-clang-fast.
Разница очевидна: AFL добавил функцию afl_maybe_log() для логирования сообщения. Эта функция принимает два аргумента:
- type — тип сообщения. Может принимать значения ERROR, WARNING, INFO или DEBUG;
- msg — сообщение, которое нужно залогировать.
Функция afl_maybe_log() используется для логирования событий, происходящих во время работы AFL++: ошибок, предупреждений, информации о покрытии кода и так далее.
Вот пример ее использования:
Код:
afl_maybe_log(ERROR, "An error occurred!");
afl_maybe_log(WARNING, "A warning occurred!");
afl_maybe_log(INFO, "Some information");
afl_maybe_log(DEBUG, "Some debug information");
Точно так же можно увидеть те самые семь инструментариев, которые вставил компилятор.
ФАЗЗИНГ
Как и в прошлый раз, нам нужно создать две директории: /in с тесткейсами и /out — для вывода крашей:
Код:
mkdir in; mkdir out; cd in; ragg2 -P 10 -r > 1.txt; cd ..
Запускаем фаззер:
Код:
afl-fuzz -m none -i $PWD/in -o $PWD/out -- ./san.elf
- -- — берется содержимое файлов, которые находятся в директории /in;
- -m none означает, что необходимо убрать ограничение памяти для каждого запускаемого семпла. Это ускоряет фаззинг;
- -i $PWD/in задает, какие тесткейсы берем для фаззинга;
- -o $PWD/out задает, куда будут вылетать мутированные данные.
Внизу можно увидеть состояние фаззера, в правом верхнем углу — количество циклов и количество крашей. Через пять минут поле state из состояния started перейдет в состояние finished. Значит, фаззинг закончился!
Все результаты мутирования входного файла находятся по пути $PWD/out/default/queue. Давай посмотрим последний файл.
Можно увидеть разницу между входным файлом и тем, что выдал фаззер.
Удобно, что фаззер сам сообщает, когда все закончилось.
ВТОРОЙ ПРИМЕР САНИТАЙЗЕРА
Допустим, у нас будет вот такая программа:
Код:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char *buffer = (char *)malloc(256);
char *a = 0;
unsigned int size = read(0, buf, 256);
if(size > 0 && buf[0] == 'H'){
if(size > 1 && buf[1] == 'I'){
if(size > 2 && buf[2] == '!'){
a = 1;
free(buffer);
strcpy(buffer, argv[1]);
puts(buffer);
}
}
}
if(a == 1){
printf("g00d pwn\n");
}
return 0;
}
Здесь в явном виде есть уязвимость Use-After-Free, но программа не падает и ошибки никакой не выдает. Наша задача — продемонстрировать работу санитайзера AFL++. Он должен обнаружить эту уязвимость, в то время как при обычном запуске фаззера, без санитайзеров, ничего произойти не должно.
Компилировать будем с флагом -fsanitize и в явном виде указываем, какой тип ошибки интересует:
Код:
afl-clang-fast -fsanitize=address main.c -o binary.elf
Далее повторяем те же шаги, что и раньше:
Код:
mkdir in; mkdir out; cd in; ragg2 -P 10 -r > 1.txt; cd ..
И запускаем фаззер:
Код:
afl-fuzz -i $PWD/in -o $PWD/out ./binary.elf @@ $PWD/in
Результат говорит о том, что была обнаружена уязвимость Use-After-Free.
Теперь проделаем такой же фокус, только без санитайзеров:
Код:
gcc main.c -o clear.elf
И запустим фаззер:
Код:
afl-fuzz -Q -D -i $PWD/in -o $PWD/out ./clear.elf @@ $PWD/in
Как видно, без санитайзера фаззер работает намного дольше.
ВЫВОДЫ
Мы убедились, что санитайзеры — это удобная вещь, которую стоит использовать при фаззинге, если есть такая возможность. Можно указать самому фаззеру, на какие дефекты ему стоит обращать внимание.Пример, который мы рассмотрели, показал достоинства санитайзера: фаззер адаптировался под код и заранее узнал, как ему надо двигаться, чтобы дойти до конца программы. Благодаря санитайзерам AFL++ смог грамотно подобрать тесткейс.
Автор Life-Hack
Источник xakep.ru
Последнее редактирование модератором: