Що ви зазвичай робите, щоб зламати шароварну прогу? Правильно, лізете в інтернет за кряком. Але якщо програма рідкісна, те крякання не може бути 🙁 От би зробити універсальний кряк, що підходить до будь-якої програми… 🙂
Ще шість років тому, коли виндовз 95 ще вважався незрозумілим извратом, мною була створена автоматична ломалка, за кілька секунд розкриває захист приблизно третини подсунутых програм без втручання людини. Але по молодості ваш покірний слуга сплутав міський конкурс прикладних програм і DefCon і мав необережність продемонструвати своє дітище (демонструвалося, як шароварний RAR стає зареєстрованим). Природно, що ідею м’яко кажучи не оцінили, і програма була закинута.
Нещодавно, зіткнувшись із захистом електронної версії журналу, я зрозумів, що ідея автоломалки все ще актуальна. Експлуатується той факт, що програмісти, не знайомі зі способами злому софта, з року в рік роблять одну і ту ж дурість: порівнюють правильний пароль з введеним.
Я не буду викладати готову автоломалку, тому що цим відіб’ю всю бажання вивчати це питання 🙂 Набагато корисніше буде опис, як її створити.
Мови програмування високого рівня (Delphi, Сі, VB) порівнюють рядка за допомогою стандартних процедур, які можна пропатчити з метою розширення їх можливостей 🙂 Наприклад, всі порівнювані рядка можна кидати в файл, таким чином можна отримати пароль. Чи можна змусити програму вважати будь-які два рядки рівними, тоді будь введений пароль буде вважатися вірним.
Насправді ця процедура порівняння викликається багато разів у «службових цілях, так що патчити її треба обережно. Я використовую таку модифікацію алгоритму: якщо одна з порівнюваних рядків починається на BUGZ, то результат порівняння — «рядка дорівнюють». Інакше запускається стандартний алгоритм порівняння. Це дозволяє ввести замість серійника/пароля слово BUGZY 😉 А якщо серійник повинен бути довшим, то залишок можна «забити» будь-яким іншим текстом.
Патчити код у виконуваному модулі (exe, dll) або прямо в пам’яті — справа хазяйська, автоломалка в обох випадку виглядає абсолютно однаково, за винятком використовуваних функцій читання і запису.
Власне про те, що і як патчити.
Крок перший: знайти функцію порівняння.
Шукати будемо по сигнатурі, тобто шматка коду, присутнього в функції. Кожен компілятор має свою сигнатуру, однак самих компилаторов не так вже й багато, до того ж, немає особливої потреби робити автоломалку для компілятора фортрану і watcom c: ти спробуй спочатку знайди проги, які на них зроблені 🙂 Вистачило б джентельменсткого набору: Delphi, MSVC і BC.
Щоб визначити сигнатуру конкретного компілятора, я не придумав нічого кращого, ніж поставити його собі, потім викликати функцію порівняння і подивитися на неї з дизассемблирующего відладчика.
Крок другий: знайти місце для додаткового коду. так як можливості функції порівняння розширюються, пропатчити код «на місці» не виходить, доводиться розміщувати додатковий код в невикористаній частині пам’яті. З основної процедури робиться стрибок на вставку, а потім керування повертається стандартною процедурою перевірки.
В якості «вільного місця» я використовую першу-ліпшу область, забиту великою кількістю нулів. Начебто працює, хоча не зобов’язане 🙂
Крок третій: підготовка патча.
Патч містить умовні та безумовні переходи від шматка коду на місці «нормальної» процедури порівняння до вставки і назад. Так як «відстань» між ними заздалегідь невідомо, доведеться розраховувати довжину стрибків виходячи з розташування частин патча.
Крок четвертий: запис патча.
дані просто нахабно пишуться в пам’ять процесу або у файл, залежно від обраного методу. Починати записувати краще з вставки, мало чого 🙂
Нарешті, практичний приклад патча (для Delphi).
Для порівняння рядків компілятор використовує функцію LStrCmp. В явному вигляді з коду високого рівня ця функція не викликається, але при дизассемблировнии її видно. Ось як виглядає її асемблерний лістинг:
push ebx
push esi
push edi
mov esi,eax
mov edi,edx
cmp eax,edx
jz StringsEqual — перевірка на порівняння рядка з самою собою
test esi,esi
jz NotEqual — перевірка на порожній рядок
test edi,edi
jz NotEqual — перевірка на порожній рядок
а далі йде те, що я використовую як сигнатуру (8b 46 fc 8b 57 fc 29 d0):
mov eax,[esi-4] — це місце
mov edx,[edi-4] — заміщається
_subptr: — сюди відбувається повернення з патча
sub eax,edx
Там, де знаходиться цей код буде розташовуватися перша частина патча — перехід на другу. Патч — 5 байт, що починаються з E9.
Друга частина патча:
mov eax,[esi-4] — те, що ми
mov edx,[edi-4] — затерли стрибком
cmp [esi], $5a475542 — рядок ‘BUGZ’
_se:
(*) jz StringsEqual — перехід в частину процедури, говорить, що рядки рівні cmp [edi], $5a475542 — то ж для другого аргументу
jz _se — щоб не возитися зайвий раз з перерахунком довжини стрибка
(*) jmp _subptr — перехід до «нормальної» процедурою порівняння
код рядків, позначених (*), розраховується виходячи з відстані між шматками патчів.
Залишається тільки додати, що для правки програми прямо в пам’яті (після того, як вона завантажилася і розпакувала сама себе, якщо була запакована asprotect etc), здійснюється з допомогою функцій OpenProcess або Застосунком, ReadProcessMemory і WriteProcessMemory, опис яких є в win32.hlp. Автоломалка успішно випробувана на тестових програмах (які просто порівнюють введену рядок з еталоном) і на электрохакере #34 (останній в якості коду активації брав слово ‘BUGZY’).