Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 024)

InterReklama Advertising

Ассемблер? Это просто! Учимся программировать

______________________________________

Выпуск N 024 (Антивирус)

Вы шумiце, шумiце, нада мною бярозы.

Калышыце, люляйце цiхай ласкай зямлю.

А я лягу-прылягу край гасцiнца старога.

Я здарожыуся трохi, я рассылку прачту...

Доброе время суток, уважаемые подписчики!

Сегодня в номере:

Сотрудничество.

Резидентный антивирус. Введение в 32-х разрядные регистры.

Несколько слов от автора.

Сотрудничество Дорогие мои друзья! Я стараюсь опубликовывать в рассылке только лучшие материалы по программированию, которые мне попадаются на глаза.

Совсем недавно ко мне пришло письмо от еще одного ведущего рассылки "Уроки для начинающих программистов" Эдуарда Дмитриева. Чем примечательна данная рассылка? Тем, что:

рассылка выходит довольно-таки давно, что доказало намерения автора довести дело до конца;

материал преподносится на доступном языке (я сам с удовольствием ознакомился с некоторыми выпусками);

вы без труда догоните ушедших вперед подписчиков;

автор рассматривает параллельно несколько языков программирования (HTML, Pascal...);

низкий уровень отписки от рассылки, что свидетельствует о заинтересованности читателей;

на сайте автора собран уникальный в своем роде материал по многим языкам программирования (я даже скачал небольшую книгу по программированию на Перле, содержанием которой очень доволен);

автор не нарушает авторских прав лиц, чей материал опубликован на его сайте;

автор излагает материал на грамотном русском языке, что, безусловно, в настоящее время является редкостью в Интернете;

у рассылки имеются подобные нашим экспертные группы, но более широкого спектра: - С, С++

- DELPHI

- HTML, XML, XHTML, CSS, CSS2, дизайн и промоутинг

- Linux

- Pascal

- Perl

- PHP

- Общие вопросы программирования

- Windows

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

Почему я рекомендую именно данную рассылку? Дело в том, что в скором времени мы будем изучать программирование на Ассемблере под Windows. Как я уже писал, было бы неплохо объединить нашу рассылку с рассылкой по программированию на языках высокого уровня. Думаю, что, если сотрудничество удастся, то мы сможем общими усилиями создать по истине стоящую вещь в Интернете, которая будет полезна широкому кругу лиц.

Каковы цели сотрудничества?

Прежде всего - объединение знаний обоих ведущих (меня и Эдуарда). Эдуард знает то, что не знаю я, а я же, в свою очередь, знаю то, что не знает Эдуард. В итоге вы, уважаемые читатели, получаете знания как мои, так и Эдуарда Дмитриева. Это разве плохо?

Вторая цель, как я уже сказал, - объединение двух рассылок и, естественно, аудиторий, что позволит еще большему количеству людей обрести знания сразу в нескольких областях.

Если вы заинтересовались, то - добро пожаловать! Буду рад видеть знакомые мне уже электронные адреса в рассылке Эдуарда.

Адрес сайта автора рассылки: http://prog.agava.ru (Библиотека программиста).

Рассылки Subscribe.Ru Уроки для начинающих программистов

HTML TEXT SMS PALM КОИ-8 Латиница windows Знаю, что пересечение аудиторий нашей рассылки и рассылки Эдуарда Дмитриева составляет более 3.800 человек. Я рад, что вы стремитесь освоить программирование под Windows не только на Ассемблере, но и на других языках. Приступив вскоре к Win32, читатели, знакомые, например, с C++, смогут проще понять принцип программирования под эту мощную операционную систему.

Итак, как говорится, вперед на мины!

Антивирус

