0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: реверс-инжиниринг кода, безопасность, фаззинг, тестирование приложений, статический анализ, динамический анализ, анализатор, american fuzzy lop, afl, security-oriented fuzzing, coverity, stdin, временный файл, address sanitizer, pvs studio и clang static analyzer</p>
1
<p>Теги: реверс-инжиниринг кода, безопасность, фаззинг, тестирование приложений, статический анализ, динамический анализ, анализатор, american fuzzy lop, afl, security-oriented fuzzing, coverity, stdin, временный файл, address sanitizer, pvs studio и clang static analyzer</p>
2
<p>В<a>предыдущей части</a>мы начали разговор об<strong>American Fuzzy Lop</strong>и провели тесты с помощью статических анализаторов, которые не обнаружили проблему. Теперь давайте посмотрим, к какому результату приведёт процесс<strong>фаззинга</strong>.</p>
2
<p>В<a>предыдущей части</a>мы начали разговор об<strong>American Fuzzy Lop</strong>и провели тесты с помощью статических анализаторов, которые не обнаружили проблему. Теперь давайте посмотрим, к какому результату приведёт процесс<strong>фаззинга</strong>.</p>
3
<p>В первую очередь качаем и собираем AFL:</p>
3
<p>В первую очередь качаем и собираем AFL:</p>
4
$ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz $ tar xvfz afl-latest.tgz $ cd afl-1.83b $ make $ cd llvm_mode $ make<p>Как правило, фаззер стартует приложение в новом процессе, потом подаёт на вход приложению тестовые данные в<strong>STDIN</strong>, либо использует<strong>временный файл</strong>. В том случае, если процесс упадёт, American Fuzzy Lop это увидит, поэтому запишет данные в директорию<strong>crashes</strong>. Важный момент для успешного фаззинга - сборка приложения с помощью<a>Address Sanitizer'а</a>. Из-за этого приложение гарантированно упадёт даже в том случае, если произойдёт перезапись хотя бы одного байта динамической памяти. Про ASAN писать не будем, он<a>описан множество раз</a>, поэтому успешно и давно применяется.</p>
4
$ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz $ tar xvfz afl-latest.tgz $ cd afl-1.83b $ make $ cd llvm_mode $ make<p>Как правило, фаззер стартует приложение в новом процессе, потом подаёт на вход приложению тестовые данные в<strong>STDIN</strong>, либо использует<strong>временный файл</strong>. В том случае, если процесс упадёт, American Fuzzy Lop это увидит, поэтому запишет данные в директорию<strong>crashes</strong>. Важный момент для успешного фаззинга - сборка приложения с помощью<a>Address Sanitizer'а</a>. Из-за этого приложение гарантированно упадёт даже в том случае, если произойдёт перезапись хотя бы одного байта динамической памяти. Про ASAN писать не будем, он<a>описан множество раз</a>, поэтому успешно и давно применяется.</p>
5
<p>Идём дальше. Для генерации тестов нужен набор тестовых данных, обрабатывающих приложение (т. н.<strong>corpus</strong>). В случае с<strong>curl</strong>речь идёт о валидных HTTP-ответах веб-сервера.</p>
5
<p>Идём дальше. Для генерации тестов нужен набор тестовых данных, обрабатывающих приложение (т. н.<strong>corpus</strong>). В случае с<strong>curl</strong>речь идёт о валидных HTTP-ответах веб-сервера.</p>
6
<p>В случае с известной уязвимостью у нас существуют 2 пути: 1) фаззить отдельные функции, которые кажутся подозрительными; 2) фаззить всё приложение полностью.</p>
6
<p>В случае с известной уязвимостью у нас существуют 2 пути: 1) фаззить отдельные функции, которые кажутся подозрительными; 2) фаззить всё приложение полностью.</p>
7
<h2>Фаззинг отдельных функций</h2>
7
<h2>Фаззинг отдельных функций</h2>
8
<p>В первом случае пишем минимальную обёртку к приложению:</p>
8
<p>В первом случае пишем минимальную обёртку к приложению:</p>
9
int main(int argc, char **argv) { unsigned char buf[2048]; char *res = NULL; assert(argc == 2); FILE *f = fopen(argv[1], "rb"); assert(f); size_t len = fread(buf, 1, sizeof(buf), f); buf[len] = 0x00; if (len == 0 || strlen(buf) == 0) { return 0; } printf("read = %zu\n", len); printf("in = %s\n", buf); /* call the code which smell */ res = sanitize_cookie_path(buf); if (res) { printf("res = %s\n", res); free(res); } return 0; }<p>Далее инструментируем обёртку, собирая её под AFL:</p>
9
int main(int argc, char **argv) { unsigned char buf[2048]; char *res = NULL; assert(argc == 2); FILE *f = fopen(argv[1], "rb"); assert(f); size_t len = fread(buf, 1, sizeof(buf), f); buf[len] = 0x00; if (len == 0 || strlen(buf) == 0) { return 0; } printf("read = %zu\n", len); printf("in = %s\n", buf); /* call the code which smell */ res = sanitize_cookie_path(buf); if (res) { printf("res = %s\n", res); free(res); } return 0; }<p>Далее инструментируем обёртку, собирая её под AFL:</p>
10
$ afl-clang-fast -g -fsanitize=address path_san.c -o path_san<p>В директорию<strong>inputs</strong>ложим один файл с подходящим URI, к примеру, "/xxx/", после чего запускаем AFL:</p>
10
$ afl-clang-fast -g -fsanitize=address path_san.c -o path_san<p>В директорию<strong>inputs</strong>ложим один файл с подходящим URI, к примеру, "/xxx/", после чего запускаем AFL:</p>
11
$ AFL_USE_ASAN=1 /path/to/afl/afl-fuzz -m none -i inputs -o out ./path_san @@<p>Параметр -m none обеспечит отключение лимита памяти, а @@ будет меняться на имя временного файла при<strong>фаззинге</strong>. Если же мы не зададим этот параметр, тестовые данные будут подаваться в<strong>STDIN</strong>.</p>
11
$ AFL_USE_ASAN=1 /path/to/afl/afl-fuzz -m none -i inputs -o out ./path_san @@<p>Параметр -m none обеспечит отключение лимита памяти, а @@ будет меняться на имя временного файла при<strong>фаззинге</strong>. Если же мы не зададим этот параметр, тестовые данные будут подаваться в<strong>STDIN</strong>.</p>
12
<p>Практически сразу после запуска<strong>AFL найдёт crash</strong>и сгенерирует тестовый вход в следующей директории:</p>
12
<p>Практически сразу после запуска<strong>AFL найдёт crash</strong>и сгенерирует тестовый вход в следующей директории:</p>
13
<p>Фаззинг отдельных функций в большом проекте может быть эффективнее фаззинга всего приложения, особенно когда для кода уже созданы<strong>юнит-тесты</strong>. Но иногда бывает полезно выполнить и фаззинг приложения целиком. Давайте сделаем это на примере<strong>curl</strong>.</p>
13
<p>Фаззинг отдельных функций в большом проекте может быть эффективнее фаззинга всего приложения, особенно когда для кода уже созданы<strong>юнит-тесты</strong>. Но иногда бывает полезно выполнить и фаззинг приложения целиком. Давайте сделаем это на примере<strong>curl</strong>.</p>
14
<h2>Фаззинг приложения целиком</h2>
14
<h2>Фаззинг приложения целиком</h2>
15
<p>Хорошо известно, что curl осуществляет взаимодействие с сервером через сокеты, а фаззер этого делать не может. Следовательно, нужно научиться<strong>передавать данные к curl от фаззера</strong>. Чтобы это выполнить, подменим функцию<strong>connect</strong>. Сделать это надо таким образом, чтобы вместо создания нового соединения результат connect возвращал нам дескриптор<strong>stdin</strong>. Используем<strong>LD_PRELOAD</strong>своей динамической библиотеки, которую, писать не обязательно - можно взять готовую (<a>preeny</a>).</p>
15
<p>Хорошо известно, что curl осуществляет взаимодействие с сервером через сокеты, а фаззер этого делать не может. Следовательно, нужно научиться<strong>передавать данные к curl от фаззера</strong>. Чтобы это выполнить, подменим функцию<strong>connect</strong>. Сделать это надо таким образом, чтобы вместо создания нового соединения результат connect возвращал нам дескриптор<strong>stdin</strong>. Используем<strong>LD_PRELOAD</strong>своей динамической библиотеки, которую, писать не обязательно - можно взять готовую (<a>preeny</a>).</p>
16
<p>Теперь собираем curl и preeny:</p>
16
<p>Теперь собираем curl и preeny:</p>
17
$ git clone https://github.com/zardus/preeny $ cd preeny && make ... $ cd curl $ mkdir build $ export CMAKE_C_FLAGS="-g -fsanitize=address" $ cmake -DCMAKE_C_COMPILER=/path/to/afl-clang-fast -DCMAKE_CXX_COMPILER=/path/to/afl-clang-fast -DCMAKE_BUILD_TYPE=release ../ $ make<p>Собранные бинари кладём в одну директорию, а рядом создаём директорию<strong>inputs</strong>. В ней - файл с HTTP-ответом сервера (если хотите увеличить покрытие, лучше создать их несколько).</p>
17
$ git clone https://github.com/zardus/preeny $ cd preeny && make ... $ cd curl $ mkdir build $ export CMAKE_C_FLAGS="-g -fsanitize=address" $ cmake -DCMAKE_C_COMPILER=/path/to/afl-clang-fast -DCMAKE_CXX_COMPILER=/path/to/afl-clang-fast -DCMAKE_BUILD_TYPE=release ../ $ make<p>Собранные бинари кладём в одну директорию, а рядом создаём директорию<strong>inputs</strong>. В ней - файл с HTTP-ответом сервера (если хотите увеличить покрытие, лучше создать их несколько).</p>
18
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1 Connection: close Set-Cookie: xx=xxx; path=xx; domain=xxx.com; httponly; secure; 1<p>Теперь возвращаемся в директорию с приложением, после чего запускаем AFL:</p>
18
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1 Connection: close Set-Cookie: xx=xxx; path=xx; domain=xxx.com; httponly; secure; 1<p>Теперь возвращаемся в директорию с приложением, после чего запускаем AFL:</p>
19
$ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" /path/to/afl/afl-fuzz -m none -i inputs -o out ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null<p>В нашем случае<strong>LD_PRELOAD</strong>задаёт путь до SO, подменяющего функцию connect.</p>
19
$ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" /path/to/afl/afl-fuzz -m none -i inputs -o out ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null<p>В нашем случае<strong>LD_PRELOAD</strong>задаёт путь до SO, подменяющего функцию connect.</p>
20
<p>Смотрим параметры<strong>curl</strong>: • 127.0.0.1 - URL-соединение. К нему будем эмулировать, поэтому важно указать не домен, а IP-адрес (мы подменили функции, резолв не пройдёт); •<strong>max-time</strong>- задаёт наибольшее время выполнения curl, которое равно одной секунде (меньше ставить мы не можем). Задаём, так как ни AFL, ни curl не закрывают дескриптор; • --cookie-jar - этот параметр использовать очень важно, ведь curl вызовет уязвимую функцию только лишь в случае использования cookies.</p>
20
<p>Смотрим параметры<strong>curl</strong>: • 127.0.0.1 - URL-соединение. К нему будем эмулировать, поэтому важно указать не домен, а IP-адрес (мы подменили функции, резолв не пройдёт); •<strong>max-time</strong>- задаёт наибольшее время выполнения curl, которое равно одной секунде (меньше ставить мы не можем). Задаём, так как ни AFL, ни curl не закрывают дескриптор; • --cookie-jar - этот параметр использовать очень важно, ведь curl вызовет уязвимую функцию только лишь в случае использования cookies.</p>
21
<p>В результате через пару минут AFL обнаружит первые тестовые данные, которые становятся причиной падения приложения.</p>
21
<p>В результате через пару минут AFL обнаружит первые тестовые данные, которые становятся причиной падения приложения.</p>
22
<p>Что же, теперь мы можем окончательно убедиться, что наше приложение действительно падает на этих входных данных:</p>
22
<p>Что же, теперь мы можем окончательно убедиться, что наше приложение действительно падает на этих входных данных:</p>
23
$ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null < out/crashes/id:000010,sig:06,src:000000,op:havoc,rep:2<p>Итак, мы ознакомились, как используют фаззер<strong>American Fuzzy Lop</strong>для тестирования приложений. Однако применяя технологию фаззинга, нужно помнить, что любой, пусть даже наиболее эффективный и быстрый фаззер с хорошим покрытием<strong>не заменяет анализатор кода, а только дополняет его</strong>.</p>
23
$ LD_PRELOAD="/path/to/preeny/x86_64-linux-gnu/desock.so" ./curl http://127.0.0.1/ --max-time 1 --cookie-jar /dev/null < out/crashes/id:000010,sig:06,src:000000,op:havoc,rep:2<p>Итак, мы ознакомились, как используют фаззер<strong>American Fuzzy Lop</strong>для тестирования приложений. Однако применяя технологию фаззинга, нужно помнить, что любой, пусть даже наиболее эффективный и быстрый фаззер с хорошим покрытием<strong>не заменяет анализатор кода, а только дополняет его</strong>.</p>
24
24