Мишень: Lemonade Tycoon v1.1.6(2100кб, ftp://ftp.gamehouse.com/pub/LemonadeTycoonInstall.exe) Что еще нужно: 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 Восстановление импорта Просто так дамп (я назвал файл 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'а (разумеется, все тестировалось после повторной загрузки программы)! Оказалось, что причиной тому был нижеприведенный участок кода: Code: 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, котоый это подтверждает. Code: 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 видим такой код:
Code: 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 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. Code: 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_57F99C seg000:004264C1 retn seg000:004264C1 sub_4264BC endp seg000:004264C1 Логично предположить, что до этого, во время проверки в эту самую ячейку производится запись в случае ввода неправильных данных. И если ячейка содержит 0, то программа будет постоянно пропускать этап проверки на зарегистрированность и сразу же запускать игру. Для поиска места проверки перезапустим жертву и установим точку останова на запись в указанную ячейки памяти: Бряк на запись в ячейку памяти, хрянящую статус зарегистрированности Жмем по F9 и перед нами появляется такой кусок кода: Code: 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 Теперь програма полностью работоспособна. Запуск игры происходи сразу же, без вывода любых нагов и прерываний посреди процесса игры. gl hf 2 all