Зняття ASProtect 1.22 - 1.23 Beta 21 і злом Lemonade Tycoon v1.1.6


Зняття ASProtect 1.22 - 1.23 Beta 21 і злом Lemonade Tycoon v1.1.6
Автор: ProTeuS [[email protected]]

Мета: Lemonade Tycoon v1.1.6 (2100кб)
Що ще потрібно:
1. OllyDbg + плагін OllyDump
2. ImpRec 1.6 Final
3. IDA
4. Hiew або будь-який інший шестнадцятірічний редактор
4. Пиво "Beck's", 0.5;)

жертва

Нашої мішенню є одна нехитра іграшка. Але найважливіше, що вона запротекчена "ASProtect 1.22 - 1.23 Beta 21", про що нам люб'язно розповів PEID:


Жертва поки недосяжна для жорстоких підступів дизассемблера;)



Пошук OEP

Користуватися автораспаковщікамі начебто AsprStripperXP ми не будемо і тому запустимо OllyDbg і відкриємо в отладчике виконуваний файл проги.


Entry Point

Для успішного знаходження оригінальної точки входу жертви ми повинні включити в Олька обробку винятків (memory access voilation, access violation). Заходимо в опції налагодження (натиснувши Alt + O) і встановлюємо прапорці як на малюнку нижче:


Включення обробки винятків

повертаємося у вікно "CPU" OllyDbg іначінаем проходити виключення (натискаючи Shift + F9). Після 27 раз (якщо ще один раз натиснути Shift + F9, наша прога пройде OEP і запуститься) ми з'явимося за адресою .D405CC


Вікно "CPU" після обробки 27 винятків

Далі необхідно поставити крапку зупинки на команді RETN (.D40609) і наказати Олька "дійти" до неї натисканням Shift + F9 ще раз. Останнім етапом у пошуку OEP буде установка бряка на доступ Аспром до кодової секції захищається ним програми. Для цього натискаємо Alt + M (щоб викликати вікно "Memory Map") і тиснемо F2 на другий за рахунком секції заданого процесу. У нашому випадку це Lemonade codesection за адресою 00401000.


Геп на доступ до кодової секції

Останній раз жмякайте по Shift + F9 і ВСЕ !!! Ми на OEP. Щоб всетаки в це повірити, можна натиснути Ctrl + A і олька проведе аналіз коду і дізассемблерний лістинг замість незрозумілих однобайтном виразів знайде "людський" вигляд =)


А ось і OEP!



робимо дамп


Вибираємо в головному меню Plugins-OllyDump-Dump debugged process і у вікні натискаємо "Dump" (перед цим не забувши зняти прапор "Rebuild import", щоб OllyDump самостійно не намагався відновлювати імпорт дампа).


Робимо дамп за допомогою плагіна OllyDump

Про адреси точки входу і секцій можна не переживати, оскільки OllyDump сам їх прораховує.

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


Просто так дамп (я назвав файл dumped.exe) запускатися не буде - йому потрібно відновити таблицю імпортованих функцій. Для цього запустимо ImpRec і виберемо в ній наш досліджуваний процес Lemonade.exe (він буде все ще "стояти" на OEP, адже Olly разом з ним ми ще не закривали). В поле "OEP" вводимо адресу нашої OEP (який дорівнює 4FB6B) і натискаємо по "IAT AutoSearch" - "Get Imports" - "Show Invalid". Отримуємо пару сотень невизначених функцій. Для їх відновлення спробуємо провести трасування в 2 етапи. У першому клацнемо правою кнопкою миші на одній з виділених невизначених функцій і вибираємо в мене "Trace Level1 (Disasm)". У другому етапі заново клацаємо по "Show invalid", а далі відновлюємо функції за допомогою плагіна Plugin Tracers - Asprotect 1.22.


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

Після проведених маніпуляцій у вікні податків з'явиться такий напис


Імпорт вдало відновлений ...

