Низкоуровневое программирование для Дzenствующих

Вид материалаДокументы

Содержание


Часть первая - теоретическая
Рецензент:Dr.Golova/UINC.RU Главный корректор:Edmond/HI-TECH
3. 64-bit PE-files4. Страшное слово XML5. Благодарности
2. Основные концепции
2.1 RVA/VA и иже с ними
VA во внутрифайловые смещения. Алгоритм примерно выглядит так: 1) По VA
LordPE и PE Tools
2.2 О секциях PE-файла
LordPE, HIEW
IDA говорит "Can't find a translation for virtual address…", и … раньше слетала, а теперь продолжает работу. HIEW
Watcom устанавливает значение поля VirualSize
Низкоуровневое программирование для Дzenствующих #37
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   42

Часть первая - теоретическая


Сложное – то, что делается мгновенно,
невозможное – то, что требует лишь
немногим большего времени
Рецензент:
Dr.Golova/UINC.RU
Главный корректор:
Edmond/HI-TECH

 

1. Лирическое отступление
2. Основные концепции
2.1 RVA/VA и иже с ними
2.2 О секциях PE-файла
2.3 Директория импорта
2.4 Директория ресурсов
2.5 Директория перемещаемых элементов
2.6 NTDLL.DLL – Windows Loader
3. 64-bit PE-files
4. Страшное слово XML
5. Благодарности

1. Лирическое отступление


Решено было разбить эту статью на две-три части. Первая часть собственно теоретическая. Она дает абсолютно необходимый минимум теории, который позволит успешно распаковывать файлы. В статье также описывается формат XML, который затем переводится в CSS-HTML при помощи XSLT-процессора. Таким образом, наряду со знанием структуры PE-файла вы, по прочтении статьи, будете иметь самые базовые представления о XML/CSS/XSLT, Perl/Java и других страшных технологиях. Мы достаточно неплохо осознем, что тема уже достаточно замусолена, поэтому нудного пересказа PE-документации здесь не будет. Скорее, вам стоит воспринимать эту статью как некий коллектор, попытку обобщения и анализа всей доступной информации о пакерах, PE-файлах, антиотладке и т.п. А поскольку есть возможность перевода статей с испанского и французского языков (помимо стандартного английского), то должно получится что-то неплохое. Вторая часть будет чисто практической. Мы рассмотрим UPX, Aspack, Asprotect, Crunch, Armadillo и вскользь упомянем о других, менее популярных пакерах. Все опыты, приведенные в первой части проводились на calc.exe – стандартном калькуляторе из поставки Windows 2000. Вторая часть тоже будет проходить вместе с calc.exe. Реальные случаи будете рассматривать сами. Последнее, но немаловажное замечание – ВСЕ, что здесь обсуждается, валидно ТОЛЬКО для Windows 2k/XP/2003 (последняя – с натяжкой!). 9x НЕ рассматривается как анахронизм. Замечание важно, т.к. 9x и NT по-разному трактуют оперативную память, загружают библиотеки, работают с процессами, обрабатывают исключения и т.п.

Для того чтобы понимать, что здесь написано, введем пару допущений. Допущение номер раз: вы знаете ассемблер; №2: вы знаете, что такое исполняемый файл, т.е. файл загружается с диска в оперативную память с помощью механизмов ОС, при этом ОС определенным образом может при загрузке корректировать информацию внутри файла, если это необходимо; №3: вы слышали о языке программирования Perl; №4 – вы имеете очень приличное представление о базовых типах языка С – структурах, массивах и т.п., последнее – пятое – вы имеете хоть и очень смутное, но все же какое-то представление о том, что такое PE-формат исполняемого файла.

2. Основные концепции


