адрес возврата).
Говорим: «bpx GetWindowTextA», выходим из отладчика, вводим серийный номер в окно редактирования, нажимаем «ОК» — отладчик всплывает (ну, будем считать, что всплывает, в действительности он может и не всплыть — все зависит от того, какую API-функцию использовал программист, так что тут возможны варианты). Даем команду «d esp->8» (если окно дампа отключено, перед этим необходимо дать команду «wd»), а затем «p ret» — в окне появляется введенная нами строка (cм рис. 5):
Все, что нам нужно — это ее адрес, равный в данном случае 2F46E0. Логично — чтобы сравнить пароль с оригиналом, защита должна его считать из памяти. И в этом момент из кустов появляется мы (в смысле, мыщъх и отладчик). Команда «bpm 2F46E0» устанавливает точку останова на адрес 2F46E0, заставляя soft-ice всплывать при каждом чтении/записи этой ячейки. Звучит прекрасно, но на практике срабатывает далеко не всегда. Вовсе не факт, что в первое же всплытие отладчика выведет нас к защитному коду. Скорее всего, здесь будет библиотечная функция, копирующая пароль в локальный буфер, передаваемый по цепочке другим функциям. И хорошо, если по ссылке! Зачастую буфер передается по значению, т. е. копируется в другой буфер целиком. На каждый из таких буферов приходится ставить точку останова, а количество точек останова равно четырем. Это не ограничение отладчика, это просто архитектура у Пня такая. Отсюда еще не следует, что точки останова на данные бесполезны, просто они сильны совсем в другой области. Вот, например, мы выяснили, что переменной x содержится флаг регистрации. Как именно выяснили, не суть важно. Допустим, встретили код типа: cmp [x],0/jz nag_screen (если переменная x равна нулю, вывести ругательный диалог). Как определить где именно этот x инициализируется? В большинстве случаев перекрестные ссылки автоматически восстанавливаются ИДОЙ, однако разработчик защитного механизма может легко ослепить ее, но едва ли он справится с командой «bpm x» (установить точку останова на доступ к переменной x). А вот другой вариант: изменили мы пару байтиков в программе, а она, обнаружив факт своего взлома, отказалась работать. Чтобы найти процедуру проверки целостности кода достаточно установить одну или несколько точек останова на модифицированные ячейки. Да много чего можно придумать, главное — фантазию иметь!
:STACK 0012E138 77E155B5 oorwiz!.text+0001AC5E 0012E168 77E15A3B USER32!DefWindowProcW+0105 0012E188 77E1FB52 USER32!SendMessageW+0043 0012E214 77E1E6C3 USER32!WINNLSGetIMEHotkey+0E15 0012E254 77E1E561 USER32!EditWndProc+0075 0012E278 77E198DF USER32!ScrollWindow+0096 0012E29C 77E13EB0 USER32!ShowCursor+0057 0012E2BC 77E16469 USER32!SetTimer+0435 0012E2E0 77E164E5 USER32!SetRect+0065 0012E300 00F7A1B6 USER32!CallWindowProcW+0019 0012E320 00F7A403 oorwiz!.text+000191B6 0012E33C 00F7BC02 oorwiz!.text+00019403 ; _AfxPostInitDialog 0012E39C 00F7BC92 oorwiz!.text+0001AC02 ; AfxWndProc 0012E3BC 77E13EB0 oorwiz!.text+0001AC92 ; 0012E3DC 77E1591B USER32!SetTimer+0435 Листинг 2. Раскрутка стека в soft-ice.
Десять первых вызовов относятся к библиотеке USER32.DLL и не представляют для нас никакого интереса (soft-ice неправильно определил принадлежность вызова 12E138h, приписав его к oorwiz, но oorwiz не может располагаться по адресам 77E155B5 — эта зона принадлежит USER32). А вот одиннадцатый вызов 12E320, ведущий к адресу F7A403 весьма интересен. Заглянув сюда дизассемблером, мы обнаружим следующий код:
.text:1001A3E6 call dword ptr [eax+10Ch] .text:1001A3EC test eax, eax .text:1001A3EE jnz short loc_1001A406 .text:1001A3F0 push [ebp+arg_8] .text:1001A3F3 mov eax, [esi] .text:1001A3F5 push [ebp+arg_4] .text:1001A3F8 mov ecx, esi .text:1001A3FA push [ebp+arg_0] .text:1001A3FD call dword ptr [eax+110h] .text:1001A403 mov [ebp+var_4], eax ; адрес возврата .text:1001A406 .text:1001A406 loc_1001A406: ; CODE XREF: CWnd::WindowProc()+24^j .text:1001A406 mov eax, [ebp+var_4] .text:1001A409 pop esi .text:1001A40A leave .text:1001A40B retn 0Ch Листинг 3. Фрагмент дизассемблерного листинга, с подозрительным условным переходом.
Функция 1001A3FD: call dword ptr [eax+110h] — та самая, к которой ведет адрес возврата. Именно она и выводит противный регистрационный диалог. Прокручивая экран дизассемблера вверх, легко найти условный переход, расположенный по адресу 101AEE, который прыгает за диалог возврата. Изменив jnz на jmp short, мы навсегда уберем диалог с экрана. Конечно, такая мера еще не зарегистрирует программу, но это все-таки кое-что!
1 2 3 4
Рисунок 5. Определение адреса, по которому записывается считанный пароль.
Все, что нам нужно — это ее адрес, равный в данном случае 2F46E0. Логично — чтобы сравнить пароль с оригиналом, защита должна его считать из памяти. И в этом момент из кустов появляется мы (в смысле, мыщъх и отладчик). Команда «bpm 2F46E0» устанавливает точку останова на адрес 2F46E0, заставляя soft-ice всплывать при каждом чтении/записи этой ячейки. Звучит прекрасно, но на практике срабатывает далеко не всегда. Вовсе не факт, что в первое же всплытие отладчика выведет нас к защитному коду. Скорее всего, здесь будет библиотечная функция, копирующая пароль в локальный буфер, передаваемый по цепочке другим функциям. И хорошо, если по ссылке! Зачастую буфер передается по значению, т. е. копируется в другой буфер целиком. На каждый из таких буферов приходится ставить точку останова, а количество точек останова равно четырем. Это не ограничение отладчика, это просто архитектура у Пня такая. Отсюда еще не следует, что точки останова на данные бесполезны, просто они сильны совсем в другой области. Вот, например, мы выяснили, что переменной x содержится флаг регистрации. Как именно выяснили, не суть важно. Допустим, встретили код типа: cmp [x],0/jz nag_screen (если переменная x равна нулю, вывести ругательный диалог). Как определить где именно этот x инициализируется? В большинстве случаев перекрестные ссылки автоматически восстанавливаются ИДОЙ, однако разработчик защитного механизма может легко ослепить ее, но едва ли он справится с командой «bpm x» (установить точку останова на доступ к переменной x). А вот другой вариант: изменили мы пару байтиков в программе, а она, обнаружив факт своего взлома, отказалась работать. Чтобы найти процедуру проверки целостности кода достаточно установить одну или несколько точек останова на модифицированные ячейки. Да много чего можно придумать, главное — фантазию иметь!
Раскрутка стека
Внешние проявления защитного механизма засечь очень легко. Как правило, это либо окошко с надписью «trial expired», либо форма для ввода серийного номера. Установить точку останова на WM_COMMAND легко, но что это дает? Мы окажемся внутри оконной процедуры, в глубоких недрах которой зарыт защитный код. Можно, конечно, и потрассировать, но это же — сколько времени уйдет? Вот бы узнать — какие команды исполнялись до этого? Обратить выполнение программы вспять и посмотреть — какой именно код определят факт регистрации программы. Некоторые отладчики поддерживают механизм обратной трассировки (back trace), запоминая все выполняемые команды и складывая их в специальный буфер, однако это сильно замедляет выполнение программы и выводит антиотладочные приемы на оперативный простор. Мы поступим иначе. Soft-ice поддерживает шикарную команду «STACK», раскручивающую стек и выводящую адреса всех материнских функций. Не совсем равноценная замена обратной трассировки, но для большинства случаев ее вполне хватает. В нашем случае ответ отладчика выглядит так::STACK 0012E138 77E155B5 oorwiz!.text+0001AC5E 0012E168 77E15A3B USER32!DefWindowProcW+0105 0012E188 77E1FB52 USER32!SendMessageW+0043 0012E214 77E1E6C3 USER32!WINNLSGetIMEHotkey+0E15 0012E254 77E1E561 USER32!EditWndProc+0075 0012E278 77E198DF USER32!ScrollWindow+0096 0012E29C 77E13EB0 USER32!ShowCursor+0057 0012E2BC 77E16469 USER32!SetTimer+0435 0012E2E0 77E164E5 USER32!SetRect+0065 0012E300 00F7A1B6 USER32!CallWindowProcW+0019 0012E320 00F7A403 oorwiz!.text+000191B6 0012E33C 00F7BC02 oorwiz!.text+00019403 ; _AfxPostInitDialog 0012E39C 00F7BC92 oorwiz!.text+0001AC02 ; AfxWndProc 0012E3BC 77E13EB0 oorwiz!.text+0001AC92 ; 0012E3DC 77E1591B USER32!SetTimer+0435 Листинг 2. Раскрутка стека в soft-ice.
Десять первых вызовов относятся к библиотеке USER32.DLL и не представляют для нас никакого интереса (soft-ice неправильно определил принадлежность вызова 12E138h, приписав его к oorwiz, но oorwiz не может располагаться по адресам 77E155B5 — эта зона принадлежит USER32). А вот одиннадцатый вызов 12E320, ведущий к адресу F7A403 весьма интересен. Заглянув сюда дизассемблером, мы обнаружим следующий код:
.text:1001A3E6 call dword ptr [eax+10Ch] .text:1001A3EC test eax, eax .text:1001A3EE jnz short loc_1001A406 .text:1001A3F0 push [ebp+arg_8] .text:1001A3F3 mov eax, [esi] .text:1001A3F5 push [ebp+arg_4] .text:1001A3F8 mov ecx, esi .text:1001A3FA push [ebp+arg_0] .text:1001A3FD call dword ptr [eax+110h] .text:1001A403 mov [ebp+var_4], eax ; адрес возврата .text:1001A406 .text:1001A406 loc_1001A406: ; CODE XREF: CWnd::WindowProc()+24^j .text:1001A406 mov eax, [ebp+var_4] .text:1001A409 pop esi .text:1001A40A leave .text:1001A40B retn 0Ch Листинг 3. Фрагмент дизассемблерного листинга, с подозрительным условным переходом.
Функция 1001A3FD: call dword ptr [eax+110h] — та самая, к которой ведет адрес возврата. Именно она и выводит противный регистрационный диалог. Прокручивая экран дизассемблера вверх, легко найти условный переход, расположенный по адресу 101AEE, который прыгает за диалог возврата. Изменив jnz на jmp short, мы навсегда уберем диалог с экрана. Конечно, такая мера еще не зарегистрирует программу, но это все-таки кое-что!