Antivr24.asm; ANTIVR24.ASM - программа к рассылке № 24 ; (С) Авторское право на файл-приложение принадлежит подписчикам рассылки "Ассемблер? Это просто! Учимся программировать" ; Автор рассылки: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru) ; http://www.Kalashnikoff.ru ; === Начало программы: === .386 ;Использовать будем регистры и команды 386 процессора cseg segment use16 ;Сегмент по умолчанию 16-и разрядный assume cs:cseg, ds:cseg, ss:cseg, es:cseg org 100h Begin: jmp Init ; на метку инициализации ; === Процедура обработки 21h-прерывания === Int_21h_proc proc cmp ax,0ACDCh ;Проверка на повторую загрузку jne No_test xchg ah,al iret No_test: cmp ah,4Bh ;Это запуск файла? je Start_check cmp ah,3Dh ;Это открытие файла? jne No_start ;Запомним сегмент и смещение стека вызывающей программы. Start_check: cli ;Запретим прерывания mov cs:[0],ss ;Сохраним сегментые регистры mov cs:[2],sp ;Установим стек на область PSP нашего резидента push cs pop ss mov sp,0FEh ;Сохраним используемые регистры pusha pushf push es push ds ;Настроим сегментные регистры push ds pop es push cs pop ds sti ; === Вызываем процедуру проверки запускаемого файла... === call Check_prog ;Сюда вернемся только после того, как проверим файл и, если необходимо, ;вылечим его... ;Восстановим сохраненные регистры cli pop ds pop es popf popa ;Восстановим регистры, отвечающие за стек mov ss,cs:[0] mov sp,cs:[2] sti ;Передадим управление оригинальному обработчику 21h-прерывания No_start: jmp dword ptr cs:[Int_21h_vect] Int_21h_vect dd ? Int_21h_proc endp ; === Головная процедура проверки запускаемого файла === Check_prog proc cld ;Направление - вперед! mov di,dx ;Ищем в имени файла точку mov al,'.' mov cx,65 ;Всего будем просматривать 65 символов Next_sym: repne scasb ;Ищем пока НЕ найдем точку. jne No_com ;Не нашли точку вообще? Тогда на выход  ;!!! Теперь DI указывает на СЛЕДУЮЩИЙ байт после точки !!! mov ebx,es:[di] ;Занесем в EBX четыре байта расширения файла + 0 cmp ebx,006D6F63h ;Это 'com'0? je Got_file ;ДА! cmp ebx,004D4F43h ;Может, тогда 'COM'0 jne Next_sym ;Нет! Это было не расширение файла ;(вероятно, каталога)! Или это вообще не com-файл? ;Попробуем найти следующую точку...  ; Итак, пользователь запускает/отрывает com-файл... Got_file: call Check_file ;Проверим на зараженность... No_com: ret Check_prog endp ;=== Проверяем файл на зараженность === Check_file proc push es ;Настроим сегментные регистры... pop ds push cs pop es mov cx,65 mov si,dx mov di,20 ;20-ый байт PSP rep movsb ;Перенесем имя файла в наш сегмент в PSP для удобства... push cs pop ds mov byte ptr cs:[19],4Eh ;Атрибуты выводимого файла внизу рамки... mov ax,3D02h ;Пытаемся открыть найденный com-файл... mov dx,20 ;Имя файла теперь у нас в PSP по смещению 20! int 99h jc Err_open ;Ошибка при открытии (вероятно, атрибуты READ-ONLY)...  mov bx,ax mov Handle,ax mov ah,3Fh mov cx,6 mov dx,10 int 99h ;Читаем первые шесть байт... jc Not_infected ;Ошибка чтения!!!  mov ah,3Eh ;Закроем файл. mov bx,Handle int 99h cmp byte ptr cs:[10],68h ;Первый байт - 68h (команда PUSH)? jne Not_infected ;Нет - тогда файл не заражен!  mov eax,dword ptr cs:[12] ;Берем следующие байты... and eax,0FFFFFF00h ;Обнулим один байт (это будет часть адреса перехода ;на точку входа вируса в зараженном файле). ;Т.к. длина файла-жертвы разная, то этот байт будет ;всегда разным. Для этого мы, собственно, и ;аннулируем его)... ;В данном случае мы так делаем для демонстрации ;работы 32-х разрядных регистров... cmp eax,1122C300h ;Итак, это метка нашего вируса (в обратном порядке, ;причем первый байт аннулирован)? jne Not_infected ;Нет! Файл чистый!  call Cure_file ;Файл заражен нашим вирусом. Вероятность 99,9%. ;Лечим его... ret Not_infected: mov bx,Handle ;Файл не заражен нашим вирусом... mov ah,3Eh ;Закроем его и тихонько выйдем из процедуры... int 99h ret ;Ошибка открытия файла! Err_open: ;Сообщим о случившейся беде пользователю... ;Лучше это сообщение убрать, т.к. оно будет всегда появляться при попытке ;открыть для записи или загрузить файл, атрибуты которого ;"Read-only" (Только чтение). push 1 ;высота рамки push 70 ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_er ;надпись вверху рамки (если 0, то не выводить) push 19 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_ErrorOpen ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку mov ah,10h int 16h call Restore_scr ;И уйдем из процедуры. А что мы можем сделать, если файл не открывается? ;Можно, конечно, сохранить атрибуты, поменять их, открыть файл, ;а потом опять восстановить атрибуты. Думаю, вы без проблем сделаете это сами. ret File_size dw ? Handle dw ? Check_file endp include display.asm ; --- Данные для файла Display.asm --- Height_X equ [bp+12] ;высота рамки Width_Y equ [bp+10] ;ширина рамки Attr equ [bp+8] ;атрибуты рамки Mess_up equ [bp+6] ;сообщение вверху рамки Mess_dn equ [bp+4] ;сообщение внизу рамки Mess_ins equ [bp+2] ;сообщение внутри рамки Other equ [bp] ;иная конфигурация окна Num_DX dw ? Video_page db 0 ;Текущая видеостраница Pos_cursor dw ? ; === Лечим зараженный файл === Cure_file proc ;Спросим у пользователя, хочет ли он вылечить зараженный файл... push 1 ;высота рамки push 70 ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_cr ;надпись вверху рамки (если 0, то не выводить) push 19 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_cure ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку mov ah,10h int 16h call Restore_scr cmp al,'y' je Cure_f cmp al,'Y' je Cure_f cmp al,13 je Cure_f ;Нет, не хочет... А странно, почему? ret Cure_f: ;Попробуем отвести блок памяти размером 64Кб. mov ah,48h mov bx,4096 int 99h jnc No_error ;Нет ошибок при отводе блока  ;Ошибка! Вероятно, вся память занята кем-то... Сообщим пользователю об этом... push 1 ;высота рамки push 70 ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_er ;надпись вверху рамки (если 0, то не выводить) push 19 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_error ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку mov ah,10h int 16h call Restore_scr ;Попробуем использовать память видеокарты... ;Сегмент видеокарты - в ES push 0B900h pop es ;К сожалению, мы сможем вылечить файл , используя память видокарты ;(7-и страниц), размер которого не больше 28669 байт... mov Bytes_read,28670 ;Лечим... call Kill_Zarazu ret No_error: ;Удалось отвести блок памяти размером 64Кб! ;Сегмент отведенного блока - в ES push ax pop es mov Bytes_read,0FFFFh ;Лечим файл! call Kill_Zarazu ;Освободим отведенный нашей программой блок. Его сегмент в ES. mov ah,49h int 99h ret Mess_cr db 0CEh, ' Внимание! ',0 Mess_cure db 4Bh, 'Файл заражен вирусом VIRUS20. Вылечить его (Y/N)?',0 Mess_er db 0CEh, ' Ошибка ',0 Mess_Error db 4Bh, 'Недостаточно базовой памяти для лечения! Попробуем видеопамять...',0 Mess_ErrorOpen db 4Bh, 'Не удалось открыть файл для чтения/записи!',0 Mess_Errormem db 4Bh, 'Файл имеет больший размер, чем отведенная память! Лечение невозможно!',0 Mess_OKup db 0CEh, ' УРА! ',0 Mess_OK db 4Bh, 'Файл успешно вылечен!',0 Cure_file endp ; === Процедура лечения файла === Kill_Zarazu proc mov ax,3D00h ;Открываем файл для чтения. mov dx,20 ;Имя файла по смещению 20 (мы его перенесли). int 99h mov bx,ax mov Handle,ax mov cx,Bytes_read ;Склько байт будем читать... ;Если не смогли отвести блок памяти, то читаем ;максимум 0FFFFh байт. ;Если используем видеокарту, то прочитаем максимум ;28670 байт. push es ;Читаем либо в сегмент отведенного блока, pop ds ;либо в сегмент видокарты. mov ah,3Fh xor dx,dx ;Смещение - 0 int 99h push cs pop ds cmp ax,Bytes_read ;В AX - количество прочитанных байт ;Если AX=Bytes_read, то, скорее всего файл больше ;отведенного размера блока (ну, или равен ему). jne Ok_size ;В таком случае - сообщим об этом пользователю... push 1 ;высота рамки push 70 ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_er ;надпись вверху рамки (если 0, то не выводить) push 19 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_errormem ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку mov ah,10h int 16h call Restore_scr jmp short Close_file Ok_size: mov File_size,ax ;Запомним количество реально прочитанных байт. mov ah,3Eh mov bx,Handle int 99h mov ah,3Ch ;Создадим файл с тем же именем, что и зараженный. xor cx,cx ;Проще говоря, перезапишем его... mov dx,20 int 99h mov Handle,ax mov bx,ax mov ah,40h ;Запишем сперва первые шесть байт файла, которые mov dx,File_size ;наш вирус сохранил в хвосте. DX - конец файл. sub dx,6 ;DX=DX-6 - теперь DX указывает на адрес этих байт! mov cx,6 ;Запишем 6 байт. push es ;Сегмент должен быть верный!!! pop ds int 99h push cs pop ds mov ah,40h ;Теперь запишем осташуюся часть файла. mov dx,6 mov bx,Handle mov cx,File_size ;CX=кол-во прочитанных байт. sub cx,282 ;CX=CX-282 - длина нашего вируса push es pop ds int 99h push cs pop ds ;Сообщим пользователю об успешном завершении оперции! push 1 ;высота рамки push 70 ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_OKup ;надпись вверху рамки (если 0, то не выводить) push 19 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_OK ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку mov ah,10h int 16h call Restore_scr Close_file: mov ah,3Eh mov bx,Handle int 99h ret Bytes_read dw 0FFFFh Kill_Zarazu endp ; === Инициализация (подготовка и настройка резидента) === Init: mov ax,0ACDCh ;Проверим на повторную загрузку. int 21h cmp ax,0DCACh jne Not_inmemory mov ah,9 mov dx,offset Mess_memory int 99h ret Not_inmemory: mov es,word ptr cs:[2Ch] ;Получим сегмент окружения DOS. mov ah,49h ;Функция освобождения памяти. int 21h ;Освобождаем память... mov ax,3521h ;Получим вектор 21h-ого прерывания int 21h mov di,offset Int_21h_vect mov [di],bx ; Сохраним его сюда mov [di+2],es mov ax,2599h ;Вместо 21h будем использовать 99h mov dx,bx ;в нашем резиденте. push es pop ds int 21h push cs pop ds mov ax,2521h ;Здесь все понятно... Повторяться не будем... mov dx,offset Int_21h_proc int 21h mov ah,9 mov dx,offset Mess_hello int 99h mov dx,offset Init int 27h ; === Сообщения === Mess_hello db 'Антивирус к рассылке № 024 "Ассемблер? Это просто! Учимся программировать".',0Ah,0Dh,0Ah db 'Автор: Калашников Олег Александрович (Assembler@Kalashnikoff.ru),',0Ah,0Dh db 'http://www.Kalashnikoff.ru, Россия, Москва, 2001 год.',0Ah,0Dh,'$' Mess_memory db 'Антивирус уже загружен в память и успешно работает!',0Ah,0Dh,'$' cseg ends end Begin