Теперь, наконец, давайте рассмотрим формат PE-файла. Скажем сразу, что в наши намерения не входило давать здесь пересказ технической документации MS. Файл со спецификациями PE/COFF при-ложен, на ссылка скрыта есть статьи Питрека на эту тему. Полагаем, в русской части Интернета сравнительно просто найти саму книгу Питрека. Из русской литературы можно предложить Румянцева, где неплохо описаны поля, и даже есть исходники, хотя их качество нам не нравится, и статью о загрузчике PE-файлов на ссылка скрыта – там все расписано очень неплохо, есть и исходники, но они на Delphi, а этот язык мы не знаем и не любим. Однако алгоритм, по справедливому замечанию автора, везде одинаков.

Да, чуть не забыли. Для хорошо знающих английский, есть абсолютно великолепная вещь, совершенно свободно распространяемая - ссылка скрыта - слейте ее целиком, и обязательно прочтите, просто изумительно! Книга будет продублирована и на ссылка скрыта, ибо вполне стоит того! Практическим примером к этой книге может служить техническая документация с сайта ссылка скрыта - Якоб Навия очень неплохо расписывает многие тонкости по созданию компилятора и линкера. Еще дополнительно прилагается англоязычное описание PE-формата (версия 1.7) от Lumnificer, где подробно расписываются все поля, и даже объясняется, как создавать PE-файл ручками, т.е. расписывается работа примитивного линкера!

Так же, посоветуем изучить исходный код программы Wine – эмулятора Windows для Linux. Wine умеет загружать pe-файлы под Linux, причем код pe_image.c написан очень развернуто. А там еще мно-о-го примеров есть… Сайт - ссылка скрыта.

Теперь очень быстренько пробежимся по списку литературы, описывающего работу NTDLL.dllWindows loader'а.

Нашими маяками в этом бушующем море станут ссылка скрыта.
А также, основанная на этом статья Russell Osterlund с сайта ссылка скрыта, название статьи – "Windows 2000 Loader: What Happens Inside Windows 2000: Solving the Mysteries of the Loader" – лежит на ссылка скрыта. Лучше пока еще ничего не написано. Примеры для статьи скачивайте только с MS, родной сайт лучше не трогать. Причем после слива можно смело удалить все dsp/dsw/ncb/suo/sln файлы, оставив только vcproj. Причина в том, что все файлы проектов повреждены, во всяком случае, VS.NET 2003 их открыть не смогла. Другое дело vcproj, который, по сути, является простым xml-файлом. Подредактируйте их немного (неправильно прописаны пути Output Directory и т.п.), и держите в уме, что проект Forwarder требует lib-файл проекта Forwarded, поэтому второй должен собираться раньше.

Во всей остальной части статьи мы будем жестко придерживаться терминов, принятых Microsoft. Все определения структур взяты из winnt.h. Эта мера кажется очень разумной, т.к. на данный момент существует дикая неразбериха в терминах - существует целая куча слов, для которых нет четких определений, например, секция импорта, таблица импорта, IAT и т.п. Каждый писатель под этими словами понимает что-то свое, в результате чего имеем полный беспредел.

Теперь взгляните на рисунок ниже:



Рис. 1.

Мы предполагаем, что вы оторветесь от чтения этой статьи и проведете несколько минут с этим рисунком и официальной документацией от MS, держа в уме, что PE файл, по сути, просто совокупность секций, которые описываются в заголовке.
Единственное что, стоит объяснить несколько нюансов.

2.1 RVA/VA и иже с ними


RVA переводится как Relative Virtual Address - относительный виртуальный адрес. Его относительность заключается в том, что он отсчитывается от адреса загрузки, который может быть, а может и не быть, равен ImageBase.
Обязательно учтите, что RVA имеет РАЗНЫЙ смысл для бинарных файлов (exe, dll, sys) и объектных файлов (obj, lib). Вторые мы здесь не рассматриваем, а для первых – файл загружается в память и RVA какого-либо элемента вычисляется так:

RVA = VA – адрес загрузки,

