гл.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?
Далее
    |