Техника защиты компакт-дисков от копирования

Принудительный выход из функции


Запускаем тестовую программу, набиваем в одном или нескольких окнах какой-нибудь текст, затем в меню "Help"

выбираем пункт "About TestCEdit"

и в появившемся диалоговом окне щелкаем по кнопке "make error". Опля! Программа выбрасывает критическую ошибку и, если мы нажмем на "ОК", все не сохраненные данные необратимо погибнут, что никак не входит в наши планы. Однако при наличии предварительно установленного отладчика мы еще можем кое-что предпринять. Пусть для определенности это будет Microsoft Visual Studio Debugger.

Нажимаем кнопку "Отменау" и отладчик немедленно дизассемблирует функцию, возбудившую исключение (см. листинг 3.5, приведенный ниже).[Y90] [n2k91] 

Листинг 3.5. Отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение

0040135C             push           esi

0040135D             mov             esi,dword ptr [esp+8]

00401361             push           edi

00401362             movsx         edi,byte ptr [ecx+esi]

00401366             add             eax,edi

00401368             inc             ecx

00401369             cmp             ecx,edx

0040136B             jl       00401362



0040136D             pop             edi

0040136E             pop             esi

0040136F             ret             8

Листинг 5 отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение

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

Переместив курсор в окно дампа, набьем в строке адреса название регистра указателя вершины стека –— "ESP" и нажмем на клавишу <Enter>. Содержимое стека тут же предстанет перед нашими глазами (листинг 3.6).:

Листинг 3.6. Поиск адреса возврата из текущей функции (выделен полужирным шрифтом)

0012F488  0012FA64  0012FA64  004012FF 

0012F494  00000000  00000064  00403458 

0012F4A0  FFFFFFFF  0012F4C4  6C291CEA 

0012F4AC  00000019  00000000  6C32FAF0 

0012F4B8  0012F4C0  0012FA64  01100059 

0012F4C4  006403C2  002F5788  00000000 

0012F4D0  00640301  77E16383  004C1E20

Листинг 6 поиск адреса возврата из текущей функции (выделен жирным шрифтом)

Первые два двойных слова соответствуют машинным командам POP EDI/POP ESI  и не представляют для нас совершенно никакого интереса. А вот следующее двойное слово содержит адрес выхода в материнскую процедуру (в приведенном выше листинге 3.6 оно выделено полужирным шрифтом). Как раз его-то нам и надо!

Нажимаем <Ctrl>+<-D> и затем 0x4012FF, отладчик послушно отображает следующий дизассемблерный текст (листинг 3.7).:

Листинг 3.7. Дизассемблерный листинг материнской функции

004012FA             call           00401350

004012FF             cmp             eax,0FFh

00401302             je       0040132D

00401304             push           eax

00401305             lea             eax, [esp+8]

00401309             push           405054h

0040130E             push           eax

0040130F             call           dword ptr ds:[4033B4h]

00401315             add             esp, 0Ch

00401318             lea             ecx, [esp+4]

0040131C             push           0

0040131E             push           0

00401320             push           ecx

00401321             mov             ecx, esi



00401323             call           00401BC4

00401328             pop             esi

00401329             add             esp,64h

0040132C             ret

0040132C

0040132D             push           0

0040132D             ; эта ветка получает управление, если Функция 401350h вернет FFh

0040132F             push           0

00401331             push           405048h

00401336             mov             ecx,esi

00401338             call           00401BC4

0040133D             pop             esi

0040133E             add             esp,64h

00401341             ret

Листинг 7 дизассемблерный листинг материнской функции

Смотрите: если регистр EAX равен FFh, то материнская функция передает управление на ветку 40132Dh и спустя несколько машинных команд завершает свою работу, передавая бразды правления функции более высокого уровня. Напротив, если EAX != FFh, то его значение передается функции 4033B4h. Следовательно, мы можем предположить, что FFh –— это флаг ошибки и есть. Возвращаемся в подопытную функцию, нажав <Ctrl>+<-G> и "EIP", переходим в окно "Registers" и меняем значение регистра EAX на FFh.

Теперь необходимо найти подходящую точку возврата из функции. Просто перейти к машинной команде "RET" нельзя, поскольку перед выходом из функции следует в обязательном порядке сбалансировать стек или нас "выбросит" неизвестно куда и программа "обрушится" окончательно.

В общем случае число PUSH-команд должно в точности соответствовать количеству POP (также учитывайте, что PUSH DWORD X эквивалентен SUB ESP, 4, а POP DWORD X –—

ADD ESP, 4). Проанализировав дизассемблерный листинг функции, мы приходим к выводу, что для достижения "гармонии добра и зла" мы должны "стащить" с вершины стека два двойных слова, соответствующие машинным командам 40135С:PUSH ESI и 401361:PUSH EDI. Это достигается передачей управления по адресу 40136Dh, где "живут" дваа "добродушныхех" команды POP'a'а, приводящие стек в равновесное состояние.Подводим сюда курсор и уверенным щелчком правой клавиши мыши вызываем контекстное меню, среди пунктов которого выбираем "Set Next Statement". Как вариант можно перейти в окно регистров и изменить значение регистра EIP с 401362h на 40136Dh.

Нажатием клавиши <F5> мы заставляем процессор продолжить выполнение программы и… о чудо! Она действительно продолжает свою работу ("незлобное ругательство" на ошибку последней операции –— не в счет!). Несохраненные данные спасены!


Содержание раздела