WinAFL на практике. Учимся работать фаззером и искать дыры в софте

Содержание статьи

  • Требования к функции
  • Компиляция WinAFL
  • Поиск подходящей цели для фаззинга
  • Поиск функции для фаззинга внутри программы
  • Аргументы WinAFL, подводные камни
  • Прокачка WinAFL — добавляем словарь
  • Особенности WinAFL
  • Побочные эффекты
  • Дебаг-режим
  • Эмуляция работы WinAFL
  • Стабильность
  • Набор входных файлов
  • Отучаем программу ругаться

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

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

Ес­ли ты сов­сем нез­наком с этой тех­никой поис­ка уяз­вимос­тей, то можешь обра­тить­ся к одной из наших ввод­ных ста­тей:

  • «Фаз­зинг, фаз­зить, фаз­зер: ищем уяз­вимос­ти в прог­раммах, сетевых сер­висах, драй­верах»
  • «Luke, I am your fuzzer. Авто­мати­зиру­ем поиск уяз­вимос­тей в прог­раммах»

Так­же по теме фаз­зинга рекомен­дуем сле­дующие статьи:

  • «Фаз­зинг гла­зами прог­раммис­та. Как в Google авто­мати­зиру­ют поиск багов»
  • «Рас­пуши пин­гви­на! Раз­бира­ем спо­собы фаз­зинга ядра Linux»

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

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

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

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

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

     

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

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

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

    Компиляция WinAFL

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

  • Ска­чай и уста­нови Visual Studio 2019 Community Edition (при уста­нов­ке выбери пункт «Раз­работ­ка клас­сичес­ких при­ложе­ний на C++».
  • По­ка у тебя уста­нав­лива­ется Visual Studio, ска­чай пос­ледний релиз DynamoRIO.
  • Ска­чай исходни­ки WinAFL из ре­пози­тория.
  • Пос­ле уста­нов­ки Visual Studio в меню «Пуск» у тебя появят­ся ярлы­ки для откры­тия коман­дной стро­ки Visual Studio: x86 Native Tools Command Prompt for VS 2019 и x64 Native Tools Command Prompt for VS 2019. Выбирай в соот­ветс­твии с бит­ностью прог­раммы, которую ты будешь фаз­зить.
  • В коман­дной стро­ке Visual Studio перей­ди в пап­ку с исходни­ками WinAFL.

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

    mkdir build32cd build32cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=..pathtoDynamoRIOcmake -DINTELPT=0 -DUSE_COLOR=1cmake --build . --config Release

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

    mkdir build64cd build64cmake -G"Visual Studio 16 2019" -A x64 .. -DDynamoRIO_DIR=..pathtoDynamoRIOcmake -DINTELPT=0 -DUSE_COLOR=1cmake --build . --config Release

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

    cd C:winafl_buildwinafl-mastermkdir build32cd build32cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=C:winafl_buildDynamoRIO-Windows-8.0.18915cmake -DINTELPT=0 -DUSE_COLOR=1cmake --build . --config Release

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

  •  

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

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

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

    info

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

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

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

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

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

     

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

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

    У нее мно­го вся­ких воз­можнос­тей, так что, думаю, ее будет инте­рес­но пофаз­зить.

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

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

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

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

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

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

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

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

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

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

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

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

    virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL);virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CAtlTransactionManager* pTM, CFileException* pError = NULL);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Источник: xakep.ru

    Ответить

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