ZF

                  Инфицирование PE-программ
                            от
                    Quark и Quantum [VLAD]

             (Вольный перевод - (c) DrMad, 1998-99)

    Переносимый формат (PE) используется в Win32,  Windows NT и  Win95,
что  делает  их  очень популярными и,  похоже,  сделает их доминирующей
формой  исполняемых  программ  в   будущем.   Заголовок   NE,   который
использовался   в  Windows  3.11,  совершенно  другой  по  отношению  к
PE-заголовку, и их нельзя путать.

    Никакая из технологий, описанных в этом документе, не тестировалась
под  Windows  NT,  потому  что  никто из вирмеров (которых мы знаем) не
имеет к ней доступа.

    В конце этого документа  размещена  копия  PE-формата,  которой  не
легко следовать,  но доступны лишь общеизвестные ссылки. Turbo Debugger
32 (TD) -  это  отладчик,  который  использовался  при  создании  этого
текста, но SoftIce'95 тоже пойдет.

  Обращение к Windows 95 API
  --------------------------

    "Нормальные" приложения  обращаются  к   Win95   API   при   помощи
использования  таблицы импорта.  Имя каждого компонета API,  к которому
приложение  хочет  обратиться,  помещается  в  таблицу  импорта.  Когда
приложение загружено,  то данные, необходимые для вызова API помещаются
в таблицу импорта.  Как было объяснено в введении в  Win95  (сходите  и
посмотрите),   мы   не   можем   модифицировать   эту   таблицу  из  за
Майкрософтовской предусмотрительности.

    Простейшее решение этой проблемы  состоит  в  том,  чтобы  вызывать
kernel  напрямую.  Мы  должны  полностью  обозреть вызывающую структуру
Win95 и обращаться прямо в точку входа dll.

    Для того,  чтобы получить обработчик dll/exe (называемых  модулем),
мы  можем  использовать  обращение к API-функции GetModuleHandle,  и не
существуют еще функции для получения доступа к точке входа в  модуль  -
включая функцию для взятия адреса API, а именно GetProcAddress.

    Но это сильно усложняет проблему курицы и яйца.  Как мне обращаться
к API,  если я еще ен могу обращаться к API. Решение заключается в том,
чтобы вызывать Win95 API,  про которые мы знаем,  что они размещаются в
памяти - это API из KERNEL32.DLL - посредством обращения к  адресу,  по
которому они всегда размещены.

    Немножко кода
    -------------

    Обращение к API "нормальными" программами выглядит так:

             call ИМЯФУНКЦИИAPI
например     call CreateFileA

    Этот вызов ассемблируется так:

        db 9ah          ; вызов
        dd ????         ; смещение в таблице переходов

    Код в таблице переходов выглядит так:

        jmp far [смещение в таблице импорта]

    Строка в таблице импорта заполняется адресом диспетчера функции для
этой API-шки.  Этот адрес доступен при помощи GetProcAddress. Диспетчер
функции выглядит так:

        push Номер функции
        call Точка входа в модуль

    Существуют API-функции    для    получения   точки   входа   любого
именованного модуля,  но нет системного доступа к номеру функции.  Если
мы обращаемся к функциям KERNEL32.DLL (которой принадлежат все функции,
пригодные для инфицирования программ), тогда нам не нужно ничего более,
кроме  самого  вызова.  Мы  просто  помещаем  номер  функции  в  стек и
обращаемся к точке входа в модуль.

    Проблемы
    --------

    При завершении вируса Bizatch мы протестировали много систем. После
долгих  экспериментов  мы  пришли  к  выводу,   что   модуль   KERNEL32
располагается  в  постоянном месте памяти - как мы и предсказывали - но
это  место  различно   для   "Июньской   бета-версии"   и   "Финального
августовского  релиза",  который  пришлось тестировать отдельно.  Более
того,  одна функция (функция  получения  текущего  времени/даты)  имела
различные  порядковые  номера в Июньском и Августовском релизах.  Чтобы
компенсировать это,  я добавил код,  который проверяет -  находится  ли
KERNEL  в  одной  из  двух возможных позиций,  если же KERNEL вообще не
найден, то вирус просто не работает и отдает управление носителю.

    Адреса и номера функций
    -----------------------

    Для Июньского релиза KERNEL располагается по адресу 0BFF93B95h, а для
Августовского - по адресу 0BFF93C1Dh.

  Функция             Июнь             Август
  --------------------------------------------------
  GetCurrentDir       BFF77744         BFF77744
  SetCurrentDir       BFF7771D         BFF7771D
  GetTime             BFF9D0B6         BFF9D14E
  MessageBox          BFF638D9         BFF638D9
  FindFile            BFF77893         BFF77893
  FindNext            BFF778CB         BFF778CB
  CreateFile          BFF77817         BFF77817
  SetFilePointer      BFF76FA0         BFF76FA0
  ReadFile            BFF75806         BFF75806
  WriteFile           BFF7580D         BFF7580D
  CloseFile           BFF7BC72         BFF7BC72

    С исользованием  отладчика  типа  Turbo  Debugger 32bit из
