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

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

hacker_frei

https://t.me/hacker_frei

Вячеслав Москвин 

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

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

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

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

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

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

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

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

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

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

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

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

ТРЕБОВАНИЯ К ФУНКЦИИ

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

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

КОМПИЛЯЦИЯ 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.
  6. Для ком­пиляции 32-бит­ной вер­сии выпол­ни сле­дующие коман­ды:
  7. mkdir build32
  8. cd build32
  9. cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=0 -DUSE_COLOR=1
  10. cmake --build . --config Release
  11. Для ком­пиляции 64-бит­ной вер­сии — такие:
  12. mkdir build64
  13. cd build64
  14. cmake -G"Visual Studio 16 2019" -A x64 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=0 -DUSE_COLOR=1
  15. cmake --build . --config Release
  16. В моем слу­чае эти коман­ды выг­лядят так:
  17. cd C:\winafl_build\winafl-master\
  18. mkdir build32
  19. cd build32
  20. cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=C:\winafl_build\DynamoRIO-Windows-8.0.18915\cmake -DINTELPT=0 -DUSE_COLOR=1
  21. cmake --build . --config Release
  22. Пос­ле ком­пиляции в пап­ке <WinAFL dir>\build<32/64>\bin\Release будут лежать рабочие бинари 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. Поп­робу­ем начать фаз­зить!

АРГУМЕНТЫ WINAFL, ПОДВОДНЫЕ КАМНИ

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

afl-fuzz.exe -i c:\inputs -o c:\winafl_build\out-plain -D C:\winafl_build\DynamoRIO-Windows-8.0.18915\bin32 -t 40000 -x C:\winafl_build\test.dict -f test.test -- -coverage_module target.exe -fuzz_iterations 1000 -target_module target.exe -target_offset 0xA4390 -nargs 3 -call_convention thiscall -- "C:\Program Files (x86)\target.exe" "@@"

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

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

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

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

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

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

ПРОКАЧКА WINAFL — ДОБАВЛЯЕМ СЛОВАРЬ

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

WinAFL уме­ет вос­ста­нав­ливать син­таксис фор­мата дан­ных цели (нап­ример, AFL смог самос­тоятель­но соз­дать валид­ные JPEG-фай­лы без какой‑либо допол­нитель­ной инфы). Обна­ружен­ные син­такси­чес­кие еди­ницы он исполь­зует для генера­ции новых кей­сов для фаз­зинга. Это занима­ет зна­читель­ное вре­мя, и здесь ты можешь ему силь­но помочь, ведь кто, как не ты, луч­ше все­го зна­ет фор­мат дан­ных тво­ей прог­раммы? Для это­го нуж­но сос­тавить сло­варь в фор­мате <имя переменной>="значение". Нап­ример, вот начало моего сло­варя:

x0="ProgVer"

x1="WrittenByVersion"

x2="FileType"

x3="Created"

x4="Modified"

x5="Name"

x6="Core"

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

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

ОСОБЕННОСТИ WINAFL

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

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

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

Дебаг-режим

Ес­ли WinAFL отка­зыва­ется работать, поп­робуй запус­тить его в дебаг‑режиме. Для это­го добавь параметр -debug к аргу­мен­там биб­лиоте­ки инс­тру­мен­тации. Пос­ле это­го в текущем катало­ге у тебя появит­ся тек­сто­вый лог. При нор­маль­ной работе в нем дол­жно быть оди­нако­вое количес­тво стро­чек In pre_fuzz_handler и In post_fuzz_handler. Так­же дол­жна при­сутс­тво­вать фра­за Everything appears to be running normally.

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

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

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

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

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

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

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

Отучаем программу ругаться

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

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

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page