Техніка зняття дампа з захищених додатків

Техніка зняття дампа з захищених додатків

Протектори типу Themida (в «дівоцтві» eXtreme Protector) та Star-Force, якими захищені багато популярних програми, дуже глибоко «вгризаються» в операційну систему, що знижує продуктивність і породжує приватні BSOD. Їх «колеги» ведуть себе не так агресивно, але проблем з сумісністю все одно вистачає, особливо при переході на 64-розрядні операційні системи або багатоядерні процесори, значно відрізняються від тих, під які проектувалася захист, активно використовує недокументовані можливості. Вже скільки разів твердили світу - не використовуйте нічого Недокументовані в комерційних додатках, та тільки все не про запас! Ось і доводиться братися за хакерський інструментарій та звільнятися від протекторів, навіть коли програма куплена легальним шляхом і «ламати» її нема чого. А адже доводиться! Як дивно влаштований світ.

Прості випадки демпінгу

Уявімо собі, що распаковщик вже відпрацював, програма зупинена в оригінальній точці входу (OEP) і ми готові зберегти образ файлу (file image) на диск, тобто скинути дамп. Отладчик soft-ice не надає такої можливості, тому доводиться діяти обхідним шляхом. Але для початку необхідно з'ясувати, який саме регіон пам'яті необхідно зберігати. За умови, що захист не робить ніяких ворожих дій, потрібна інформація може бути здобута командами MOD і MAP32 (див. Рис. 1 і лістинг 1).

Лістинг 1. Визначення дислокації модуля в пам'яті. Тут «test_dump» - ім'я процесу,

з якого ми збираємося зняти дамп (soft-ice відображає його в правому нижньому кутку)

hMod Base PEHeader Module Name File Name

00400000 004000D0 test_dum TEMP est_dump.exe

# Дивимося на карту модуля в пам'яті

Owner Obj Name Obj # Address Size Type

test_dump text 0001 001B: 00401000 00003B46 CODE RO

test_dump rdata 0002 0023: 00405000 0000080E IDATA RO

test_dump .data 0003 0023: 00406000 00001DE8 IDATA RW

Малюнок 1. Визначення регіону пам'яті для зняття дампа

Лістинг 2. Дамп пам'яті, знятий через history

db 400000 L 7DE8

010: 00400000 4D 5A 90 00 03 00 00 00-04 00 00 00 FF FF 00 00 MZР.

010: 00400010 B8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00. @.

010: 00400040 0E 1F BA 0E 00 B4 09 CD-21 B8 01 4C CD 21 54 68. L.! Th

010: 00400050 69 73 20 70 72 6F 67 72-61 6D 20 63 61 6E 6E 6F is program canno

010: 00400060 74 20 62 65 20 72 75 6E-20 69 6E 20 44 4F 53 20 t be run in DOS

010: 00400070 6D 6F 64 65 2E 0D 0D 0A-24 00 00 00 00 00 00 00 mode. $.

Команда! DUMP (див. Рис. 2 і лістинг 3) дозволяє зберігати блоки пам'яті на диск в двійковому вигляді, що дуже зручно:

Лістинг 3. Дамп пам'яті, знятий командою! DUMP плагіна IceExt

Dump memory to disk

dump FileName Addr Len

dump c: dump.dat 400000 1000

dump. c: dump.dat 400000 1000

dump. c: dump.dat edx + ebx ecx

DUMP C: dumped 400000 7DE8

DUMP. C: dumped 400000 7de8

Малюнок 2. Зняття дампа в soft-ice за допомогою плагіна IceExt

Інший плагін - icedump (programmerstools.org/system/files?file=icedump6.026.zip) теж вміє дампи пам'ять, і так само, як і IceExt, він поширюється на безкоштовній основі разом з вихідними текстами.

Малюнок 3. Зняття дампа в OllyDbg за допомогою плагіна OllyDump

Малюнок 4. Зовнішній вигляд утиліти PE-TOOLS

У пошуках самого себе

Найпростіше - дослідити карту пам'яті піддослідного процесу, що повертається API-функціями VirtualQuery / VirtualQueryEx. Регіони, помічені як MEM_IMAGE, належать виконуваного файлу або однієї з використовуваних їм DLL (в PE-TOOLS за побудову карти відповідає команда «dump region» (див. Рис. 5)).

Природно, функції OpenProcess / ReadProcessMemory / VirtualProtectEx можуть бути перехоплені захистом, і тоді замість дампа ми отримаємо error, а то і reboot. Низькорівневі функції NtOpenProcess / NtReadVirtualMemory / NtProtectVirtualMemory перехоплюються з тією ж легкістю, до того ж деякі захисту змінюють маркер безпеки процесу, забороняючи відкриття його пам'яті на читання навіть адміністратору!

Вважається, що зняття дампа на рівні ядра відкриває великі можливості для реверс інжинірингу та протистояти цьому ніяк неможливо, оскільки драйвер працює з найвищим рівнем привілеїв, який дозволяє все. Але ж і драйверу захисту, що працює на рівні ядра, теж є все, в тому числі і модифікація ядра операційної системи, в яких потребує драйвер дампера. Причому ніяких задокументованих функцій для читання пам'яті чужого процесу (за винятком вищезгаданих) в системі немає!