Display.asm; ===== DISPLAY.ASM - процедуры работы с экраном ====== ; === Рисуем рамку заданного размера в центре экрана === Draw_frame proc mov bp,sp add bp,2 push es push 0B800h pop es mov ax,Height_X shr al,1 mov dh,11 sub dh,al mov ax,Width_Y shr al,1 mov dl,39 sub dl,al mov Num_DX,dx mov ax,Other test al,1 jz No_copyscr mov ax,Height_X add ax,2 call Copy_scr No_copyscr: call Get_linear push di mov ax,Attr mov al,'г' stosw mov al,'=' mov cx,Width_Y rep stosw mov al,'¬' stosw pop di add di,160 inc dh mov cx,Height_X Next_lined: push cx push di mov al,'¦' stosw mov al,32 mov cx,Width_Y rep stosw mov al,'¦' stosw pop di add di,160 inc dh pop cx loop Next_lined mov al,'L' stosw mov al,'=' mov cx,Width_Y rep stosw mov al,'-' stosw mov si,Mess_dn call Draw_messfr mov dx,Num_DX push dx mov si,Mess_up call Draw_messfr pop dx add dx,0101h mov si,Mess_ins or si,si jz No_draw mov ah,[si] inc si call Print_string No_draw: mov ax,Other test ax,10b jz No_upline mov dx,Num_dx add dh,2 call Get_linear mov ax,Attr mov al,'¦' mov cx,1 stosw mov cx,Width_Y mov al,'-' rep stosw mov al,'¦' stosw No_upline: pop es ret 14 Draw_frame endp ; --- Вывод сообщениий вверху и внизу рамки --- ;Вспомогательна процедура. Draw_messfr proc or si,si jz No_drawup mov ah,[si] inc si call Count_strmid call Print_string ;Выводим строку на экран No_drawup: ret Draw_messfr endp ; === Вычисляем середину строки === Count_strmid proc push es push di push ax push cs pop es mov di,si xor al,al mov cx,0FFFFh repne scasb sub di,si dec di shr di,1 mov ax,40 sub ax,di mov dl,al pop ax pop di pop es ret Count_strmid endp ; === Вывод стоки на экран === Print_string proc call Get_linear Next_symstr: lodsb or al,al jz Stop_outstr stosw jmp short Next_Symstr Stop_outstr: ret Print_string endp ; === Преобразование DH:DL в линейный массив === Get_linear proc push ax push bx push dx shl dl,1 mov al,dh mov bl,160 mul bl mov di,ax xor dh,dh add di,dx pop dx pop bx pop ax ret Get_linear endp ; === Копируем часть экрана === Copy_scr proc pusha push es push ds xor dl,dl call Get_linear mov bl,160 mul bl mov cx,ax mov si,di xor di,di mov Num_copySI,si mov Num_copyDI,di mov Num_copyCX,cx push 0B800h pop ds push 0BA00h pop es rep movsb pop ds pop es popa ret Num_copySI dw ? Num_copyDI dw ? Num_copyCX dw ? Copy_scr endp ; === Восстанавливаем часть экрана === Restore_scr proc pusha push es push ds mov di,Num_copySI mov si,Num_copyDI mov cx,Num_copyCX push 0BA00h pop ds push 0B800h pop es rep movsb pop ds pop es popa ret Restore_scr endp ; === Прячем курсор, сохранив предварительно его текущую позицию === Hide_cursor proc mov ah,3 mov bh,Video_page int 10h mov Pos_cursor,dx mov ah,2 mov bh,1 int 10h mov bh,Video_page mov dx,1900h int 10h ret Hide_cursor endp ; === Восстановим курсор === Restore_cursor proc mov ah,2 mov bh,Video_page mov dx,Pos_cursor int 10h ret Restore_cursor endp

