Игорь Гульев - Создаем вирус и антивирус
Возможно, в недалеком будущем компьютерный вирус сможет полностью заменить своим кодом программу-супервизора и сам будет поддерживать интерфейсы DPMI, EMS/VCPI, XMS, INT 15h. Кто знает.
Приведенная ниже программа позволяет программисту перевести процессор в защищенный режим. В этом режиме вирус может, например, расшифровать некоторые данные.
;Данная программа делает следующее:
; – создает таблицы GDT и LDT, используя текущие значения
; CS,DS,SS
; – запрещает все прерывания, открывает линию A20
; для доступа к RAM>1Мбайт
; – переводит процессор в защищенный режим
; – в первый символ строки qw заносит символ L
; – выходит в реальный режим
; – разрешает прерывания, закрывает A20
; – выводит на экран строку qw (”Light General”)
; – выход в DOS
.286
.model tiny
.code
org 100h
;Определения для защищенного режима работы программы
;Структура дескриптора
desc_struc STRUC
limit dw 0
base_l dw 0
base_h db 0
access db 0
rsrv dw 0
desc_struc ENDS
ACC_PRESENT equ 10000000b
ACC_CSEG equ 00011000b
ACC_DSEG equ 00010000b
ACC_EXPDOWN equ 00000100b
ACC_CONFORM equ 00000100b
ACC_DATAWR equ 00000010b
DATA_ACC=ACC_PRESENT or ACC_DSEG or ACC_DATAWR
; 10010010b
CODE_ACC=ACC_PRESENT or ACC_CSEG or ACC_CONFORM
; 10011100b
STACK_ACC=ACC_PRESENT or ACC_DSEG or ACC_DATAWR or ACC_
EXPDOWN; 10010110b
;Размеры сегментов (реальные размеры на единицу больше)
CSEG_SIZE=65535
DSEG_SIZE=65535
STACK_SIZE=65535
;Смещения используемых дескрипторов
CS_DESCR=(gdt_cs–gdt_0)
DS_DESCR=(gdt_ds–gdt_0)
SS_DESCR=(gdt_ss–gdt_0)
;Константы значений портов
CMOS_PORT equ 70h
STATUS_PORT equ 64h
SHUT_DOWN equ 0FEh
A20_PORT equ 0D1h
A20_ON equ 0DFh
A20_OFF equ 0DDh
INT_MASK_PORT equ 21h
KBD_PORT_A equ 60h
start:
;Инициализируем необходимые данные для перехода
;в защищенный режим
call init_protected_mode
;Переходим в защищенный режим
call set_protected_mode
;Теперь компьютер работает в защищенном режиме!
;Так как таблица прерываний реального режима не может быть
;использована в защищенном, прерывания запрещены!
;Именно тут можно вставить инструкции, нужные вирусу
;Возвращаемся в реальный режим
call set_real_mode
;Печатаем сообщение ”Light General”
mov ah,09h
lea dx,qw
int 21h
;Выходим в DOS
mov ax,4C00h
int 21h
;Макрокоманда для установки адреса для дескриптора
;в глобальной таблице дескрипторов GDT.
;На входе регистры DL:AX должны содержать
;абсолютный адрес сегмента
setgdtentry MACRO
mov [desc_struc.base_l][bx],ax
mov [desc_struc.base_h][bx],dl
ENDM
;Процедура инициализации необходимых данных
;для перехода в защищенный режим
init_protected_mode PROC
;Вычисляем абсолютный адрес для сегмента данных
;в соответствии со значением регистра DS
mov ax,ds
mov dl,ah
shr dl,4
shl ax,4
;Устанавливаем адрес сегмента данных
;в глобальной таблице дескрипторов
mov bx,offset gdt_ds
setgdtentry
;Вычисляем абсолютный адрес для сегмента GDT: прибавляем
;к уже вычисленному абсолютному адресу сегмента данных
;смещение в нем таблицы дескрипторов
add ax,offset gdtr
adc dl,0
;Устанавливаем адрес сегмента GDT
;в глобальной таблице дескрипторов
mov bx,offset gdt_gdt
setgdtentry
;Вычисляем абсолютный адрес для сегмента кода
;в соответствии со значением регистра CS
mov ax,cs
mov dl,ah
shr dl,4
shl ax,4
;Устанавливаем адрес сегмента кода
;в глобальной таблице дескрипторов
mov bx,offset gdt_cs
setgdtentry
;Вычисляем абсолютный адрес для сегмента стека
;в соответствии со значением регистра SS
mov ax,ss
mov dl,ah
shr dl,4
shl ax,4
;Устанавливаем адрес сегмента стека
;в глобальной таблице дескрипторов
mov bx,offset gdt_ss
setgdtentry
;Перехватываем рестарт. Так как процессор i286 (а эта программа
;рассчитана именно на такой процессор) не имеет возможности
;возврата в реальный режим из защищенного, возврат в реальный
;режим будем производить следующим образом: перехватим рестарт,
;сгенерируем CPU Reset, после которого получим управление, когда
;процессор будет находится уже в реальном режиме. На процессоре
;i386 возврат в реальный режим происходит
;значительно проще и ”естественнее”.
push ds
mov ax,40h
mov ds,ax
mov word ptr ds:[0067h],offset shutdown_return
mov word ptr ds:[0069h],cs
pop ds
;Запрещаем маскируемые прерывания
cli
in al,INT_MASK_PORT
or al,0FFh
out INT_MASK_PORT,al
;Запрещаем немаскируемые прерывания. Данная последовательность
;команд не запрещает ”незапрещаемые” прерывания в процессоре
;(этого сделать по определению нельзя), а ”не пускает” сигнал
;немаскируемого прерывания к процессору
mov al,8Fh
out CMOS_PORT,al
jmp $+2
mov al,5
out CMOS_PORT+1,al
ret
init_protected_mode ENDP
;Подпрограмма, переводящая процессор в защищенный режим
set_protected_mode PROC
;Открываем адресную линию A20 для доступа свыше 1Мбайт.
;При закрытой линии адресное пространство
;”зацикливается” в пределах 1Мбайт
call enable_a20
;Сохраняем значение регистра SS для реального режима
mov real_ss,ss
;Переводим компилятор Turbo Assembler в улучшенный режим.
;IDEAL – это не команда и не оператор, это директива, влияющая
;только на интерпретацию дальнейших строк листинга
ideal
p286
;Загружаем регистр глобальной таблицы дескрипторов GDTR
lgdt [QWORD gdt_gdt] ;db 0Fh,01h,16h dw offset gdt_gdt
;Переводим процессор в защищенный режим
mov ax,0001h
lmsw ax ;db 0Fh,01h,F0h
;Переводим компилятор Turbo Assembler назад в режим MASM
masm
.286
;Производим длинный переход для того,
;чтобы очистить внутреннюю очередь
;команд процессора
jmp far flush
db 0EAh
dw offset flush
dw CS_DESCR
flush:
;Устанавливаем в регистр SS селектор сегмента стека
mov ax,SS_DESCR
mov ss,ax
;Устанавливаем в регистр DS селектор сегмента данных
mov ax,DS_DESCR
mov ds,ax
;Записываем в строку qw символ ”L” и выходим из подпрограммы
mov byte ptr ds:[offset qw+2],”L”
ret
set_protected_mode ENDP
;Подпрограмма, возвращающая процессор в реальный режим
set_real_mode PROC
;Сохраняем значение регистра SP для реального режима
mov real_sp,sp
;Выполняем CPU Reset (рестарт процессора)
mov al,SHUT_DOWN
out STATUS_PORT,al
;Ждем, пока процессор перезапустится
wait_reset:
hlt
jmp wait_reset
;С этого места программа выполняется после перезапуска процессора
shutdown_return:
;Устанавливаем регистр DS в соответствии с регистром CS
push cs
pop ds
;Восстанавливаем указатели на стек
;по ранее сохраненным значениям
mov ss,real_ss
mov sp,real_sp
;Закрываем адресную линию A20
call disable_a20
;Разрешаем немаскируемые прерывания
mov ax,000dh
out CMOS_PORT,al
;Разрешаем маскируемые прерывания
in al,INT_MASK_PORT
and al,0
out INT_MASK_PORT,al
sti
ret
set_real_mode ENDP
;Процедура, открывающая адресную линию A20. После открытия
;адресной линии программам будет доступна память свыше 1Мбайт
enable_a20 PROC
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_ON
out KBD_PORT_A,al
ret
enable_a20 ENDP
;Процедура, закрывающая адресную линию A20. После закрытия
;адресной линии программам будет недоступна память свыше
1Мбайт.
;Адресное пространство будет ”зацикленным” в пределах 1Мбайт
disable_a20 PROC
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_OFF
out KBD_PORT_A,al
ret
disable_a20 ENDP
;Здесь сохраняется адрес стека
real_sp dw ?
real_ss dw ?
;Эта строка выводится на экран после работы программы
;Символ ”?” заменяется на ”L” в защищенном режиме
qw db 13,10,”?ight General”,13,10,”$”
;Глобальная таблица дескрипторов. Нулевой дескриптор
;обязательно должен быть ”пустым”
GDT_BEG=$
gdtr label WORD
gdt_0 desc_struc <0,0,0,0,0>
gdt_gdt desc_struc <GDT_SIZE−1,,,DATA_ACC,0>
gdt_ds desc_struc <DSEG_SIZE−1,,,DATA_ACC,0>
gdt_cs desc_struc <CSEG_SIZE−1,,,CODE_ACC,0>
gdt_ss desc_struc <STACK_SIZE−1,,,DATA_ACC,0>
GDT_SIZE=($–GDT_BEG)
END startОбход резидентных антивирусных мониторов
Обычно все программы используют сервис DOS так:
mov ah,... int 21h
По команде INT управление передается в точку, адрес которой определяется двумя словами, находящимися в таблице векторов прерываний по адресу 0000h:0084h. С этого момента начинается исполнение команд многочисленных обработчиков прерывания INT 21h и не менее многочисленных резидентных программ до тех пор, пока управление, наконец, не получит оригинальный обработчик операционной системы (рис. 5.1):
Рис. 5.1Разумеется, среди этих многочисленных обработчиков может «затесаться» обработчик, принадлежащий антивирусному монитору, который не дает спокойно работать не только вирусам, но и обычным программам. Поэтому серьезные вирусы и некоторые хорошо написанные программы пытаются определить адрес оригинального обработчика и обратиться к нему напрямую, в обход остальных обработчиков:
mov ah,...
pushf
call dword ptr O21...O21 dw ?
S21 dw ?
Но антивирусные мониторы учитывают эту возможность и принимают свои меры.
Определение адреса оригинального обработчика DOS
Для того чтобы обратиться к DOS напрямую, нужно знать адрес оригинального обработчика. Получить этот адрес не так просто.
Метод трассировки Чаще всего используется метод трассировки при помощи отладочного прерывания INT 1. Суть метода заключается в том, что вирус трассирует прерывание INT 21h (включает флаг трассировки, при этом после каждой команды происходит прерывание INT 1) и проверяет значение сегмента, в котором идет обработка прерывания. Если значение сегмента меньше 0300h, то это обработчик DOS. Например, так поступал много лет назад вирус Yankee 2C (M2C, Музыкальный). Вот листинг соответствующего фрагмента с комментариями: