$ mkdir in_dir
$ echo 'hello' >in_dir/hello
$ ./afl-fuzz -i in_dir -o out_dir ./jpeg-9a/djpeg
В сущности, я создал текстовый файл только со словом "hello" и попросил фаззер выдавать поток в программу, которая ожидает на входе изображение JPEG (djpeg это простая утилита, которая идёт вместе с распространённой графической библиотекой IJG jpeg; libjpeg-turbo тоже должна подойти). Конечно, мои входные данные не похожи на валидное изображение, так что утилита быстро отвергает их:
$ ./djpeg '../out_dir/queue/id:000000,orig:hello'
Not a JPEG file: starts with 0x68 0x65
Обычно такой фаззинг был бы совершенно бессмысленным: по существу, нет никаких шансов, что традиционный форматонезависимый фаззер способен когда-нибудь превратить слово "hello" в действительное изображение JPEG. Вероятность того, что десятки случайных настроек выстроятся друг за другом, астрономически мала.
К счастью, afl-fuzz может использовать в своих целях простой инструментарий на уровне ассемблера — и в течение миллисекунды или около того он замечает, что хотя установка первого байта в значение 0xff не изменяет внешне наблюдаемый вывод, можно запустить немного иной внутренний путь в тестовом приложении. Обладая этой информацией, он решает использовать этот тестовый случай как основу для будущих раундов фаззинга:
$ ./djpeg '../out_dir/queue/id:000001,src:000000,op:int8,pos:0,val:-1,+cov'
Not a JPEG file: starts with 0xff 0x65
Обрабатывая затем тестовый случай второго поколения, фаззер почти сразу замечает, что установка второго байта в значение 0xd8 делает нечто даже более интересное:
$ ./djpeg '../out_dir/queue/id:000004,src:000001,op:havoc,rep:16,+cov'
Premature end of JPEG file
JPEG datastream contains no image
Здесь фаззер умудрился синтезировать валидный заголовок файла — и действительно понял его значимость. Используя такую выдачу как основу для следующего раунда фаззинга, он быстро начинает погружаться всё глубже и глубже в суть. Через несколько сотен поколений и несколько сотен миллионов вызовов execve() он находит всё больше и больше управляющих структур, которые необходимы для валидного файла JPEG — SOF'ы, таблицы Хаффмана, таблицы квантования, маркеры SOS и т. д.:
$ ./djpeg '../out_dir/queue/id:000008,src:000004,op:havoc,rep:2,+cov'
Invalid JPEG file structure: two SOI markers
...
$ ./djpeg '../out_dir/queue/id:001005,src:000262+000979,op:splice,rep:2'
Quantization table 0x0e was not defined
...
$ ./djpeg '../out_dir/queue/id:001282,src:001005+001270,op:splice,rep:2,+cov' >.tmp; ls -l .tmp
-rw-r--r-- 1 lcamtuf lcamtuf 7069 Nov 7 09:29 .tmp
Первая картинка, полученная через шесть часов фаззинга на 8-ядерной системе, выглядит весьма скромно: это чистый серый прямоугольник высотой 3 пикселя и шириной 748 пикселей. Но с момента её открытия фаззер начинает использовать эту картинку как основу — и быстро производит широкий спектр более интересных картинок для каждого нового пути исполнения:
Конечно, синтез полного изображения из ниоткуда — это исключительный случай, и вряд ли полезный на практике. Но в более прозаичных целях фаззеры подходят для нагрузочного тестирования любой функции в целевой программе. Оборудованный оснасткой, эволюционный фаззинг с использованием менее известных функций (например, JPEG с прогрессивным или арифметическим кодированием, чёрно-белые JPEG) можно использовать как альтернативу гигантскому высококачественному корпусу разнообразных тестовых случаев, с которых начинается фаззинг.
Замечательной особенностью случая с libjpeg является то, что он работает без какой-либо специальной подготовки: в строке "hello" нет ничего особенного, фаззер ничего не знает о парсинге изображений, он не предназначен и не настроен для работы конкретно с этой библиотекой. Нет даже каких-то ключей командной строки, которые следует активировать. Вы можете натравить afl-fuzz на многие другие виды парсеров с такими же результатами: с bash он будет писать валидные скрипты; с giflib производить GIF'ы; с fileutils производить файлы ELF и выставлять флаги, создавать бинарники для Atari 68xxx, загрузочные секторы x86 и UTF-8 с BOM. Почти во всех случаях влияние оснастки на производительность тоже минимально.
Конечно, не всё так гладко. По сути своей afl-fuzz остаётся программой для брутфорса. Это делает её простой, быстрой и надёжной, но также означает, что определённые типы атомизированных проверок в большом пространстве поиска могут стать непреодолимым препятствием для фаззера. Вот хороший пример:
if (strcmp(header.magic_password, "h4ck3d by p1gZ")) goto terminate_now;
На практике это означает, что afl-fuzz вряд ли сумеет «изобрести» с нуля файлы PNG или нетривиальные документы HTML — и ему нужна начальная точка получше, чем просто "hello". Чтобы неизменно работать с конструкциями кода как в вышеприведённом примере, универсальному фаззеру нужно понимать работу целевого бинарника совершенно на ином уровне. Учёные добились некоторого прогресса в этом отношении, но нам придётся ещё годы ждать появления фреймворков, которые способны быстро, просто и надёжно работать с разнообразными и сложными кодовыми базами.
Несколько человек спросили меня о символическом исполнении и других вещах, под влиянием которых создан afl-fuzz; я собрал некоторые заметки в этом документе.
Комментарии (21)
Z0K
15.05.2017 23:23>>>с bash он будет писать валидные скрипты
А если он случайно напишет вечный цикл вроде
while `true`; do true; done
?
lumag
15.05.2017 23:47+2А чем принципиально отличается JPEG от PNG, что первые фаззер нафаззил, а вторые — не сможет?
encyclopedist
16.05.2017 02:06+3В том, что PNG защищен с помощью CRC. Так то фаззер дальше проверки CRC не продвинется за реалистичное время.
raacer
16.05.2017 16:32Автор, от Вас что, убудет, если Вы в начале статьи вставите цитату из вики и ссылку?
raacer
16.05.2017 16:36Или хотя бы так:
"Вот интересная демонстрация возможностей afl — утилиты для тестирования программ на случайном наборе данных".
Ну посудите сами: Вы привлекаете в статью всех, кто интересуется генерацией изображений, и сразу же пишите о какой-то afl, как буд-то это что-то столь же широко известное, как vim или bash.
ilya_pu
17.05.2017 11:26Все мы так накинулись на «автора», а ведь на самом деле эта статья — перевод (конечно, если верить тегу рядом с названием). Соответственно, сама статья в оригинале вполне могла быть частью какого-то более крупного произведения (книги или цикла статей), а следовательно — из других его частей могло быть понятно, о какой проблеме идёт речь. Все вопросы (и претензии), которые мы тут поназадавали (предъявили) переводчику, могли быть закрыты им небольшим введением от своего лица, где можно было указать, кому эта статья может быть полезна, о решении какой проблемы пойдёт речь… и… пока ещё не поздно это сделать, обновив статью…
encyclopedist
17.05.2017 13:27Ссылка на источник есть под статьёй. Автор статьи — это автор AFL-FUZZ, и опубликована она в его блоге, посвященном AFL. Так что там, действительно, контекст очевиден.
softi
Что такое фаззер? Почему бы не дать пояснение в начале статьи? Меня всегда учили в вузе: пиши пояснительную записку так, чтобы было все понятно случайному человеку, когда он ее откроет.
khim
Так вот откуда берутся статьи, начинающися примерно со слов «для тех, кто не знает как складывать числа столбиком мы сейчас кратко опишем алгоритм», а потом, после описания оного алгоритма, без какого-либо перехода оперируют кучей терминов, касающихся нейронных сетей, для изучения которых нужно прочитать не одну толстую книжку!
Смысл рассказывать про то, что такое фаззер в начале статьи, если дальше идёт речь о тонкостях работы разных фаззоров, в том числе, конкретно afl? Википедия про это подробно рассказывает, при желании оттуда можно вытащить описаний всех терминов и, тем самым, увеличить заметку по обьёму примерно так раз в пять, но… зачем?
Если честно мне гораздо более полезным кажется строго противополжный: если человек может прочитать первый абзац и понять его — то ему имеет смысл читать и всё остальное.
ilya_pu
Смысл в самом начале статьи рассказывать про то, что такое фаззер, есть — хотя бы для того, чтобы те, кому эта тема на самом деле не очень-то интересна, могли не читать статью с многообещающим названием. Для меня «создание jpeg из ниоткуда» и иллюстрация к статье ассоциируются прежде всего с нейросетями и их алгоритмами обработки фотографий (а эта тема мне интересна). О том, что речь в статье о методах тестирования, понимаешь только под конец. Пришлось прочитать всю статью целиком, чтобы понять, что мне эта информация совершенно не нужна… Кто ж знал, что нейросети можно применить для генерации по сути дела безинформативного шума, пусть и в формате картинки?
Не поверите, но первого абзаца (как говорят журналисты, лида), отвечающего на вопросы «кто/что», «где», «когда» и «зачем», здесь нет. А есть вот это:Представьте себе ситуацию, что вы не знаете, что это за программа — afl, что она делает. Что вы узнали из этого абзаца? Только то, что неизвестная вам программа работает, и это кого-то реально удивило...
khim
Зачем всю статью-то после этого читать?
Я понимаю почему всякие вступления и рассусоливания делались в «бумажные» времена. Но сейчас-то зачем?
ilya_pu
Во все времена введение (к статье ли, к книге ли — к чему угодно!) писалось из уважения к читателю. Чтобы он не тратил время на то, что ему не нужно — и в то же время, если он заинтересован в теме, чтобы мог понять, что именно он может почерпнуть из данного текста для себя. Не пишете введение = не уважаете читателя.
Зачем такие сложности? Почему я — обычный читатель — должен проводить целое расследование (туда сходи, сюда сходи) только для того, чтобы понять, о чём статья и нужно ли мне её читать?symbix
Мы в интернете, и у нас есть куда более мощный инструмент, чем записки — ссылки.
В самом начале стоит ссылка на afl, там достаточно пройти по ссылке на актуальный сайт проекта, где первая же ссылка "fuzzer" — на Википедию.
Krypt
Простите, а зачем тогда нужна эта статья, если вместо неё я могу сразу перейти на википедию и документацию?
khim
Если сможете найти место в википедии и документации, где обсуждается генерация JPEG'ов с помощью AFL — то да, ссылки будет достаточно.
Я — не нашёл.
Krypt
Я думаю очевидно, что я до сих пор не имею понятия, что такое afl и искать не собираюсь. Краткая справка в начале статьи даёт возможность понять, нужна ли мне эта штука, я могу прочитать это чисто из любопытства или я могу спокойно пройти мимо. Отсутствие оставляет 2 варианта: пройти мимо и высказать своё «фи».
А в текущем варианте получается «я открыл для себя фингербоксинг 5 лет и теперь решил применить его для покраски потолка»
ilya_pu
raacer
Вы думаете, все программисты знают, что такое фаззинг?
ilya_pu
Для нормального понимания ещё вот что нужно учитывать: изображения могут генерироваться не только «из ниоткуда», но и (удивительно, но это происходит гораздо чаще!) из наборов данных — например, таблиц. Более того — всем нам знакомо выражение «щи из топора» — вроде бы как тоже «из ничего», но мы же понимаем, что смысл этого выражения достаточно глубокий: даже при видимом отсутствии необходимых ингредиентов (в случае с графиками — данных) всегда можно найти какую-то информацию (на худой конец — сгенерировать случайным образом или преобразовать другой набор данных), которая может быть применима к нашей задаче. А задач таких тоже — множество. Начиная от учебных «сейчас я покажу вам на условных данных, как строится вот такой хитрый график» (в случае конкретно этого перевода — «сейчас я покажу вам инструмент, позволяющий генерировать наборы данных для такой вот специфической задачи») и до практических «восстановим скрытые зависимости и пропущенные данные». Ни из названия статьи, ни из введения (по сути, оно отсутствует) в неё — неясно, о чём вообще идёт речь. Какая-то угадайка («угадай, о чём статья») получается… Только вот беда — о том, что тебе её не нужно было читать, ты поймёшь только тогда, когда доберёшься до комментариев…