где VA (Virtual Address) – виртуальный адрес элемента в памяти, а адрес загрузки берется из поля Op-tionalHeader.ImageBase, в том случае, если он равен ImageBase, либо вычисляется лоадером.
Обратите внимание на то, что элементарные просмотрщики файла, например, QVIEW/WinHEX и т.п. отображают абсолютные offset-ы, HIEW способен переключаться (Alt+F1) между показом VA и внутрифайловых смещений (local/global mode), а IDA вообще частично эмулирует работу ntdll.dll – Windows-loader'a, поэтому показывает только VA (если вы не укажете иное, например, загрузив файл как "binary").
Давайте рассмотрим пример для практического усвоения материала. Точка входа calc.exe (также часто называемая EP – entry point) выглядит так:

.01012420: 55 8B ;VA

А в виде абсолютных внутрифайловых смещений – вот так:

00011A20: 55 8B

;обратите внимание на отсутствие точки!

;HIEW требует точку, если хотите VA!

Давайте попытаемся разобраться, какой же механизм пересчета VA во внутрифайловые смещения.

Алгоритм примерно выглядит так:

1) По VA рассчитать RVA

VA = RVA + ImageBase

2) Для RVA найти секцию, в которой мы находимся (о секциях мы поговорим чуть ниже).

3) Пересчитать RVA во внутрифайловый offset по формуле:

offset = RVA - IMAGE_SECTION_HEADER.VirtualAddress +

+ IMAGE_SECTION_HEADER.PointerToRawData

Теперь давайте посчитаем ручками. В качестве примера возьмем адрес точки входа calc.exe - 0x01012420.

Сразу смотрим в HIEW на значение ImageBase -

¦ Image base 01000000 ¦ Subsystem GUI ¦

VA = RVA + ImageBase;

01012420 = RVA + 1000000;

RVA = 12420;

//что можно сразу увидеть из OptionalHeader.EntryPoint

Теперь определяемся, где же у нас лежит этот RVA. Ага, RVA лежит в секции .text. Обратите вни-мание на то, что лежит он ближе к концу секции:

Number

Name

VirtSize

RVA

PhysSize

Offset

Flag

1

.text

000124EE

00001000

00012600

00000600

60000020

Теперь VA во внутрифайловое смещение:

offset = 12420 - 1000 + 600 = 11A20;

Заметим, что процесс этот уже давным-давно автоматизирован. Есть такие утилиты как LordPE и PE Tools, а в них есть такая вещь как FLC (File Location Calculator), который по данному VA отобразит RVA и внутрифайловый offset.
Имея на руках некоторые API-функции - GetModuleHandle, ImageRvaToVa и ImageRvaToSection, несложно написать программу, которая быстренько переведет любой VA во внутрифайловый оффсет. Например, аналог функции от MS ImageRvaToSection может выглядеть где-то так:

/***********************

* Return value: Возвращается структура IMAGE_SECTION_HEADER, для данного RVA

* Parameters: Первый – это структура IMAGE_FILE_HEADER

* Parameters: Второй – это RVA для которого нужно найти секцию

************************/


PIMAGE_SECTION_HEADER RvaToSection(PIMAGE_FILE_HEADER pFH, DWORD dwRVA)

{

UINT i;

IMAGE_SECTION_HEADER *pSH;


pSH = (PIMAGE_SECTION_HEADER)((DWORD)(pFH + 1)

+ pFH->SizeOfOptionalHeader);

for (i = 0; i < pFH->NumberOfSections; i++)

{

if (dwRVA >= pSH->VirtualAddress

&& dwRVA < pSH->VirtualAddress

+ pSH->Misc.VirtualSize)

return pSH;

++pSH;

}

return NULL;

}

Примеры функций RtlImageRvaToVa /RtlImageRvaToSection /RtlImageDirectoryEntryToData есть в файле loader.c из wine.

Например, есть у вас есть dll, загруженная по адресу, отличному от ImageBase. Вы нашли защиту в Soft-Ice, и хотите пропатчить это место HIEW'вом - тут-то и пригодится умение.

2.2 О секциях PE-файла


