Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29233
Скачиваний: 1689
Глава 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" ("Программа корректно завершилась").
Часть 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
...
Глава 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).
Часть 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".
Глава 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.