Просунутий фазинг Хитрі трюки пошуку вразливостей
Зміст статті
Ми вже багато говорили про те, як написати експлойт, обійти різні технології від Microsoft, які заважають нам експлуатувати ту чи іншу дірку. Але сьогодні ми говоритимемо про те, як шукати ці самі дірки. На сторінках журналу ти вже неодноразово зустрічав термін «фазинг». Крім того, ми неодноразово розкривали суть та значення цієї техніки. Сьогодні ми розглянемо більш прогресивні види фазінгу.
Fuzz me baby one more time!

Далі це «щось» аналізує людина і робить висновок про те, що знайдений стан процесу при даному наборі вхідних даних можна використовувати зі злим наміром, тобто виконати довільний код. Самі дані можуть генеруватися за відомим форматом, тобто якщо нам відома специфікація формату, ми можемо заздалегідь підготувати купу вхідних даних, які відповідають формату (структура файлу, протокол мережі на транспортному рівні), але містять «випадкові значення». Наприклад, нам відомо, що в якійсь частині формату даних йде довжина, під яку виділено два байти, а потім вміст зазначеної довжини:
Знаючи цей формат, можна згенерувати безліч варіантів:
І, можливо, програма, що обробляє ці дані, впаде при обробці першого набору… Подальший аналіз покаже, що причина падіння криється через цілечисленне переповнення (0xFFFF це у нас «-1») з наступним переповненням буфера в стеку за допомогою функції memcpy, що є справжньою вразливістю, а не просто прикре помилкою.
char buffer[32000]; short int length=getLen(filename, offset); // length=-1
Якщо формат невідомий або занадто складний (або просто ліньки), то можна використовувати мутаційні алгоритми для зміни існуючихправильні набори даних. На тому ж прикладі:
Остання мутація з тих же причин викличе переповнення буфера в стеку (0xFF00 - "-256" або "65280" без знака), якщо після цього будуть ще якісь дані достатньої довжини.
I’ll crash you!
Зрозуміло, що ефективність фазингу безпосередньо залежить від складності формату вхідних даних та різноманіття підготовлених семплів. Якщо ми говоримо про відомий формат, то дослідник готує опис цього формату (що може бути дуже трудомістким), за яким можуть генеруватися дані. Далі чим більше варіантів буде створено, тим більша ймовірність, що ми щось знайдемо. У випадку, якщо ми використовуємо алгоритми, що мутують, важливо мати велику кількість «зразків», які ми будемо мутувати і «підсовувати» досліджуваному процесу. Очевидно, що мутація – «дешевший» засіб, а генерація – «повніший». Обидва підходи мають свої переваги та недоліки і, очевидно, що можна якось посилити ефект (швидкість та/або повноту) фазингу, тим самим підвищивши показники падінь процесу. З кількості «падінь» потім вибираються унікальні, тобто якщо при трьох наборах даних процес впав за тією ж «причиною», це вважається одним унікальним падінням. Чим більша кількість унікальних падінь, тим ефективніший фазер.
More profit…
Як відомо, все у цьому світі прогресує, включаючи і фазинг. Здавалося б, проста технологія, але дослідники хочуть знайти більшу кількість унікальних станів процесу, що експлуатуються, тому вони вигадують нові фінтифлюшки та алгоритми. Так, вже в 2006 році Шаун Емблітон (Shawn Embleton), Шеррі Спаркс (Sherri Sparks) і Райн Каннінгхем (Ryan Cunningham) поставили питання збільшення продуктивності. Вони справедливо відзначили переваги фаззингу, але йпоказали його недоліки (відсутність метрик, невизначений час фаззингу тощо) і розробили свій проект, який вже фазив з розумом. Будував граф (бінарним аналізом), від функції отримання вхідних даних, до, наприклад, небезпечної API (strcpy) і генерував нові вхідні дані, дивлячись у своїй, як обходиться граф і чи йде він до мети. Завдання – відкинути заздалегідь тупикові набори даних, які призведуть до потрібного шляху. При цьому використовується генетичний алгоритм для генерації нових семплів (враховуються попередні пробіги та їх результат). Так чи інакше, хлопці, які народили ці ідеї та реалізували свій проект, вже працюють в АНБ :). Докладніше з їхніми ідеями можеш ознайомитись на blackhat.com/presentations/bh-usa-06/BH-US-06-Embleton.pdf.
In-Memory Fuzzing
Повернемося до ситуації, коли вхідний формат розбирати дуже ліньки. Як проводити фазинг без знання формату і без гри в мутацію існуючих даних? Відповідь виявилася простою - фаззити прямо в процесі. Тобто, програма - це набір функцій, при отриманні вихідних даних ці функції викликаються так чи інакше обробляючи вхідні дані, тому можна організувати виклик цих функцій безпосередньо із заздалегідь згенерованими даними. Таким чином можна заощадити час на розборі формату та підготовці даних відповідно до цього формату. Крім того, це може підвищити швидкість самого процесу пошуку дір; так, наприклад, ми обходимо обмеження, які можуть бути в програмі (наприклад, при мережевому фазингу ми обмежені швидкістю обробки підключення, кількість одночасно дозволених з'єднань і т.д.). А якщо перебиратимемо дані вже в самому процесі, обійшовши accept, recv, то зможемо відразу фазити потрібні нам функції. Допоможе нам у цьому реліз від CorelanSecurity Team, який можна завантажити звідси.redmine.corelan.be:8800/projects/inmemoryfuzzing/files. Насправді цього мало, ще необхідні Pydasm (therning.org/magnus/archives/278) і Paimei (openrce.org/downloads/details/208/PaiMei). Природно, потрібний інтерпретатор Пітона та Immunity Debuger (debugger.immunityinc.com/register.html. До речі, з дистриб'ютором дебагера йде і Пітон, так що качати інтерпретатор окремо не треба) з плагіном pvefindaddr.py (redmine.core0/be: projects/pvefindaddr). Як ти вже зрозумів, все буде не так просто, все це шаманство ще треба зібрати в наступній послідовності:
- Ставимо Імуніті Дебагер із Пітоном;
- Качаємо pvefindaddr, кидаємо в папку PyCommand (директорії дебагера);
- Качаємо та встановимо pydasm для Пітона 2.5;
- Качаємо ПайМей, розпаковуємо, йдемо в папку installers, запускаємо установник;
- Видаляємо з папки пітона ПейМєєвський pydasm - Python25Libsite-packagespydbgpydasm.pyd.
Тепер PyDbg готовий працювати з Пітоном 2.5. Після цього можна пробувати що-небудь фазити. Але спочатку треба зрозуміти, що саме, а точніше, які функції процесу працюють безпосередньо з даними.
Напишемо просту програму, яка бере з файлу параметри через символ-розділювач і копіює на згадку. Програма братиме три параметри, кожен із яких обробляється окремою функцією. Вразливість буде у другій функції – переповнення буфера.
void func1 (char * input) char buffer [255]; unsigned int len=strlen(input);
void func2 (char * input) char buffer [255]; strcpy(buffer,input); fputs("FUNC2: done!n",stdout); >
void func3 (char * input) char buffer [255]; strncpy(buffer,input,254); fputs("FUNC3: done!n",stdout); >
0x00401000 0x0040106d ESP+4 0x00401070 0x004010c7 ESP+4 0x004010d0 0x00401125 ESP+4
Статистика семплів при фаззингу
Для складного формату файлу:
- мутуючим фаззером – з 3*10^6 виходить 5*10^3 падінь, з них 1-3 експлуатовані;
- описуючим протокол – з 1*10^6 виходить 15*10^3 падінь, їх 6-10 эксплуатируемые;
Для простих форматів файлів:
- мутуючим фаззером - з 1 * 10 ^ 5 виходить 150 падінь, з них 0-3 експлуатовані;
- описуючим протокол – з 1*10^4 виходить 150 падінь, їх 0-1 эксплуатируемые.
Приклад простого фазінгу можна знайти на http://sites.google.com/site/felipeandresmanzano. Там продемонстровано, як за допомогою дуже простих фаззерів, що мутують, досягти дуже хороших результатів.
Тепер розглянемо види фазерів, за допомогою яких можна досягти практичного успіху. Почнемо зі «старих».
Sulley та peach
Це дуже потужні фазери з можливістю описувати протокол для фазінгу. Наприклад, для фаззинга FTP необхідний конфіг у 329 рядків, у тому числі більшість – опис самого протоколу. Для складніших протоколів конфіг буде великим, та її складання буде дуже трудомістким. Дуже актуальним рішенням є проект hotfuzz (hotfuzz.atteq.com).
Схема його роботи здавалося б громіздка, але насправді дуже проста.

