Фаззинг программ с помощью WinAFL

Фаззинг WinAFL

В этой статье мы будем учиться фаззингу и поможет нам в этом программа WinAFL. В сети можно найти документацию по работе с WinAFL, которая мало поможет новичкам. Куда полезнее будет данная статья, в которой покажу, как скачать, установить и использовать WinAFL.

Еще по теме: Отладка программ с помощью WinDbg

Что такое фаззинг

На этот вопрос лучше ответит статья «Что такое Фаззинг».

Фаззинг с помощью WinAFL

Инструмент WinAFL — это форк популярного фаз­зера AFL, пред­назна­чен­ный для фаз­зинга прог­рамм с зак­рытым исходным кодом под ОС Windows.

Так же как и AFL, WinAFL собира­ет информа­цию о пок­рытии кода. Делать это он может тре­мя спо­соба­ми:

  • ди­нами­чес­кая инс­тру­мен­тация с помощью DynamoRIO;
  • ста­тичес­кая инс­тру­мен­тация с помощью Syzygy;
  • трей­синг с помощью IntelPT.

Мы оста­новим­ся на клас­сичес­ком пер­вом вари­анте как самом прос­том и понят­ном.

Фаз­зит WinAFL сле­дующим обра­зом:

  • В качес­тве одно­го из аргу­мен­тов вы дол­жны передать сме­щение так называ­емой целевой фун­кции внут­ри бинарника.
  • WinAFL инжектит­ся в приложение и ожидает, пока не нач­нет выпол­нятся целевая фун­кция.
  • WinAFL начина­ет записы­вать информа­цию о пок­рытии кода.
  • При выходе из целевой фун­кции WinAFL при­оста­нав­лива­ет работу приложения, под­меня­ет вход­ной файл, переза­писы­вает RIP/EIP адре­сом начала фун­кции и про­дол­жает работу.
  • Ког­да чис­ло таких ите­раций дос­тигнет какого-то мак­сималь­ного зна­чения (его вы опре­деля­ете сами), WinAFL пол­ностью переза­пус­кает приложение.

Данный под­ход поз­воля­ет не тра­тить лишнего вре­мени на запуск и ини­циали­зацию приложения и ускорить фаз­зинг.

Требования к функции

Из логики работы WinAFL вытека­ют прос­тые тре­бова­ния к целевой фун­кции для фаз­зинга. Целевая фун­кция дол­жна:

  • От­кры­вать вход­ной файл.
  • Пар­сить файл и завер­шать свою работу мак­сималь­но чис­то: зак­рывать файл и все откры­тые хен­длы, не менять гло­баль­ные перемен­ные и т.д. В реаль­нос­ти не всег­да получа­ется най­ти иде­аль­ную фун­кцию пар­синга, но об этом будем говорить поз­же.
  • Вы­пол­нение дол­жно доходить до воз­вра­та из фун­кции, выб­ранной для фаз­зинга.

Компиляция WinAFL

В ре­пози­тории WinAFL на GitHub уже лежат ском­пилиро­ван­ные бинарники но в моем случае они попрос­ту не работали, поэто­му для того, что­бы не приш­лось решать проблемы, лучше ском­пилировать WinAFL вмес­те с самой пос­ледней вер­сией DynamoRIO.

К счастью, WinAFL ком­пилиру­ются довольно просто на любом компе.