Це говорить про те, що імпорт відновлений і можна зробити модифікацію нашого дампа (dumped.exe) натисканням на кнопці "Fix Dump"


Дамп вдало модифікований ...

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


PEID підтверджує, що аспр вдало знятий

Приступимо до його аналізу ...

Налагодження і злом жертви за допомогою патча


Завантажуємо розпаковану прогу в отладчике. Оскільки для введення реєстраційних даний вона використовує стандартні вікна введення,

Вікно введення реєстраційних даних

і стандартне вікно помилки, то було решінію по-старому встановити точки зупину на викликах функцій MessageBoxA (виводять повідомлення про помилку) або GetDlgItemTextA (зчитувальних вводиться текст).


Установка геп на виклик функцій MessageBoxA

Натискання на кнопку "Enter License Information" привело на такий шматок коду в Олька:


Припинилися на виведенні помилку довжини регкода (вона повинна бути дорівнює 20)

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

  seg000: 00425D99 call esi;
  DialogBoxParamA;
  Create a modal dialog box from a
 
  seg000: 00425D99;
  dialog box template resource
 
  seg000: 00425D9B mov edi, eax seg000: 00425D9D
 
  seg000: 00425D9D loc_425D9D:;
  CODE XREF: sub_425C59 + 12Dj
 
  seg000: 00425D9D cmp edi, 3EBh seg000: 00425DA3 jnz short loc_425DAA;
  jmp якщо не вводилися рег.данние
 seg000: 00425DA5 call sub_425228;
  CreateProcessA
 seg000: 00425DAA
 
  seg000: 00425DAA loc_425DAA:;
  CODE XREF: sub_425C59 + 14Aj
 
  seg000: 00425DAA mov eax, edi seg000: 00425DAC
 
  seg000: 00425DAC loc_425DAC:;
  CODE XREF: sub_425C59 + 178j
 
  seg000: 00425DAC pop edi seg000: 00425DAD pop esi seg000: 00425DAE pop ebx seg000: 00425DAF leave
 
  seg000: 00425DB0 retn
 

Тобто, після введення рег.данних, головне вікно програми просто дезактивувати і додаток закривалося. Але найцікавіше, що до цього (.00425DA5) йшов виклик функції, яка створювала новий процес-копію щойно запущеного нами процесу-жертви (аля CopeMemII АРМи, хоча процесу злому цей фінт, по-моєму, ніяких складнощів не додав, крім як незвичайності). Ось шматок коду процедури sub_425228, кото це підтверджує.

  seg000: 00425278 lea eax, [ebp + ProcessInformation]
 
  seg000: 0042527B mov [ebp + StartupInfo.cb], 44h seg000: 00425282 mov [ebp + StartupInfo.dwFlags], 40h seg000: 00425289 push eax;
  lpProcessInformation
 seg000: 0042528A lea eax, [ebp + StartupInfo]
 
  seg000: 0042528D push eax;
  lpStartupInfo
 seg000: 0042528E push esi;
  lpCurrentDirectory
 seg000: 0042528F push esi;
  lpEnvironment
 seg000: 00425290 push esi;
  dwCreationFlags
 seg000: 00425291 push esi;
  bInheritHandles
 seg000: 00425292 push esi;
  lpThreadAttributes
 seg000: 00425293 push esi;
  lpProcessAttributes
 seg000: 00425294 lea eax, [ebp + ApplicationName] seg000: 0042529A push esi;
  lpCommandLine
 seg000: 0042529B push eax;
  lpApplicationName
 seg000: 0042529C call ds: CreateProcessA seg000: 004252A2 pop esi seg000: 004252A3 leave
 
  seg000: 004252A4 retn
 
  seg000: 004252A4 sub_425228 endp

 seg000: 004252A4
 

