| Вернуться на сайт |
|
Пример 5: Работа с PE-заголовками
Для данного примера нам потребуются следующие программы:
Все эти средства общедоступны в Интернете, желательно скачать самые последние версии.
Формат исполняемых файлов Portable Executables (PE) существует уже очень долго, даже в архитектуру Windows 3.11 была заложена возможность запуска подобных файлов при установленном Win32s. Формат файла достаточно прост, но, тем не менее, в нем есть множество подводных камней, из-за которых некоторые программы умирают, а некоторые программисты пользуются ими в своих целях. В Примере №3 кратко описывалось перемещение точки входа в программу для защиты от антивируса на свободное место в файле. Теперь мы рассмотрим подробно, как появилось это место, и что еще можно придумать с исполняемым файлом. Начнем с огромного количества скучной, но важной теории.
Структура EXE-файла, показанная в таблице, отражает его достаточно сильную структурированность:
Смещение |
Описание |
00h |
DOS 2 Header |
1Ch |
4 байта для выравнивания форматированной области заголовка с 1Ch до 20h. |
20h |
OEM Identifier & OEM Info |
3Ch |
Offset to PE Header |
min 40h |
Программы-заглушки, на это поле указывает ReloOfs заголовка DOS 2 Header, соответственно его значение должно быть >=40h. |
min 40h + XXh |
Тело DOS программы, которая чаще всего говорит о невозможности запуска ("This program cannot be run in DOS mode"). 40h-нижняя граница данного поля. |
XXh |
PE Header Заголовок PE файла, который нас и интересует |
XXh |
Object Table |
XXh |
Image
Pages (import, export, fixup, resource, debug, ...) |
Для запуска файл должен быть в формате PE, минимально необходимо для этого, чтобы он был: во-первых EXE (байты по смещению 0h равны 5A4Dh – "MZ"), во-вторых, слово по смещению 18h должно быть >=40h, тогда и только тогда поле смещения PE Header по адресу 3Ch имеет смысл.
Для нахождения заголовка PE в файле воспользуемся полем Offset to PE Header, находящемуся по смещению 3Ch от начала файла.
Слова DWord и Word, обозначающие тип данных, имеют размеры 4 и 2 байта соответственно.
Формат PE-заголовка представлен в следующей таблице:
Смещение |
Размер |
Название |
Описание |
00h |
DWord |
Signature Bytes |
Сигнатура того, что этот файл является PE - должна быть 4550h – "PE", два последних байта зарезервированы и должны быть равны 0h. |
04h |
Word |
CPU Type |
Поле указывает на тип процессора, под которым желательно запускать данную программу, обычно равно 14Ch – "i386" |
06h |
Word |
Num of Objects |
Поле указывает на количество элементов таблицы Object Table |
08h |
DWord |
Time/Date Stamp |
Поле даты и времени создания/модификации файла при сборке |
0Ch |
DWord |
Pointer to COFF table |
Указатель, определяющий местонахождение отладочной COFF таблицы в файлах |
10h |
DWord |
COFF table size |
Количество символов в COFF таблице |
14h |
Word |
NT Header Size |
Размер заголовка PE файла, начиная с поля Magic, таким образом, полный размер заголовка PE файла составляет NT Header Size + 18h (смещение поля Magic) |
16h |
Word |
Flags |
Флаги, указывающие на предназначение программы, с битовыми значениями:
|
18h |
Word |
Magic |
Поле указывает на предназначение программы, обычно равно 010Bh. |
1Ah |
Byte |
Link Major |
Старший номер версии использовавшегося при создании линкера |
1Bh |
Byte |
Link Minor |
Младший номер версии использовавшегося при создании линкера |
1Ch |
DWord |
Size of Code |
Размер программного кода в файле, используется для фактического отведения памяти под загружаемую программу |
20h |
DWord |
Size of Init Data |
Размер секции инициализированных данных |
24h |
DWord |
Size of UnInit Data |
Размер секции неинициализированных данных |
28h |
DWord |
Entry point RVA |
Адрес относительно ImageBase, по которому передается управление при запуске программы или адрес инициализации/завершения библиотеки, т.н. точка входа |
2Ch |
DWord |
Base of Code |
RVA секции, которая содержит программный код |
30h |
DWord |
Base of Data |
RVA секции, которая содержит данные |
34h |
DWord |
Image Base |
Виртуальный начальный адрес загрузки программы (ее первого байта). |
38h |
DWord |
Object align |
Выравнивание программных секций, должен быть степенью 2 между 512 и 256М включительно |
3Ch |
DWord |
File align |
Выравнивание секций в программном файле, указывает на границу, по которую секции дополняются 0 при размещении в файле. Должен быть степенью 2 в диапазоне от 512 до 64К включительно. |
40h |
Word |
OS Major |
Старший номер версии ОС, необходимый для запуска программы. |
42h |
Word |
OS Minor |
Младший номер версии ОС |
44h |
Word |
USER Major |
Пользовательский номер версии, задается пользователем при сборке программы |
46h |
Word |
USER Minor |
Аналогично, младший номер |
48h |
Word |
SubSys Major |
Старший номер версии подсистемы |
4Ah |
Word |
SubSys Minor |
Аналогично, младший номер |
4Ch |
DWord |
Reserved |
Зарезервировано |
50h |
DWord |
Image Size |
Виртуальный размер в байтах всего загружаемого образа, вместе с заголовками, кратен Object align |
54h |
DWord |
Header Size |
Общий размер всех заголовков: DOS Stub + PE Header + Object Table |
58h |
DWord |
File CheckSum |
Контрольная сумма всего файла, как правило ее никто не контролирует. |
5Ch |
Word |
SubSystem |
Подсистема, необходимая для запуска файла (GUI-0002h, консоль-0003h, …) |
5Eh |
Word |
DLL Flags |
Указывает на специальные потребности при загрузке, устарел и не используется. |
60h |
DWord |
Stack Reserve Size |
Память, резервируемая для стека приложения. |
64h |
DWord |
Stack Commit Size |
Память, отводимая в стеке немедленно после загрузки. |
68h |
DWord |
Heap Reserve Size |
Максимально возможный размер локальной кучи (heap) |
6Ch |
DWord |
Heap Comit Size |
Размер отводимой при загрузке кучи |
70h |
DWord |
Loader Flags |
Не используется, связано с поддержкой отладки |
74h |
DWord |
Num of RVA and Sizes |
Указывает размер массива VA/Size, который следует ниже, зарезервирован и равен 10h |
78h |
DWord |
Export Table RVA |
RVA адрес таблицы экспорта |
7Ch |
DWord |
Export Data Size |
Размер таблицы экспорта |
80h |
DWord |
Import Table RVA |
RVA адрес таблицы импорта |
84h |
DWord |
Import Data Size |
Размер таблицы импорта |
88h |
DWord |
Resource Table RVA |
RVA адрес таблицы ресурсов |
8Ch |
DWord |
Resource Data Size |
Размер таблицы ресурсов |
90h |
DWord |
Exception Table RVA |
RVA адрес таблицы исключений |
94h |
DWord |
Exception Data Size |
Размер таблицы исключений |
98h |
DWord |
Security Table RVA |
RVA адрес таблицы безопасности |
9Ch |
DWord |
Security Data Size |
Размер таблицы безопасности |
A0h |
DWord |
Fix Up's Table RVA |
RVA адрес таблицы настроек |
A4h |
DWord |
Fix Up's Data Size |
Размер таблицы настроек |
A8h |
DWord |
Debug Table RVA |
RVA адрес таблицы отладочной информации |
ACh |
DWord |
Debug Data Size |
Размер таблицы отладочной информации |
B0h |
DWord |
Image Description RVA |
RVA адрес строки описания модуля |
B4h |
DWord |
Description Data Size |
Размер строки описания модуля |
B8h |
DWord |
Machine Specific RVA |
RVA адрес таблицы значений, специфичных для микропроцессора |
BCh |
DWord |
Machnine Data Size |
Размер таблицы значений, специфичных для микропроцессора |
C0h |
DWord |
TLS RVA |
RVA указатель на локальную область данных цепочек |
C4h |
DWord |
TLS Data Size |
Размер области данных цепочек |
C8h |
DWord |
Load Config RVA |
? |
CCh |
DWord |
Load Config Data Size |
? |
D0h |
08h |
Reserved |
Зарезервировано |
D8h |
DWord |
IAT RVA |
Указывает на таблицу адресов импорта в файле (помимо структуры импорта) |
DCh |
DWord |
IAT Data Size |
Размер поля IAT |
E0h |
08h |
Reserved |
Зарезервировано |
E8h |
08h |
Reserved |
Зарезервировано |
F0h |
08h |
Reserved |
Зарезервировано |
При этом VA - виртуальный адрес, который уже базирован на смещении Image Base, RVA - относительный адрес, ссылающийся на Image Base. RVA в PE Header, имеющий нулевое значение, указывает на то, что соответствующее поле не используется.
Сразу за заголовком в файле располагается таблица объектов. Число входов в таблице объектов (секций) определяется полем Num of Objects заголовка PE Header. Последовательность секций кода и данных в памяти выбирается линкером. Каждая секция (объект) располагает именем, которое никого ни к чему не обязывает, имя может быть произвольным, но вообще-то смысл содержания секции и ее наименования как правило совпадают.
Структура таблицы объектов показана в следующей таблице:
Смещение |
Размер |
Название |
Описание |
00h |
08h |
Object Name |
Имя объекта, остаток заполнен нулями |
08h |
DWord |
Virtual Size |
Виртуальный размер секции, именно столько памяти будет отведено под секцию. Если Virtual Size превышает Physical Size, то разница заполняется нулями, так определяются секции неинициализированных данных (Physical Size = 0) |
0Ch |
DWord |
Section RVA |
Размещение секции в памяти, её виртуальный адрес относительно Image Base. |
10h |
DWord |
Physical Size |
Размер секции (ее инициализированной части) в файле, должно быть меньше или равно Virtual Size. |
14h |
DWord |
Physical Offset |
Физическое смещение относительно начала EXE файла. |
18h |
0Ch |
Reserved |
Зарезервировано |
28h |
DWord |
Object Flags |
Битовые флаги секции со значениями:
|
Примеры стандартных названий секций Object Name:
.text |
- исполняемый код Microsoft |
При этом следует отметить, что практически все упаковщики и крипторы исполняемых файлов создают свои секции, у многих из них также стандартные имена, например UPX0, .decode, pec1 и т.д. |
CODE |
- исполняемый код Borland |
|
.data |
- секция данных Microsoft |
|
DATA |
- секция данных Borland |
|
.bss |
- неинициализированные данные |
|
.CRT |
- инициализированные данные Borland C/C++ |
|
.rsrc |
- ресурсы |
|
.idata |
- секция импорта |
|
.edata |
- секция экспорта |
|
.reloc |
- таблица настроек |
|
.tls |
- данные, на базе которых Windows запускает цепочки |
|
.rdata |
- отладочная информация |
Пример заголовка программы "Калькулятор" с указанием всех элементов представлен на рисунке. По клику на картинке можно скачать архив с полной BMP-версией [23kb]. На данном фрагменте легко прослеживаются попытки Microsoft все выровнять, выстроить и структурировать. Вследствие этого существует достаточно много способов заражения (в данном контексте – записи своего кода в код существующей программы без потери ее работоспособности) файлов формата PE, основные три из них:
|
Рассмотрим подробно способ внедрения в пустое пространство
В некоторых других статьях этот метод называется внедрением в заголовок, хотя это не совсем так. Классическим примером использования является вирус Win95.CiH (Чернобыль).
|
Откуда же берется это пустое пространство? Участок, помеченный на рисунке красным цветом, находится между последним элементом таблицы объектов и смещением первой секции. А появился он в результате выравнивания, за которое отвечает поле File Align по смещению 3Ch в заголовке. Чтобы получить "координаты" и размер указанной области необходимо знать: a- Размер PE-заголовка (обычно F8h, но лучше NT Header Size+18h, значение берется из PE Header) b- Смещение PE-заголовка (поле Offset To PE header по смещению 3Ch от начала файла, содержит DWord) c- Размер таблицы объектов (значение поля Num of Objects по смещению 06h из PE Header умножаем на размер объекта, т.е. на 40) d- Физическое смещение первой секции (значение Physical Offset по смещению 14h из таблицы объектов для первого элемента). Таким образом, началом пустой области у нас будет место в файле с физическим смещением a+b+c, а концом – место в файле со смещением d, соответственно размер области равен d-(a+b+c) Попробуем получить это значение для какого-нибудь файла (сразу оговорюсь, с "Калькулятором" это не пройдет из-за оптимизации расположения импорта). |
Следует сразу оговориться, что размер этой области не резиновый, поэтому кусок
внедряемого кода очень ограничен. Приступим к программированию
|
Создаем новый проект в Builder, пишем несколько строчек кода, компилируем и берем полученный EXE файл на исследование. Можно взять любой другой не сжатый и не оптимизированный EXE файл. Builder можно не закрывать, программировать еще будем ;-) После открытия файла считываем из него значение смещения PE заголовка (PEHeaderOffset), это DWord по смещению 3Ch. На рисунке показано это значение в Hex-редакторе, оно равно 0200h (вспоминаем про порядок расположения байтов). |
|
|
Так и есть, по адресу 0200h наблюдаем начало PE заголовка. Создаем для него структуру:
|
На рисунке отмечены необходимые нам поля: синее – количество элементов в таблице объектов; красное – размер заголовка от поля Magic, зеленое – RVA точки входа в программу.
|
Выяснив количество элементов (NumObjects) можно рассчитать размер таблицы объектов. В данном случае по смещению 06h от начала заголовка у нас находится число 8, таким образом размер таблицы составляет 8*40=320 байт. К этому моменту мы уже нашли 3 необходимые нам переменные: a = F8h (получаем как поле NTheaderSize+18h, у нас NTheaderSize=E0h) b = 200h (получили из PEheaderOffset) c = 140h (рассчитали из количества объектов NumObjects*40) Осталось найти последний элемент, а именно физический адрес смещения первой секции в файле. Для этого мы используем структуру элемента таблицы объектов, а конкретно ее поле PhysicalOffset. |
|
Считываем значения в созданную структуру, получаем, физическое смещение первой секции равное 0600h. Чтобы не запутываться дальше, посмотрим текст программы, который производит все необходимые операции. |
|
В данном случае мы считали PE заголовок, нашли количество объектов и создали динамический массив структур элементов таблицы объектов. Также мы нашли начало таблицы объектов на основании того, что она следует сразу за заголовком. Считываем все элементы таблицы объектов в созданный массив структур и находим физическое смещение первой секции. После этого вычисляем начало пустого пространства как сумму размера всех заголовков и размера таблицы объектов. Концом пустого пространства является начало 1-й секции, разница между ними и будет размером. |
В данном случае началом у нас является сумма Смещение заголовка PE (200h) + размер заголовка (F8h)+ размер таблицы объектов (140h)= 438h
Конец пустой области = 600h
Разница: 1C8h
|
Проверим, так ли это. Открываем файл в Hex-редакторе и идем к смещению 438h. Так и есть, на этом месте заканчивается таблица объектов описанием последней секции .reloc и начинается пустое место, заполненное нулями вплоть до адреса 600h. Таким образом, мы получили 456 байт неиспользуемого пространства в файле, которое можно занять под свои нужды. |
А дальше можно попробовать несколько вариантов: поменять точку входа (обозначена зеленым цветом на иллюстрации PE заголовка) на этот кусок, как мы уже делали в Примере №3, или сделать еще хитрее – по адресу точки входа поставить jmp на эту область, не меняя точки входа. Но это уже дело фантазии. В заключение хочется сказать бессмертную фразу: не пишите вирусы и трояны, они не делают мир лучше!
Статья основана на труде Sars/HiTech, взятого с http://wasm.ru, а также описании Hard Wisdom'а, которое выложено на сайте. Его я настоятельно рекомендую скачать и изучить, потому что последующие примеры будут также основаны на нем.