Шаг 1: Ска­чайте и уста­новите Visual Studio 2019 Community Edition (при уста­нов­ке выберите пункт «Раз­работ­ка клас­сичес­ких при­ложе­ний на C++».

Шаг 2: Во время  установки Visual Studio, ска­чайте пос­ледний релиз DynamoRIO.

Шаг 3: Загрузите исходни­ки WinAFL из ре­пози­тория.

Шаг 4: Пос­ле уста­нов­ки Visual Studio в меню «Пуск» у вас появят­ся несколько ярлыков для откры­тия коман­дной стро­ки Visual Studio: x86 Native Tools Command Prompt for VS 2019 и x64 Native Tools Command Prompt for VS 2019. Выбирайте в соот­ветс­твии с бит­ностью прог­раммы, которую вы собираетесь фаз­зить.

Шаг 5: В коман­дной стро­ке Visual Studio зайдите в пап­ку с исходни­ками WinAFL.

Для ком­пиляции 32-бит­ной вер­сии выпол­ните эти коман­ды:

Для ком­пиляции 64-бит­ной вер­сии — эти:

В моем слу­чае эти коман­ды выг­лядят следующим образом:

Пос­ле ком­пиляции в пап­ке \build<32/64>\bin\Release будут находиться рабочие бинарники WinAFL. Ско­пируйте их и каталог с DynamoRIO на вир­туал­ку, которую будете исполь­зовать для фаз­зинга.

Поиск подходящей цели для фаззинга

Изначально AFL был создан для фаз­зинга приложений, которые пар­сят фай­лы. Хотя WinAFL мож­но при­менять для приложений, которые используют дру­гие спо­собы вво­да, путь наимень­шего соп­ротив­ления — это выбор цели, исполь­зующей имен­но фай­лы.

Ес­ли же вам, как и мне, нра­вит­ся допол­нитель­ный чел­лендж, вы можете пофаз­зить сетевые прог­раммы. В таком слу­чае вам при­дет­ся исполь­зовать custom_net_fuzzer.dll из сос­тава WinAFL или писать свою собс­твен­ную обер­тку.

К сожале­нию, custom_net_fuzzer будет работать медленее, потому что он отправ­ляет сетевые зап­росы сво­ей цели, а на их обра­бот­ку будет уходить допол­нитель­ное вре­мя.

Но фаз­зинг сетевых программ выходит за рам­ки этой статьи.

Та­ким обра­зом:

  • иде­аль­ная цель работа­ет с фай­лами;
  • при­нима­ет путь к фай­лу как аргу­мент коман­дной стро­ки;
  • мо­дуль, содер­жащий фун­кции, который вы хотите пофаз­зить, дол­жен быть ском­пилиро­ван не ста­тичес­ки. В про­тив­ном слу­чае WinAFL будет инс­тру­мен­тировать мно­гочис­ленные биб­лиотеч­ные фун­кции. Это не при­несет допол­нитель­ного резуль­тата, но замедлит фаз­зинг.

Уди­витель­но, но раз­работ­чики не дума­ют о WinAFL, ког­да пишут свои приложения. Поэто­му если ваша цель не соот­ветс­тву­ет этим кри­тери­ям, то ее все рав­но мож­но при желании адап­тировать к WinAFL.

Поиск функции для фаззинга внутри программы

Мы погово­рили об иде­аль­ной цели, но реаль­ная может быть от иде­ала далека, поэто­му для при­мера я взял старую софтину, соб­ранную ста­тичес­ки, а ее основной исполня­емый файл занима­ет 8 Мб.

У нее хороший функционал, так что, думаю, ее будет инте­рес­но пофаз­зить.

Наша цель при­нима­ет на вход фай­лы, поэто­му начнем заг­рузки бинарника в IDA Pro, — это най­дем фун­кцию CreateFileA в импортах и пос­мотрим перек­рес­тные ссыл­ки на нее.

Поиск функции для фаззинга внутри программы

Мы видим, что она исполь­зует­ся в четырех фун­кци­ях. Вмес­то того что­бы ревер­сить их всех в ста­тике, пос­мотрим в отладчи­ке, какая имен­но фун­кция вызыва­ется для пар­синга фай­ла.

От­кро­ем нашу прог­рамма в отладчи­ке (я как правило исполь­зую x64dbg) и добавим аргу­мент к коман­дной стро­ке — тес­товый файл. Отку­да он взялся? Прос­то открыл прог­раммы, выс­тавил мак­сималь­ное чис­ло опций для докумен­та и сох­ранил его на диск.

Поиск функции для фаззинга внутри программы

После этого на вклад­ке Symbols выбираем биб­лиоте­ку kernelbase.dll и с­тавим точ­ки оста­нова на экспор­ты фун­кций CreateFileA и CreateFileW.

Использование WinAFL

Один интересный момент. «Офи­циаль­но» фун­кции CreateFile* пре­дос­тавля­ются биб­лиоте­кой kernel32.dll. Но если хорошенько посмотреть, то это биб­лиоте­ка содер­жит толь­ко jmp на соот­ветс­тву­ющие фун­кции kernelbase.dll.

Как пользоваться WinAFL

Я люблю ста­вить брей­ки имен­но на экспор­ты в соот­ветс­тву­ющей биб­лиоте­ке. Это страховка от слу­чая, ког­да мы ошиб­лись и эти фун­кции вызыва­ет не основной исполня­емый модуль (.exe), а, нап­ример, какие‑то из биб­лиотек наших целей. Так­же это полез­но, если нашему приложению захочется выз­вать фун­кцию с помощью GetProcAddress.

Пос­ле уста­нов­ки брей­кпой­нтов про­дол­жим выпол­нение приложения и уви­дим, как она совер­шает пер­вый вызов к CreateFileA. Но если обратить внимание на аргу­мен­ты, то понятно, что наша цель хочет открыть какой‑то из сво­их слу­жеб­ных фай­лов, не наш тес­товый файл.

Как пользоваться WinAFL

Про­дол­жаем выпол­нение приложения, пока не уви­дим в спис­ке аргу­мен­тов путь к нашему тес­товому фай­лу.

Использование WinAFL

Пе­реходим на вклад­ку Call Stack и видин, что CreateFileA вызыва­ется не из нашей прог­раммы, а из фун­кции CFile::Open биб­лиоте­ки mfc42.

WinAFL инструкция

Так как мы толь­ко ищем фун­кцию для фаз­зинга, нам следует пом­нить, что она дол­жна при­нимать путь к вход­ному фай­лу, делать что‑то с фай­лом и завер­шать свою работу нас­толь­ко чис­то, нас­коль­ко это воз­можно. Поэто­му мы будем под­нимать­ся по сте­ку вызовов, пока не най­дем нужную фун­кцию.

Ско­пиру­ем адрес воз­вра­та из CFile::Open (125ACBB0), переходим по нему в IDA и пос­мотрим на фун­кцию. Мы тут же уви­дим, что данная фун­кция при­нима­ет два аргу­мен­та, которые потом исполь­зуют­ся как аргу­мен­ты к двум вызовам CFile::Open.

WinAFL инструкция

Су­дя по про­тоти­пам CFile::Open из докумен­тации MSDN, наши перемен­ные a1 и a2 — это пути к фай­лам. Обра­тите вни­мание, что в IDA путь к фай­лу переда­ется фун­кции CFile::Open в качес­тве вто­рого аргу­мен­та, так как исполь­зует­ся thiscall.

Данная фун­кция уже выг­лядит очень инте­рес­но, и сто­ит пос­тарать­ся рас­смот­реть ее под­робнее. Для это­го поставим брей­ки на начало и конец фун­кции, что­бы изу­чить ее аргу­мен­ты и понять, что с ними про­исхо­дит к кон­цу фун­кции.

Сде­лав это, переза­пускаем приложение и ви­дим, что два аргу­мен­та — это пути к нашему тес­товому фай­лу и вре­мен­ному фай­лу.

Фаззинг с помощью WinAFL

Настало вре­мя пос­мотреть на содер­жимое данных фай­лов. Судя по содер­жимому нашего тес­тового фай­ла, он сжат, зашиф­рован или каким‑то обра­зом закоди­рован.

AFL фаззинг

Вре­мен­ный же файл прос­то пуст.

Meтод фаззинга

Вы­пол­няем фун­кцию до кон­ца и ви­дим, что наш тес­товый файл теперь рас­шифро­ван. А вот вре­мен­ный файл пос­ле выхода из фун­кции по‑преж­нему пуст.

Теперь уберем точ­ки оста­нова с этой фун­кции и про­дол­жим отсле­живать вызовы CreateFileA. Сле­дующее обра­щение к CreateFileA дает нам такой стек вызовов.

Фаззинг программы

Фун­кция, которая вызыва­ет CFile::Open, ока­зыва­ется очень похожей на пре­дыду­щую. Таким же образом с­тавим точ­ки оста­нова в ее начале и кон­це и с­мотрим, что произойдет.

фаззинг бинарного файла

Спи­сок аргу­мен­тов этой фун­кции напоми­нает то, что мы уже видели.

Пассивный фаззинг

Сра­баты­вает брейк в кон­це этой фун­кции, и во вре­мен­ном фай­ле мы можем видеть рас­шифро­ван­ное, а ско­рее даже разар­хивиро­ван­ное содер­жимое тес­тового фай­ла.

Фаззинг программа

Та­ким обра­зом, данная фун­кция разар­хивиру­ет файл. Поэк­спе­римен­тировав с приложением, я выяс­нил, что оно при­нима­ет на вход как сжа­тые, так и нес­жатые фай­лы. Это для нас хорошо — с помощь фаз­зинга нес­жатых фай­лов мы смо­жем добить­ся большего пок­рытия кода и, как следс­твие, доб­рать­ся до более инте­рес­ных фич.

Пос­мотрим, смо­жем ли мы най­ти фун­кцию, которая выпол­няет какие‑то дей­ствия с уже рас­шифро­ван­ным фай­лом.

Один из под­ходов к выбору фун­кции для фаз­зинга — это поиск фун­кции, которая одной из пер­вых начина­ет вза­имо­дей­ство­вать с вход­ным фай­лом. Дви­гаясь вверх по сте­ку вызовов, нахдоим самую пер­вую фун­кцию, которая при­нима­ет на вход путь к тес­товому фай­лу.

WinAFL

Фун­кция для фаз­зинга дол­жна выпол­нять­ся до кон­ца, соответственно ста­вим точ­ку оста­нова на конец фун­кции, что­бы быть уве­рен­ными, что эти тре­бова­ния выпол­нятся, и нажимаем F9 в отладчи­ке.

WinAFL

Так­же следует  убе­диться, что данная фун­кция пос­ле воз­вра­та зак­рыва­ет все откры­тые фай­лы. Для это­го про­веряем спи­сок хен­длов про­цес­са в Process Explorer — нашего тес­тового фай­ла там нет.

Фаззинг

Ви­дим, что наша фун­кция соот­ветс­тву­ет тре­бова­ниям WinAFL. Поп­робу­ем начать фаз­зить!

Аргументы WinAFL

Мои аргу­мен­ты для WinAFL выг­лядят при­мер­но так. Давайте раз­берем по поряд­ку самые важ­ные из них.

Все аргу­мен­ты делят­ся на три груп­пы, которые отде­ляют­ся друг от дру­га дву­мя про­чер­ками.

Пер­вая груп­па — аргу­мен­ты WinAFL:

  • D — путь к бинарникам DynamoRIO;
  • t — мак­сималь­ный тайм‑аут для одной ите­рации фаз­зинга. Если целевая фун­кция не выпол­нится до кон­ца за это вре­мя, WinAFL под­счи­тает, что прог­рамма завис­ла, и переза­пус­тит ее;
  • x — путь к сло­варю;
  • f — с помощью это­го парамет­ра мож­но передать имя и рас­ширение вход­ного фай­ла прог­раммы. Полез­но, ког­да прог­рамма реша­ет, как будет пар­сить файл, в зависи­мос­ти от его рас­ширения.

Вто­рая груп­па — аргу­мен­ты для биб­лиоте­ки winafl.dll, которая инс­тру­мен­тиру­ет целевой про­цесс:

  • coverage_module — модуль для сня­тия пок­рытия. Может быть нес­коль­ко;
  • target_module — модуль с фун­кци­ей для фаз­зинга. Может быть толь­ко один;
  • target_offset — вир­туаль­ное сме­щение фун­кции от базово­го адре­са модуля;
  • fuzz_iterations — количес­тво ите­раций фаз­зинга меж­ду переза­пус­ками прог­раммы. Чем мень­ше это зна­чение, тем чаще WinAFL будет переза­пус­кать всю прог­рамму целиком, что будет занимать допол­нитель­ное вре­мя. Одна­ко если дол­го фаз­зить прог­рамму без переза­пус­ка, могут накопить­ся нежела­тель­ные побоч­ные эффекты;
  • call_convention — сог­лашение о вызове. Под­держи­вают­ся sdtcall, cdecl, thiscall;
  • nargs — количес­тво аргу­мен­тов фун­кции. This тоже счи­тает­ся за аргу­мент.

Третья груп­па — путь к самой прог­рамме. WinAFL изме­нит @@ на пол­ный путь к вход­ному фай­лу.

Добавление словаря

На­ша цель прос­тая — уве­личить количес­тво путей, находи­мых за секун­ду. Для это­го вы можете рас­парал­лелить работу фаз­зера, поиг­рать с чис­лом fuzz_iterations или поп­робовать фаз­зить умнее. И в этом вам поможет сло­варь.

WinAFL уме­ет вос­ста­нав­ливать син­таксис фор­мата дан­ных цели (нап­ример, AFL смог самос­тоятель­но соз­дать валид­ные JPEG-фай­лы без какой‑либо допол­нитель­ной инфы). Обна­ружен­ные син­такси­чес­кие еди­ницы он исполь­зует для генера­ции новых кей­сов для фаз­зинга.

Это занима­ет зна­читель­ное вре­мя, и здесь вы можете ему силь­но помочь, ведь кто, как не вы, луч­ше все­го зна­ет фор­мат дан­ных вашей прог­раммы? Для это­го нуж­но сос­тавить сло­варь в фор­мате <имя переменной>=»значение». Нап­ример, вот начало моего сло­варя:

Итак, мы наш­ли фун­кцию для фаз­зинга, попут­но рас­шифро­вав вход­ной файл прог­раммы, соз­дали сло­варь, подоб­рали аргу­мен­ты и можем наконец‑то начать фаз­зить!

Использование WinAFL

И пер­вые же минуты фаз­зинга при­носят пер­вые кра­ши! Но не всег­да все про­исхо­дит так глад­ко. Ниже я при­вел нес­коль­ко осо­бен­ностей WinAFL, которые могут вам помочь (или помешать) отла­дить про­цесс фаз­зинга.

Особенности WinAFL

А теперь разберем особенности программы.

Побочные эффекты

Вна­чале я писал, что фун­кция для фаз­зинга не дол­жна иметь побоч­ных эффектов. Но это в иде­але. Час­то быва­ет так, что раз­работ­чики забыва­ют добавить в свои прог­раммы такие кра­сивые фун­кции, и при­ходит­ся иметь дело с тем, что есть.

Так как некото­рые эффекты накап­лива­ются, воз­можно, вам удас­тся успешно пофаз­зить, умень­шив чис­ло fuzz_iterations — с ней WinAFL будет переза­пус­кать вашу прог­рамму чаще. Это негатив­но пов­лияет на ско­рость, но зато умень­шит количес­тво побоч­ных эффектов.

Дебаг-режим

Ес­ли WinAFL отка­зыва­ется работать, поп­робуйте запус­тить его в дебаг‑режиме. Для это­го добавьте параметр -debug к аргу­мен­там биб­лиоте­ки инс­тру­мен­тации. Пос­ле это­го в текущем катало­ге у вас появит­ся тек­сто­вый лог.

При нор­маль­ной работе в нем дол­жно быть оди­нако­вое количес­тво стро­чек In pre_fuzz_handler и In post_fuzz_handler. Так­же дол­жна при­сутс­тво­вать фра­за Everything appears to be running normally.

Дебаг WinAFL

Не забудьте вык­лючить дебаг‑режим! С ним WinAFL отка­жет­ся фаз­зить, даже если все работа­ет, ссы­лаясь на то, что целевая прог­рамма вылете­ла по тайм‑ауту. Не верьте ему и вык­лючайте отладку.

Эмуляция работы WinAFL

Иног­да при фаз­зинге прог­рамму так перемы­кает, что она кра­шит­ся даже на под­готови­тель­ном эта­пе работы WinAFL, пос­ле чего он разум­но отка­зыва­ется дей­ство­вать даль­ше. Что­бы хоть как‑то в этом разоб­рать­ся, вы можете вруч­ную эму­лиро­вать работу фаз­зера. Для это­го ставьте точ­ку оста­нова на начало и конец фун­кции для фаз­зинга. Ког­да выпол­нение дос­тигнет кон­ца фун­кции, правьте аргу­мен­ты, рав­няйте стек, меняйте RIP/EIP на начало фун­кции — и так, пока что‑то не сло­мает­ся.

Стабильность

Stability — очень важ­ный параметр. Он показы­вает, нас­коль­ко кар­та пок­рытия кода меня­ется от ите­рации к ите­рации. 100% — на каж­дой ите­рации прог­рамма ведет себя абсо­лют­но оди­нако­во. 0% — каж­дая ите­рация пол­ностью отли­чает­ся от пре­дыду­щей. Разуме­ется, нам нуж­но зна­чение где‑то посере­дине.

Автор AFL решил, что ори­енти­ровать­ся надо где‑то на 85%. В нашем при­мере ста­биль­ность дер­жится на уров­не 9,5%. Полагаю, это может быть свя­зано в том чис­ле с тем, что прог­рамма соб­рана ста­тичес­ки и на ста­биль­ность негатив­но вли­яют какие‑то из исполь­зуемых биб­лиотеч­ных фун­кций. Воз­можно, и муль­типоточ­ность тоже пов­лияла на это.

Набор входных файлов

Чем боль­ше пок­рытие кода, тем выше шанс най­ти баг. А мак­сималь­ного пок­рытия кода мож­но добить­ся, соз­дав хороший набор вход­ных фай­лов. Если вы задал­ись целью пофаз­зить пар­серы фай­лов каких‑то хорошо извес­тных фор­матов, то, как говорит­ся, гугл в помощь: некото­рым иссле­дова­телям уда­валось соб­рать вну­шитель­ный набор фай­лов имен­но с помощью пар­синга выдачи Google.

Такой набор потом мож­но миними­зиро­вать с помощью скрип­та [winafl-cmin.py](http://winafl-cmin.py) из того же репози­тория WinAFL. А если вы, как и я, пред­почита­ете пар­силки фай­лов проп­риетар­ных фор­матов, то поис­ковик не так час­то будет спо­собен помочь. При­ходит­ся посидеть и поковы­рять­ся в прог­рамме, что­бы нагене­риро­вать набор инте­рес­ных фай­лов.

Как отучить WinAFL ругаться?

Моя прог­рамма доволь­но мно­гос­ловна и ругалась на невер­ный фор­мат вход­ного фай­ла, показы­вая всплы­вающие сооб­щения.

WinAFL

Та­кие проб­лемы вы лег­ко смо­жете вылечить, про­пат­чив исполь­зуемую прог­раммой биб­лиоте­ку или саму прог­рамму.

Заключение

На этом все. Теперь вы сможете сами фаззить программы с помощью WinAFL.

Еще по теме: Удаленная отладка вредоносных программ

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *