Снятие 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 [cybercracker@narod.ru]

Мишень: 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, eaxseg000:00425D9D
seg000:00425D9D loc_425D9D: ; CODE XREF: sub_425C59+12Dj
seg000:00425D9D cmp edi, 3EBhseg000: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, ediseg000:00425DAC
seg000:00425DAC loc_425DAC: ; CODE XREF: sub_425C59+178j
seg000:00425DAC pop ediseg000:00425DAD pop esiseg000:00425DAE pop ebxseg000:00425DAF leave
seg000:00425DB0 retn

Т.е., после ввода рег.данных, главное окно приложения просто дезактивировалось и приложение закрывалось. Но самое интересное, что до этого (.00425DA5) шел вызов функции, которая создавала новый процесс-копию только что запущенного нами процесса-жертвы(аля CopeMemII армы, хотя процессу взлома этот финт, по-моему, никакой сложности не прибавил, кроме как необычности). Вот кусок кода процедуры sub_425228, котоый это подтверждает.

seg000:00425278 lea eax, [ebp+ProcessInformation]
seg000:0042527B mov [ebp+StartupInfo.cb], 44hseg000:00425282 mov [ebp+StartupInfo.dwFlags], 40hseg000: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:CreateProcessAseg000:004252A2 pop esiseg000: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 ; --------------- S U B R O U T I N E ---------------------------------------
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 ebpseg000:004247A1 mov ebp, espseg000:004247A3 sub esp, 200hseg000:004247A9 push esiseg000:004247AA push ediseg000:004247AB call sub_437940seg000:004247B0 mov dword ptr [eax+20h], offset aLemonade ; "Lemonade"
seg000:004247B7 call sub_437940seg000:004247BC mov dword ptr [eax+24h], offset aLemonadeTycoon ; "Lemonade Tycoon"
seg000:004247C3 call sub_437940seg000:004247C8 mov dword ptr [eax+28h], offset a1_1_5 ; "1.1.5"
seg000:004247CF call sub_437940seg000:004247D4 mov dword ptr [eax+28h], offset a1_1_6 ; "1.1.6"
seg000:004247DB call sub_437940seg000:004247E0 mov dword ptr [eax+1Ch], offset aB6081ca706b415 ; "{B6081CA-706B-415E-AE52-910C4FB06016}"
seg000:004247E7 call sub_437940seg000:004247EC mov dword ptr [eax+10h], offset a1_0 ; "1.0"
seg000:004247F3 call sub_437940seg000:004247F8 mov dword ptr [eax+14h], offset a72733b3Ac0f4d4 ; "{72733B3-AC0F-4D43-BED1-25EE1194A7BA}"
seg000:004247FF call sub_437940seg000:00424804 mov dword ptr [eax+18h], offset a48033dc6A54144 ; "{48033DC6-A541-4454-A9CE-3186C3365B75}"
seg000:0042480B call sub_437940seg000:00424810 mov dword ptr [eax+88h], 96hseg000:0042481A call sub_437940seg000:0042481F xor edi, ediseg000:00424821 push 68hseg000:00424823 mov [eax+34h], ediseg000:00424826 pop esiseg000:00424827
seg000:00424827 loc_424827: ; CODE XREF: sub_4247A0+98j
seg000:00424827 call sub_437940seg000:0042482C mov [eax+esi], ediseg000:0042482F add esi, 4seg000:00424832 cmp esi, 84hseg000:00424838 jl short loc_424827seg000:0042483A call sub_4263DD ; функЦия проверки
seg000:0042483F test eax, eaxseg000:00424841 jz short loc_42484Aseg000:00424843 push 1 ; int
seg000:00424845 call _exitseg000: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, eaxseg000:00424851 jz short loc_42489B ; JMP if eax=1 (в до4ернем процессе)
seg000:00424853 mov esi, ds:GetModuleHandleAseg000: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_426404seg000:00424869 pop ecxseg000:0042486A lea eax, [ebp+var_200]seg000:00424870 pop ecxseg000:00424871 mov ds:dword_57F988, ediseg000: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 ecxseg000:00424882 cmp eax, 3EBhseg000:00424887 pop ecxseg000:00424888 jnz short loc_424891seg000: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, 1seg000:0042489B
seg000:0042489B loc_42489B: ; CODE XREF: sub_4247A0+B1j
seg000:0042489B pop ediseg000:0042489C pop esiseg000: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 ; --------------- S U B R O U T I N E ---------------------------------------
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_57F99Cseg000:004264C1 retn
seg000:004264C1 sub_4264BC endp
seg000:004264C1

Логично предположить, что до этого, во время проверки в эту самую ячейку производится запись в случае ввода неправильных данных. И если ячейка содержит 0, то программа будет постоянно пропускать этап проверки на зарегистрированность и сразу же запускать игру. Для поиска места проверки перезапустим жертву и установим точку останова на запись в указанную ячейки памяти:

Бряк на запись в ячейку памяти, хрянящуюю статус зарегистрированности

Жмем по F9 и перед нами появляется такой кусок кода:

seg000:00426296
seg000:00426296 push ebpseg000:00426297 mov ebp, espseg000:00426299 sub esp, 530hseg000:0042629F push esiseg000:004262A0 xor esi, esiseg000:004262A2 cmp ds:dword_57F9A0, esiseg000:004262A8 jnz loc_4263DAseg000:004262AE lea eax, [ebp+KeyName]seg000:004262B4 push offset a1831 ; "183-1"
seg000:004262B9 push eaxseg000: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

Дата створення/оновлення: 25.05.2018

stop war in Ukraine

ukrTrident

stand with Ukraine