Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29233

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

Глава 16. Принципы работы отладчиков 

169 

Как вы уже, видимо, обратили внимание, команда ассемблера может занимать 1, 

2 и более байтов. Например, 

mov ah,2

 —

 0B402h

 — 2 байта; 

ret

 —

 0C3h

 — 1 байт. 

Если мы "вручную" в процессе работы рассматриваемой нами программы заменим 

0B4h

,  скажем,  на 

0C3h

,  то  вместо 

mov  ah,2

  получим  инструкцию 

ret

  (машинный 

код которой 

0C3h

). Но команда 

ret

 однобайтовая, в отличие от 

mov ah,2

. Куда же 

денется второй байт команды 

mov ah,2

 (

02h

)? 

Процессор  просто  поймет 

02h

  как 

add  dh,[CD51+BP+SI]

.  Но  поскольку  данная 

команда занимает 4 байта, то процессор "присоединит" к 

02h

 еще и 

0B2h

51h

0CDh

Все,  код  начал  путаться...  Вот  именно  это  и  происходит  по  смещению 

105h

  

в табл. 16.3. Процессор распознал 

21h

  как  инструкцию 

and

. Но после 

and

  должны 

идти  приемник  и  источник  (например, 

and  ax,11b

).  Следовательно,  процессор 

возьмет  следующий  за 

21h

  байт,  которым  будет 

0C3h

21C3h

  как  раз  и  есть 

and 

bx,ax

! Отсюда и появляется по смещению 

103h

 какой-то 

push cx

 в табл. 16.2. 

Вывод: если вы "на лету" меняете код программы, имейте в виду, что однобай-

товую  команду  нужно  менять  на  однобайтовую,  двухбайтовую  на  двухбайтовую  
и т. д. Можно также менять две однобайтовые на одну двухбайтовую и пр. А можно, 
изменив  всего  1 байт,  сделать  всю  программу  (или  ее  часть)  другой.  Например,  
в Hacker's View вы видите один код, а выполняет программа совсем другие действия. 

Почему  же  тогда  отладчик  выполняет  программу  корректно,  даже  после  того, 

как код "искривился"? Дело в том, что обработчик прерывания 

03h

 отладчика вос-

станавливает код, который был изменен. После нажатия клавиши <F2> третий раз 
мы получим буквально следующее (табл. 16.4). 

Таблица 16.4. Пользователь нажал клавишу <F2> третий раз 

Смещение 

Инструкция ассемблера 

Машинный код 

Что на экране 

0100h 

mov ah,2 

0B402h 

MOV AH,02 

0B402h 

0102h 

mov dl,'Q' 

0B251h 

MOV DL,51 

0B251h 

0104h 

int 21h 

0CD21h 

INT 21 

0CD21h 

0105h 

int 3 

0CCh 

RET 

0C3h 

 
В данном случае получилось все корректно: команды 

ret

 и 

int 3

 занимают по 

одному  байту.  В  табл. 16.5  показано,  что  происходит  после  четвертого  нажатия 
клавиши <F2>. 

Таблица 16.5. Нажали клавишу <F2> четвертый и последний раз 

Смещение 

Инструкция ассемблера 

Машинный код 

Что на экране 

0100h 

mov ah,2 

0B402h 

MOV ah,02 

0B402h 

0102h 

mov dl,'Q' 

0B251h 

MOV dl,51 

0B251h 

0104h 

int 21h 

0CD21h 

INT 21 

0CD21h 

0105h 

ret 

0C3h 

RET 

0C3h 

 
Отладчик сообщает: "Program terminated OK" ("Программа корректно завершилась"). 


background image

 

Часть III. Файловая оболочка, вирус, резидент 

170 

16.2. Способы обойти отладку программы 

Как  же  нам  сделать  так,  чтобы  AFD  не  смог  производить  отладку  нашей  про-

граммы? 

Очень просто. Рассуждаем: AFD перехватывает прерывание 

03h

, затем вставля-

ет 

0CCh

 (

int 3

) после каждой команды. После этого процедура обработки прерыва-

ния 

03h

 останавливает нашу программу. 

Рассмотрим один из способов. Нам нужно записать в самое начало обработчика 

прерывания 

03h 

команду 

iret

.  Получить  адрес  обработчика  того  или  иного  пре-

рывания  можно  при  помощи  функции 

35h

  прерывания 

21h

.  Однако  можно  полу-

чить  и  поменять  адрес,  не  прибегая  к  помощи  операционной  системы,  хотя 
Microsoft делать так не советует. Но мы все равно рассмотрим такой способ. 

16.2.1. Таблица векторов прерываний 

Все текущие адреса обработчиков прерываний находятся в так называемой таб-

лице  векторов  прерываний,  которая  расположена  по  адресу 

