Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

О PE файлах и длинах секций

Z0mbie
2001

[Вернуться к списку] [Комментарии]

Здесь будет рассказано о трудностях, связанных с увеличением длины последней секции в PE файлах.

Вроде бы ясная и простая вещь. Однако, практически КАЖДЫЙ задает на эту тему вопросы, причем - одни и те же. В связи с этим остается одно: или подробно и публично осветить эту проблему, или сдохнуть. Выберем трудный путь.

Рассмотрим некий PE файл, лучше всего CALC.EXE.

  1. В PE хеадере хранится число секций в файле, это WORD, назовем его pe_numofobjects, оффсет в PE заголовке равен +06.

    Примечание: обычно число секций не равно нулю, хотя маздаю (win9x) на это насрать, и даже если в файле нет ни одной секции, он все равно будет работать, особенно если после PE заголовка всунуть какой-нибудь код. Однако, если в файле совсем не будет импортов, то возникнут трудности с вызовом win32 api-шек. Но и это не проблема, если вместо апишек вызывать процедуру [email protected] по адресу BFF712B9.

    Примечание: сразу после WORD pe_numofobjects идет DWORD pe_datetime (оффсет +08), то есть датавремя создания файла. Если работать с WORD'ом в падлу, то можно предварительно занулить pe_datetime и принять что pe_numofobjects это DWORD.

  2. Сразу после PE заголовка идет таблица секций (==таблица объектов, object table), элементы (записи, object entry) которой описывают секции файла. Сколько записей, столько и секций, всего pe_numofobjects штук. Длина PE заголовка в абсолютном большинстве случаев равна 0F8h байт. Но, поскольку всякое бывает, то берут WORD по +14h, прибавляют 18h, и получают точную длину PE заголовка.

    Формат object entry (одной записи таблицы секций):

    oe_struc                struc
    oe_name                 db      8 dup (?);00 01 02 03 04 05 06 07
    oe_virtsize             dd      ?       ; 08 09 0a 0b
    oe_virtrva              dd      ?       ; 0c 0d 0e 0f  need objectalign
    oe_physsize             dd      ?       ; 10 11 12 13
    oe_physoffs             dd      ?       ; 14 15 16 17  need filealign
    oe_xxx                  dd      ?       ; for obj file
                            dd      ?       ; --//--
                            dd      ?       ; --//--
    oe_flags                dd      ?        ; 24 25 26 27
    ; ---- total size == 0x28 ---------
    oe_struc                ends
     

    Теперь отметим самый важный момент: В таблице объектов хранятся не выровненные значения физической и виртуальной длин секций. Что это значит? Это значит, что чтобы получить настоящие длины секций надо взять эти значения из соответствующих записей в таблице секций и выровнять: физическую длину на pe_filealign, а виртуальную длину на pe_objectalign.

    Смещения же секций (физическое в файле и виртуальное (rva) в памяти) выровнены всегда.

    Примечание: из выравнивания смещений следует, что после всех заголовков но до начала первой секции может быть пустое неиспользуемое место. Например туда записывался CIH.

    Поля pe_filealign и pe_objectalign (DWORD'ы, смещения +3Ch и +39h) суть степени двойки, причем pe_filealign кратно 512 (сектор), а pe_objectalign кратно 4096 (страница в памяти).

    Поэтому процесс выравнивания для одной секции выглядит так: (на C)

    #define ALIGN(x,y)      (((x)+(y)-1)&(~((y)-1)))

         // oe=таблица секций
         // i=номер секции
         oe[i].oe_physsize = ALIGN(oe[i].oe_physsize, pe->pe_filealign);
         oe[i].oe_virtsize = ALIGN(oe[i].oe_virtsize, pe->pe_objectalign);
     

    или на asm'е:

         ; esi=PE-заголовк
         ; edi=элемент таблицы секций
         ; 1. выровняем физическу длину
         mov eax, [esi].pe_filealign
         dec eax
         add [edi].oe_physsize, eax
         not eax
         and [edi].oe_physsize, eax
         ; 2. выровняем виртуальную длину
         mov eax, [esi].pe_objectalign
         dec eax
         add [edi].oe_virtsize, eax
         not eax
         and [edi].oe_virtsize, eax
     

    Кроме того, бывает, что виртуальная длина у всех секций == 0. Такую дрянь производит watcom. И при этом, оно работает. Откуда брать виртуальную длину в этом случае? Я брал вместо нее физическую длину и выравнивал ее на objectalign.

    Я настоятельно рекомендую всем, кто открыл для себя много нового в этом тексте, перед началом заражения файла выровнять в таблице объектов все длины секций, с которым будете хоть как-то оперировать. Не следует делать этого по мере обращения к ним; необходимо сделать это один раз и в самом начале. Это сэкономит массу времени и сил, и иногда не только ваших.

    Важно: далее в этом тексте мы имеем в виду, что физическая и виртуальная длины секций уже выровнены; "выровненная" перед "длина" далее подразумевается само собой.

  3. Для чего PE файл разбит на секции? Для того, чтобы в образе PE файла в памяти (который обычно не соответствует образу на диске) могли чередоваться инициализированные и неинициализированные данные. Кроме того, есть секции со специальным назначением, в них обычно хранятся таблицы импортов, экспортов, ресурсов, фиксапов, отладочной информации и некоторые другие.

    Каждая секция файла имеет <физическую длину> и <виртуальную длину>. Физическая длина - это то, сколько секция занимает на диске. А виртуальная длина - это то, сколько секция занимает в памяти. Разница между этими длинами на ассемблере представляется как DB ?, то есть это и есть неинициализированные данные. Неинициализированные данные могут быть у любой секции. В результате получается такая ситуация:

      ФАЙЛ НА ДИСКЕ                 ПРОГРАММА В ПАМЯТИ
    
       +--------+                       +--------+
       |MZxxxxxx| <---- заголовки ----> |MZxxxxxx|
       +--------+                       |00000000|<--alignment
       |xxxxxxxx|                       +--------+
       |xxxxxxxx| <---- cекция#1 -----> |xxxxxxxx|\~~~~~~~\
       |xxxxxxxx|                       |xxxxxxxx| }-физ.   \
       +--------+                       |xxxxxxxx|/ длина    }-виртуальная длина
       | ...    |             неиниц.  /|00000000|  секции  /   секции
                              данные~~~\|00000000| _______/
                                        +--------+
                                        | ...    |
    

    Несмотря на кажущуюся простоту, возможны такие варианты:

    • Физическая длина секции равна виртуальной. Идеальный вариант, мечта поэта. Обычно встречается когда filealign == objectalign.
    • Физическая длина меньше виртуальной. Это значит, что у секции есть неинициализированные данные.
    • Физическая длина больше виртуальной. Это значит что у секции есть оверлей или alignment. Помните, что если виртуальная длина меньше физической, то некоторая (скорее всего состоящая из нулей) часть образа секции с диска не загрузится - это будет небольшой файловый алигнмент. Хотя никто не запрещает таким образом вставить не загружаемый в память "оверлей" не в конец файла, а между его секциями.

    Alignment - это разница между физической и виртуальной длинами. Можно понимать его по разному: для секции - (A) если длины выровнены, и (B) если длины НЕ выровнены; и для файла, если это (C) заполненный нулями оверлей длиной меньше pe_objectalign. Причем в случаях (A) и (B) разница длин у одной и той же секции может иметь разный знак. Например, если невыровенная physsize=512, невыровненная virtsize=100, выровненная physsize=512, выровненная virtsize=4096, то в случае (A) alignment=3584, а в случае (B) alignment=412. Причем один из них - в файле, а другой - в памяти. Так что решите для себя, о каком из алигнментов вы думаете и/или говорите.

    Замечу, что секции не всегда идут "впритык" друг к другу. Между ними возможны неиспользуемые странички памяти, хотя бывает такое редко. Например бывает, что оффсеты всех секций выровнены на 64k, а виртуальные их длины всего по несколько страниц.

  4. Лирическое отступление в область теории

    ImageBase - это адрес в памяти, куда должен быть загружен PE файл. По умолчанию большинство адресов в файле настроены на указанный в PE заголовке ImageBase (DWORD, смещение +34h). Обычно выровнен на 64k, и навряд ли линкер даст сделать меньше; однако маздайный загрузчик проглотит многие нестандартные значения с самым разным результатом.

    Если файл - это DLL, и загрузить его по указанному адресу нельзя, то происходит перенастройка на другой ImageBase, для этого используется таблица настроек (==фиксапов, релокаций). Если же в этом случае ее не окажется, то файл загружен не будет. Поните об этом, заражая PE DLL'ки, и при отдаче управления в файл вместо PUSH OFFSET/RETN делайте JMP.

    ImageSize (DWORD, оффсет +50h) - это виртуальная длина файла, то есть размер образа файла в памяти, вычисляется как виртуальный адрес (rva) последней секции + виртуальная длина последней секции. Обычно выровнен на pe_objectalign.

    BaseOfCode - это RVA первой кодовой секции, просто копируется сюда из object table при линковке.

    BaseOfData - та же фигня, для второй, обычно секции данных.

    SizeOfCode - длина кода, обычно выставлена корректно и равна вирт. длине первой секции

    SizeOfInitData & SizeOfUninitData - полная херь, выставлено как попало и кое-где. Каким бы ни был метод заражения, менять их нет смысла.

    pe_subsystem (WORD по смещению +5Ch) - равно 2 для GUI приложений, и 3 для консольных аппликух. Иногда встречаются другие значения, и тогда файл заражать не следует.

  5. Оверлеи

    Оффсет оверлея вычисляется как физический оффсет последней секции плюс физическая длина последней секции.

    Длина оверлея вычисляется как длина файла минус оффсет оверлея.

    Оверлей в память вместе с PE файлом не грузится, но pe checksum по нему считается.

    Если длина оверлея меньше pe_objectalign, а длина файла на pe_objectalign выровнена, причем сам оверлей состоит из нулей, то это не орвелей, а алигнмент, и его можно поскипать.

    Если у оверлея первый DWORD=00000001h, а чуть дальше где-то идет '.dbg', то это дебаговая инфа. Есть и другие ее виды.

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

    Замечу, что в NT'ях и далее - большинство файлов с оверлеями, в основном с дебаговой инфой в них.

  6. Собственно, что нужно чтобы дописаться к последней секции.
    • проверить файл на валидность: alredy-infected, subsystem=2/3, ...
    • выровнять длины последней секции
    • передвинуть overlay если он есть или поскипать дебаговую инфу
    • записать вирус в конец последней секции
    • увеличить длины последней секции на соответственно выровненные длины вируса
    • увеличить imagesize на выровненную-на-objectalign длину вируса
    • проапдейтить заголовки

    Пример добавления к последней секции: win9X.Examplo

  7. Некоторые другие методы заражения PE файлов:
    • запись в алигнмент
      • хеадера
      • секции
    • добавление новой секции
    • добавление нескольких секций, возможно фэйковых
    • запись на место секции .reloc (с убиванием фиксапов)
    • запись в ресурсы
    • запись в начало/в конец какой-нибудь (обычно кодовой) секции, с ее увеличением (раздвигаем файл); необходима таблица фиксапов, придется перепатчить весь файл, но это не сложно
    • запись в конец кодовой секции с предварительной ее упаковкой, длина файла не меняется
    • нахождение в секции данных области нулей и запись в нее
    • нахождение в кодовой секции неиспользуемых "пятен" и запись по кусочкам в них
    • выдирание из кодовой секции целой процедуры и запись вместо нее
[Вернуться к списку] [Комментарии]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxer.org aka vx.netlux.org
deenesitfrplruua