Обстеживши код трохи вище, я зрозумів чому записані в реєстрі рег.данние НЕ зчитувалися при запуску. Виявилося, що при создани процесу створювався унікальний мютекс, який "давав знати" батьківському процесу про те, чи запущений дочірній процес як "запустити іграшку", або як "перевірити коректність рег.данних". Оскільки перевірки після введення імені-коду проходили вже в доцернем процесі, а в OllyDbg їх відловити не вдавалося (за момент підключення до толькочто створеному процесу всі перевірки вже були пройденими, а можливості використання гідного ring0-mode відладчика не було), то я вирішив піти на деяку хитрість: модифікувати бінарник досліджуваного файлу таким чином, щоб першою з рядків його виконуваної функцій (наприклад, WinMain) було зациклення (оппкоди EBFE). Таким чином створений процес зациклився і я б зміг підключиться до нього вчасно і відновивши оригінальні 2 байта коду спокійно поісследовать потрібні ділянки. Але, на жаль, пробуючи реалізувати описаний метод було витрачено півгодини, а дочірній процес ніяк не зациклюються! Аби не допустити більше витрачати час на знаходження причини (яка, мабуть, полягала в дублюванні функцій Main в жертві) я вирішив піти більш "традиційним" методом і поставити геп в батьківському процесі на виклик Функ TerminateProcess і ExitProcess;)


Ставимо геп на виклики функцій завершення роботи програми

Запускаємо на виконання наш файл, вводимо необхідні рег.данние (я вводив ім'я: ProTeuS і код: 1234567890abcdeABCDE) і без проблем отримуємо шукану точку (.44А841)


Виявлення точки виклику функції завершення роботи програми

поднявщісь на кілька рівнів вгору по структурі дізассемблерного лістингу в IDA бачимо такий код:

  seg000: 004247A0
 
  seg000: 004247A0;
  --------------- SUBROUTINE ---------------------------------- -----
 
  seg000: 004247A0
 
  seg000: 004247A0;
  Attributes: bp-based frame
 
  seg000: 004247A0
 
  seg000: 004247A0 sub_4247A0 proc near;
  CODE XREF: sub_437940 + 26p
 
  seg000: 004247A0;
  sub_437940 + 3Bp
 
  seg000: 004247A0
 
  seg000: 004247A0 var_200 = dword ptr -200h
 
  seg000: 004247A0
 
  seg000: 004247A0 push ebp seg000: 004247A1 mov ebp, esp seg000: 004247A3 sub esp, 200h seg000: 004247A9 push esi seg000: 004247AA push edi seg000: 004247AB call sub_437940 seg000: 004247B0 mov dword ptr [eax + 20h], offset aLemonade;
  "Lemonade"
 
  seg000: 004247B7 call sub_437940 seg000: 004247BC mov dword ptr [eax + 24h], offset aLemonadeTycoon;
  "Lemonade Tycoon"
 
  seg000: 004247C3 call sub_437940 seg000: 004247C8 mov dword ptr [eax + 28h], offset a1_1_5;
  "1.1.5"
 
  seg000: 004247CF call sub_437940 seg000: 004247D4 mov dword ptr [eax + 28h], offset a1_1_6;
  "1.1.6"
 
  seg000: 004247DB call sub_437940 seg000: 004247E0 mov dword ptr [eax + 1Ch], offset aB6081ca706b415;
  "{B6081CA-706B-415E-AE52-910C4FB06016}"
 
  seg000: 004247E7 call sub_437940 seg000: 004247EC mov dword ptr [eax + 10h], offset a1_0;
  "1.0"
 
  seg000: 004247F3 call sub_437940 seg000: 004247F8 mov dword ptr [eax + 14h], offset a72733b3Ac0f4d4;
  "{72733B3-AC0F-4D43-BED1-25EE1194A7BA}"
 
  seg000: 004247FF call sub_437940 seg000: 00424804 mov dword ptr [eax + 18h], offset a48033dc6A54144;
  "{48033DC6-A541-4454-A9CE-3186C3365B75}"
 
  seg000: 0042480B call sub_437940 seg000: 00424810 mov dword ptr [eax + 88h], 96h seg000: 0042481A call sub_437940 seg000: 0042481F xor edi, edi seg000: 00424821 push 68h seg000: 00424823 mov [eax + 34h], edi seg000: 00424826 pop esi seg000 : 00424827
 
  seg000: 00424827 loc_424827:;
  CODE XREF: sub_4247A0 + 98j
 
  seg000: 00424827 call sub_437940 seg000: 0042482C mov [eax + esi], edi seg000: 0042482F add esi, 4 seg000: 00424832 cmp esi, 84h seg000: 00424838 jl short loc_424827 seg000: 0042483A call sub_4263DD;
  функції перевірки
 seg000: 0042483F test eax, eax seg000: 00424841 jz short loc_42484A seg000: 00424843 push 1;
  int
 seg000: 00424845 call _exit seg000: 0042484A;
  -------------------------------------------------- -------------------------
 
  seg000: 0042484A
 
  seg000: 0042484A loc_42484A:;
  CODE XREF: sub_4247A0 + A1j
 
  seg000: 0042484A call sub_4264BC;
  eax = ds: dword_57F99C
 seg000: 0042484F test eax, eax seg000: 00424851 jz short loc_42489B;
  JMP if eax = 1 (в до4ернем процесі)
 seg000: 00424853 mov esi, ds: GetModuleHandleA seg000: 00424859 lea eax, [ebp + var_200] seg000: 0042485F push eax;
  char *
 seg000: 00424860 push edi;
  lpModuleName
 seg000: 00424861 call esi;
  GetModuleHandleA
 
  seg000: 00424863 push eax;
  hInstance
 seg000: 00424864 call sub_426404 seg000: 00424869 pop ecx seg000: 0042486A lea eax, [ebp + var_200] seg000: 00424870 pop ecx seg000: 00424871 mov ds: dword_57F988, edi seg000: 00424877 push eax;
  int
 seg000: 00424878 push edi;
  lpModuleName
 seg000: 00424879 call esi;
  GetModuleHandleA
 
  seg000: 0042487B push eax;
  hInstance
 seg000: 0042487C call sub_425C59;
  ReadParametrs
 seg000: 00424881 pop ecx seg000: 00424882 cmp eax, 3EBh seg000: 00424887 pop ecx seg000: 00424888 jnz short loc_424891 seg000: 0042488A push 1;
  int
 seg000: 0042488C call _exit;
  закриваємо додатком після введення рег.данних
 seg000: 00424891;
  -------------------------------------------------- -------------------------
 
  seg000: 00424891
 
  seg000: 00424891 loc_424891:;
  CODE XREF: sub_4247A0 + E8j
 
  seg000: 00424891 mov ds: dword_57F988, 1 seg000: 0042489B
 
  seg000: 0042489B loc_42489B:;
  CODE XREF: sub_4247A0 + B1j
 
  seg000: 0042489B pop edi seg000: 0042489C pop esi seg000: 0042489D leave;
  стрибаємо на .4365C9
 seg000: 0042489E retn
 
  seg000: 0042489E sub_4247A0 endp

 seg000: 0042489E
 

Нескладно здогадатися, що 0042483A call sub_4263DD є ні що інше, як виклик функції перевірки, що впливає на хід перевірки за адресою .0042484F. Саме від її результатів перевірки залежить, чи буде викликана функція ExitProcess, або чи буде відновлений звичайний порядок роботи в разі введення зареєстрованих користувачів і запуск іграшки.
Заглянувши в функцію, що викликається 0042484A call sub_4264BC бачимо, що в регістр eax заноситься 1, зміст комірки пам'яті за адресою 57F99C.
  seg000: 004264BC
 
  seg000: 004264BC;
  --------------- SUBROUTINE ---------------------------------- -----
 
  seg000: 004264BC
 
  seg000: 004264BC
 
  seg000: 004264BC sub_4264BC proc near;
  CODE XREF: sub_413B78 + 5Cp
 
  seg000: 004264BC;
  sub_4157E8 + 531p ...
 
  seg000: 004264BC mov eax, ds: dword_57F99C seg000: 004264C1 retn
 
  seg000: 004264C1 sub_4264BC endp

 seg000: 004264C1
 

Логічно припустити, що до цього, під час перевірки в цю саму осередок проводиться запис в разі введення неправильних даних. І якщо осередок містить 0, то програма буде постійно пропускати етап перевірки На оформлене і відразу ж запускати гру. Для пошуку місця перевірки перезапустити жертву і встановимо точку зупину на запис в зазначену осередки пам'яті:

Геп на запис в комірку пам'яті, хрянящуюю статус зареєстрованих

Тиснемо по F9 і перед нами з'являється такий шматок коду:

  seg000: 00426296
 
  seg000: 00426296 push ebp seg000: 00426297 mov ebp, esp seg000: 00426299 sub esp, 530h seg000: 0042629F push esi seg000: 004262A0 xor esi, esi seg000: 004262A2 cmp ds: dword_57F9A0, esi seg000: 004262A8 jnz loc_4263DA seg000: 004262AE lea eax , [ebp + KeyName] seg000: 004262B4 push offset a1831;
  "183-1"
 
  seg000: 004262B9 push eax seg000: 004262BA mov ds: dword_57F99C, 1;
  записує 1 в я4ейку пам'яті в слу4ае введення неправильних рег.данних
 seg000: 004262C4 call ds: lstrcpy

За адресою .004262A2 бачимо цікаву команду в порівнянні осередку 57F9A0 з 0. Це місце перевірки на відлік 60 секунд з моменту запуску гри (клацаємо по .4262A2, натискаємо праву кнопку миші, вибираємо "Find References to - Address constant" і бачимо за адресою. 00426046 відповідний код MOV DWORD PTR DS: [57F9A0], 1. Він виконується тільки в разі зареєстровані юзера і "дає знати" грі, що не потрібно перериватися після хвилини виконання). Якщо вміст комірки 57F9A0 буде 0, то гра вилетить навіть після пропатчіванія змінної статусу зареєстрованих! Отже, для повного злому захисту потрібно поміняти результат перевірки за адресою 004262A2, щоб хоч один з операндів дорівнював 1 і замість порівняння вмісту комірки 57F9A0 на 0 (адреса .4261D6) записати туди 1. Щоб не використовувати більш масивні операції з модифікацією осередків пам'яті можна просто занести 1 в регістр esi. Для цього можна замінити команду xor esi, esi за адресою 004262A0 на inc esi і nop (оппкоди 46 90). Замість CMP DWORD PTR DS: [57F9A0], 0 потрібно записати INC DWORD PTR DS: [57F9A0], адже за замовчуванням незареєстрована прога буде зберігати там 0 і ми таким чином оптимізуючи код внесемо в комірку пам'яті 1. За бажанням наступний умовний перехід JNE cracked.00426292 можна замінити на безумовний JMP cracked.00426292. Це і зробимо в будь-якому шестнадцатиричном редакторі. Я вибрав Hiew. Для пропатчіваніе відкриваємо в ньому файл dumped_.exe, 2 рази тиснемо Enter, потім F5, вводимо .4262A0, тиснемо F3, вводимо 46 90. Те ж робимо і для інших команд, оппкоди яких можна побачити на скрині нижче. Зберігаємо зміни F9.


Патчим бінарник гри в шестнадцатиричном редакторі Hiew

Тепер програма повністю працездатна. Запуск гри станься відразу ж, без виведення будь-яких нагов і переривань посеред процесу гри.

Файли до статті:

Мета: Lemonade Tycoon v1.1.6 (2100кб)
Дамп з відновленим імпортом: dumped_.rar

gl hf 2 all
05.01.2006