гл.4 СОЗДАНИЕ РЕЗИДЕНТНЫХ ПРОГРАММ (практическая реализация)
============================================================
Итак, мы познакомились с механизмом работы прерываний и научились реали-
зовывать вызов прерываний на уровне обычный JMP-ов и CALL-ов и даже не ис-
пользовать при этом таблицу векторов.
Чудесно!
Теперь об создании резидентных программ (именно таковыми являются 'дра-
коны' и 90% всех 'вирусов').
Что такое резидентная программа (в дальнейшем - просто - резидент) ?
Это такая программа, которая находится в оперативной памяти постоянно
(обычные, нерезидентные программы присутствуют в памяти лишь во время их не-
посредственного исполнения; когда их выполнение заканчивается -- они "умира-
ют" - память занятая ими - освобождается . Резидент же может обитать в ОЗУ,
[кстати rezide - по англ. 'обитать'] не будучи в данный момент исполняем, но
в то же время, - готовый к действию).
Вот как распределяется память при выполнении обычных и резидент. прог-
рамм (КРАЙНЕ упрощенно):
-----------------------------------------------------------¬
¦ рис.2 ¦:
L-----------------------------------------------------------
обычная программа:
До загрузки чего-либо:
---------T--------------T----------------------------------------
¦таблица ¦ ¦
¦векторов¦ область DOS ¦ свободная память . . . . . . . . . .
¦прерыв-й¦ ¦
L--------+--------------+----------------------------------------
0:0 ----> старшие адреса
Загрузили и исполняем обычную программу:
¦
-----------------
---------T--------------T--------V----------T--------------------
¦таблица ¦ ¦ TETRIS.EXE ¦
¦векторов¦ область DOS ¦(есть такая глупая ¦ свободная память .
¦прерыв-й¦ ¦ игрушка) ¦
L--------+--------------+-------------------+--------------------
0:0
После того, как прогр-а завершилась:
---------T--------------T----------------------------------------
¦таблица ¦ ¦
¦векторов¦ область DOS ¦ свободная память . . . . . . . . . .
¦прерыв-й¦ ¦ (все вернулось на кругИ своя...)
L--------+--------------+----------------------------------------
0:0
===========================================================================
Теперь - что касается резидентов:
До загрузки чего-либо:
---------T--------------T----------------------------------------
¦таблица ¦ ¦
¦векторов¦ область DOS ¦ свободная память . . . . . . . . . .
¦прерыв-й¦ ¦
L--------+--------------+----------------------------------------
0:0
Загрузили резидент:-------------¬
¦
-----------------
---------T--------------T--------V----------T--------------------
¦таблица ¦ ¦ KEYR23.COM ¦
¦векторов¦ область DOS ¦(драйвер рус. кла- ¦ свободная память .
¦прерыв-й¦ ¦ виатуры) ¦
L--------+--------------+-------------------+--------------------
0:0 ---------------------------
¦
И так- -- до окончания работы на PC! Резидент будет сидеть в ОЗУ и быть
всегда готов вам услужить. Если Вы теперь загрУзите tetris, он попадет вот
куда:
---------T--------------T-------------------T----------T---------
¦таблица ¦ ¦ KEYR23.COM ¦ ¦
¦векторов¦ область DOS ¦(драйвер рус. кла- ¦TETRIS.EXE¦свободная
¦прерыв-й¦ ¦ ¦ виатуры) ¦ ¦память...
L--------+--------------+-¦-----------------+----------+---------
0:0 Lстал резидентом
Только программы типа Volkov Comander могут безболезненно удалять рези-
денты из памяти (и то лишь те, которые были загружены после них {удаляющих}).
Сделать программу резидентной (постоянно присутствующей в памяти) --
ЭЛЕМЕНТАРНО. Вот один из способов (в дальнейшем мы рассмотрим их все):
(прокоментируем ниже)
-----------------------------------------------------------¬
¦ пример 4 ¦:
L-----------------------------------------------------------
TITLE Это - COM. программа N4 для демонстрации посадки резидента
ASSUME CS:CodeSegment
;---------------------------------------------------------------------------
CodeSegment SEGMENT PARA
ORG(100h)
Start:
MainProcedure PROC NEAR
;
;
;
;
;
MOV AX,0E61h ; напечатать
INT 10h ; символ 'a'
resident_end: ;
;
MOV DX,OFFSET resident_end ; DX<-- адрес последней команды
; ; рез-та+1
INT 27h ; вернуться в DOS
; ; оставив программу резидентной
;
;
;
MainProcedure ENDP
;
CodeSegment ENDS
END Start
Если Вы захотите откомпилировать и запустить вышеданный пример, то лучше
перед этим выйти из Norton Comander-а и запустить Volkov Comander (если у Вас
его еще нет -- настоятельно советуем приобрести !). Volkov Comander позволяет
по нажатию комбинации клавиш Alt/F5 показать карту памяти PC и Вы можете уда-
лить любой резидент, загруженный после Volkov Comander-а.
А теперь -- коментарии к примеру 4:
Чтобы после выполнения программы выйти в DOS и одновременно оставить
прогр-му резидентной всего-то лишь и нужно: ВМЕСТО КОМАНДЫ RET ДАТЬ ПРЕРЫВ-Е
INT 27h и при этом в регистр DX нужно поместить адрес последнего оператора
остающегося резидентно куска+1. Адрес этот = смещению от PSP программы (если
не знаете ничего о PSP, то почитайте хотя бы /1/; или -- чуть подождите, мы
кратко расскажем об этом позднее -- в гл.5). Прерывание INT 27h -- програм-
мное прерыв-е. П/п-ма его обработки изменяет структуру ОЗУ так, что часть па-
мяти теперь навечно закреплена за фрагментом Вашей программы.
Итак, мы оставили в ОЗУ резидентный фрагмент, который печатает букву
'a'. Вопрос -- когда же он будет выполняться? А вот -- никогда! Вернее --
всего один раз - когда мы оставляли резидент в памяти. Но, тем не менее, он
останется в ОЗУ. Он больше никогда не получит управление. Это - настоящий
программный труп, который просто занимает место. Ради прикола запустите эту
программу еще разок. И в ОЗУ останется еще один памятник глупости. Расботая в
Volkov Comander-е, нажав Alt/F5, Вы можете в этом убедиться. Сколько бы раз
эта программа не запускалась - каждый раз она будет отхватывать все новые и
новые кусочки памяти и оставлять в них код печати буквы 'a', который никогда
больше не будет выполнен.
Но ведь резиденты пишутся не для этого! Они остаются в памяти ДЛЯ того,
чтобы в ОПРЕДЕЛЕННЫЕ МОМЕНТЫ получать управление и выполнять определенные
действия. Когда и каким образом? ПРИ ПОМОЩИ МЕХАНИЗМА ПРЕРЫВАНИЙ!
ПОДРОБНЕЕ:
Что будет если программа заменит в таблице векторов прерываний адрес ис-
ходной п/п-мы обработки прерывания на адрес своей собственной п/п-мы? В этом
случае, в момент, когда будет дано это прерыв-е, управление получит эта ейная
п/п-ма. Возникнет некий самозванец, который будет обрабатывать прерывание по
своему. Например, -- вы нажали букву "А", а PC напечатал в на экране "О"; Вы
начали печатать документ на принтере, а одновременно его текст запишется в
специальный скрытый файл (пример шпионящего "дракона"); программа TETRIS.EXE
загружена на выполнение (прерывание 21h), а вирус "почуял" это и впридачу к
запуску программы "заразил" ее своей копией.
Прерывания происходят постоянно, и если, окончив свою работу, прогр-ма,
на адрес которой был заменен вектор прерывания, исчезнет из памяти - "умрет"
и при этом не восстановит исходное значение вектора (адрес исходной п/п-мы
обработки прерывания), то PC по-прежнему всякий раз, когда будет генериться
прерывание, будет передавать управление в область, где раньше сидел самозван-
ный обработчик, а теперь остался лишь мусор. И PC зависнет (в лучшем случае).
Зависнет намертво.
Следовательно, если мы решимся перехватить прерывание то существуют
только две возможности:
1. Либо, если мы не будем сажать в память резидент, то, перед тем как
"умрем", отреставрируем адрес исходного обработчика прерываний;
2. Либо мы не будем реставрировать адрес исходного обработчика прерыва-
ний, и, сохранив прерогативу обработки прерывания за собою, оставим наш ори-
гинальный обработчик резидентно в памяти;
Нас интересует второй вариант, т.к. созданные нами программы будут кон-
тролировать работу PC во время всей DOS-овской сессии, во время выполнения
других программ, во время всех действий пользователя (который об этом и знать
не будет).
ОЧЕНЬ ВАЖНОЕ ДОБАВЛЕНИЕ -- очень часто стандартная п/п-ма обработки пре-
----------------------- рывания все же ДОЛЖНА его обработать, независимо
от того, обрабатывали ли мы его сами. Следовательно, после того, как Вы, пе-
рехватиши вектор, в момент генерации прерывания сделали то что Вам нужно, Вы
все же должны отдать управление исходному обработчику. Это похоже на случай,
когда некто с целью наварить баксов перехватывает выгодный подряд на строи-
тельство моста, но сам, не будучи строителем, все же вынужден нанять про-
фессионалов (выдать субподряд).
РЕЗЮМИРУЕМ: вот что нам нужно сделать, чтобы создать активный резидент,
реагирующий на появление определенного прерывания и не грохающий работу PC:
1. Сохранить в своем теле адрес истинного обработчика прерыв-я.
2. Заменить в таблице векторов адрес истинного обработчика прерыв-я на
адрес собственной п/п-мы.
3. Завершить программу, оставив собственную п/п-му обработки прерывания
в памяти PC резидентно.
4. Сконструировать резидентную часть так, чтобы после обработки перехва-
ченного прерывания позволить исходному обработчику тоже его обработать.
Если представить все это наглядно, то получится, что наш резидент вкли-
нивается в некую цепочку осуществляющую реагирование PC на некое прерывание.
Вот какая была цепочка до нас:
-----------------------------------------------------------¬
¦ рис.3 ¦:
L-----------------------------------------------------------
¦
¦ ------¬ ------>выполняется
¦ \ ¦ / +TTTTT+ ¦ п/п-ма
L - * - --------> -+++++++ ¦ обработки
/ ¦ \ ¦+++++++ ¦ (стандартная)
возник ¦¦ ¦ ¦ IRET------------¬
сигнал -- ¦L------ ¦ ¦
-- прерывание Lв таблице ---------T---- ¦
векторов делается ¦
---------------¬ найден адрес переход ¦
V L¬ обработчика по этому ¦
¦ (стандартного) адресу ¦
¦ ¦
L------T------------------------------------------
делается переход обратно - в пользовательскую программу,
в ту точку, перед которой прерыв-е возникло
А вот какая стала цепочка после того, как мы перехватили прерыв-е
¦ г================================¬
¦ ------¬ ¦ ------>выполняется ¦
¦ \ ¦ / +TTTTT+ ¦ ¦ ---НАША---- ¦
L - * - --------> -+++++++ ¦ ¦ п/п-ма обработки ¦
/ ¦ \ ¦+++++++ ¦ ¦ (она резидентна) ¦
возник ¦¦ ¦ ¦ ¦ ¦ ¦
сигнал -- ¦L------ ¦ ¦ L---->------¬ ¦
-- прерывание Lв таблице ---->----T---- ¦ ¦
векторов ¦ делается ¦ ¦
------------¬ найден адрес ¦ переход ¦ ¦
¦ L¬ ---НАШЕГО---- ¦ по этому ¦ ¦
V L¬ обработчика ¦ адресу ¦ ¦
¦ ¦ возвращаем ¦ ¦
¦ ¦ управление ¦ ¦
¦ ¦ стандартно- ----+ ¦
¦ ¦ му обработ- ¦ ¦
¦ наше новое-¦ чику (JMP Far) ¦ ¦
¦ звено L=============================¦==-
¦ ---------<--------
¦ выполняется
¦ п/п-ма
¦ обработки
¦ (стандартная)
¦ IRET-----------------¬
L--------T------------------------------------------
делается переход обратно - в пользовательскую программу,
в ту точку, перед которой прерыв-е возникло
Прочитаем еще раз пп.1-3 РЕЗЮМЕ.
А ведь все это мы уже делали! (правда по отдельности)
Попробуем теперь сделать все вместе.
Т.е. перехватим какое-нибудь прерывание <=> сделаем для него резидентный
обработчик.
Какое прерывание выбрать? Предложим для простоты прерывание N 5 (печать
экрана). Оно возникает, если Вы нажмете клавишу Print Scrin. Вызываемая при
этом п/п-ма печатает на принтере копию экрана PC. Прерывание N 5 не требует и
не возвращает никаких параметров (чУдно!), и еще его не обязательно возвра-
щать стандартному обработчику; ничего не случится, если Вы его зажмете (весь-
ма редкое свойство). Вы наверное не раз замечали что если Вы нажмете Print
Scrin, а принтер не готов -- раздается гудок, -- это стандартный обработчик
прерывания N 5 'назвал' Вас раздолбаем).
Создадим-ка резидент, который, перехватывая прерывание N 5 , ну ска-
жем,.. выведет на экран 'сердечко' (символ с кодом 3), и после этого возвра-
тит управление стандартному обработчику (вежливость - высшая добродетель).
Таким образом, при нажатии Print Scrin сначала будет напечатано 'сердечко'
(работает наш резидентный обработчик), а уже потом раздастся гудок (заработа-
ет стандартный обработчик и обругает Вас за отключенный принтер).
Итак -- вперед!
(подробно прокоментируем ниже)
-----------------------------------------------------------¬
¦ пример 5 ¦:
L-----------------------------------------------------------
TITLE Это - COM. программа N5 для демонстрации посадки резидента
ASSUME CS:CodeSegment
;-----------------------------------------------------------------
CodeSegment SEGMENT PARA
ORG(100h)
Start:
MainProcedure PROC NEAR
;
;
JMP initial ; перепрыгнем через данные
; ; и наш обработчик прер-я 05
; ; на инициализирующую часть
;
;
saved_int05: DD 0 ; данные (хранилище для
; ; адреса стандартного обра-
; ; ботчика прерывания 05 --
; ; -- 2 слова)
;
;-----------наша п/п-а обработки прерывания 05------------------¬
;¦ (она останется резидентной в памяти) ¦
;¦ здесь мы можем приколоться как хотим.. ¦
;¦ ; ¦
int05_treater:PUSH AX ; ¦
; ; ¦
MOV AH,0Eh ;входные параметры прерыв-я 10h:¦
MOV AL,03h ; (печатать 'сердечко' - код 03)
INT 10h ;печатаем 'сердечко' ¦
; ; ¦
POP AX ;PUSH и POP ОЧЕНЬ важны (см. ко-¦
; ; ментарий после примера) ¦
; ; ¦
; ; ¦
JMP dword ptr CS:[saved_int05] ; длин. JMP по адресу, котор. ¦
rezident_end: ;¦ ; находится теперь в хранилище ¦
;¦ ; saved_int05 (возвращаем управ-¦
;¦ ; ление стандартному обработчику¦
;¦ ; прерывания 05) ¦
;L---------------------------------------------------------------
; ;
;-----------инициализирующая часть------------------------------¬
;¦ (здесь мы сажаем резидент, переопределяя адреса) ¦
;¦ ; ¦
initial: XOR DX,DX ; ---¬ ¦
MOV DS,DX ; ---+--> DS = 0 ¦
; ; ¦
MOV AX,DS:[5*4] ;сохраняем в хранилище saved_
MOV word ptr CS:[saved_int05 ],AX ; int05 адрес стандартного ¦
MOV AX,DS:[5*4+2] ; обработчика прерывания 05¦
MOV word ptr CS:[saved_int05+2],AX ; ( OFFSET и SEGMENT ) ¦
; ; ¦
; ; ¦
CLI ;запрещаем прерывания ¦
MOV AX,OFFSET int05_treater ; ¦
MOV word ptr DS:[5*4],AX ;кладем в таблицу векторов ¦
PUSH CS ; адрес нашего обработчика ¦
POP AX ; прерывания 05 ¦
MOV word ptr DS:[5*4+2],AX ; ¦
STI ;разрешаем прерывания ¦
; ; ¦
; ; ¦
MOV DX,OFFSET rezident_end ;DX<--конец резид. части ¦
INT 27h ;закончить программу и ¦
;¦ ; вернуться в DOS, оставив ¦
;¦ ; резидентной п/п-му ¦
;¦ ; int05_treater ¦
;L---------------------------------------------------------------
MainProcedure ENDP
;
CodeSegment ENDS
END Start
коментарии:
Сначала упростим текст программы до наглядной схемы, чтобы лучше все по-
нять:
-----------------------------------------------------------¬
¦ рис.4 ¦:
L-----------------------------------------------------------
-------------¬
¦JMP initial¦======>=======>====>¬ перепрыгнем через данные
L------------- ¦ и наш обработчик прер-я 05
saved_int05: -----------------------------¬ V на инициализирующую часть
¦данные: (хранилище для ¦ ¦ L---------T----------
¦адреса стандартного обра - ¦ ¦--------------
¦ботчика прерывания 05 -- ¦ ¦
¦-- 2 слова) ¦ ¦
L----------------------------- ¦
V
int05_treater:----------------------------¬ ¦
¦наша п/п-ма обработки ¦ ¦
¦прерывания 05 (она оста- ¦ ¦
¦нется резидентной в памяти) ¦ V
rezident_end:L----------------------------- ¦
¦
г=========<=============<============<=-
¦
¦
L=> -----------------------------¬
initial: ¦инициализирующая часть ¦
¦(здесь мы сажаем резидент, ¦
¦переопределяя адреса ¦
¦ ¦
¦MOV DX,OFFSET rezident_end ¦
¦INT 27h (выход в DOS) ¦
L-----------------------------
Как видно из рисунка, прогр-ма состоит из двух главных частей: той, что
остается в памяти резидентно и не выполняется при запуске самой прогр-мы
(данные + наша п/п-ма обработки прерывания 05), и -- инициализирующей части.
Почему порядок следования этих частей именно такой?
Почему инициализирующая часть не остается в памяти резидентно?
Почему наша п/п-ма обработки прерывания 05 начинается оператором PUSH AX
и заканчивается оператором POP AX?
Что за новые операторы CLI и STI?
Далее
|