Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29230
Скачиваний: 1689
Часть III. Файловая оболочка, вирус, резидент
154
ратном порядке). Если после команды
call
следует
dword ptr
, то в стек заносится
не только смещение, но и сегмент.
Возникает вопрос: как компьютер различает, когда нужно извлечь из стека
только смещение, а когда смещение и сегмент? Ответ простой: существуют две
разновидности инструкции
ret
:
ret
и
retf
. Инструкция
retf
(от англ. return far —
дальний возврат) извлекает из стека не только смещение, но и сегмент (в отличие
от команды
ret
, достающей смещение). Если мы вызовем дальнюю процедуру
(
call dword ptr [Far_proc]
), а выйдем из нее, используя
ret
, то компьютер про-
сто "зависнет". В листингах 15.5 и 15.6 приведены примеры.
Листинг 15.5. Вызов ближней процедуры
...
[1234:0200h] call Near_proc ;Вызов ближней процедуры
...
;Ближняя процедура Near_proc (находится в том же сегменте, что и
;и основная программа)
[1234:4569h] Near_proc proc
...
;Правильный выход из ближней процедуры (процедуры, которая располагается
;в том же сегменте, что и программа, ее вызывающая)
[1234:6789h] ret
[1234:6789h] Near_proc endp
...
Листинг 15.6. Вызов дальней процедуры
...
[1234:0200h] call dword ptr cs:[New_proc]
...
;Дальняя процедура New_proc (находится в другом сегменте)
[3456:0300h] New_proc proc
...
;Правильный выход из дальней процедуры (процедуры, которая находится
;в другом сегменте, в отличие от программы, ее вызывающей)
[3456:0534h] retf
[3456:0534h] New_proc endp
...
Одна и та же процедура не может быть вызвана двумя разными способами. Если
это дальняя процедура, то вызвать ее стандартными способами, как ближнюю, не
получится, даже если она будет находиться в том же сегменте, в котором выполня-
ется код ее вызова.
Глава 15. Обработка аппаратных прерываний
155
Возможно, это покажется вам на первый взгляд сложным, но, поверьте, все на
самом деле очень просто. Более того, путаницы не возникает с вызовами и возвра-
тами. Нужен, прежде всего, опыт работы с ассемблером.
15.3. Механизм работы
аппаратных прерываний. Оператор iret
"Ага! — скажет читатель. — Что-то мы тут упустили! А почему тогда перед
call dword ptr [Int_21h_vect]
мы заносим в стек командой
pushf
регистр фла-
гов, но впоследствии его не достаем?"
...
pushf ;Заносим в стек флаги
call dword ptr [Int_21h_vect] ;Вызываем дальнюю процедуру
...
Зачем нужно заносить флаги в стек командой
pushf
перед вызовом оригинального
обработчика прерывания? Попробуем дать ответ на этот вопрос. Сперва рассмотрим,
для чего вообще вызываются прерывания, и что происходит в этот момент.
Существуют два вида прерываний: программные и аппаратные. Программные
прерывания вызываются программами при помощи инструкции
int
, что мы уже
неоднократно делали. К таким прерываниям можно отнести, в частности,
21h
,
16h
,
10h
и пр.
В настоящий момент нас больше интересуют аппаратные прерывания, которые
вызываются автоматически аппаратурой (процессором) в тех или иных ситуациях.
Например, при делении на ноль, обновлении таймера, нажатии пользователем кла-
виши и пр. В листинге 15.7 приведен фрагмент кода обработчика прерывания
09h
(клавиатура) и некоторой программы, выполняемой в данный момент (обращайте
внимание на сегменты).
Листинг 15.7. Обработчик прерывания 09h
;Обработчик прерывания 09h, расположенный где-то в памяти, который
;активизируется при нажатии и отпускании какой-нибудь клавиши:
...
[0900:0050h] mov al,bl
;Здесь неважно, какой код находится. Главное то, что сегмент другой,
;отличный от того, в котором находится наша программа (см. ниже)
[0900:0052h] ...
[0900:0345h] iret
...
;Это часть нашей программы, которая в данный момент выполняется:
...
(1) [1234:0200h] mov ax,Num_regax
Часть III. Файловая оболочка, вирус, резидент
156
(2) [1234:0205h] cmp ax,17
(3) [1234:0208h] jne Not_equal
...
Аппаратное прерывание
09h
вызывается всегда, когда пользователь нажимает
или отпускает какую-нибудь клавишу, даже тогда, когда процессор чем-то занят
(например, копирует файл или форматирует диск).
Допустим, пользователь нажал клавишу в тот момент, когда наша программа
выполнила строку (2). Что произойдет? На первый взгляд кажется, что ничего осо-
бенного. Процессор занесет клавишу в буфер, которая будет храниться там до того
момента, пока DOS ее оттуда не запросит и не выполнит определенные действия.
Однако на самом деле процессор проделает уйму работы за считанные доли мил-
лисекунд. Рассмотрим это подробней.
У нас в регистрах находятся определенные числа. Более того, после выполнения
строки (2) изменится регистр флагов. В строке (3) мы проверяем состояние флага
нуля, который сигнализирует нам в данном случае о том, равен ли
ax
17. Наша про-
грамма находится в момент вызова прерывания в сегменте
1234h
, смещение
0208h
(адрес следующей команды).
Как сделать так, чтобы передать управление прерыванию
09h
, которое выполнит
свою работу (занесет в буфер клавиатуры код клавиши, которую программы могут
затем получить при помощи функции
10h
прерывания
16h
), при этом вернуться в то
место, с которого произошло прерывание, не нарушив работы нашей программы?
Процессору нужно запомнить номер текущего сегмента (
cs
), смещение сле-
дующей команды (оно всегда в
ip
), а также состояние флагов в стеке. Этого доста-
точно для того, чтобы вернуться назад после того, как прерывание
09h
отработает,
не нарушив при этом ход выполнения нашей программы. Заметьте, что при вызове
прерывания регистры не сохраняются, кроме флагов и
cs:ip
! В случае необходи-
мости сохранять регистры должно то прерывание, которое получило управление
(в случае нажатия клавиши —
09h
).
После того как процессор сохранил регистры
cs:ip
и флаги в стеке, он передает
управление обработчику аппаратного прерывания (в нашем случае —
09h
), адрес
которого находится в определенном месте памяти (где именно находятся адреса
прерываний, мы рассмотрим позже). Что значит "передает управление"? Да просто
"прыгает" на определенный адрес. Это будет называться "дальним безусловным
переходом", т. к. мы прыгаем не только на смещение внутри сегмента (как в случа-
ях, которые мы рассматривали уже, например,
jmp Init
в разд. 15.1.2), а на сег-
мент и смещение.
Итак, управление получило прерывание
09h
. Если в процессе его работы меня-
ются какие-то регистры, то оно должно их предварительно сохранить, иначе наша
программа, после отработки прерывания
09h
, получит совсем другие значения
в регистрах.
Например, если
09h
изменит
ax
, предварительно не сохранив его, а затем, соот-
ветственно, не восстановив, то при выходе из данного прерывания мы получим, что
ax
не равен
Num_Regax
(1).
Глава 15. Обработка аппаратных прерываний
157
Отсюда жесткое правило: если вы пишете свой обработчик, то обязательно со-
храняйте все регистры, которые он меняет. Если вы написали обработчик, напри-
мер, прерывания
21h
, и после загрузки его в память компьютер "зависает" или ве-
дет себя не так, как хотелось бы, то ищите ошибку в вашем резиденте. Возможно,
вы забыли сохранить тот или иной регистр в стеке. Хотя причин, по которым
"зависает" компьютер, может быть очень много...
Итак, прерывание
09h
(т. е. обработчик прерывания
09h
) отработало: процессор
дошел до инструкции
iret
(от англ. interrupt return — возврат прерывания). Эта
инструкция отличается от
ret
тем, что при ее выполнении процессор извлечет из
стека сегмент (
cs
), смещение (
ip
) и регистр флагов, вместо одного смещения (
ip
)
(как
ret
). Вот и вся разница между
ret
и
iret
.
Резюмируем:
ret
достает из стека только смещение для возврата; процедура должна нахо-
диться в том же сегменте, из которого ее вызывают (ближняя процедура —
near
(по умолчанию));
retf
достает из стека сегмент и смещение; процедура может находиться в лю-
бом сегменте, независимо от того, откуда ее вызывают (дальняя процедура —
far
или
dword ptr
);
iret
достает из стека сегмент, смещение и адрес флагов. Используется для вы-
хода из прерываний.
Команда
iret
вытаскивает из стека адрес возврата (
cs:ip
). В примере из лис-
тинга 15.7
cs=1234h
,
ip=0208h
, а в регистре флагов установлен флаг нуля, т. е. его
значение 1. Затем управление передается на этот адрес. Наша программа продол-
жает работать, не догадываясь даже о том, что кто-то ее прервал. Естественно, все
эти процедуры выполняются чрезвычайно быстро.
Теперь ответ на главный вопрос: почему перед вызовом прерывания командой
вида
call dword ptr cs:[Int_21h_vect]
мы заносим в стек регистр флагов ко-
мандой
pushf
и впоследствии его не достаем?
Ответ прост. Стоит только посмотреть на отличие оператора
retf
от
iret
(см. ранее). При передаче управления (вызове) прерывания командой
call dword
ptr ...
процессор заносит в стек только сегмент (
cs
) и смещение (
ip
) следующей
за командой
call
инструкции. А
iret
извлечет из стека сегмент, смещение и фла-
ги. Но флаги ведь не заносятся командой
call dword ptr
! Мы их заносим сами,
"вручную". Иначе произойдет нарушение работы стека, и компьютер "зависнет".
Как мы уже убедились, за стеком нужно следить очень внимательно!
15.4. Практика
Что делает наш обработчик прерывания
21h
?
Он умышленно передает неверные данные программе, которая ищет файлы
в каталоге, используя функции
4Eh
и
4Fh
. Подробнее эти функции мы рассмотрим,
когда будем писать оболочку. Здесь все вкратце.
Часть III. Файловая оболочка, вирус, резидент
158
Функция
4Eh
ищет первый файл или каталог на диске, и, если какой-нибудь
файл найден, заносит в DTA информацию о нем, которая содержит, в частности:
имя и расширение файла;
размер файла;
дату и время создания файла;
атрибуты файла.
То же самое делает функция
4Fh
. Ее отличие только в том, что она ищет второй
и последующие файлы. Так устроена MS-DOS.
Наш резидент контролирует прерывание
21h
. Если вызывается одна из упомяну-
тых выше функций, то резидент подменяет информацию, которая находится в DTA
после вызова прерывания
21h
(листинг 15.8).
Листинг 15.8. Проверка вызовов прерывания 21h
...
;Проверяем: вызывает ли какая-то программа функцию 4Eh или 4Fh (поиск файлов)
cmp ah,4Eh
je Do_not
cmp ah,4Fh
je Do_not
;Если вызывается другая функция, то просто передадим управление оригинальному
;обработчику 21h командой "дальний jmp". Здесь заносить в стек флаги не нужно,
;т. к. мы больше не вернемся в наш обработчик. Уходим навсегда...
Go_21h:
jmp dword ptr cs:[Int_21h_vect]
;Итак, кто-то вызывает функцию 4Fh или 4Eh...
Do_not:
pushf
call dword ptr cs:[Int_21h_vect]
...
Прежде чем менять информацию о найденных файлах в DTA, нам нужно вы-
звать прерывание
21h
самим, для того чтобы оно занесло необходимые данные
в эту область. Мы, конечно, можем самостоятельно записать туда что угодно, не
вызывая указанное выше прерывание, но это будет просто и неинтересно.
Обратите внимание на две команды, следующие за меткой
Do_not
в листин-
ге 15.8. Здесь мы вызываем прерывание
21h
. Но каким образом! Ведь данный
фрагмент кода — это и есть наш обработчик прерывания
21h
. Следовательно, если
мы вызовем прерывание стандартной командой
int 21h
, то она передаст управле-
ние на наш обработчик, что зациклит процедуру и "повесит" компьютер. Нам это