CSEG	SEGMENT	PARA	PUBLIC	'CODE'
VirCheck	PROC	FAR
	ASSUME CS:CSEG,DS:CSEG,SS:CSEG

	; Следующие 34 байта готовятся программой-инсталлятором:

Head	dw	12 dup(?)	; зашифрованный ключ
StartS	dw	?		; относительный сегмент
StartO	dw	?		; и смещение точки запуска
Leng1	dw	?		; эталонная длина -24 байт
                                ; (младшая часть)
Leng2	dw	?		; старшая часть
Key	dw	?		; шифр ключа

	;------------------------;
	; Константы и переменные ;
	;------------------------;

PSP	dw	?		; сегмент PSP
CS0	dw	?		; сохранение CS
SP0	dw	?		; сохранение SP
NameFE	db	80 dup(?)	; ASCIIZ-имя EXE-файла
NameF$	db	80 dup(?)	; ASCIIZ-имя $V$-файла
NameFV	db	80 dup(?)	; ASCIIZ-имя VIR-файла
Buffer  equ word ptr NameF$
LName	dw	?		; длина имени без учета 
				; расширения и нуля
HandExe	dw	0		; описатель EXE-файла 
Txt0	db	13,10,'$'	; EOL
Txt1	db	'Файл $'
Txt2	db	' заражен вирусом!',13,10,'$'
Txt21	db	'Вирус поразил начало файла'
	db	' - удаление невозможно!',13,10
Txt20	db	'Нажмите любую клавишу...$'
Txt22	db	'Удалять (Y/N,умлч.Y) ?$'
Vir	db	'VIR',0
Ext$	db 	'$V$',0
HandVir	dw	0
Txt3	db	'Ошибка доступа к файлу',13,10,'$'
Txt4	db	'Вирус удален. Проверьте другие файлы!'
	db	13,10,'$'
Mem	dw	?		; размер буфера
MemS	dw	?		; сегмент буфера
MemF	dw	0		; признак распределения памяти

	;--------------------;
	; Основная программа ;
	;--------------------;

START:	
	push	ax		; спасти AX
	mov	ax,ds		; запомнить сегмент PSP
	mov	bx,sp		; и указатель стека
	push	cs		; назначить сегмент данных
	pop	ds		; равным сегменту кода
	mov	CS0,ds		; сохранить CS
	mov	PSP,ax		; спасти DS = PSP
	mov	SP0,bx		; спасти SP
	add	ax,10h		; сегмент начала программы
	add	ax,StartS	; сегмент точки запуска
	mov	StartS,ax	; подготовить выход

	call	GetExeName	; получить имя EXE-файла

	call	ReadHead	; прочитать его заголовок

	call 	CheckHead	; проверить заголовок 
	
	je	Exit		; кончить, если норма

	call	Dialog		; сообщить и запросить

	je	Exit		; пользователь запретил удаление

	call	NewFile		; создать $V$-файл

	call	WriteHead	; записать в него эталонный заголовок

	call	CopyFile	; копировать незараженную часть

	call 	Rename		; переименовать файлы

	; Выход
EXIT:
	call	CloseFiles	; закрыть открытые файлы
	call	FreeMem		; вернуть зарезервированную память
	mov	sp,SP0		; восстановить SP
	mov	bx,StartS	; подготовить сегмент
	mov	cx,StartO	; и смещение точки запуска
	mov	es,PSP		; восстановить ES
	mov	ds,PSP		; восстановить DS
	pop	ax		; восстановить AX
	push	bx
	push	cx
	retf			; передать управление программе

	;-----------;
	; Процедуры ;
	;-----------;
;---------------------------------------------------------------
GetExeName	PROC	NEAR

  ; Анализирует окружение ДОС, выделяет имя программы
  ; и копирует его в буфер NameFE	

	mov	es,PSP		; ES = PSP
	mov	es,es:[2Ch]	; ES - сегмент окружения

	; Искать начало дополнительных параметров

	mov 	cx,1000		; искать в буфере длиной 1000 байт
	mov	bx,-1		; начать с первого байта