PE-файлы были спроектированы для работы в ОС со страничной адресацией памяти. Известно, что Windows делит память на страницы различного размера, поэтому и PE-файл разбит на секции. Это очень важно понимать, поскольку секции PE-файла в памяти и на диске - это вовсе не одно и то же! В том случае, если сами данные в секции имеют общий объем менее размера кластера - секция на диске выравнивается (т.е., дополняется нулями, int3 - CCh или nop – 90h) под размер кластера (не путать с сек-тором – кластеры состоят из многих секторов диска!), который обычно равен 512 байтам (или другое значение – поле OptionalHeader.FileAlignment).

Когда файл загружается, то секция попадает в страницу памяти большего размера – 4 кб (или дру-гое значение - OptionalHeader.SectionAlignment), и выравнивается вновь, если это необходимо, уже только нулями. Обратите вниманиме, мы говорим "секция" – т.е., секция .text, CODE, .data, DATA, .aspack или что-либо еще. Каждая секция будет проецироваться на одну или несколько страниц памяти.

Не путайте понятие "Секция" (section) и "Директория" (directory). Директории (импорта, экспорта, ресурсов, исключений или чего-то еще) находятся ВНУТРИ секций. Заметьте, секция определяет разбиение PE-файла на части, но секции глубоко все равно, какой тип данных будет находится внутри нее. Для того, чтобы охарактеризовать тип, у нас есть директории! Директории характеризуют данные, содержащиеся в PE, по их типу и функциональности. Таким образом, можно говорить, что секция является физическим разбиением PE файла на состовляющие, а директория - логическим.

Иногда размер секции может занимать много кластеров, и отображаться Windows на несколько страниц памяти. Общее количество секций должно в точности соответствовать полю FileHeader.NumberOfSections. Теоретически, с этим шутить не стоит, так как шаг в сторону – стреляю. Такой файл не будет опознан ntdll.dll, поэтому на поле NumberOfSections можно смело(?) опираться. Однако и здесь нам уже радость испортили! Некоторые крипторы, например, telock, заменяют это значение в ПАМЯТИ, например, на 0xFFA4. Угадайте, что случится, когда вы попытаетесь запустить дамп с такого файла? А что будет, если утилита, просматривающая содержимое полей секций в цикле, будет ориентироваться на такую переменную как на счетчик цикла, нечто вроде такого:

for (i=0; i<= FileHeader.NumberOfSections; i++)

//где FileHeader.NumberOfSections == 0xFFA4

{

//Здесь обрабатываем содержимое секции

//А теперь представим, что секции закончились. О-о-о-о-й!

//да, попробуйте такой файл посмотреть HIEW. Забавная реакция!

}

Как вариант, можно использовать значение NumberOfSections с диска. Другую альтернативу мы рассмотрим, после того как ознакомимся со структурой секции изнутри.

LordPE, HIEW на этот трюк покупаются! Все версии IDA - тоже! PE Tools – нет! Прекрасная защита от сброса дампа на диск (имеется в виду, дамп сбросить можно, а вот заставить его заработать потом.... тоже можно, но чуть посложнее)! Хотя, с другой стороны, такой файл не будет загружен самой Windows. Цитирую Евгения Сусликова: "не должно быть хиеву умнее виндов". Что ж, вполне справедливо.
Для каждой секции в заголовке PE-файла указан ее размер на диске и в памяти. Структура выглядит так:


#define IMAGE_SIZEOF_SHORT_NAME 8

