Альтернативні методи трасування додатків

    image
 
Трасування використовується в багатьох видах ПО: у емуляторах, динамічних Розпакувальник, фаззерах. Традиційні трейсери працюють по одному з чотирьох принципів: емуляція набору інструкцій (Bochs), бінарна трансляція (QEMU), патчінга бінарних файли для зміни потоку управління (Pin), або робота через відладчик (PaiMei, заснований на IDA). Але зараз мова піде про більш цікавих підходах.
 
 Навіщо відслідковувати?
Завдання, які вирішують за допомогою трасування можна умовно розділити на три групи залежно від того, що саме відстежується: виконання програми (потік управління), потік даних або взаємодію з ОС. Давай поговоримо про кожного докладніше…
 
 
 Потік управління
Відстеження потоку управління допомагає зрозуміти, що робить бінарник під час виконання. Це хороший спосіб роботи з обфусцірованним кодом. Також, якщо ти працюєш з фаззером, це допоможе з аналізом покриття коду. Або візьмемо, наприклад, антивірусне ПЗ, де трассировщик простежить за виконанням бінарного файлу, сформулює якийсь патерн його поведінки, а також допоможе з динамічною розпакування виконуваного файлу.
Трасування може відбуватися на різних рівнях: відстеження кожній інструкції, базових блоків або тільки певних функцій. Як правило, вона здійснюється шляхом перед / постінструментаціі, тобто патчінга потоку управління в найбільш «цікавих» місцях. Інший метод полягає в тому, щоб просто пріаттачіть відладчик до досліджуваної програмі і обробляти пастки і точки зупину. Однак є ще один не дуже поширений спосіб — задіяти функції центрального процесора. Одна з цікавих можливостей процесорів Intel — прапор MSR-BTF, який дозволяє відстежувати виконання програми на рівні базових блоків — на розгалуження (бранч). Ось що говориться з приводу даного прапора в документації:
«Коли ПО встановлює прапор BTF в MSR-регістрі MSR_DEBUGCTLA і встановлює прапор TF в регістрі EFLAGS, процесор буде генерувати налагоджувальне переривання тільки після зустрічі з розгалуженням або виключенням.»
 
 Потік даних
У цьому сценарії трасування застосовується для розпакування коду, а також для спостереження за обробкою цінної інформації — під час його можна виявити неправильне використання об'єктів, переповнення та інші помилки. Крім того, воно також може використовуватися для збереження і відновлення контексту в процесі трасування. Зазвичай це робиться так: досліджувана бібліотека повністю дізассембліруется, після цього в ній локалізуються всі інструкції читання / запису, а потім у процесі виконання коду відбувається їх парсинг і визначається адресу призначення. Є й інший варіант — за допомогою відповідної API-функції встановлюється захист віртуальної пам'яті, після чого відслідковуються всі порушення доступу до неї. Рідше використовується метод, коли в пам'яті змінюється таблиця сторінок.
 
 Ð Ð¸Ñ. 1. Трансляция виртуальных адресов в физические
 
 
 Взаємодія з ОС
Моніторинг взаємодії з ОС дозволяє відфільтровувати спроби доступу до реєстру, контролювати зміни файлів, відстежувати взаємодію процесу з різними системними ресурсами, а також виклики певних API-функцій. Як правило, це реалізується через перехоплення API-функцій, шляхом вставки «трамплінів», inline-хуков, модифікацію таблиці імпорту, установку брейкпоинтов. Інший варіант — задіяти системний виклик SYSCALL. Адже якщо згадати, то кожна API-функція, яка вносить якісь зміни в ОС, насправді являє собою не що інше, як просту обгортку для певного системного виклику.
 
 Ð Ð¸Ñ. 2. Нумерация идентификаторов (ID) SYSCALL в Windows 8
 
 
Механізм SYSCALL являє собою швидкий спосіб переключити CPL (Current Privilege Level) з режиму користувача в режим супервайзера, таким чином, додаток режиму користувача може вносити зміни в ОС (рис. 4).
 
 Ð Ð¸Ñ. 4. Обработка операций SYSCALL (по учебнику Intel)
 
 
 Занурюємося в ядро ​​