Tasm 4.0 могут быть найдены другие функции.

  Соглашения по вызову
  --------------------

    Windows 95 написан на С++ и Ассемблере,  в основном на С++. Но хотя
Си-шные  соглашения кажутся гораздо удобней для реализации,  Майкрософт
не использует их.  Все Win95 API вызываются по правилам языка  Паскаль.
Например, вот пример API из help-файла для Visual C++:

        FARPROC GetProcAddress(
                HMODULE  hModule,              // хэндл модуля
                        LPCSTR  lpszProc       // имя функции
        );

    Сначала можно подумать,  что  все  что  вам  нужно  сделать  -  это
поместить в стек хэндл следом за указателем на имя функции и обратиться
к API - но нет. В соответствии с Паскалевскими соглашениями по вызовам,
параметры должны помещаться в стек в обратном порядке:

          push offset lpszProc
          push dword ptr [hModule]
          call GetProcAddress

    Используя отладчик    типа    Turbo   Debugger   32bit   мы   можем
протрассировать обращение (по шагам) и проследовать до  вызова KERNELа,
как  установлено  ранее.  Это  позволит нам получить номер функции и мы
можем добраться до таблицы импорта.

  Инфицирование PE-формата
  ------------------------

    Поиск начала  действительного  РЕ-заголовка аналогичен применяемому
для NE-файлов,  проверкой ДОСовского поля Relocations на больше  40h  и
переходом  на  адрес,  указанный  двойным  словом  3Ch.  Если заголовок
начинается с 'NE', то это программа для Windows 3.11, если 'PE', то это
программа для Win32/WinNT/Win95.

    Внутри PE-заголовка  имеется  "таблица объектов",  которая является
наиболее важной особенностью формата с  точки  зрения  программирования
вирусов.  Для  добавления  кода  к  носителю  и  переназначения  начала
управления на вирус необходимо добавить еще одно вхождение  в  "таблицу
объектов".  К счастью,  Майкрософт, оьуянный идеей о выравнивании всего
до 32-битной границы,  так что в большинстве случаев должно быть  место
для  дополнительного  вхождения в пустом пространстве,  что означает чт
онет необходимости перемещать какие-либо таблицы.

    Обзор инфицирования PE-программ:

         Найдите смещение в файле для PE-заголовка
         Прочитайте достаточную часть PE-заголовка для расчета
         полного размера
         Прочитайте весь PE-заголовок и таблицу объектов
         Добавьте новый объект к таблице объектов
         Установите "Точку входа RVA" для нового объекта
         Припишите вирус к программе в рассчитанном физическом
         местоположении
         Запишите PE-заголовок назад в файл

    Чтобы найти таблицу объектов:

    Переменная "Размер заголовка" (не совпадающий с "Размером Заголовка
в  NT")  -  это размер ДОС-заголовка,  РЕ-заголовка и таблицы объектов,
совместно.  Для того,  чтобы прочитать таблицу объектов,  прочитайте от
начала файла HeaderSize байтов.

    Таблица объектов непосредственно следует за NT-заголовком. Величина
"NTheadersize" показывает,  как много байтов следует за полем  'flags'.
Для   того,   чтобы  получить  смещение  таблицы  об  ёектов,  возьмите
NTheaderSize и сложите со смещением поля флагов (24).

    Добавление объекта:

    Возьмите "количество объектов" и умножьте его на 5*8 (размер поля в
таблице  объектов).  Это  даст смещение пустого пространства для нового
поля в таблице объектов.  Содержимое для нового поля  таблицы  объектов
должно    быть    вычислено    с    использованием    информации    для
программы-носителя.

    RVA =  ((prev  RVA  +  prev Virtual Size)/OBJ Alignment+1)
           *OBJ Alignment
    Virtual Size  =  ((size  of  virus+buffer  any  space)/OBJ
                     Alignment+1) *OBJ Alignment
    Physical Size  =  (size  of  virus/File  Alignment+1)*File
                      Alignment
    Physical Offset  =  prev  Physical  Offset + prev Physical
                        Size
    Object Flags = db 40h,0,0,c0h
    Entrypoint RVA = RVA

    Увеличьте "количество объектов" на 1.

    Запишите вирусный   код   по  вычисленному  "физическому  смещению"
(Physical Offset), общим количеством Physical Size байтов.

    Примечания
    ----------

    Майкрософт больше  не включает информацию о РЕ-заголовках в свои CD
для разработчиков. Вероятно, они надеялись, что это затруднит написание
вирусов для Win95.  Информация,  содернжащаяся в следующей статье, была
выдрана из бета-версии Win32 SDK CDROM-а.

    Инструменты
    -----------

    Есть много  хороших  книжек по низкоуровневому программированию для
Windows 95.  "Недокументированный Windows 95" - необыкновенно  полезная
книга (она посвящена в-основном взаимоотношениям DOS и Windows),  к ней
и на их WWW-сайте прилагаются на утилиты,  бесценные для тех, кто хочет
изучать инфицирование для Win95.


(C) NF, 1998-2004