typedef struct _IMAGE_SECTION_HEADER {

//имя не более 8 символов

BYTE Name[IMAGE_SIZEOF_SHORT_NAME];

union

{

DWORD PhysicalAddress;

DWORD VirtualSize;

//помеченные красным значения характеризуют секцию в памяти

//VirtualSize – общий размер секции в памяти.

//если это число БОЛЬШЕ размера секции на диске

// – секция дополняется нулями

} Misc;

DWORD VirtualAddress;

//RVA первого байта секции в памяти

//синий шрифт – данные о секции на диске

DWORD SizeOfRawData;

//Размер ИНИЦИАЛИЗИРОВАННЫХ данных на диске

//Ой, какое интересное поле! Поговорим о нем чуть ниже.

//Вкупе с VirtualSize им часто пользуются протекторы

DWORD PointerToRawData;

//очень полезное поле – offset от начала файла до первого байта секции

DWORD Characteristics;

//характеристики секции – это поле тоже очень любят менять,

// поговорим ниже и о нем

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


Особый интерес в этой структуре вызывают два поля – VirtualAddress и SizeOfRawData. Все остальные тоже очень интересны, но эти два широко используются протекторами для сокрытия от наших глаз того, что должно быть скрыто. К примеру, вам, вероятно, часто доводилось видеть нечто вроде такого в IDA:

.data1:005B6FFD db 0 ;

.data1:005B6FFE db 0 ;

.data1:005B6FFF db 0 ;

.data1:005B7000 db ? ;

.data1:005B7001 db ? ;

.data1:005B7002 db ? ;

.data1:005B7003 db ? ;

в то время как самый обычный hex-редактор выдавал вполне осмысленные цифры. Проблема и ее решение достаточно просты. Обратите пристальное внимание на поля VirtualSize и SizeOfRawData той секции, где есть такой подарочек. Значение SizeOfRawData должно быть меньше значения поля VirtualSize:

Number

Name

VirtSize

RVA

PhysSize

Offset

Flag

1

.text

00195C5C

00001000

00000000

00000000

60000020

2

.data

00008DD4

00197000

00000000

00000000

C0000040

3

.text1

00010000

001A0000

0000F000

00001000

60000020

4

.data1

00020000

001B0000

00007000

00010000

C0000040

5

.pdata

000B0000

001D0000

000B0000

00017000

C0000040

6

.rsrc

00001000

00280000

00001000

000C7000

40000040

Именно поэтому и появляются знаки вопроса – IDA ориентируется только на raw-байты (хотя, в случае секции кода с этим можно поспорить). Чтобы исправить положение, надо просто приравнять значение SizeOfRawData значению VirtualSize. И, вуаля:

.data1:005B7000 aPdata000_0 db 'PDATA000'

.data1:005B7008 db 2 ;

.data1:005B7009 db 1 ;

Заметим также, что ни VirtualSize, ни SizeOfRawData различных секций перекрываться не могут. Таким образом подправленный файл просто не будет загружен.
Возможен и еще, например, такого рода трюк. Положим, есть файл, у которого ручками создана несуществующая директория отладки (Debug Directory):

#define IMAGE_DIRECTORY_ENTRY_DEBUG 6

Структура директории выглядит так:

typedef struct _IMAGE_DEBUG_DIRECTORY {

DWORD Characteristics;

DWORD TimeDateStamp;

WORD MajorVersion;

WORD MinorVersion;

DWORD Type;

DWORD SizeOfData;

DWORD AddressOfRawData;

DWORD PointerToRawData;

} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;

Положим, мы задали заведомо огромные значения полей SizeOfData, AddressOfRawData и PointerToRawData. Лоадера эти поля абсолютно не интересуют (ему важна лишь валидность параметров в IMAGE_DATA_DIRECTORY). Прямо сказано, что эти поля касаются только отладчика (или дизассемблера). Так вот, IDA на таком файле (до версии 4.3 включительно) слетит наглухо! Такого рода трюк (© Dr. Golova) был применен в программе, которую рассматривал Sten в своей статье – "Исследование подаруночка" на ссылка скрыта, если кто помнит.

Популярны также трюки с директориями импорта-экспорта. Некоторые поля (OriginalFirstThunk, например) забивают 0xFFFFFFFF или чем-нибудь не менее гадким - RVA OriginalFirstThunk должно, согласно действиям лоадера (Win2k SP4), укладываться в диапазон

cmp ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders]

jb loc_77F9373F ;если меньше SizeOfHeaders

cmp ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage]

jnb loc_77F9373F ;и больше либо равно SizeOfImage