Для виконання згаданих функцій необхідно опуститися на рівень ядра (ring 0). Однак у режимі супервайзера вже з'являється доступ до деяких функцій, що надаються самою операційною системою:
LoadNotify
,
ThreadNotify
,
ProcessNotify
. Їх використання допомагає зібрати інформацію по завантаженню і вивантаженню для цільового процесу, таку як: список модулів, діапазони адрес стека якого-небудь потоку, список дочірніх процесів та інше.
Друга група функцій включає дампер пам'яті, що використовує MDL (memory descriptor list — список дескрипторів пам'яті), монітор пам'яті процесів, заснований на VAD (Virtual Address Descriptor), монітор взаємодії з системою, який задіює
nt!KiSystemCall64
, перехоплення доступу до пам'яті і пасткам через IDT (Interrupt Descriptor Table).
Монітор пам'яті використовує для своєї роботи VAD-дерево, яке являє собою AVL-дерево , що використовується для зберігання інформації про адресний простір процесу. Воно ж використовується, коли необхідно ініціалізувати PTE (Page Table Entry) для конкретної сторінки пам'яті.
 
 Ð Ð¸Ñ. 3. Пример VAD-дерева
 
 
Як я запропонував вище, відстеження доступу до пам'яті може здійснюватися через механізм захисту пам'яті (така от тавтологія), але його реалізація в режимі користувача за допомогою API-функцій може занадто сильно відбитися на продуктивності. Однак якщо взяти до уваги, що захист пам'яті заснована на механізмі MMU — пейджинг, тобто більш простий спосіб: змінювати таблицю сторінок в режимі ядра, після чого порушення режиму доступу до пам'яті буде оброблятися через генерацію процесором винятку PageFault, а управління буде передаватися на обробник IDT [PageFault]. Установка перехоплювача на обробник PageFault дозволить швидко отримати сигнал про запит на доступ до вибраних сторінкам.
Все тому, що процес може використовувати тільки сторінки пам'яті, помічені як Valid (тобто вивантажені в пам'ять), в іншому ж випадку буде виникати виняток PageFault, яке і буде перехоплюватися. Це означає, що якщо ми навмисно поставили Valid-прапор обраної сторінки пам'яті в значення invalid (0), то кожна спроба доступу до цієї сторінки буде викликати оброблювач PageFault, що дозволяє легко відфільтрувати і обробити відповідний запит (викликаючи callback до трейсеру і виставляючи Valid- прапор для конкретного PTE).
 
 Ð Ð¸Ñ. 5. Флаги PTE
 
 
 Копаємо глибше — йдемо в VMM!
У попередньому розділі я запропонував деякі «брудні» методи для режиму ядра. Взагалі, установка хуков — це неправильний спосіб, і мені він не подобається, точно так само, як не подобається він і хлопцям з Microsoft. Для боротьби з такими методами мелкомягкіе і розробили PatchGuard. На щастя, є й інший спосіб для вилову PageFaults, пасток або SYSCALL'ов — це гіпервізор. Правда, даний варіант має як свої плюси, так і свої мінуси.
Мінуси:
 
     
  • віртуалізувати не окремий додаток, а вся система — на рівні ядра ЦП.
  •  
  • Оператор
    switch( VMMExit )
    відбирає трохи продуктивності, так само як і код гіпервізора, що виконується для кожного з варіантів switch'а.
  •  
Плюси:
 
     
  • Більш високий рівень прав, ніж рівень супервайзера, а також цілий набір callback'ов, наданий технологією віртуалізації.
  •  
При цьому сам VMM (Virtual Machine Monitor) може бути мінімалістичним (мікроVMM) і реалізовувати тільки необхідну обробку, займаючи при цьому мінімальний обсяг коду (приклад ).
 
 Ð Ð¸Ñ. 6. Некоторые callback’и, предоставляемые Intel VTx
 
Крім усього, в даному випадку замість того, щоб ставити хукі на IDT, можна все обробляти безпосередньо за допомогою дебагом-винятки в VMM. Те ж саме відноситься і до перехоплення помилок сторінок за допомогою виключення PageFault в VMM або через реалізацію EPT (Extended Page Table).
 
 Ð Ð¸Ñ. 7. Включаем вывод VMX для ловушек и сбоев
 
 Підводні камені VMM
Можна відзначити деякі основні особливості описаного підходу:
 
     
  • цільової файл залишається практично незміненим
  •  
  • для відстеження (як покрокового, так і на рівні розгалужень) впроваджується прапор TRAP;
  •  
  • адресні брейкпоінт через 0xCC або використання DRx;
  •  
  • моніторинг пам'яті шляхом зміни таблиці сторінок процесу;
  •  
  • не потрібно патчити бінарний файл;
  •  
  • можна використовувати як трассіровочние модуль з іншої програми;
  •  
  • можна відстежувати кілька додатків одночасно;
  •  
  • можна відстежувати кілька потоків однієї програми;
  •  
  • реалізовані швидкі виклики для перемикання CPL.
  •  
Виділення трейсера з простору цільового процесу в інший процес дає кілька переваг: можна використовувати його як окремий модуль, можна зробити Біндінг для Python, Ruby та інших мов. Однак у цього рішення є і недолік — дуже великий удар по продуктивності (взаємодія між процесами: читання з пам'яті іншого процесу, подієвий механізм очікування). Для прискорення трасування необхідно перенести логіку в адресний простір цільового процесу, щоб можна було швидко отримувати доступ до його ресурсів (пам'яті, стеку, вмісту регістрів), а також опціонально відмовитися від VMM за негативного впливу обробки VMMExit на продуктивність і повернутися назад до установки хуков для пасток і обробників PageFault. Але з іншого боку, в майбутніх процесорах технології віртуалізації, напевно, стануть більш ефективними і не надаватимуть настільки великого впливу на продуктивність. До того ж можливості віртуалізації для трасування можна використовувати набагато ширше, ніж ми розглядаємо в рамках статті, тому плюси можуть компенсувати зниження продуктивності.
 
 Трейсер для ядра
Що стосується трасувальника для ядра, то тут діють всі ті ж принципи:
 
     
  • відстеження через пастки (TRAP);
  •  
  • моніторинг пам'яті через зміну таблиці сторінок;
  •  
  • callback'і трейсера передаються в додатки рівня користувача;
  •  
  • не потрібно патчити бінарні файли цільового програми.
  •  
Головна особливість таких трейсерів в тому, що не треба патчити бінарний файл, а також що трасування (включаючи розпакування та фаззінга) можна здійснювати з рівня користувача (наприклад, з трейсера, написаного на Python), хоча з точки зору продуктивності набагато більш ефективно робити це безпосередньо з режиму ядра.
З іншого боку, за всі ці можливості теж доводиться розплачуватися:
 
     
  • адресний простір драйвера належить не йому;
  •  
  • фаззінга в пам'яті — не таке вже й проста справа;
  •  
  • невірне значення RIP, регістрів, пам'яті… маніпулювання ними може дуже погано закінчитися;
  •  
  • необхідно чітко уявляти собі, що саме ти відстежуєш або перевіряєш;
  •  
  • необхідно протягом всього процесу трасування пам'ятати про численні IRQL;
  •  
  • обробка виключень.
  •  
Відділення від цільового процесу, а також інкапсуляція в модуль дають нам високу масштабованість і можливість спільної роботи з іншими модулями для створення більш складного інструменту. Таким чином, у разі реалізації трейсера, наприклад, на Python, можна буде використовувати IDA Python, прив'язки LLVM, Dbghelp для налагоджувальних символів, дизасемблери (движки capstone і bea) і багато іншого. Щоб показати, наскільки легко і швидко можна реалізувати трассировщик на Python, наведу пару прикладів.
У першому прикладі контролюється більше трьох варіантів доступу (RWE) в задану область пам'ять:
 
 
target = tracer.GetModule("codecoverme")
dis = CDisasm(tracer)
for i in range(0, 3):
    print("next access")		
    tracer.SetMemoryBreakpoint(0x2340000, 0x400)
    tracer.Go(tracer.GetIp())
    inst = dis.Disasm(tracer.GetIp())
    print(hex(inst.VirtualAddr), " : ", inst.CompleteInstr)
    tracer.SingleStep(tracer.GetIp())

 