Важливо пам'ятати!
Для аналізу падіння програми можна використовувати різні засоби. Щоб автоматизувати цей процес, я, наприклад, використовую winappdbg.
Я написав простий скрипт, який смикає тестований додаток у середовищі winappdbg і в автоматичному режимі може визначити експлуатацію вразливості.
Найрозумніші: avalanche та klee
Avalanche можеаналізувати лише клієнтські програми (для випадку читання із сокетів). І додатки, які читають із файлів. Підтримка аналізу серверних програм буде додана в наступних версіях, але поки що до цього досить далеко. Avalanche повинен бути встановлений і запускатися з тієї ж директорії, що stp і valgrind (і важливий нюанс – повинні збиратися всі разом).
Команди зборки такі:
$ wget http://avalanche.googlecode.com/files/avalanche-0.2.tar.gz $ tar -xvf avalanche-0.2.tar.gz $ cd avalanche-0.2 $ configure -- prefix= pwd /inst $ make $ make install
$ ./inst/bin/avalanche --filename=samples/simple/seed --debug samples/simple/sample2 samples/simple/seed
Спочатку повинні бути всі опції для Avalanche, а аналізований додаток та опції для нього – наприкінці. Зброя до бою готова :).
Чому avalanche стоїть біля верхівки піраміди? Розглянемо трохи теорії про avalanche та динамічний аналіз загалом, після чого ти все сам зрозумієш.
Отже, нещодавно були запропоновані різні способи, що дозволяють використовувати динамічний аналіз для одночасного знаходження помилок та вхідних даних, що дозволяють їх відтворювати. Суть цих методів зводиться приблизно до наступної схеми: вводиться поняття символічних або помічених даних - даних, отриманих програмою із зовнішнього джерела (стандартний потік введення, файли, змінні оточення і т.д.), тим чи іншим способом збирається інформація про всіх використання помічених даних у програмі. Ця інформація записується у вигляді булевих обмежень на значення помічених даних (до цих обмежень може потрапляти інформація про переходи, що залежать від помічених даних, та інформація про використані помічені дані у потенційно небезпечних місцях програми). Знаходженнязначень позначених даних, які роблять ці обмеження здійсненними, може означати можливість виникнення помилки в програмі, або можливість обходу інших, відмінних від обійдених під час першого запуску, частин програми.
Avalanche реалізує подібний підхід на основі відкритого фреймворку для динамічної інтерпретації Valgrind та інструменту для перевірки здійсненності обмежень (solver/рішальник) STP. Інструмент Avalanche складається з чотирьох основних компонентів: двох модулів розширення (плагінів) Valgrind – Tracegrind та Covgrind, інструменту перевірки здійсненності обмежень STP та модуля, що управляє. Tracegrind динамічно відстежує потік помічених даних в аналізованій програмі та збирає умови для обходу її не пройдених частин та спрацьовування небезпечних операцій. Ці умови за допомогою модуля, що управляє, передаються STP для дослідження їх здійсненності. Якщо якусь з умов можна здійснити, то STP визначає ті значення всіх вхідних до умов змінних (у тому числі й значення байтів вхідного файлу), які звертають умову в істину. У разі виконання умов для спрацювання небезпечних операцій програма запускається керуючим модулем повторно з відповідним вхідним файлом для підтвердження знайденої помилки.
Виконані умови для обходу не пройдених частин програми визначають набір можливих вхідних файлів нових запусків програми. Таким чином, після кожного запуску програми інструментом STP автоматично генерується безліч вхідних файлів для подальшого аналізу. Далі постає завдання вибору з цієї множини найбільш «цікавих» вхідних даних, тобто в першу чергу повинні оброблятися вхідні дані, на яких найімовірніше виникнення помилки. Для вирішення цього завдання використовується евристичнаметрика – кількість раніше не обійдених базових блоків у програмі (базовий блок тут розуміється тому, як його визначає фреймворк Valgrind). Для вимірювання значення евристики використовується компонент Covgrind, функції якого входить також фіксація можливих помилок виконання. Covgrind – набагато більш легкий модуль, ніж Tracegrind, тому можливий порівняно швидкий вимір значень евристики для всіх отриманих раніше вхідних файлів та вибір вхідного файлу з найбільшим значенням.
З усього вищесказаного можна дійти невтішного висновку: Avalanche – перспективний проект, який реалізує обхід графа виконуваної програми, що є досить новий підхід у аналізі якості ПЗ. Також у цьому проекті йдеться про тентування (tainted analysis[2-5]), що, на мій погляд, є революційним підходом у цьому напрямку, і останнім часом дуже багато уваги приділяється саме цьому питанню.
Трохи про STP
У STP і у всіх bitvector'них вирішувачів (та й взагалі у всіх вирішувачів) є низка недоліків. Ну, хоча б те, що вони не підтримують цикли (loop). Зараз багато робіт, присвячених саме розробці теорії та реалізації алгебраїчної форми, яка може бути використана вирішувачем для представлення циклу (loop) у САП (control flow graph). Ось посилання на останні досягнення в цій галузі: