Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29249
Скачиваний: 1689
Глава 24. Резидентный антивирус
239
но никак не
.8086
.286
Это связано с тем, что 32-разрядные регистры появились только в процессоре 80386.
Соответственно, нужно использовать и ассемблер-программу (MASM/TASM), кото-
рая поддерживает инструкции процессоров 386+. Если в процессе ассемблирования
программа выдает ошибку на первой строке (
.386
), то вам необходимо искать ас-
семблер-программу более новой версии. Например, TASM 5.0 или MASM 6.13 (по-
следнюю можно загрузить с сайта http://www.Kalashnikoff.ru).
Далее приведены простые примеры использования 32-разрядных регистров:
mov eax,0 ;eax=0, ax=0
mov eax,15h ;eax=15h, ax=15h
mov ax,0FF00h ;ax=0FF00h, eax=0FF00h
mov eax,12345678h ;eax=12345678h, ax=5678h
Не стоит забывать, что данные в компьютере хранятся в обратном порядке.
Примеры:
...
(1) mov Variable,12345678h
(2) mov eax,Variable
(3) mov ax,word ptr Variable
...
(4) Variable dd ?
...
В строке (1) мы заносим в переменную
Variable
32-разрядное число
12345678h
.
Обратите внимание, что сама переменная должна иметь тип
dd
(от англ. double
word — двойное слово) (строка (4)). В строке (2) мы загружаем в
eax
это число,
при этом 32-разрядный аккумулятор становится равным
12345678h
. Ничего не ме-
няется, поскольку мы обращаемся к данной переменной как к двойному слову
и читаем ее как двойное слово. Однако в памяти число
12345678h
будет распола-
гаться в обратном порядке, т. е.
78563412h
. При загрузке его в 32-разрядный ре-
гистр оно снова "перевернется".
Другое дело, когда программист обращается к подобным 32-разрядным пере-
менным как к слову, выделяя из него два байта (строка (3)). Учитывая, что число
хранится в памяти в обратном порядке, то после выполнения строки (3)
ax
будет
равен
5678h
. Поэкспериментируйте с этим, т. к. только на экспериментах можно
полностью понять принцип хранения чисел в памяти.
Обратите также внимание, как мы загружаем в
ax
число (строка (3)). Перед
Variable
находится
word ptr
. Это указывает процессору на то, что в
ax
нужно за-
нести два байта (слово).
Word ptr
опускается, если переменная имеет тип
dw
, и за-
гружаем мы в 16-разрядный регистр. Однако если программисту необходимо за-
грузить в 16-разрядный регистр число из 32-разрядной переменной, имеющей тип
dd
, то указывать
word ptr
нужно обязательно.
Часть III. Файловая оболочка, вирус, резидент
240
24.1.1. 16-
и 32-разрядные отладчики
До сих пор мы часто использовали в своей практике 16-разрядный отладчик
AFD Pro версии 1.0, распознающий инструкции процессоров 8086—80286. Если
с его помощью мы попробуем отладить программу, использующую регистры и ко-
манды процессоров 80386+, то отладчик будет отображать их некорректно по вполне
понятным причинам. Данная проблема может быть разрешена путем использования
отладчиков, поддерживающих и распознающих машинные коды процессо-
ров 80386+. Таковыми являются, например, CodeView, который входит в комплект
MASM 6.13, TurboDebugger от компании Borland из комплекта Turbo Assembler
либо SoftIce, предназначенный для опытных программистов.
На рис. 24.1 и 24.2 показано, как отладчики AFD и CodeView отображают 32-
разрядные регистры. На рис. 24.3 показано, как включить режим просмотра 32-
разрядных регистров в отладчике CodeView.
Рис. 24.1. Корректное отображение 32-разрядных регистров в CodeView
Рис. 24.2. Некорректное отображение 32-разрядных регистров в AFD
Глава 24. Резидентный антивирус
241
Рис. 24.3. Включение режима просмотра 32-разрядных регистров в CodeView
24.1.2. Директива use16/use32
При исследовании файла-приложения к данной главе обратите внимание на но-
вую директиву:
use16
.
cseg segment use16 ;По умолчанию 16-разрядные данные
use16
сообщает ассемблеру о том, что по умолчанию будут использоваться 16-
разрядные регистры, данные и в целом адресация.
Дело в том, что если в листинге программы стоит директива
.386
и выше, то ас-
семблер воспринимает всю адресацию как 32-разрядную по умолчанию (
use32
),
которая в основном используется при написании полноценных 32-разрядных при-
ложений под операционную систему Windows. Мы же пока пишем 16-разрядные
программы, которые только в некоторых случаях будут использовать 32-разрядную
адресацию. Поэтому, с точки зрения программирования, будет удобней явно ука-
зать
use16
. Здесь мы затронули эту директиву лишь по той причине, что она тесно свя-
зана с директивой
.386
. В настоящий момент вам необходимо уяснить следующее.
Убирая данную директиву
use16
, ассемблер-программа будет использовать
use32
(т. е. 32-разрядную адресацию) и выдавать ошибку на строках вида:
mov dx,offset Message
В данном случае (если мы уберем
use16
) нам следует записывать:
mov edx,offset Message
24.1.3. Сопоставление ассемблера
и языков высокого уровня
Почему программы, написанные на языках высокого уровня, имеют больший
объем, чем аналогичные на ассемблере?
В нашем примере мы постарались это показать. Обратите внимание, что файлу
antivr24.asm сопутствует уже знакомый нам файл display.asm из оболочки, в кото-
Часть III. Файловая оболочка, вирус, резидент
242
ром собраны процедуры для работы с видеокартой. Для того чтобы заново не пи-
сать процедуру вывода окна на экран (а наш антивирус выводит окна прямым ото-
бражением в видеобуфер), мы просто воспользуемся уже готовыми и написанными
нами процедурами.
Однако в файле display.asm существуют также подпрограммы, которые, например,
прячут и восстанавливают курсор. Наш антивирус не будет управлять курсором, сле-
довательно, подобные процедуры можно убрать, тем самым сократив объем про-
граммы. Да, на ассемблере это возможно. Но на языках высокого уровня — нет.
Например, в С используется директива
#include stdio.h
, которая подобна ас-
семблерной
include display.asm
. Однако программист не может посмотреть или
изменить то, что находится внутри
stdio.h
. А в ней есть множество процедур, ко-
торые, скорее всего, программист и вызывать-то не будет. Отсюда и рост размера
программы.
Обратите внимание, что мы специально не убирали подпрограмму
Hide_cursor
и подобную ей, имитируя, насколько это возможно, языки высокого уровня.
Вы можете добавлять файлы из оболочки для применения в своих программах,
как мы это делаем в нашем антивирусе. Только не стоит забывать о переменных,
используемых во вставляемых процедурах! Их можно и нужно размещать в той про-
цедуре, которая их использует, тем самым упростив перенос в другие программы.
24.2. Резидентный антивирус. Практика
Все, что расположено после метки
Init
, вам уже известно. Обращаем только
ваше внимание на то, что вызывать прерывание
21h
в резидентной части мы будем
как
int 99h
, для чего скопируем оригинальный вектор
21h
-го в
99h
-й. Для чего это
нужно — мы уже рассматривали в прошлых главах.
Наш резидентный антивирус будет находиться в памяти и контролировать от-
крытие или запуск программы. "На лету" проверит на зараженность, и если про-
грамма заражена, то вылечит ее. Искать будем только наш вирус, который писали
в главе 20. Для экспериментов возьмите его из этой главы. Еще раз предупреждаем:
будьте предельно осторожны! Используйте только тот вирус, код которого приве-
ден в главе 20, не изменяя в нем ни одного байта.
Наш резидент будет интенсивно использовать стек, поэтому лучше мы восполь-
зуемся свободной памятью, предназначенной для PSP, на которую установим реги-
стры
ss:sp
, во избежание переполнения стека программы, вызывающей прерыва-
ние
21h
(листинг 24.1).
Листинг 24.1. Перенос стека в область PSP
...
(1) cli ;Запретим прерывания
(2) mov cs:[0],ss ;Сохраним сегментные регистры
(3) mov cs:[2],sp
(4) ;Установим стек на область PSP нашего резидента
Глава 24. Резидентный антивирус
243
(5) push cs
(6) pop ss
(7) mov sp,0FEh
...
Обратите внимание, что перед сменой стековых регистров необходимо запре-
тить прерывания командой
cli
(строка (1)).
Как видно из приведенного выше кода, мы сохраняем
ss:sp
в нашем сегменте
по смещению 0 (т. е. в PSP нашего антивируса) (строки (2)—(3)). Отдельные пере-
менные заводить не будем, т. к. это увеличит размер программы.
После того как мы изменили стек, необходимо настроить сегментные регистры
(листинг 24.2).
Листинг 24.2. Настройка сегментных регистров
...
push ds
pop es
push cs
pop ds
...
Как мы уже рассматривали, перед вызовом функций
4Bh
(запуск программы)
и
3Dh
(открытие файла),
ds
должен указывать на сегмент, а
dx
на смещение имени
файла. Для того чтобы не затереть эти регистры, мы
ds
перенесем в
es
, а сам же
ds
сделаем равным
cs
. Теперь адрес запускаемого или открываемого другой програм-
мой файла находится в
es:dx
. Нужно внимательно следить за тем, чтобы корректно
сохранить
dx
. Можно, конечно, занести его в переменную и не волноваться, но так,
как мы делаем в нашем примере, будет проще, да и меньше байтов потребуется.
Хотя, если подобных данных нужно хранить много, то лучше завести отдельную
переменную.
Теперь вызываем процедуру
Check_prog
, которая проверяет, файл какого типа
запускается/открывается — COM-файл или иной (листинг 24.3).
Листинг 24.3. Часть подпрограммы Check_prog
...
(1) cld ;Направление — вперед!
(2) mov di,dx ;Ищем в имени файла точку
(3) mov al,'.'
(4) mov cx,65 ;Всего будем просматривать 65 символов
(5) Next_sym:
(6) repne scasb ;Ищем в es:di, пока НЕ найдем точку.
(7) jne No_com ;Не нашли точку вообще? Тогда на выход
...