А наступний ділянку коду демонструє трасування програми на рівні розгалужень, при цьому пропускаючи їх обробку поза основного модуля:
 
 
for i in range(0, 0xffffffff):
    
  if (target.Begin > tracer.GetIp() or target.Begin + target.Size < tracer.GetIp()):    
    ret = tracer.ReadPrt(tracer.GetRsp())
    tracer.SetAddressBreadkpoint(ret)
    tracer.Go(tracer.GetIp())
    print("out-of-module-hook")   
  isnt = dis.Disasm(tracer.GetPrevIp())
  print(hex(inst.VirtualAddr), " : ", inst.CompleteInstr)
  tracer.BranchStep(tracer.GetIp())

 
Як бачиш, код дуже лаконічний і зрозумілий.
 
 DbiFuzz-фреймворк
Всі розглянуті вище підходи до трасуванні я втілив у DbiFuzz-фреймворку , який демонструє, як можна відстежувати роботу виконуваного файлу альтернативними методами. Як ми вже відзначали, деякі з відомих методів використовують інструментації, яка дає швидке рішення, але при цьому передбачає серйозне втручання в цільової процес і не зберігає цілісності бінарного файлу. На відміну від них, DbiFuzz залишає бінарний файл практично незайманим, змінюючи тільки PTE, BTF і вставляючи прапор TRAP. Інша сторона цього підходу полягає в тому, що при цікавому подію включається переривання: перехід ring 3-ring 0 — ring 3. Так як DbiFuzz увазі прямолінійне втручання в контекст і потік управління цільового процесора, то його можна використовувати для написання власних інструментів (навіть на Python) для доступу до цільового бінарним файлу і його ресурсів.
 
 
WWW
Детальніше дізнатися про DbiFuzz-фреймворк ти можеш на моєму сайті , на SlideShare і на порталі ZeroNights
Дереву VAD присвячена дуже цікава стаття Брендана Долан-Гевітта «The VAD tree: A process-eye view of physical memory5».
 
 
 Show time
Для багатьох завдань, що вирішуються за допомогою трасування, може виявитися корисною динамічна бінарна інструментація. Що стосується DbiFuzz-фреймворка, то його можна використовувати в наступних випадках:
 
     
  • коли необхідно відстежувати код на льоту;
  •  
  • при розпакуванні бінарного файлу, трасуванні пакувальника шкідливої ​​програми;
  •  
  • для моніторингу обробки конфіденційних даних;
  •  
  • для фаззінга в пам'яті (легко відстежувати і змінювати потік);
  •  
  • при використанні в різних інструментах, не обов'язково написаних на С.
  •  
Немає жодних проблем у запуску DbiFuzz на льоту, просто встанови пастку чи INT3-перехоплювач. Оскільки ми не чіпаємо бінарний код цільового файлу, то не буде ніяких проблем з перевіркою цілісності, а прапор TRAP може бути замінений на MTF. Відстеження цінних даних теж не представляє ніяких проблем, потрібно просто встановити відповідний PTE — і твій монітор готовий! Інструменти Python / Ruby / ...? Просто створи потрібні прив'язки (bindings) — і вперед!
Звичайно, у цього фреймворка теж є свої недоліки, але в цілому він володіє багатьма корисними можливостями. І ти завжди можеш пограти з DbiFuzz, використовувати вхідні в нього інструменти для своїх потреб і відстежувати все, що забажаєш.
 
 To be continued
Як бачиш, динамічна бінарна інструментація — не єдиний метод трасування. Альтернатив їй досить багато, і більшість з них представлені в DbiFuzz-фреймворку. Вже зараз деякі можливості цього проекту можуть допомогти з роботою в кодом на рівні ядра, а надалі я планую перевести в цей простір весь трейсер. До речі, вже зараз ти можеш використовувати исходники фреймворка, покращувати концепцію і експериментувати з новими ідеями…
 
 
Корисні посилання
Блоги:
 Intel:
 Щодо VAD:
 Віртуалізація:
 Модулі Python (дизасемблери):
  
 
 Вперше опубліковано в журналі «Хакер» від 02/2014.
 
Підпишись на «Хакер»
  
 
 
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.