Test24.asm

; TEST24.ASM - программа к рассылке № 024 ; Для эксперименов с заражениями. CSEG segment assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG org 100h Begin: mov ah,9 mov dx,offset Message int 21h ret Message db 'Здорово, пацаны!',0Ah,0Dh,'$' For_test db 35000 dup (0) CSEG ends end Begin

 

Файл-приложение в Интернете: http://www.Kalashnikoff.ru/Assembler/Issues/Enclosures/Antivr24.rar.

ВНИМАНИЕ!!! Наш антивирус корректно лечит только неизмененный вирус, код которого приведен в выпуске №020. Если вы добавили или убрали хоть один байт в коде вируса, то антивирус будет лечить его неверно!

Прежде, чем приступим к рассмотрению работы нашего антивируса, ознакомимся вкратце с некоторыми регистрами 80386/80486 процессора. Все очень просто! Вот таблица:

EAX (32 разряда) AX (16 разрядов) AX (16 разрядов) AH (8 разрядов) AL (8 разрядов) AH (8 разрядов) AL (8 разрядов) Как видно, EAX - 32-х разрядный регистр. Он может хранить число 65535*65535 (т.е. 65535 в квадрате).

До сих пор мы пользовались только 16-и разрядными регистрами (AX, BX, CX и пр.). Теперь надо потихоньку привыкать к 32-х разрядным. В принципе, острой необходимости пользоваться ими в нашем примере нет. Единственная цель - показать вам возможности 32-х разрядных регистров, а также попробовать использовать их на практике на простейшем примере.