SEARCH:
	inc	bx		; текущий байт
	mov	ax,es:[bx]	; текущее слово
	cmp	ax,0		; искать два нуля
	jz	EndSearch	; переход, если нашли
	loop	Search		; повторить для следующего байта

	jmp	Exit		; не найдено имя - выход!

	; Перенести имя файла
ENDSEARCH:
	add	bx,2		; смещение количества параметров
	mov	ax,es:[bx]	; получить количество параметров
	cmp	ax,0		; есть параметры?
	je	Exit		; -нет, выход
	add	bx,2		; начало имени
	lea	di,NameFE       ; индекс копии
	mov	cx,0		; подготовить счетчик длины
LAB1:	mov	ah,es:[bx]	; очередной символ
	mov	[di],ah 	; перенести его
	inc	bx		; нарастить указатели
	inc	di
	inc	cx		; и счетчик
	cmp	ah,0
	jnz	Lab1		; повторять до символа 0
	add	cx,-4		; CX - длина имени без расширения и 0
	mov	LName,cx
	ret
;---------------------------------------------------------------
ReadHead	PROC	NEAR

  ; Открывает EXE-файл, читает из него заголовок

	; Открыть файл с именем в NAMEF

	lea	dx,NameFE       ; начало имени
	mov	ah,3Dh		; код операции
	mov	al,0		; открыть для чтения
	int	21h		; обратиться к DOS
	jnc	Lab0            ; если нет ошибки
	jmp	Exit		; выйти, если ошибка
LAB0:	mov 	HandExe,ax	; сохранить описатель файла
	
	; Читать заголовок файла

	mov 	bx,ax		; BX - описатель файла
	mov	ah,3Fh		; код операции
	lea	dx,Buffer       ; DS:DX - адрес буфера
	mov 	cx,24		; читать 24 байт (заголовок)
	int	21h
        jnc     m0
	jmp	Exit		; если ошибка
m0:	ret
;---------------------------------------------------------------
CheckHead	PROC	NEAR

  ; Дешифрует ключ и проверяет заголовок файла

	; Дешифровать ключ
	
	mov	cx,12		; счетчик повторений 
	lea	bx,Head         ; BX - смещение эталонного заголовка
LAB2:	mov	ax,[bx] 	; взять очередное слово
	xor	ax,Key		; дешифровать его
	mov	[bx],ax 	; и поместить назад
	add	bx,2		; сместить к следующему слову
	loop	Lab2		; повторить

	; Сравнить заголовок с эталонным

	mov	cx,12		; длина заголовка в словах
	mov	es,cs0		; ES - сегмент кода
	lea	si,Head         ; эталон
	lea	di,Buffer       ; заголовок
	cld			; направление - вперед
repe	cmpsb	                ; сравнить
	ret
;---------------------------------------------------------------
Dialog	PROC 	NEAR

  ; Сообщает пользователю о наличии вируса
  ; и запрашивает его действия

	; Сообщить пользователю

	lea	dx,Txt1
	call	Print		; 'Файл '
	lea	bx,NameFE
	add	bx,LName
	add	bx,3		; BX - смещение нуля в конце имени
	mov 	Mem,bx		; сохранить в Mem
	mov	ah,24h		; поместить '$' в конец имени
	mov	[bx],ah
	lea	dx,NameFE
	call	Print		; вывести имя файла
	mov	bx,Mem		; восстановить
	mov	ah,0		; ноль в конце имени
	mov	[bx],ah
	lea	dx,Txt2
	call	Print		; ' заражен вирусом!'

        ; Проверить расположение вируса

        mov     ax,word ptr [Buffer+16h]
        cmp     ax,word ptr [Head+16h]
        ja      Delete          ; вирус в конце файла

        ; Вирус в начале файла
        ; Сообщить и завершить работу

        lea    dx,Txt21
        call   Print
        mov    ah,1
        int    21h              ; ждать реакцию пользователя
        lea    dx,Txt0
        call   Print            ; EOL
        jmp    Exit             ; завершить работу

        ; Вирус в конце файла