если это не так – файл загружен не будет. В этих случаях IDA говорит "Can't find a translation for virtual address…", и … раньше слетала, а теперь продолжает работу. HIEW в своей работе на winnt.h не опирается вообще, он был создан раньше. Валидность адресов директории импорта/экспорта проверяется валидностью VA. Мы поговорим о подобных приемах немного позднее.

Флаги секции – тоже вещь небезынтересная! Здесь мы не будем углубляться в детали. Скажем лишь, что флаги секции могут преобразовываться лоадером в атрибуты страниц и сегментов, биты CR-регистров и т.д. Это достаточно сложная тема, требующая неплохого понимания принципов работы защищенного режима. Мы бы порекомендовали Рендалла Хайда "Art of Assembly" и Михаила Гука. Заодно можно почитать Фроловых, а также ознакомится со статьями Broken Sword на ссылка скрыта. После этого можно смело утверждать, что вы будете является одним из очень твердых специалистов по защищенному режиму процессоров х86 :). Такое знание не будет бесполезным. Например, понимание принципов защиты страниц виртуальной памяти помогает практически однозначно идентифицировать Aspack. Сбросьте флаг разрешения записи у секции кода в программе, предположительно запакованной Aspack, и взгляните на результаты. Почему такое происходит – мы поясним во второй части. Обязательно обратите внимание - это поле НИКОГДА не должно быть равным нулю. Как это можно использовать - см. ниже.

Сейчас уместным было бы посоветовать на какое-то время отвлечься от статьи, и попробовать поэкспериментировать с секциями файла. Да, мы сделаем это немного позднее, во второй и третьей частях, когда будем чистить файл от мусора, оставленного крипторами, однако, то, что сделано собственными руками, едва ли когда-нибудь забудется. Например, попробуйте поставить поле VirtualAddress какой-нибудь секции в ноль (как, например, это делает линкер от Watcom). Также учтите, что количество страниц в памяти не обязательно будет соответствовать количеству секций в PE-файле. Во-первых, размер, во-вторых – сама Windows просто не загрузит страницу памяти до тех пор, пока она не нужна. Только если страница нужна, тогда она будет подгружена.

Еще нюанс. Все упаковщики, пакеры и крипторы очень нежно относятся к секции .rsrc. Секция кода, директрия импорта – все это безжалостно калечится, однако секцию ресурсов трогать боятся. При-чина проста. В исходном коде UPX можно найти следующие комментарии:

// after some windoze debugging I found that the name of the sections

// DOES matter :( .rsrc is used by oleaut32.dll (TYPELIBS)

// and because of this lame dll, the resource stuff must be the

// first in the 3rd section - the author of this dll seems to be

// too idiot to use the data directories... M$ suxx 4 ever!

// ... even worse: exploder.exe in NiceTry also depends on this to

// locate version info

И действительно. В файле oleaut32.dll можно найти следующие строки, сохранившиеся там со времен 95-го по 2k и выше:


.77A078C7: 7825 js .077A078EE -----v (2)

.77A078C9: 8D4580 lea eax,[ebp][-80]

.77A078CC: 687CB7A377 push 077A3B77C -----v (3)

.77A078D1: 50 push eax

.77A078D2: FF15F8229B77 call lstrcmpiA ;KERNEL32.dll


.77A3B770: 44 00 49 00-52 00 00 00-2A 00 00 00-2E 72 73 72 D I R * .rsr

.77A3B780: 63 00 00 00-00 00 00 00-74 79 70 65-6C 69 62 00 c typelib

И из-за откровенно неумного, недальновидного, …, не будем перечислять, поведения программиста, кракеры получили большой подарок, а авторы пакеров – подарок поменьше. Спасибо, Microsoft! И абсолютно без всякой иронии! Так что, если автор хочет, чтобы его файл имел красивую иконку, то он должен предоставить ресурсы файла в наглядном виде. Конечно, не все так просто, секция ресурсов то-же безжалостно калечится, однако, вероятность того, что название секции будет сохранено наряду с иконкой, очень велика. Что до остального.… Разберемся с этим чуточку попозже.

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

