Авторские статьи Снятие ASProtect 1.22 - 1.23 Beta 21 и патчинг Lemonade Tycoon v1.1.6

Discussion in 'Статьи' started by ProTeuS, 14 Aug 2006.

  1. ProTeuS

    ProTeuS --

    Joined:
    26 Nov 2004
    Messages:
    1,239
    Likes Received:
    542
    Reputations:
    445
    Мишень: 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:

    [​IMG]
    Жертва пока недосягаема для жестоких козней дизассемблера ;)

    Поиск OEP
    Пользоваться автораспаковщиками вроде AsprStripperXP мы не будем и поэтому запустим OllyDbg и откроем в отладчике исполняемый файл проги.
    [​IMG]
    Entry Point​

    Для успешного нахождения оригинальной точки входа жертвы мы должны включить в ольке обработку исключений (memory access voilation, access violation). Заходим в опции отладки (нажав Alt+O) и устанавливаем флажки как на рисунке ниже:
    [​IMG]
    Включение обработки исключений при работе с памятью​

    возвращаемся в окно "CPU" OllyDbg иначинаем проходить исключения(нажимая Shift+F9). После 27 раз (если еще один раз нажать Shift+F9, наша прога пройдет OEP и запустится) мы появимся по адресу .D405CC
    [​IMG]
    Окно "CPU" после обработки 27 исключений​

    Далее необходимо поставить точку останова на команде RETN (.D40609) и приказать ольке "дойти" до нее нажатием Shift+F9 еще раз. Последним этапом в поиске OEP будет установка бряка на доступ аспром к кодовой секции защищаемого им приложения. Для этого нажимаем Alt+M (чтобы вызвать окно "Memory Map") и жмем F2 на второй по счету секции заданного процесса. В нашем случае это Lemonade codesection по адресу 00401000.

    [​IMG]
    Бряк на доступ к кодовой секции​

    Последний раз жмякаем по Shift+F9 и ВСЕ!!! Мы на OEP. Чтобы всетаки в это поверить, можно нажать Ctrl+A и олька проведет анализ кода и дизассемблерный листинг вместо непонятных однобайтным выражений обретет "человеческий" вид =)

    [​IMG]
    А вот и OEP!

    Делаем дамп

    Выбираем в главном меню Plugins-OllyDump-Dump debugged process и в появившемся окне нажимаем "Dump" (перед этим не забыв снять флаг "Rebuild import", чтобы OllyDump самостоятельно не пытался восстанавливать импорт дампа).

    [​IMG]
    Делаем дамп с помощью плагина 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.

    [​IMG]
    Imprec восстанавливает импорт...

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

    [​IMG]
    Импорт удачно восстановлен...

    Это говорит о том, что импорт восстановлен и можно произвести модификацию нашего дампа (dumped.exe) нажатием на кнопке "Fix Dump"

    [​IMG]
    Дамп удачно модифицирован...

    Итак, мы получили ПОЛНОСТЬЮ работоспособный распакованый файл (dumped_.exe)!

    [​IMG]
    PEID подтверждает, что аспр удачно снят

    Приступим к его анализу...
     
    2 people like this.
  2. ProTeuS

    ProTeuS --

    Joined:
    26 Nov 2004
    Messages:
    1,239
    Likes Received:
    542
    Reputations:
    445
    Отладка и взлом жертвы с помощью патча

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


    [​IMG]
    Окно ввода регистрационных данных

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


    [​IMG]
    Установка бряков на вызове функций MessageBoxA

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


    [​IMG]
    Прервались на выводе ошибке длинны регкода (она должна быть равна 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 ;)


    [​IMG]
    Ставим бряки на вызовы функций завершения работы приложения

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


    [​IMG]
    Обнаружение точки вызова функции завершения работы приложения

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

    ProTeuS --

    Joined:
    26 Nov 2004
    Messages:
    1,239
    Likes Received:
    542
    Reputations:
    445
    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, то программа будет постоянно пропускать этап проверки на зарегистрированность и сразу же запускать игру. Для поиска места проверки перезапустим жертву и установим точку останова на запись в указанную ячейки памяти:


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

    Жмем по 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.


    [​IMG]
    Патчим бинарник игры в шестнадцатиричном редакторе Hiew​

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

    gl hf 2 all
     
    #3 ProTeuS, 14 Aug 2006
    Last edited: 14 Aug 2006
    1 person likes this.