Для новачків в крекінгу, мішень HP-crackme




Мета: HP-crackme (272кб)
Що ще потрібно:
1. OllyDbg + плагіни
2. ImpRec 1.6 Final
3. IDA
4. Ну і, естессно, некрівие рики;)

Для кого це написано?

Сію стаття я написав, перш за все, для новачків в крекінгу, яким (як я чув) часто не все зрозуміло в кілька односторонньому сучасному підході до питання крекінгу. Адже з часом прямо на очах зникають Тутори, де б не описувалося тільки один з підходів реверсінга (тільки розпакування, відновлення імпорту, правка в пам'яті коду програми, знаходження вірного серійник, або пропатчіваніе проги), а всі вони використовувалися комплексно. Предчуття, що "аси" крекінгу c усмішкою прочитали минулої рядок і почнуть мені твердити, що, наприклад, розпакування - це вже більше половини виконаної роботи. Але, незважаючи на це, я вирішив написати тутор "від А до Я" - від ручної розпакування "жертви" до її злому патчінга файлу і знаходженням вірних реєстраційних даних. Думаю, багатьом початківцям буде корисно "прощупати" різні варіанти злому крекмі.

Ручна розпакування


Так виглядає наш крекмі

Запустимо OllyDbg і завантажимо в нього наш крекмі. Ми зупинимося на EP і побачимо таку картину:


Знаходимо оригінальну точку входу

бачимо, що викликається якась функція, якій передаються 3 аргументу. Далі виконується стрибок на теж поки не відомий код. Помічаємо, що за адресою 00482019 знаходиться масив цікавих рядків. Судячи з ним, наша програма запакована пакувальником PKLite і щоб її дослідити, модифікувати її доведеться розпаковувати. (Пішовши шляхом найменшого сопративления було вирішено скористатися плагіном автораспаковкі в PEID 0.92, але йому, чомусь, пакер припав "не по зубах"). Трохи протрассировать код можна дізнатися, що за адресою 0040200F знаходиться процедура розпакування упакованого "тіла" програми. Після завершення її роботи крекмі в пам'яті буде знаходитися в повністю функціонільном стані. Щоб його отдампіть потрібно знайти OEP. Найцікавіше те, що відразу після процедури розпакування і виконується стрибок на цю саму OEP (JMP 00468A48). Тиснемо F8 і опинимося на ній. Тепер можна і дампи. Для цього можна використовувати плагін до Олі OllyDump (Plugins-OllyDump-DumpDebuggedProcess-Dump). З'являється таке вікно:


Дампи ...



відновлення імпорту


Просто дампнутий крекмі (я назвав файл dumped.exe) запускатися не буде (видається помилка знаходження точки входу однієї з апі) - йому потрібно відновити таблицю імпортованих функцій. Для цього запустимо крекмі (hp.exe) і після нього ImpRec. У ньому вводимо адресу нашої OEP і натискаємо по "IAT AutoSearch" - "Get Imports" - "Fix Dump"


Imprec відновлює імпорт ...



отже, ми отримали ПОВНІСТЮ працездатний распакований крекмі (dumped_.exe)! Приступимо до його аналізу ...



Дизасемблювання і злом знаходженням вірного ключа


Дізассембліруем дамп Ідой і завантажуємо сигнатуру Delphi (оскільки прога написана саме в цьому середовищі програмування). Навіть при побіжному перегляді коду в очі відразу кидається величезна кількість операцій зі рядками (починаючи з адреси 00466BC7). Дивимося трохи вище:
loc_466B0C: ; CODE XREF: CODE:00466B11j push 0 push 0 dec ecx jnz short loc_466B0C push ecx push ebx push esi push edi mov [ebp- 4 ], eax mov ebx, offset unk_46BC58 mov esi, offset unk_46BC78 xor eax, eax push ebp push offset loc_468100 push dword ptr fs:[eax] mov fs:[eax], esp lea edx, [ebp- 8 ] mov eax, [ebp- 4 ] mov eax, [eax+ 308h ] call @TControl@GetText$qqrv ; считываем имя юзера с Edit1 cmp dword ptr [ebp- 8 ], 0 jnz short loc_466B58 ; прыгаем если ввели хоть что-то mov eax, offset _str________________.Text call @Dialogs@ShowMessage$qqrx17System@AnsiString ; Dialogs::ShowMessage(System::AnsiString) jmp loc_4680C0 ; jmp на выход 
Далі (00466C43) йде код зчитування імені, ключа, серійника і перевірка ввели ли ми їх:
 call @TControl@GetText$qqrv ; eax = length(name) mov edx, [ebp- 0Ch ] mov eax, offset dword_46BC64 call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) lea edx, [ebp- 10h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F4h ] call @TControl@GetText$qqrv ; eax = length(key) mov edx, [ebp- 10h ] ; edx = *key mov eax, offset unk_46BC5C call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) lea edx, [ebp- 14h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F0h ] call @TControl@GetText$qqrv ; eax=length(serial) mov edx, [ebp- 14h ] ; edx= *serial mov eax, offset unk_46BC60 call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) mov eax, ds:dword_46BC64 call @System@_16823 ; eax=length(name) mov edi, eax test edi, edi jle loc_46740E ; если не ввели имя, то выходим mov ds:dword_46BC7C, 1