Але це ще не найстрашніше. Все більше і більше протекторів переходять на динамічну розпакування, розшифровуючи сторінки в міру звернення до них, а потім зашіфровивая їх знову. Навіть якщо ми проб'ємося крізь захист і Дорвей до процесу, дампи буде нічого. вірніше, отриманий дамп буде практично на 99% зашифрований.

Механізми динамічної розшифровки

Решта сторінок залишаються зашифрованими, і при зверненні до них знову збуджується виключення, яке передається серверу через WaitForDedugEvent. Сервер зашифровує попередню сторінку, відбираючи все атрибути доступу, які у неї тільки є, і розшифровує поточну сторінку, порушила виняток (в дійсності для збільшення продуктивності захист підтримує примітивний кеш, дозволяючи клієнту мати кілька розшифрованих сторінок одночасно).

Потреба в отладочном процесі-сервері пояснюється тим, що по іншому ловити виключення на прикладному рівні просто не виходить. А як же механізм структурних винятків або, скорочено, SEH? Реєструємо свій власний обробник і ловимо виключення, що називається за місцем виникнення. Це позбавляє нас від API-викликів, які забезпечують межпроцессорной взаємодія, які елементарно перехоплюються хакером. На жаль! Якщо захищається додаток використовує SEH (а переважна більшість додатків його використовують), наш обробник виявиться перекритий іншим. Зіткнувшись з «нашим» винятком, він просто не знатиме, що з ним робити, і з імовірністю, близькою до одиниці, просто завершить додаток в аварійному режимі.

Останні версії протектора Armadillo, недавно перейменованого в Software Passport, реалізують набагато надійніший, хоча і надзвичайно низько продуктивний механізм трассирующей розшифровки, при якому весь код програми зашифрований цілком. Сервер трассирует клієнта, розшифровуючи по одній інструкції за раз (попередня інструкція при цьому зашифрована). Зняти дамп тупим зверненням до пам'яті вже не виходить, оскільки захист цікавлять тільки виключення, що виникають при виконанні. Все, що ми можемо, це «вклинитися» між зашифрованим додатком і розшифровує, «збираючи» розшифровані інструкції, що утворюють трасу потоку виконання. Оскільки, досягти 100% покриття коду практично неможливо, отриманий дамп буде неповноцінним, але тут є один маленький нюанс. Покомандного розшифровка не може використовувати ні блокові, ні контекстно-залежні криптоалгоритми, оскільки трасуючий розшифровщик ніколи не знає наперед, яка інструкція буде виконана наступної. Залишаються тільки потокові алгоритми типу XOR або RC4, які дуже легко розшифрувати - варто тільки знайти гаму, яку протектор, незважаючи ні на які зусилля, занадто глибоко заховати все одно не зможе! Природно, повністю автоматизувати процес зняття дампа в цьому випадку вже не вдасться і доведеться вдатися до дизассемблирования, а можливо, навіть до налагодження. На щастя, подібні схеми захисту не набули широкого поширення і навряд чи отримають його в доступному для огляду майбутньому. Трасування уповільнює швидкість роботи програми в десятки разів, в результаті чого воно стає неконкурентоспроможним.

Класичний спосіб впровадження коду реалізується так: відкриваємо процес функцією OpenProcess, виділяємо блок пам'яті викликом VirtualAllocEx, копіюємо код дампера через WriteProcessMemory, а потім або створюємо віддалений потік функцією CreateRemoteThread (тільки на NT-подібних системах), або змінюємо регістр EIP в контексті чужого потоку, звертаючись до SetThreadContext (діє на всіх системах). Природно, попередній EIP повинен бути збережений, а сам потік - зупинений.

Стривайте! Але ж це мало чим відрізняється від звичайного межпроцессорного взаємодії! Функції NtAllocateVirtualMemory / NtSetContextThread / NtCreateThread будь-який захист перехопить зі смаком! (Ніякої помилки тут немає, API-функція CreateRemote Thread в дійсності представляє собою «обгортку» навколо ядерної функції NtCreateThread.)

Добре, ось інший класичний шлях. Розміщуємо дампер в DLL і прописуємо її в HKLMSoftwareMicrosoftWindows NTCurrentVersionWindowsAppInit_DLLs, в результаті чого вона буде відображатися на всі процеси, які тільки є в системі, і перед передачею управління на черговий запускається процес першою отримає управління наша DLL! На жаль, про цю гілці знають не тільки протектори, а й інші програми (антивіруси, персональні брандмауери) і стежать за нею. Наша запис може бути видалена ще до того, як дампер приступить до роботи! Якщо ж йому все-таки вдасться отримати управління, перше, що він повинен зробити, - виділити себе блок пам'яті всередині процесу, скопіювати туди весь необхідний код і повернути гілку AppInit_DLLs в початковий стан. Оскільки дампер отримує управління ще до того, як захист почне працювати, вона ніяк не зможе виявити, що тут хтось вже побував.