0000:0000h

.  Как  вы 

знаете, адрес обработчика любого прерывания занимает 4 байта: сегмент (2 байта) 
и смещение (2 байта), причем первые 2 байта — это смещение, а вторые — сегмент 
(мы уже знаем, что все данные в компьютере хранятся в обратном порядке, вклю-
чая  расположение  "сегмент:смещение").  Таким  образом,  адрес  нулевого  прерыва-
ния  будет  расположен  по  адресу 

0000:0000h

,  первого — 

0000:0004h

,  второго — 

0000:0008h

  и  т. д.  В  листинге 16.2  приведен  пример  чтения  адреса  обработчика 

прерывания 

21h

 напрямую из таблицы векторов прерываний. 

Листинг 16.2. Получаем адрес вектора прерывания из таблицы векторов прерываний 

... 
xor ax,ax 
mov es,ax               ;Аннулируем es 
mov bx,es:[21h*4]       ;В bx — смещение 
mov es,es:[21h*4+2]     ;В es — сегмент 
 
;Сохраним адрес прерывания 21h на будущее 
mov Int_21h_offset,bx   ;Сохраним смещение 
mov Int_21h_segment,es  ;Сохраним сегмент 
 
;Вызываем прерывание 21h 
mov ah,2 
mov dl,'!' 
pushf                   ;Зачем здесь pushf — вы знаете... 
call dword ptr [Int_21h_offset]  ;Равносильно int 21h 
... 
 


background image

Глава 16. Принципы работы отладчиков 

171 

Int_21h_offset dw ? 
Int_21h_segment dw ? 
... 

В  целом,  пример  не  нуждается  в  пояснениях,  т. к.  все  операторы  мы  уже  рас-

сматривали. Однако стоит обратить внимание на следующие строки: 

mov bx,es:[21h*4] 

mov es,es:[21h*4+2] 

Ассемблер, в отличие от языков высокого уровня, вычислит выражение в квад-

ратных скобках всего один раз, если в них находятся только числа, а не регистры. 
То есть  процессор,  выполняя  приведенные  выше  строки, не станет постоянно вы-
числять, сколько будет 

21h*4+2

. Давайте разберемся, как такое возможно. 

Для  начала  вычислим  сами: 

21h*4+2=134

  (

86h

).  Подобные  выражения,  встре-

чающиеся  в  программе,  вычисляются  на  стадии  ассемблирования,  т. е.  програм-
мой-ассемблером. Запустив программу под отладчиком, мы увидим, что строка 

mov 

es,es:[21h*4+2]

  будет  отображаться  как 

mov  es,es:[86h]

.  Учитывая  это, можно 

смело записывать, например, так: 

mov ax,(23+5)*3 

mov cx,34h+98/2 

не  беспокоясь  за  то,  что  подобные  выражения  будут  постоянно  вычисляться про-
цессором, тем самым замедляя работу. 

А  зачем  это  нужно?  Неужели  программисту  самому  сложно  посчитать  и  запи-

сать  результат?  Ответ  прост:  для  удобства  и  наглядности  в  процессе  написания 
программы. В нашем случае мы сразу видим, что в 

es

 заносится сегмент прерыва-

ния 

21h

mov es,es:[21h*4+2]    ;21h — прерывание; +2 — сегмент. 

А если бы мы посчитали и сразу записали результат — 

mov es,es:[86h]

 —

 

то 

нам пришлось бы тогда при просмотре кода своей программы когда-нибудь в бу-
дущем постоянно высчитывать, вспоминать, что именно мы загружаем в 

es

. Мож-

но, конечно, делать пометки в виде комментариев. Но это уж на ваше усмотрение: 
кому как удобнее... 

Следует иметь в виду, что выражения типа 

mov  ax,[bx+di]

  остаются  как  есть, 

т. е. вычисление значения суммы регистров 

bx

  и 

di

 будет производиться постоян-

но, каждый раз при проходе процессором этого участка кода! Откуда знать ассемб-
лер-программе, какие значения находятся в регистрах 

bx

 и 

di

 в момент ассембли-

рования? В данном выражении в 

ax

  поместится  сумма  чисел,  которые  в  процессе 

выполнения этой команды находятся в регистрах 

bx

 и 

di

А  с  какой  целью  мы  перед 

[21h*4+2]

  указываем  регистр 

es

?  Конечно,  можно 

опустить 

es

, но тогда мы получим не адрес прерывания 

21h

, а что-то совсем другое. 

Регистр 

es

 указывает на то, что нужно загрузить слово в 

es

, которое расположено 

по смещению 

21h*4+2

, причем не из какого-нибудь сегмента, а именно из того, ко-

торый указан в 

es

. Поэтому мы, собственно, и загружали в начале ноль (т. е. сег-

мент таблицы векторов прерываний) в 