DELETE:
        lea    dx,Txt22
        call   Print            ; запросить пользователя

	; Ввести ответ пользователя

	mov	ah,1
	int	21h
        push    ax
        lea     dx,Txt0
        call    Print           ; EOL
        pop     ax
	cmp	al,'N'
	je	ExitP		; выйти, если ответ 'N'
	cmp	al,'n'		; сравнить с 'n'
EXITP:	ret
;---------------------------------------------------------------
Print	PROC 	NEAR

  ; Выводит на экран текстовую строку
  ; При входе DX - смещение строки

	mov 	ah,9
	int	21h
	ret
;---------------------------------------------------------------
NewFile	PROC	NEAR

  ; Подготавливает имена $V$ и VIR-файлов
  ; и создает $V$-файл

	lea	di,NameF$
	lea	bx,ext$
	call 	CopyName	; имя $V$-файла
	lea	di,NameFV
	lea	bx,Vir
	call	CopyName	; имя VIR-файла

	; Создать $V$-файл

	mov	ah,3Ch
	mov	al,1
	lea	dx,NameF$
	int	21h
        jnc     m1
	jmp	Error
m1:     mov     HandVir,ax      ; описатель незараженного файла
        ret
;---------------------------------------------------------------
CopyName	PROC	NEAR

  ; Копирует строку имени файла с нужным расширением
  ; При входе DI - смещение приемника, BX - смещение расширения

	mov	es,cs0
	lea	si,NameFE
	mov	cx,LName
	cld
rep	movsb			; копировать до расширения
	mov	si,bx
	mov	cx,4
rep	movsb			; копировать расширение
	ret
;---------------------------------------------------------------
WriteHead	PROC	NEAR
  ; Записывает в $V$-файл эталонный заголовок

	mov	ah,40h
	mov	cx,24		; длина заголовка 24 байт
	lea	dx,Head
	mov	bx,HandVir
	int	21h
        jnc     m2
	jmp	Error
m2:	ret
;---------------------------------------------------------------
CopyFile	PROC	NEAR

  ; Определяет длину буфера и копирует незараженную часть

	; Найти длину буфера

	mov	ax,SP0		; вершина стека (в стеке AX!)
	cmp	ax,0FFD0h	; стек 64 Кбайт?
	ja	LargStack	; -да
	add	ax,17		; округлить с избытком
        mov     cl,4
	shr	ax,cl		; перевести в параграфы
	jmp	GetSize
LargStack:
	mov 	ax,1000h	; 64 К = 4096 параграфов
GetSize:
        mov     cx,ss
	add	ax,cx		; AX - сегмент буфера
	mov	es,PSP		; сегмент PSP
	mov	bx,es:[2]	; получить вершину памяти
	sub	bx,ax		; BX - доступная память в параграфах
	cmp	bx,1000h	; больше 64 Кбайт?
	ja	LargMem		; -да
        mov     cl,4
	shl	bx,cl		; BX - доступная память в байтах
	jmp	m5
LargMem:
	mov	bx,0FFFFh	; длина буфера = 64 Кбайт

m5:	cmp	bx,0		; есть свободная память?
	jne	SetMem		; -да

	; Нет свободной памяти - резервировать

	mov	ah,48h
	mov	bx,1000h
	int	21h		; резервировать 64 К
	jnc	OKay		; удачно
	cmp	bx,0		; есть хоть сколько памяти?
	jne	GetLowMem	; -да
	
	; Выделить часть стека
GetStack:
	mov	ax,ss
	mov	bx,sp
	add	bx,-2
	jmp	SetMem

GetLowMem:
	mov	ah,48h          ; запросить у ДОС блок памяти
	int 	21h             ; длиной BX параграфов
	jc	GetStack

