Загрузка...

Fuzzing executable files using AFL++ with sanitizers

Thread in Web vulnerabilities created by БезПротокола Feb 28, 2024. 386 views

  1. БезПротокола
    Фаззинг, как ты, наверное, знаешь, — это техника, которая позволяет автоматизировать поиск уязвимостей в софте. Бывает фаззинг методом «черного ящика», когда у нас нет исходников программы, а бывает основанный на покрытии — то есть применяемый к исходникам. В этой статье сосредоточимся на втором виде и на примере AFL++ разберем, какую роль здесь играют санитайзеры и как их применять.
    INFO


    О фаззинге методом черного ящика читай в статье «WinAFL на практике. Учимся работать фаззером и искать дыры в софте».
    Суть фаззинга заключается в том, чтобы автоматически подавать программе неправильные или испорченные данные и отлавливать потенциальные уязвимости.
    На свете существует множество фаззеров: AFL++, libfuzz, WinAFL, CI Fuzz, PeachTech Peach Fuzzer, FuzzDB, go-fuzz и прочие, в том числе самописные. Фаззеры применимы не только для программ, но и для сайтов. Например, wfuzz — это веб‑фаззер, который нужен, чтобы вводить любые данные в любое поле HTTP-запроса.
    В этой статье идет речь о фаззинге программ. Нашим инструментом будет AFL++ — фаззер, ориентированный на покрытие. Это значит, что он собирает информацию о покрытии для каждого измененного входа, чтобы обнаружить новые пути выполнения и потенциальные ошибки. Если у тебя есть исходник программы, AFL может вставлять вызовы функций в начало каждого базового блока, например в функции и циклы.
    О том, как при помощи AFL тестировать блекбоксы, написано множество статей, в том числе о том, как его распараллеливать. Но этот фаззер умеет компилировать программы и делать свои вставки в код, что облегчает фаззинг. Такие вставки называются санитайзерами.
    WARNING


    Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
    ЧТО ТАКОЕ САНИТАЙЗЕРЫ?


    Санитайзеры — это утилиты, которые обычно поставляются вместе с компиляторами. Они помогают искать ошибки в коде во время исполнения. Если обобщить, можно выделить три разновидности санитайзеров:
    • address — ищет ошибки работы с памятью;​
    • thread — ищет проблемы в многопоточных вычислениях (состояние гонки);​
    • undefined — ищет неопределенное поведение в программе.​
    Использование санитайзеров вместе с AFL++ — это хороший тон при фаззинге, потому что с ними фаззер будет лучше знать, какого рода ошибки ему нужно искать. С санитайзерами фаззинг всегда будет проходить успешнее, чем без них.
    В AFL++ доступны разные санитайзеры, которые помогают обнаруживать ошибки в коде. Вот краткое описание каждого из них.
    1. AddressSanitizer (ASAN) предназначен для обнаружения ошибок чтения или записи в память, таких как переполнение буфера или использование освобожденной памяти. Он обеспечивает детальную информацию об ошибках и помогает в их отладке.​
    2. MemorySanitizer (MSAN) призван обнаружить неинициализированную память. Он помогает выявить ошибки, связанные с использованием неинициализированных данных и способные привести к непредсказуемому поведению программы.​
    3. ThreadSanitizer (TSAN) предназначен для поиска гонок данных в многопоточных программах. Он помогает выявить ситуации, когда несколько потоков одновременно обращаются к одним и тем же данным, что иногда приводит к непредсказуемым результатам​
    4. UndefinedBehaviorSanitizer (UBSAN) помогает обнаружить неопределенное поведение в программе, такое как деление на ноль, переполнение целочисленных типов или неправильное использование указателей. Он предупреждает о таких проблемах и помогает предотвратить возможные ошибки.​
    5. Control Flow Integrity (CFI) обеспечивает защиту от атак, связанных с изменением потока управления программы. Он проверяет целостность вызовов функций и предотвращает нежелательные изменения потока выполнения.​
    6. Safe-stack предназначен для защиты от переполнения стека. Он обеспечивает дополнительные механизмы безопасности, чтобы предотвратить возможные атаки, связанные с переполнением стека.​
    В разных случаях могут пригодиться разные санитайзеры, в том числе это зависит от особенностей и требований проекта.
    Например, санитайзер ASAN может выявить следующие проблемы:
    • Use-After-Free — использование чанка кучи после его освобождения;​
    • HeapOverflow — переполнение одного чанка и добавление данных в другой чанк;​
    • StackOverflow — классическое переполнение буфера.​
    INFO


    Подробнее о возможностях ASAN читай в документации на GitHub.
    ТЕНЕВАЯ ПАМЯТЬ


    Одна из техник, которую используют санитайзеры, — это теневая память. По сути, они поддерживают параллельную структуру данных наряду с фактической памятью программы, при этом каждый байт в теневой памяти соответствует байту в фактической памяти. Эта теневая память используется для хранения метаданных о каждом выделении памяти. Используя теневую память, санитайзеры могут эффективно отслеживать использование памяти и обнаруживать нарушения.
    Например, в программе определяется какой‑то указатель:
    *address = ...; // or: ... = *address;
    Санитайзер меняет его использование на такое:
    if (IsPoisoned(address)) {ReportError(address, kAccessSize, kIsWrite);}*address = ...; // or: ... = *address;
    Санитайзер помечает любую высвобождаемую память как «отравленную», а затем следит за доступом к ней. Если произошло переполнение буфера, который выделялся через malloc, то получается, что кто‑то пишет в отравленную область, а в эту область писать нельзя. Так фаззер обнаруживает баг.
    Подобная же ситуация и с переполнением буфера на стеке. Предположим, у нас есть такая программа:
    void foo() {char a[8];...return;}
    Санитайзер ее изменит на вот такую:
    void foo() {char redzone1[32]; // 32-byte alignedchar a[8]; // 32-byte alignedchar redzone2[24];char redzone3[32]; // 32-byte alignedint *shadow_base = MemToShadow(redzone1);shadow_base[0] = 0xffffffff; // poison redzone1shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'shadow_base[2] = 0xffffffff; // poison redzone3...shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison allreturn;}
    Тут искусственно добавляется отравленная область, и, если произойдет запись в нее, фаззер сработает и сообщит о переполнении.
    КОМПИЛИРУЕМ ТЕСТОВУЮ ПРОГРАММУ С 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
    INFO


    Если скомпилировать не получается, стоит пробовать варианты afl-clang или afl-gcc.
    В результате мы получаем инструментированный бинарный файл. При компиляции ты увидишь сообщение о том, что AFL нашел определенное количество места для инструментации и вставил свое логирование в места ветвления.
    [IMG]
    Для проверки работы скомпилированного таким образом бинарного файла можно просто его запустить. Если программа падает, значит, фаззер будет врать:

    $ ./san.elf
    HI!
    g00d pwn

    Теперь загружу два бинарных файла в IDA Pro, чтобы показать, как это все выглядит внутри.
    ИЩЕМ ОТЛИЧИЯ БИНАРНЫХ ФАЙЛОВ С ПОМОЩЬЮ IDA PRO


    Слева загружена оригинальная программа, скомпилированная с помощью GCC. Справа — программа, скомпиленная при помощи afl-clang-fast.
    [IMG]
    Разница очевидна: AFL добавил функцию afl_maybe_log() для логирования сообщения. Эта функция принимает два аргумента:
    • type — тип сообщения. Может принимать значения ERROR, WARNING, INFO или DEBUG;​
    • msg — сообщение, которое нужно залогировать.​
    Функция afl_maybe_log() будет выводить сообщение в стандартный вывод только в том случае, если уровень логирования установлен на значение не ниже типа сообщения. Например, если уровень логирования установлен на INFO, то сообщения с типом ERROR и WARNING будут выведены в стандартный вывод, а сообщения с типом INFO и DEBUG — нет.
    Функция 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");
    Здесь сообщение об ошибке будет выведено в стандартный вывод, WARNING уйдет туда же, а INFO — нет, потому что уровень логирования не установлен на значение, которое не ниже типа этих сообщений.
    Точно так же можно увидеть те самые семь инструментариев, которые вставил компилятор.

    ФАЗЗИНГ


    Как и в прошлый раз, нам нужно создать две директории: /in с тесткейсами и /out — для вывода крашей:
    mkdir in; mkdir out; cd in; ragg2 -P 10 -r > 1.txt; cd ..
    INFO


    Ragg2 — это утилита из набора Radare2, с помощью которой можно генерировать мусорные строки. Например, это удобно, когда нужно вычислять данные для переполнения буфера.
    Запускаем фаззер:
    afl-fuzz -m none -i $PWD/in -o $PWD/out -- ./san.elf
    Давай посмотрим, что делает каждый из параметров:
    • -- — берется содержимое файлов, которые находятся в директории /in;​
    • -m none означает, что необходимо убрать ограничение памяти для каждого запускаемого семпла. Это ускоряет фаззинг;​
    • -i $PWD/in задает, какие тесткейсы берем для фаззинга;​
    • -o $PWD/out задает, куда будут вылетать мутированные данные.​
    После запуска наблюдаем такую картину.
    [IMG]
    Внизу можно увидеть состояние фаззера, в правом верхнем углу — количество циклов и количество крашей. Через пять минут поле state из состояния started перейдет в состояние finished. Значит, фаззинг закончился!

    Все результаты мутирования входного файла находятся по пути $PWD/out/default/queue. Давай посмотрим последний файл.

    Можно увидеть разницу между входным файлом и тем, что выдал фаззер.
    [IMG]
    Удобно, что фаззер сам сообщает, когда все закончилось.
    ВТОРОЙ ПРИМЕР САНИТАЙЗЕРА


    Допустим, у нас будет вот такая программа:
    #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

    [IMG]
    Результат говорит о том, что была обнаружена уязвимость Use-After-Free.
    Теперь проделаем такой же фокус, только без санитайзеров:
    gcc main.c -o clear.elf
    И запустим фаззер:
    afl-fuzz -Q -D -i $PWD/in -o $PWD/out ./clear.elf @@ $PWD/in

    [IMG]
    Как видно, без санитайзера фаззер работает намного дольше.
    ВЫВОДЫ


    Мы убедились, что санитайзеры — это удобная вещь, которую стоит использовать при фаззинге, если есть такая возможность. Можно указать самому фаззеру, на какие дефекты ему стоит обращать внимание.
    Пример, который мы рассмотрели, показал достоинства санитайзера: фаззер адаптировался под код и заранее узнал, как ему надо двигаться, чтобы дойти до конца программы. Благодаря санитайзерам AFL++ смог грамотно подобрать тесткейс.

     
  2. WhatACat
    WhatACat Feb 28, 2024 https://lolz.live/account/upgrades покупайте уники, ау, автобай 17,504 Dec 16, 2023
    Добротно расписан материал, единственное может быть стоило по спойлерам разделить для большего удобства. :ameat:
     
  3. ebqlq
    что-то для умных, надо почитать
     
  4. morphlyk
    morphlyk Feb 28, 2024 люблю щенков 58 Aug 23, 2023
    авторку
     
Loading...
Top