Таким чином, захист не зможе виявити змін ні в файлі, ні в пам'яті (при спробі визначення імені поточного файлу операційна система буде повертати то ім'я файлу, яке він мав на момент запуску, ігноруючи факт його «онлайнового» перейменування). Але це занадто громіздкий і наворочений алгоритм, до того ж активного захисту нічого не варто перехопити SetTimer і заборонити установку таймера всередині «свого» процесу до завершення розпакування / передачі управління на OEP.

Правильно спроектована і належним чином реалізована захист повинен перешкоджати нелегальному використанню програми, але не має ні морального, ні юридичного права заважати чесним користувачам і вже тим більше втручатися в операційну систему, виробляючи ніким не санкціоновані зміни. Останні версії протекторів Themida і Software Passport впритул наближаються до rootkit. Ще трохи і вони перетворяться на справжні віруси, створення яких переслідується по закону.

«Нечесні» захисні прийоми

Нормальні пакувальники (UPX, PKLITE, PECOMPACT) стискають виконуваний файл без втрат, і після завершення розпакування він повертається до свого первісного вигляду, що робить процес зняття дампа тривіальним завданням. Протектори в прагненні посилити захист часто йдуть на досить ризикований крок - вони злегка «корчать» обробляється файл з таким розрахунком, щоб він міг працювати тільки під протектором, а після звільнення від нього стає нежиттєздатним. Найбільш популярні способи подібної «нечесної» захисту розглянуті нижче.

Крадіжка байт з OEP. Найпростіша і широко поширена підлість, яка використовується навіть в таких нешкідливих протектора як, наприклад, ASProtect. Суть її полягає в тому, що пакувальник «краде» кілька інструкцій з оригінальною точки входу, зберігає їх у потаємному місці (можливо, в замаскованому або зашифрованому вигляді), а після завершення розпакування емулює виконання крадених байт. Найчастіше для цієї мети використовується стек (тоді крадені байти зазвичай стають операндами інструкцій PUSH), рідше - прямий вплив на регістри і пам'ять (при цьому крадені інструкції трансформуються в псевдокод і в явному вигляді ніде не зберігаються). Суть в тому, що в точці входу розпакованого образу оригінальних байт вже не виявляється і знятий дамп стає непрацездатним. На наше щастя, переважна більшість програм починається зі стартового коду, який є частиною бібліотеки часу виконання (RTL), що поставляється разом з компіляторами. Використовуючи залишився «хвіст» стартового коду, ми легко ототожнив компілятор і відновимо крадені байти з його бібліотеки. Якщо ж цього компілятора в нашому розпорядженні не виявиться, перші кілька байт стартового коду в 9 з 10 випадків цілком передбачувані, і часто їх вдається відновити самостійно (природно, для цього необхідно мати досвід роботи з різними RTL). До речі, IDA Pro розпізнає компілятор саме по першим байтам стартового коду, і, якщо вони відсутні або перекручені, механізм FLIRT працювати не буде. Це означає, що ми залишимося без імен бібліотечних функцій і процес дизассемблирования займе набагато більше часу.

Поліморфний сміття в OEP. Замість того щоб красти байти з OEP, деякі протектори воліють модифікувати стартовий код, розбавляючи значущі інструкції безглуздим поліморфним сміттям. Це ніяк не впливає на працездатність знятого дампа, але засліплює «FLIRT», змушуючи нас або вичищати поліморфний сміття, або визначати версію компілятора «на око», завантажуючи сигнатури вручну (IDA Pro це дозволяє).

Перетворення в байт-код. Протектори Themida і Start-Force дозволяють перетворювати частину машинного коду захищається програми в мову віртуальної машини, тобто в байт-код (також званий p-кодом). Якщо віртуальна машина глибоко «імплантована» всередину протектора, то відламати захист, не «убити» при цьому додаток, стає практично неможливо, як неможливо безпосередньо аналізувати код байт-код. Щонайменше для цього необхідно розібратися з алгоритмом роботи віртуальної машини і написати спеціальний процесорний модуль для IDA Pro або свій власний дизассемблер. Це дуже трудомістке заняття, що віднімає у дослідника купу сил і часу, але ж байт-код віртуальної машини в наступних версіях протектора може бути змінений і раніше написаний процесорний модуль / дизассемблер виявиться непридатним. Це найбільш стійка захист з усіх, що існують на сьогоднішній день, однак не варто забувати про дві речі: по-перше, якщо протектор стає популярним, а його нові версії виходять рідко, створення процесорних модулів стає економічно виправданим і захист починають ламати всі бажаючі, якщо ж нові версії виходять мало не щодня, то навряд чи у розробника протектора буде достатньо часу для радикальної перебудови віртуальної машини і йому доводиться обмежуватися дрібними змінами байт-коду, які виливаються в дрібні зміни процесорного модуля, і протектор продовжать ламати. По-друге, хакер може «віддерти» віртуальну машину від протектора, зовсім не вникаючи в тонкощі інтерпретації байт-коду, зайвий раз підтверджуючи відому тезу: зламати можна все ... з часом.