Okay:	mov	MemF,bx		; размер зарезервированной
        mov     cl,4            ; памяти в параграфах
        shl     bx,cl           ; перевести в байты
	
SetMem:
	mov	Mem,bx		; запомнить в MEM длину буфера
	mov	MemS,ax		; и в MemS сегмент

	; Копировать незараженную часть файл

Copy:	mov	ax,Leng2	; AX - старшая часть остатка
	mov 	cx,Mem		; CX - максимальный буфер
	cmp	ax,0		; осталось больше длины буфера?
	jnz	Read		; -да
	cmp	cx,Leng1	; сравнить с длиной буфера
	jb	Read		; -буфер меньше остатка
	mov	cx,Leng1	; копировать остаток целиком

	; Читать файл
Read:
	mov	ah,3Fh
	mov 	dx,0		; смещение буфера = 0
	mov	bx,HandExe	; читать из зараженного файла
	push	ds		; сохранить DS
	mov	ds,MemS		; буфер - сразу за концом программы
	int	21h
	pop	ds
        jnc     Write
	jmp	Error		; если ошибка чтения

	; Записать файл
Write:
	mov	cx,ax		; число фактически прочитанных байт
	mov	ah,40h
	mov	dx,0
	mov	bx,HandVir	; писать в $V$-файл
	push	ds
	mov	ds,MemS
	int	21h
	pop	ds
        jnc     m3
	jmp	Error		; если ошибка записи

	; Изменить длину остатка

m3:	mov	dx,Leng1	; DX - младшая часть длины
	mov	cx,Leng2	; CX - старшая часть длины
	sub	dx,ax		; AX - длина записанного
	sbb	cx,0		; CX:DX - длина остатка
	mov	Leng2,cx
	mov	Leng1,dx
	cmp	cx,0
	jnz	Copy		; повторять
	cmp	dx,0
	jnz	Copy		; пока остаток не станет нулевым
	ret
;---------------------------------------------------------------
Rename	PROC	NEAR

  ; Переименовывает EXE-файл в VIR-файл
  ; и $V$-файл в EXE-файл. Сообщает о конце работы

	call 	CloseFiles	; закрыть файлы
	mov	ah,41h	
	lea	dx,NameFV
	int	21h		; удалить VIR-файл
	mov	ah,56h
	lea	dx,NameFE
        push    ds
	pop	es
	lea	di,NameFV
	int	21h             ; переименовать EXE в VIR
	jc	Error
	mov	ah,56h
	lea	dx,NameF$
	lea	di,NameFE
	int	21h             ; переименовать $V$ в EXE
	jc	Error

        ; Сообщить о конце работы

        lea 	dx,Txt4
	call	Print           ; 'Вирус удален...'
        lea     dx,Txt20
        call    Print           ; 'Нажмите...'
        mov     ah,1
        int     21h             ; дождаться реакции пользователя
        lea     dx,Txt0
        call    Print
	ret
;---------------------------------------------------------------
CloseFiles	PROC NEAR
  
  ; Закрывает открытые файлы

	mov	bx,HandExe
	cmp	bx,0
	je	Next
	mov	ah,3Eh
	int	21h
	jc	Error
	mov	HandExe,0

Next:	mov	bx,HandVir
	cmp	bx,0
	je	All
	mov	ah,3Eh
	int	21h
	jc	Error
	mov	HandVir,0
All	:ret
;---------------------------------------------------------------
FreeMem	PROC 	NEAR

  ; Возвращает зарезервированную память

	mov	bx,MemF
	cmp	bx,0
	je	Return
	mov	ah,49h
	mov	es,MemS
	int	21h
Return:	ret
;---------------------------------------------------------------	
	; Ошибка доступа к диску
ERROR:
	lea	dx,Txt3
	call	Print		; 'Ошибка доступа к диску'
	jmp	Exit
CSEG	ENDS
	END	START