гл.8 СОЗДАНИЕ ВЕСЬМА ПРОДВИНУТОГО (по сравнению с предыдущими) ВИРУСА,
ПОДОБНО V2, ПРЯЧУЩЕМУСЯ В ЗАГОЛОВОК ЕХЕ-ФАЙЛА, НЕПОРТЯЩЕГО ДАННЫЕ
И ЗАПУСКАЮЩЕГО ПОРАЖЕННЫЙ ФАЙЛ С ПЕРВОГО РАЗА (самая длинная глава)
=======================================================================
Для того чтобы создать этот вирус, нам нужно научиться двум новым, край-
не важным фенькам и одной мелкой фигне.
Первая из двух крайне важных фенек :--------------¬
НОВЫЙ СПОСОБ ПОСАДКИ РЕЗИДЕНТОВ. MCB-БЛОКИ. ¦
------==========================================---
Всего существует 6 основных способов посадки резидента. Вирусы реализуют
их все. Перечислим эти способы.
1. При помощи прерывания INT 27h (мы до сих пор лишь его и использовали).
2. При помощи прерывания INT 21h (функция 31h). Очень похоже на способ 1.
3. Работая с МСВ-блоками (чисто вирусный метод).
4. Внедряясь в тело системного драйвера (чисто вирусный метод).
5. Внедряясь в таблицу векторов или др. системную область (чисто вирус-
ный метод).
6. Изменяя счетчик свободной памяти при загрузке системы (чисто вирусный
метод).
Скажем сразу, что если программист слабо знает особенности системы
MS-DOS, то использование методов 4 и 5 может кончиться аварией.
Скажем сразу, что если используются классические методы 1 и 2, то анти-
вирусные мониторы легко обнаружат вирус, он отображается даже в карте памяти
под именем зараженной программы, которая посадила резидент.
Скажем сразу, что метод 6 используется исключительно при загрузке PC
(см. одну из следующих глав "Бутовые вирусы").
Остается единственный годный к употреблению метод 3 -- посадка с коррек-
тировкой МСВ-блоков. Ниже мы рассмотрим один из способов такой корректировки.
Вся память PC разбита на так называемые МСВ-блоки (Memory Control Block)
-- блоки управления памятью. Эти блоки выстроены в цепочку и каждый снабжен
своим дескриптором (описателем). В дескрипторе в частности говорится какая
длина у блока, является ли он последним в цепочке блоков и т.д. Следующий
блок начинается сразу за предыдущим. Выглядит это так:
-----------------------------------------------------------¬
¦ рис.8 ¦:
L-----------------------------------------------------------
-TTTTTTTTT---------------TTTTTTTTT----------------TTTTTTTTT----------
L+++++++++--------------L+++++++++----------...--L+++++++++----------
описатель блок 1 описатель блок 2 описатель блок n
блока 1 блока 2 блока n
Описатель последнего блока отмечен по-особому. Вот как устроен описатель
МСВ-блока (по данным thelp /4/):
Смещ. Длина Содержимое
----- ----- ----------------------------------------------------------------
----¬ ---> 'M'(4dH) - за этим блоком есть еще блоки
+0 1 ¦Тип+--+--> 'Z'(5aH) - данный блок является последним
+---+---¬
+1 2 ¦Владелец параграф владельца (для FreeMem); 0 = владеет собой сам
+---+---+
+3 2 ¦Размер ¦ число параграфов в этом блоке распределения
+---+---+-------- - ----¬
+5 0bH ¦зарезервировано ¦
L---+---+---+---+ - +----
+10H ? Блок памяти начинается здесь и имеет длину (Размер*10H) байт.
Параграф по этому адресу возвращает функция 48H AllocMem.
Замечания:
* блоки памяти всегда выравнены на границу параграфа ("сегмент блока")
* блоки M-типа: следующий блок находится по (сегмент_блока+Размер):0000
* блоки Z-типа: (сегмент_блока+Размер):0000 = конец памяти (намек: a000H=640K)
* После функции 4bH Exec, Z-блок начинается с ( PSP -1):0000 нового процесса
Обратим внимание на последнее предложение. Из него можно сделать следу-
ющий вывод: в момент, когда выполняется какая-либо программа -- последний
МСВ-блок закреплен за нею. PSP этой программы строится сразу за дескриптором
МСВ-блока. Когда программа завершается (процесс "умирает") специальная сис-
темная подпрограмма освобождает этот последний из МСВ-блоков для других це-
лей. При этом информация о том, сколько памяти следует освободить, берется
из дескриптора МСВ-блока. Отметим еще, что сама выполняемая программа для се-
бя также отмечает, сколько памяти находится в ее распоряжении (как правило
новый запускаемый процесс получает всю доступную в этот момент память). Эта
информация хранится в PSP (слово по смещению 2 от начала PSP указывает на
вершину свободной памяти. Системная п/п-ма, освобождающая память, может вос-
пользоваться и этими сведениями.
Резюме: Если мы хотим урвать себе немного памяти под наш резидент, мы
должны как-то обмануть Системную п/п-му, освобождающую память. Это можно сде-
лать, заменив данные в дескрипторе последнего (который будет освобожден)
МСВ-блока а также -- данные в PSP процесса, занимающего этот блок. Мы должны
их уменьшить на величину освобождаемой памяти. Если мы сделаем это, то после
"смерти" процесса и освобождения принадлежавшей ему памяти, останется нигде
не описываемая область памяти, потерянная для DOS. Туда-то мы и можем смело
сажать резидент. Вот как это выглядит (ХХ1 и ХХ2 -- значения длины МСВ-блока
и вершины свободн. памяти в PSP процесса):
-----------------------------------------------------------¬
¦ рис. 9 ¦:
L-----------------------------------------------------------
до старта процесса:
----------------------------------------------------------------------¬
L----------------------------------------------------------------------
L----------------------------свободная---------------------------------
память
после старта процесса: (образовался новый МСВ-блок)
------ХХ1-----TХХ2--------T-------------------------------------------¬
L-------------+-----------+--------------------------------------------
дескриптор ¦PSP процесса процесс ¦¦
последнего L-----------------------последний-----------------------¦
МСВ-блока МСВ-блок вершина сво--T-
ХХ1 - его размер бодной памяти-
ХХ2
после нашей корректировки: (меняем значения ХХ1,ХХ2 на ХХ1',ХХ2')
/процесс при этом не пострадает -- он использует лишь малую часть
имеющейся у него памяти/
------ХХ1'----TХХ2'-------T----------------------------------T--------¬
L-------------+-----------+----------------------------------+---------
дескриптор ¦PSP процесса процесс ¦¦
последнего L--------------------последний-----------------¦
МСВ-блока МСВ-блок вершина сво--T-
ХХ1' бодной памяти-
ХХ2'
после "гибели" процесса:
-------------------------------------------------------------T--------¬
L------------------------------------------------------------+---------
L----------------------------свободная------------------------L---T----
память ¦
¦
этот кусок теперь наш ---------------------
Вот пример реализации посадки резидента в МСВ-блок:
-----------------------------------------------------------¬
¦ пример 11 ¦:
L-----------------------------------------------------------
TITLE Это - COM. программа N11 - резидент, сажаемый в МСБ-блок
ASSUME CS:CodeSegment
;-----------------------------------------------------------------------------
CodeSegment SEGMENT PARA
ORG(100h)
Start:
MainProcedure PROC NEAR
;
;
MOV AX,CS:[02] ;берем вершину свободной памяти
; ; (в параграфах) из PSP
;
SUB AX,4h ;уменьшаем ее на 4h (память
; ; уменьшится при этом на 40h)
; ; (мы просто прикинули, что
; ; 4 параграфов нам хватит, хо-
; ; тя,- может хватить и одного)
;
; ;-----пересылка резидента--¬
MOV SI,OFFSET int05_treater ;копируем из источника ¦
; ; DS:int05_treater ¦
; ; ¦
MOV ES,AX ;копируем в приемник ES:00 ¦
XOR DI,DI ; (в ES - новая вершина ¦
; ; свободной памяти) ¦
MOV CX,OFFSET rezident_end - OFFSET int05_treater ; ¦
CLD ; ¦
REPE MOVSB ;---------------------------
;
MOV BX,DS ;¬перенацеливаем DS с PSP
DEC BX ;+на дескриптор МСВ-блока
MOV DS,BX ;-
;
SUB word ptr DS:[03h],4h ;уменьшаем размер МСВ-блока
; ; (слово в описателе МСВ)
SUB word ptr DS:[12h],4h ;уменьшаем вершину свободной
; ; памяти (слово в PSP)
;
CLI ;кладем в таблицу векторов
XOR BX,BX ; адрес нашего обработчика¬
MOV DS,BX ; прерывания 05 +¬
MOV word ptr DS:[5*4 ],0 ; ------> OFFSET ¬ -¦
MOV word ptr DS:[5*4+2],AX ; ------> SEGMENT +--------
STI ;в AX - сегмент места, куда
; ; был переслан резидент
RET ;обычный выход в DOS
;
int05_treater:PUSH AX ;Tтело резидента; теперь нам
MOV AX,0E04h ;¦абсолютно не важно в каком
INT 10h ;¦месте программы его распо-
POP AX ;¦лагать
IRET ;-
rezident_end: ;
;
;
MainProcedure ENDP
;
CodeSegment ENDS
END Start
Коментарии: Мы посадили резидент на прерывание 05 (печать экрана, - если
помните). При нажатии Print Scrin он напечатает ромбик. Мы все сделали, как
описывали выше; - уменьшили значения величин "длина МСВ-блока" (слово по сме-
щению 03 в дескрипторе МСВ-блока) и "вершина свободной памяти" (слово по сме-
щению 02 в PSP процесса). Вследствии этого мы стали владельцами куска памяти,
начинающегося с адреса новой вершины свободной памяти. Величина этого куска =
тому, на сколько мы уменьшили длину МСВ-блока и вершину свободной памяти (в
параграфах). После этого в полученное убежище мы переслали код резидента.
Скомпилируйте и запустите эту программу. А после этого посмотрите карту
владельцев памяти (либо при помощи Volcov Comander-а, либо -- какой-нибудь
специальной утилиты). В карте Volcov Comander-а наш резидент не виден!!! В
картах, выдаваемыми продвинутыми утилитами фиксируется кем-то занятый
МСВ-блок, но не показаны зацепленный им вектор прерывания (нужно отдельно
изучать список векторов).
Вышепоказанный метод посадки резидентов ко всему прочему еще и сильно
экономит оперативную память; не вызывает ее фрагментации. Экономия возможна,
т.к. резиденты, сажаемые методами 1 и 2 состоят на 256 байт из PSP посадившей
их программы-инициализатора (самому резиденту такое наследство нА фиг не нуж-
но).Есть и дополнительные потери памяти.
И еще одно преимущество - при таком способе программа-инициализатор не
обязана завершаться в момент окончания посадки резидента.
Вторая из двух крайне важных фенек :----------------------------------------¬
ЗАПУСК ПРОГРАММЫ ИЗ ПРОГРАММЫ. Функция 4Bh (EXEC) прерывания 21h DS-DOS¦
------======================================================================-
Очень часто программы, запущенные нами, запускают другие программы (свои
дочерние процессы). Например - игрушка F-115. Мы запускаем ее при помощи фай-
ла f115.com , который столь мал, что вряд-ли является основной исполняемой
программой. Этот файл, в свою очередь запускает что-то другое.
Очень часто вирусу необходимо запустить какую-то внешнюю программу. => И
мы должны это изучить. Вирусы запускают внешние программы, как правило, при
помощи прерывания 21h функции 4Bh (EXEC). => изучим эту фишку.
Обычное прерывание, обычная функция. Мы знаем как такими вещами пользо-
ваться. ОДНАКО ПОДГОТОВИТЬ НЕОБХОДИМЫЕ УСЛОВИЯ ДЛЯ РЕАЛИЗАЦИИ EXEC ОКАЗЫВАЕТ-
СЯ НЕ ТАК ПРОСТО.
Для этого мы должны познакомиться с двумя часто используемыми фиговина-
ми: DTA (область передачи данных -- Disk Transfer Area) и EPB (блок парамет-
ров функции выполнения процесса -- Exec Parameter Block). Весьма важные фиго-
вины.
DTA (область передачи данных) -- специальная область, начинается в PSP
исполняемой программы по адресу 80h и простирается до FFh. Сразу после начала
выполнения программы-владелца-PSP устроена она так: в первом ее байте содер-
жится число символов, введенных с клавиатуры после ввода имени программы. Со
второго байта начинаются введенные символы. О DTA мы пока-что должны лишь
знать то, что такая штука существует, где она находится и -- какого она раз-
мера.
Перед запуском EXEC мы должны сохранить где-то нашу DTA (некоторые ви-
рус-халявщики этого не делают), а после завершения дочернего процесса и воз-
врата управления основной программе -- восстановить исходную DTA.
EPB (блок параметров функции выполнения процесса) -- без него не обой-
тись. Если хотите воспользоваться функцией EXEC, Вы должны сначала соорудить
EPB. EPB -- некий описатель. Вот его формат (по данным thelp):
--------объясним ниже
Блок параметров EXEC для DOS Fn 4Bh (при AL=0)
Смещ. Длина Содержимое
----- --- ------------------------------------------------------------------
--------¬
+0 2 ¦ ¦ сегмент среды для порождаемого (0000=наследовать текущий)
+---+---+-------¬
+2 4 ¦ смещ. сегмент¦ адрес командной строки для помещения в PSP + 80H
+---+---+---+---+
+6 4 ¦ смещ. сегмент¦ адрес блока FCB для помещения в PSP + 5cH
+---+---+---+---+
+0ah 4 ¦ смещ. сегмент¦ адрес блока FCB для помещения в PSP + 6cH
L---+---+---+----
Первое слово (сегмент среды для порождаемого) нас пока-что не волнует,
пусть будет нулем. Это означает, что порождаемый процесс наследует сегмент
среды процесса-родителя.
Далее -- четырехбайтный адрес командной строки. Он будет формироваться
нами так: PSP:80h (первое слово -- 80h; второе -- сегментный адрес PSP).
Далее -- четырехбайтный адрес блока FCB1. Он будет формироваться нами
так: PSP:5Ch (первое слово -- 5Ch; второе -- сегментный адрес PSP).
Далее -- четырехбайтный адрес блока FCB2. Он будет формироваться нами
так: PSP:6Ch (первое слово -- 6Ch; второе -- сегментный адрес PSP).
Обратите внимание, - все объекты описанные в EPB, имеют сегментный адрес
PSP родительского процесса. => все эти объекты наследуются дочерним процес-
сом. Иногда это бывает неприемлемо. Иногда необходимо определить уникальную
командную строку, сегмент среды и т.д. Здесь мы дадим упрощенный вариант ис-
пользования EXEC, когда этого не требуется.
Ну вот и все о EPB.
Перед запуском EXEC мы должны освободить место для дочернего процесса,
т.к. основной (родительский) процесс владеет ВСЕМ доступным объемом памяти.
Для этого надо воспользоваться специальной функцией 4Ah прерывания 21h. Эта
функция сжимает/раздувает владения процесса до указанной ей длины. Вот вход-
ные и выходные параметры функции:
Вход AH ¦ 4Ah (код функции)
ES ¦ сегмент распределенного блока памяти
BX ¦ желаемый размер блока в 16-байтовых параграфах
----------+----------------------------------------------------
Выход AX ¦ код ошибки если CF установлен
BX ¦ наибольший доступный блок (если расширение неудачно)
Понятно, что сегментом распределенного блока памяти будет сегментный ад-
рес PSP процесса-родителя. Желаемый размер блока -- сколько памяти оставить
за процессом-родителем (включая его PSP!).
Перед запуском EXEC мы должны переместить стек родителя в его новые вла-
дения (ведь прежние мы значительно уменьшили).
Вот как выглядит наш мудрЕж с распределением памяти и переопределением
стека:
-----------------------------------------------------------¬
¦ рис.10 ¦:
L-----------------------------------------------------------
до подготовки к вызову EXEC:
------T----родительский процесс (узурпировал всю память)-----------------
LPSP--+------T==================================T------------------------
SS(если не СОМ-файл) SP
L----------------------стек---------
вот как должно быть перед вызовом EXEC:
------Tродительский процесс-------T-здесь возникнет дочерний процесс ----
LPSP--+--T=====================T--+--------------------------------------
----SS(если не СОМ-файл) SP
¦ L---------стек---------
¦
L--а если это СОМ-файл - то SS указывает на PSP и менять его не надо
Перед запуском EXEC мы должны сохранить в теле нашей основной программы
значения регистров SS,SP,DS,ES, а после завершения дочернего процесса -- вос-
становить их.
Далее
|