es

 (листинг 16.3).  


background image

 

Часть III. Файловая оболочка, вирус, резидент 

172 

Листинг 16.3. Заносим в es сегмент таблицы векторов прерываний 

... 
xor ax,ax 
mov es,ax              ;Аннулируем es 
... 
mov es,es:[21h*4+2]    ;В es — сегмент 
... 

16.3. Практика 

Теперь вернемся к отладчику. Итак, нам нужно занести в первый байт процеду-

ры обработки прерывания 

03h

 команду 

iret

, тем самым "вырубив" отладчик. Пре-

жде получим адрес обработчика прерывания 

03h

 (листинг 16.4). 

Листинг 16.4. Получаем вектор прерывания 03 из таблицы векторов прерываний 

... 
xor ax,ax 
mov es,ax 
mov bx,es:[03h*4]      ;В bx — смещение 
mov es,es:[03h*4+2]    ;В es — сегмент 
... 

Теперь занесем 

iret

 (машинный код 

iret

 — 

0CFh

) в самое начало обработчика: 

mov byte ptr es:[bx],0CFh 

После выполнения этой команды отладчиком, при вызове прерывания 

03h

 про-

изойдет  моментальный  возврат.  Следовательно,  отладчик  не  сможет  получить 
управление после выполнения каждой команды отлаживаемой программы. Поэто-
му  весь  остальной  код  (вплоть  до 

int  20h

)  выполнится  до  конца  без  остановок 

(см. файл dbg16_01.asm). 

На  команде  же 

int  20h

  отладчик  остановится  и  сообщит  о  нормальном  завер-

шении программы, т. к. прерывание 

20h

 также перехватывается им после загрузки, 

дабы контролировать завершение работы отлаживаемой программы. 

Для  того  чтобы  лучше  понять  работу  отладчика  в  файле-приложении,  найдите 

"!DEBUG.ASM"

. Что нужно сделать:  

1.  Изучите описания в данном файле. 
2.  Получите 

"!DEBUG.COM"

3.  Запустите его в режиме эмуляции DOS (Пуск | Выполнить | 

cmd

) или из файло-

вой оболочки типа Far Manager. 

4.  Запустите файл под отладчиком AFD или CodeView. 
5.  Обратите внимание, что после команды 

call

 следует 

nop

6.  В  AFD  нажмите  4 раза  клавишу  <F2>,  до  тех  пор  пока  не  появится  надпись 

"Program terminated OK". 


background image

Глава 16. Принципы работы отладчиков 

173 

7.  В  CodeView  нажмите  4  раза  клавишу  <F10>,  до  тех  пор  пока  не  появится  над-

пись "Process XXXX terminated normally". 

8.  Выйдите из отладчика. 
9.  Запустите снова файл под отладчиком. Все внимание на команду 

nop

, если тако-

вая имеется! 
Вот вам и доказательства всего вышеизложенного... 
Усвоив  приведенный  в  данной  главе  материал,  вы  без  труда  поймете,  почему 

при  чтении  программы  самой  себя  в  память  отладчик  работает  неверно.  Причем 
что именно он делает? Это показано в табл. 16.6.  

Таблица 16.6. Замена отладчиком инструкции 

ret

 

на 

int 3

 

Смещение 

Инструкция ассемблера 

Машинный код 

Что на экране 

0120h 

int 21h 

0CD21h 

INT 21 

0122h 

int 3 

0CCh 

RET 

 
Здесь  приведен  фрагмент  кода  программы,  которая  считывает  себя  в  память. 

Предположим, что полный код этой программы в листинге 16.5. 

Листинг 16.5. Полный код программы 

... 
mov ah,3Fh 
mov bx,Handle 
mov cx,offset Finish-100h 
mov dx,offset Begin 

int 21h      ;Эта строка... 
 
ret          ;...и эта строка приведена в таблице 16.6 
... 

Внимательно посмотрите на колонки Инструкция ассемблера и Что на экране

Подумайте, что произойдет, если мы колонку Что на экране скопируем в колонку 
Инструкция ассемблера

В этом случае просто затрется 

int  3

 (

0CCh

). То же самое происходит, если мы 

читаем свою программу в память с диска. На диске ведь вместо 

int  3

 будет 

ret

!  

А как отладчик получит управление, если 

int 3

 в памяти затирается (перезаписы-

вается)? 

На  компакт-диске,  в  каталоге  016,  вы  найдете  файлы  типа  dbg16_0?.asm.  По-

смотрите  их,  почитайте  описания,  запустите  под  отладчиком.  Однако  обращаем 
ваше  внимание,  что  некоторые  из  этих  программ  могут  зависать  при  их  отладке 
под ОС Windows 9x