В приведенной выше таблице мы рассмотрели только регистр EAX. Проводя аналогию с ним, можно добавить:

EBX, ECX, EDX, EDI, ESI, EBP.

Все эти регистры, как не трудно понять, 32-х разрядные. Думаю, что проблем с ними не будет...

Единственное условие - использовать директивы:

.386

.486 и пр.

но никак не

.8086

.286

Это связано с тем, что 32-х разрядные регистры появились только в 80386 процессоре. Соответственно, нужно использовать и программу-ассемблер (MASM / TASM), которая поддерживает инструкции 386+ процессоров. Если при ассемблировании ассемблер выдает ошибку на первой строке (.386), то вам необходимо искать более современную программу-ассемблер. Например, TASM 5.0 или MASM 6.13 (MASM 6.13 можно взять на нашем сайте).

Примеры:

mov eax,0 ;EAX=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 это число. EAX будет равен 12345678h. Т.е. ничего не меняется, т.к. мы обращаемся к данной переменной как к двойному слову и читаем из нее как двойное слово.

Однако, в памяти число 12345678h будет располагаться таким образом:

78563412h

т.е. "задом наперед". При загрузке же его в 32-х разрядный регистр, оно обратно как бы "перевернется".

Другое дело, когда мы хотим получить слово из подобных 32-х разрядных переменных в 16-и разрядный регистр (например, AX). Смотрите строку (3).