В заключение, рассмотрим алгоритм подсчета количества секций PE-файла. Как уже упоминалось, поле FileHeader.NumberOfSections не может служить достаточно надежным проводником. Тогда что? Пока мы используем этот алгоритм (к слову, тоже ненадежный), но, возможно, после выхода статьи, он уже не будет работать в силу вполне определенных причин. Тогда нам с вами останется последний верный и надежный способ – считать значение из файла диске. Многие утилиты это позволяют. Итак, производим подсчет секций, и смотрим на поля PointerToRelocations, PointerToLinenumbers, NumberOfRelocations, NumberOfLinenumbers. Если все эти поля равны нулю, а поле Characteristics - НЕТ, значит - это секция.

Алгоритм прост:


/***********************

* Return value: настоящее значение NumberOfSections

* Parameters: pMem - возвращаемое значение

* от CreateFileMapping, либо от GetModuleHandle

************************/


WORD GetRealNumberOfSections(PVOID pMem)

{

PIMAGE_DOS_HEADER pDosh;

PIMAGE_NT_HEADERS pNTh;

PIMAGE_SECTION_HEADER pSh;


WORD iRealNumOfSect = 0;


// Устанавливаем SEH

__try

{

// Считывает IMAGE_DOS_HEADER

pDosh = (PIMAGE_DOS_HEADER)pMem;

// Считываем IMAGE_NT_HEADERS

pNTh = (PIMAGE_NT_HEADERS)((DWORD)pDosh + pDosh->e_lfanew);

// Получаем указатель на первую секцию

pSh = IMAGE_FIRST_SECTION32(pNTh);


// Считываем секции по очереди

for(word i = 0; i < (pNTh->FileHeader.NumberOfSections); i++)

{

if(!pSh->PointerToRelocations &&

!pSh->PointerToLinenumbers &&

!pSh->NumberOfRelocations &&

!pSh->NumberOfLinenumbers &&

pSh->Characteristics)

// Увеличиваем счётчик секций

iRealNumOfSect++;

else

return iRealNumOfSect;

++pSh; // Переходим к следующей секции

}

return iRealNumOfSect;

}

__except(EXCEPTION_EXECUTE_HANDLER)

{

// Ошибка “Access Violation!”

return 0;

}

}

Уже упоминалось, что линкер Watcom устанавливает значение поля VirualSize в нуль. Тогда возникает вопрос - откуда брать VirualSize? Рекомендуется использовать значение поля SizeOfRawData, выровненное по SectionAlignment. Ниже указан макрос на C++ для выравнивания секции по SectionAlignment:

#define RALIGN(dwToAlign, dwAlignOn) ((dwToAlign % dwAlignOn == 0) ?

dwToAlign : dwToAlign - (dwToAlign % dwAlignOn) + dwAlignOn)

Используется так: VirtualSize = RALIGN(VirtualSize, SectionAlignment);

Eще немного об ImageBase. Понятия «директория кода» не существует. Есть только секция кода, которая может называться, как душе угодно. Начало секции вычисляется из IMAGE_OPTIONAL_HEADER.BaseOfCode. В случае, если значение поля AddressOfEntryPoint не укладывается в диапазон,

BaseOfCode<=AddressOfEntryPoint<=SizeOfCode


то это внимательному человеку может говорить о многом. Во-первых, файл, вероятно, запакован (почему так – см. часть вторую). Во-вторых, теперь вам придется сушить мозги с его распаковкой. Некоторые утилиты, например, OllyDbg, это дело подмечают, о чем вежливо предупреждают. Мало ли что?

Теперь перейдем к директории импорта.

(продолжение следует...)

Низкоуровневое программирование для Дzenствующих #37

часть 2

###########################################################################
VOLODYA/HI-TECH
NEOx/UINC.RU

Об упаковщиках в последний раз