
;              ANTIVR24.ASM - программа к Главе № 24

; (С) Авторские права на файлы-приложения принадлежат автору книги
; "Ассемблер? Это просто! Учимся программировать под MS-DOS"
; Автор: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru)
;	 http://www.Kalashnikoff.ru

; --- Ассемблирование (получение *.com файла) ---
;При использовании MASM 6.11 - 6.13:
;ML.EXE antivr24.asm /AT

;При использовании TASM:
;TASM.EXE antivr24.asm
;TLINK.EXE antivr24.obj /t/x


;   === Начало программы: ===
.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 ;= 65535 = 64Кб
;Лечим файл!
      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 ;Склько байт будем читать...
                        ;Если получилось отвести блок памяти 64 Кб, то читаем
                        ;максимум 0FFFFh (65536) байт.
                        ;Если используем видеокарту, то прочитаем максимум
                        ;всего 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 'Антивирус к книге "Ассемблер? Это просто! Учимся программировать", Глава № 24.',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