Учитывая, что число хранится в памяти "задом наперед", то после выполнения строки (3) в AX будет число 5678h. Поэкспериментируйте с этим. Т.к. только на экспериментах можно будет понять принцип хранения чисел в памяти...

Обратите еще внимание, как мы загружаем в AX число (строка (3)). Перед Variable идет word ptr. Это указывает процессору на то, что в AX нужно занести два байта (слово). Word ptr опускается, если переменная имеет тип DW, и загружаем мы в 16-и разрядный регистр. Однако, если мы хотим загрузить в 16-и разрядный регистр число из 32-х разрядной переменной (DD), то указать word ptr нужно обязательно!

______

При работе с инструкциями 386+ процессоров, возникает еще одна проблема: отладчик AFD версии 1.0 не распознает данные инструкции и, естественно, 32-х разрядные регистры. Дело в том, что в момент написания этой программы еще не были придуманы 80386+ процессоры...

Данная проблема может быть разрешена путем использования отладчика CV, который входит в комплект MASM 6.13 либо (для опытных программистов) SoftIce. Я пока постараюсь в ближайших выпусках использовать 16-и разрядные регистры, но вам необходимо потихоньку искать новые отладчики...

________

Еще обратите внимание на строку:

cseg segment use16 ;По умолчанию 16-и разрядные данные

USE16 сообщает ассемблеру, что по умолчанию будут использоваться 16-и разрядные регистры, данные и пр.

Убрав данную директиву, программа-ассемблер будет использовать USE32 (т.е. 32-х разрядные данные) и выдавать ошибку на строках вида:

mov dx,offset Message

В данном случае (если мы уберем USE16) нам следует записывать

mov edx,offset Message

Более подробно директиву USE32 мы рассмотрим в последующих выпусках. Здесь она нужна нам, т.к. мы используем команды (регистры) 80386 процессора...

_________

Еще момент. Почему программы, написанные на языках высокого уровня, имеют больший объем, чем аналогичные на Ассемблере?

В нашем примере я постарался это показать. Обратите внимание, что к файлу Antivr24.asm идет уже знакомый нам файл Display.asm, взятый из оболочки. В данном файле собраны процедуры для работы с видеокартой. Для того, чтобы заново не писать процедуру вывода окна на экран (а наш резидент выводит окошки прямым отображением в видеобуфер), я просто использовал уже готовые процедуры. Однако, в файле Display.asm существуют процедуры, которые, например, прячут курсор и восстанавливают его. Наш антивирус не будет управлять курсором. Следовательно, подобные процедуры можно убрать, тем самым сократив объем программы. Да, на Ассемблере это возможно. Но на языках высокого уровня - нет.

Например, в С используется директива #include stdio.h (кажется, так), которая подобна ассемблерной include display.asm. Однако, программист не может посмотреть или изменить то, что находится внутри stdio.h (возможно я и ошибаюсь). А в ней есть множество процедур, которые скорее всего программист и вызывать-то не будет. Отсюда и рост размера программы.

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

Но это так, для общей информации... Если ошибся - извините. Я не спец в С... Верить мне на слово не нужно!

Еще скажу, что таким образом вы можете добавлять файлы из оболочки для использования в своих программах, как я это сделал в нашем антивирусе. Только не забывайте про переменные, используемые в процедурах! Их можно было размещать в той процедуре, которая их использует, тем самым упростив перенос в другие программы.

_________

Теперь ближе к программе.

Все, что идет после метки Init, вам уже известно. Обращаю только ваше внимание на то, что вызывать 21h прерывание в резидентной части мы будем как int 99h, для чего скопируем оригинальный вектор 21h-ого в 99h-ый. Я полагаю, вы помните, для чего так нужно делать...