call @TControl@GetText$qqrv ; eax = length(name) mov edx, [ebp- 0Ch ] mov eax, offset dword_46BC64 call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) lea edx, [ebp- 10h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F4h ] call @TControl@GetText$qqrv ; eax = length(key) mov edx, [ebp- 10h ] ; edx = *key mov eax, offset unk_46BC5C call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) lea edx, [ebp- 14h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F0h ] call @TControl@GetText$qqrv ; eax=length(serial) mov edx, [ebp- 14h ] ; edx= *serial mov eax, offset unk_46BC60 call @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void) mov eax, ds:dword_46BC64 call @System@_16823 ; eax=length(name) mov edi, eax test edi, edi jle loc_46740E ; если не ввели имя, то выходим mov ds:dword_46BC7C, 1 
Пропустивши близько 100 операцій копіювання і перетворення рядків і стільки ж арифметичних (автор, мабуть, думав що таке їх кількість повинна відлякати початківців крекерів) бачимо вже більш цікаву картину: IDA - C: \ стаття \ dumped_.idb (dumped_.exe)
 push eax lea edx, [ebp- 6Ch ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F4h ] call @TControl@GetText$qqrv ; считываем ключ mov edx, [ebp- 6Ch ] pop eax ; eax = valid key! call @System@@LStrCmp$qqrv ; System::__linkproc__ LStrCmp(void) jnz loc_4680C0 ; прыгаем если не равны push ds:dword_46BD00 push ds:dword_46BD04 push ds:dword_46BD08 push ds:dword_46BD0C push offset _str___22.Text push ds:dword_46BC98 push ds:dword_46BC9C push ds:dword_46BCA0 push ds:dword_46BCA4 push ds:dword_46BCA8 push offset _str___22.Text push ds:dword_46BCF4 push ds:dword_46BCF8 push ds:dword_46BCFC lea eax, [ebp- 70h ] mov edx, 0Eh ; --------------- SUBROUTINE --------------------------------------- sub_468062 proc near call @System@@LStrCatN$qqrv ; подс4ет валидного регномера mov eax, [ebp- 70h ] ; сохраняем его в eax push eax lea edx, [ebp- 74h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F0h ] call @TControl@GetText$qqrv ; TControl::GetText(void) mov edx, [ebp- 74h ] pop eax call @System@@LStrCmp$qqrv ; финальное сравнение ! jnz short loc_4680C0 ; прыгаем, если BadGuy mov edx, 190h mov eax, ds:dword_46BC50 call @Forms@TCustomForm@SetClientWidth$qqri ; Forms::TCustomForm::SetClientWidth(int) mov eax, [ebp- 4 ] mov eax, [eax+ 310h ] mov dl, 1 call @Controls@TControl@SetVisible$qqro ; Controls::TControl::SetVisible(bool) mov eax, [ebp- 4 ] mov eax, [eax+ 314h ] mov dl, 1 call @Controls@TControl@SetVisible$qqro ; Controls::TControl::SetVisible(bool) mov ds:dword_46BCC4, 1 loc_4680C0: ; CODE XREF: CODE:00466B53j ; CODE:00468002j ... xor eax, eax ; выходим... pop edx pop ecx pop ecx mov fs:[eax], edx push offset loc_468107
push eax lea edx, [ebp- 6Ch ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F4h ] call @TControl@GetText$qqrv ; считываем ключ mov edx, [ebp- 6Ch ] pop eax ; eax = valid key! call @System@@LStrCmp$qqrv ; System::__linkproc__ LStrCmp(void) jnz loc_4680C0 ; прыгаем если не равны push ds:dword_46BD00 push ds:dword_46BD04 push ds:dword_46BD08 push ds:dword_46BD0C push offset _str___22.Text push ds:dword_46BC98 push ds:dword_46BC9C push ds:dword_46BCA0 push ds:dword_46BCA4 push ds:dword_46BCA8 push offset _str___22.Text push ds:dword_46BCF4 push ds:dword_46BCF8 push ds:dword_46BCFC lea eax, [ebp- 70h ] mov edx, 0Eh ; --------------- SUBROUTINE --------------------------------------- sub_468062 proc near call @System@@LStrCatN$qqrv ; подс4ет валидного регномера mov eax, [ebp- 70h ] ; сохраняем его в eax push eax lea edx, [ebp- 74h ] mov eax, [ebp- 4 ] mov eax, [eax+ 2F0h ] call @TControl@GetText$qqrv ; TControl::GetText(void) mov edx, [ebp- 74h ] pop eax call @System@@LStrCmp$qqrv ; финальное сравнение ! jnz short loc_4680C0 ; прыгаем, если BadGuy mov edx, 190h mov eax, ds:dword_46BC50 call @Forms@TCustomForm@SetClientWidth$qqri ; Forms::TCustomForm::SetClientWidth(int) mov eax, [ebp- 4 ] mov eax, [eax+ 310h ] mov dl, 1 call @Controls@TControl@SetVisible$qqro ; Controls::TControl::SetVisible(bool) mov eax, [ebp- 4 ] mov eax, [eax+ 314h ] mov dl, 1 call @Controls@TControl@SetVisible$qqro ; Controls::TControl::SetVisible(bool) mov ds:dword_46BCC4, 1 loc_4680C0: ; CODE XREF: CODE:00466B53j ; CODE:00468002j ... xor eax, eax ; выходим... pop edx pop ecx pop ecx mov fs:[eax], edx push offset loc_468107 
Отже, вантажимо Олю і ставимо геп на 00467FFD і 00468080, де в eax'ах зберігаються наші вірні реєстраційні дані. Підглядає їх і вводимо в крякмі. Мої валідниє дані такі:


Злом jump-корекцією

А для небажаючих довго колупатися в отладчике можу запропонувати просто створити нескладний * .crk файл і пропатчити кряк. Тепер він не буде перевіряти довжину вводяться імен та думатиме, що пройшов стандартизацію дані вводяться завжди =)
00066B47: 75 EB
00068002: 0F 90
00068003: 85 90
00068004: B8 90
00068005: 00 90
00068006: 00 90
00068007: 00 90
00068085: 75 90
00068086: 39 90

Copyright © 2005 NGH Group