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

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

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

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

Добавлен: 16.02.2019

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

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

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

Глава 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

 нужно обязательно.  


background image

 

Часть 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 


background image

Глава 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 из оболочки, в кото-


background image

 

Часть 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 нашего резидента 


background image

Глава 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    ;Не нашли точку вообще? Тогда на выход 
...