Наш резидентный антивирус будет "сидеть" в памяти и контролировать открытие или запуск программы, "на лету" проверит на зараженность и, если программа заражена, то вылечит ее (искать будем наш вирус, который писали в 20 выпуске. Для экспериментов вытащите его из 20 номера). Еще раз предупреждаю: будьте осторожны! Используйте только тот вирус, код которого приведен в 20 выпуске, не изменяя никаких байт!

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

Смотрим:

______

(1) cli ;Запретим прерывания

(2) mov cs:[0],ss ;Сохраним сегментные регистры

(3) mov cs:[2],sp

(4) ;Установим стек на область PSP нашего резидента

(5) push cs

(6) pop ss

(7) mov sp,0FEh

______

Обратите внимание, что мы должны запретить прерывания прежде, чем менять регистры SS:SP (строка (1)).

Как видите мы сохраняем SS:SP в нашем сегменте по смещению 0 (т.е. в PSP нашего антивируса) (строки (2) - (3)). Не будем заводить отдельные переменные, т.к. это увеличит размер программы. Все равно ведь память там свободная...

Затем нужно настроить сегментные регистры:

push ds

pop es

push cs

pop ds

Как известно, перед вызовом функций 4Bh (запуск программы) и 3Dh (открытие файла), DS должен указывать на сегмент, а DX на смещение имени файла. Для того, чтобы не затереть эти регистры, мы DS перенесем в ES, а сам же DS сделаем равным CS. Теперь адрес файла для запуска / открытия в ES:DX. Нужно внимательно следить за тем, чтобы сохранить DX. Можно, конечно, занести его в переменную и не волноваться, но так, как мы делаем в нашем примере, будет проще, да и меньше байт потребуется. Хотя, если подобных данных нужно хранить много или одно число в регистре нужно долго "хранить", то лучше завести отдельную переменную. Так будет наглядней...

Теперь вызываем процедуру Check_prog, которая будет проверять, запускается / открывается ли com-файл или какой-то иной.

_________

(1) cld ;Направление - вперед!

(2) mov di,dx ;Ищем в имени файла точку

(3) mov al,'.'

(4) mov cx,65 ;Всего будем просматривать 65 символов

(5) Next_sym:

(6) repne scasb ;Ищем пока НЕ найдем точку.

(7) jne No_com ;Не нашли точку вообще? Тогда на выход

_________

Оператор SCASB ищет в строке, адрес которой должен быть в ES:DI, символ, который находится в AL. Максимальная длина строки для поиска задается в регистре CX.

Как мы помним, в DX осталось смещение имени запускаемого/открываемого файла, в ES - сегмент. Следовательно, нам нужно в DI загрузить DX (ES у нас уже готов) (смотрите строку (2)). В AL заносим символ "." (точку), а в CX - 65 (т.е. длина строки с именем файла не должна превышать 65 байт).

Почему именно 65? Дело в том, что в DOS имя файла с полным путем к нему (т.е. имя диска + каталоги + имя файла + расширение) не должны превышать 65 байт.

Только заметил недочет. Представим, что открывается файл README (без расширения). Тогда точку мы, естественно, не найдем. А что, если после имени файла идут машинные коды и один из кодов - "."? Тогда мы будем выполнять лишние команды... А если после точки идет и "com", а затем ASCII 0? Тогда вообще наша программа посчитает, что это и есть имя файла...

В общем, лучший способ - искать ASCII 0, а затем проверить три байта перед найденным нулем на "com" или "COM". Я думаю, что вы без труда переделаете программу, если будет желание...

Вернемся к инструкции SCASB. Обратите внимание, что как только данная команда нашла нужный байт, DI будет указывать НЕ на него, а на следующий за ним символ!

Итак, нашли точку в имени файла. Это может быть точка как каталога, так и файла. Чтобы удостовериться, что это именно имя файла, нам нужно проверить четыре байта, идущие за точкой + ASCII 0:

это com+ASCII 0

это COM+ASCII 0

Как видите в Ассемблере очень важно следить за регистром (т.е. маленькие и большие символы различаются). Это как при нажатии на клавишу.

Из приведенных выше проверок видно, что мы проверяем расширение файла плюс следующий за ним байт, который должен быть "нуль" (т.е. ASCII 0).

Однако, если кто-то запустит файл примерно таким образом:

prog.cOm

или

prog.CoM

то наша программа "не поймет", что это com-файл. В данном примере мы не будем проверять все варианты. Можно сделать все очень просто, но об этом в следующих выпусках.

Вот проверка:

