Z0mbie
2001
Здесь будет рассказано о трудностях, связанных с увеличением длины последней секции в PE файлах.
Вроде бы ясная и простая вещь. Однако, практически КАЖДЫЙ задает на эту тему вопросы, причем - одни и те же. В связи с этим остается одно: или подробно и публично осветить эту проблему, или сдохнуть. Выберем трудный путь.
Рассмотрим некий PE файл, лучше всего CALC.EXE.
Примечание: обычно число секций не равно нулю, хотя маздаю (win9x) на это насрать, и даже если в файле нет ни одной секции, он все равно будет работать, особенно если после PE заголовка всунуть какой-нибудь код. Однако, если в файле совсем не будет импортов, то возникнут трудности с вызовом win32 api-шек. Но и это не проблема, если вместо апишек вызывать процедуру kernel@int21 по адресу BFF712B9.
Примечание: сразу после WORD pe_numofobjects идет DWORD pe_datetime (оффсет +08), то есть датавремя создания файла. Если работать с WORD'ом в падлу, то можно предварительно занулить pe_datetime и принять что pe_numofobjects это DWORD.
Формат object entry (одной записи таблицы секций):
Теперь отметим самый важный момент: В таблице объектов хранятся не выровненные значения физической и виртуальной длин секций. Что это значит? Это значит, что чтобы получить настоящие длины секций надо взять эти значения из соответствующих записей в таблице секций и выровнять: физическую длину на pe_filealign, а виртуальную длину на pe_objectalign.
Смещения же секций (физическое в файле и виртуальное (rva) в памяти) выровнены всегда.
Примечание: из выравнивания смещений следует, что после всех заголовков но до начала первой секции может быть пустое неиспользуемое место. Например туда записывался CIH.
Поля pe_filealign и pe_objectalign (DWORD'ы, смещения +3Ch и +39h) суть степени двойки, причем pe_filealign кратно 512 (сектор), а pe_objectalign кратно 4096 (страница в памяти).
Поэтому процесс выравнивания для одной секции выглядит так: (на C)
или на asm'е:
Кроме того, бывает, что виртуальная длина у всех секций == 0. Такую дрянь производит watcom. И при этом, оно работает. Откуда брать виртуальную длину в этом случае? Я брал вместо нее физическую длину и выравнивал ее на objectalign.
Я настоятельно рекомендую всем, кто открыл для себя много нового в этом тексте, перед началом заражения файла выровнять в таблице объектов все длины секций, с которым будете хоть как-то оперировать. Не следует делать этого по мере обращения к ним; необходимо сделать это один раз и в самом начале. Это сэкономит массу времени и сил, и иногда не только ваших.
Важно: далее в этом тексте мы имеем в виду, что физическая и виртуальная длины секций уже выровнены; "выровненная" перед "длина" далее подразумевается само собой.
Каждая секция файла имеет <физическую длину> и <виртуальную длину>. Физическая длина - это то, сколько секция занимает на диске. А виртуальная длина - это то, сколько секция занимает в памяти. Разница между этими длинами на ассемблере представляется как DB ?, то есть это и есть неинициализированные данные. Неинициализированные данные могут быть у любой секции. В результате получается такая ситуация:
ФАЙЛ НА ДИСКЕ ПРОГРАММА В ПАМЯТИ
+--------+ +--------+
|MZxxxxxx| <---- заголовки ----> |MZxxxxxx|
+--------+ |00000000|<--alignment
|xxxxxxxx| +--------+
|xxxxxxxx| <---- cекция#1 -----> |xxxxxxxx|\~~~~~~~\
|xxxxxxxx| |xxxxxxxx| }-физ. \
+--------+ |xxxxxxxx|/ длина }-виртуальная длина
| ... | неиниц. /|00000000| секции / секции
данные~~~\|00000000| _______/
+--------+
| ... |
Несмотря на кажущуюся простоту, возможны такие варианты:
Alignment - это разница между физической и виртуальной длинами. Можно понимать его по разному: для секции - (A) если длины выровнены, и (B) если длины НЕ выровнены; и для файла, если это (C) заполненный нулями оверлей длиной меньше pe_objectalign. Причем в случаях (A) и (B) разница длин у одной и той же секции может иметь разный знак. Например, если невыровенная physsize=512, невыровненная virtsize=100, выровненная physsize=512, выровненная virtsize=4096, то в случае (A) alignment=3584, а в случае (B) alignment=412. Причем один из них - в файле, а другой - в памяти. Так что решите для себя, о каком из алигнментов вы думаете и/или говорите.
Замечу, что секции не всегда идут "впритык" друг к другу. Между ними возможны неиспользуемые странички памяти, хотя бывает такое редко. Например бывает, что оффсеты всех секций выровнены на 64k, а виртуальные их длины всего по несколько страниц.
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 для консольных аппликух. Иногда встречаются другие значения, и тогда файл заражать не следует.
Оффсет оверлея вычисляется как физический оффсет последней секции плюс физическая длина последней секции.
Длина оверлея вычисляется как длина файла минус оффсет оверлея.
Оверлей в память вместе с PE файлом не грузится, но pe checksum по нему считается.
Если длина оверлея меньше pe_objectalign, а длина файла на pe_objectalign выровнена, причем сам оверлей состоит из нулей, то это не орвелей, а алигнмент, и его можно поскипать.
Если у оверлея первый DWORD=00000001h, а чуть дальше где-то идет '.dbg', то это дебаговая инфа. Есть и другие ее виды.
В любом случае, при дописывании к последней секции, оверлей, если он есть, надо двигать, а если это дебаговая инфа, то можно попробовать ее похерить.
Замечу, что в NT'ях и далее - большинство файлов с оверлеями, в основном с дебаговой инфой в них.
Пример добавления к последней секции: win9X.Examplo