mov ebx,es:[di] ;Занесем в EBX четыре байта расширения файла + 0

cmp ebx,006D6F63h ;Это 'com'0 ?

je Got_file ;ДА!

cmp ebx,004D4F43h ;Может, тогда 'COM'0 ?

jne Next_sym ;Нет! Это было не расширение файла

Как видите здесь мы ради демонстрации 32-х битных регистров, загрузили четыре байта в EBX, а затем проверили. Посмотрите внимательно, как мы это делаем, но не забывайте про хранение данных в памяти и в регистрах "задом наперед". Следовательно и приемник в команде CMP должен быть "задом наперед", хотя в памяти храниться будет как "com". Еще раз подчеркну: очень сложно объяснить это на словах, но просто понять принцип на практике. Отмечу также, что путаницы не возникает. Главное - привыкнуть и понять принцип.

Следующий шаг. Если оказывается, что запускаемый / открываемый файл - com, то нужно проверить его на зараженность. Это делает процедура Check_file.

В первых ее строках мы переносим имя запускаемого / открываемого файла в сегмент нашего антивируса по смещению 20. По смещению же 19 заносим атрибут для вывода имени файла в окно (так требует процедура Draw_frame). Здесь все понятно...

Дальше читаем первые шесть байт файла. Помните, наш вирус сохранял два байта - 1122h в начале файла. Мы их (а также первый байт 68h (т.е. команда PUSH)) будем проверять...

Если файл заражен, то его нужно лечить (что вполне логично!). Здесь следует обратить внимание на использование команды AND, которая обнуляет один "лишний" байт. В файле-приложении все указано...

Лечит файл процедура Cure_file, вопросов к которой не должно возникнуть...

В двух словах:

Отводим блок памяти размером 64Кб. Если отвести не удалось (вдруг вся память отведена какой-то программой, как, например при попытке открытия его в оболочках DOS), то будем использовать память текстовых режимов видеостраниц, которой всего-то 28Кб. Но все-таки...

Как только отвели память, занесем сегмент ее в ES, а количество возможных байт для чтения - в переменную Bytes_read.

Вызываем процедуру Kill_zarazu, которая убьет заразу...

Как это происходит, думаю вы без труда разберетесь, т.к. в файле-приложении всегда достаточно описаний...

Дерзайте, друзья мои! Скоро будет Windows. Вам следует хорошо разобраться с DOS!

__________

В принципе, особой новизной данный выпуск "не блещет", но нам нужно было пройти написание антивируса. Полагаю, что вам стал понятен принцип работы вирусов и антивирусов...

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

Скажу более, я не ставил перед собой задачу написать оптимальный алгоритм, а также красивый вывод на экран информации. Думаю, что вы без труда все дополните сами либо в настоящий файл-приложение, либо в какую-то свою программу, используя материал из данной рассылки и из всех предыдущих.

Вот вроде и все!!!

Несколько слов от автора.

Итак, простейший вирус и антивирус мы рассмотрели. Несколько резидентов тоже (правда, научимся еще удалять резидент из памяти). Осталась только оболочка, несколько новых операторов, некоторое количество прерываний и функций DOS, а также алгоритмы... После этого со смелой душой приступим к Windows. Уже скоро! Думаю, еще 5-10 выпусков и мы переходим на новый уровень программирования.

В связи с этим я напомню еще раз: мне нужны помощники для ведения рассылки по программированию на Ассемблере под Windows. Дополнительная информация будет указана в последующих выпусках. Если вы чувствуете, что сможете обучить людей программированию под Win32, то ждите анкету в следующих выпусках.

__________

Скоро весна... В связи с этим, в апреле (как только станет тепло) планируется грандиозная акция под кодовым названием "Ассемблер? Это просто! Учимся программировать". Вас ждет прекрасное времяпрепровождение, общение, знакомства, а также еще кое-что. Подробности читайте в первых апрельских выпусках...

Всем счастливо!

С уважением,

Автор рассылки: Калашников Олег

URL сайта подписчиков: http://www.Kalashnikoff.ru

E-mail автора: Assembler@Kalashnikoff.ru

ICQ: 68951340

Москва, 2001.

(C) Авторское право принадлежит автору рассылки. Использование материала из рассылки в коммерческих и иных подобных целях, а также публичное размещение без письменного согласия автора влечет ответственность за нарушение авторских прав. [Следующий выпуск] [На главную страницу]

u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("

Соседние файлы в папке Выпуски