5. PE-файлы: каталоги данных

5.1. Список каталогов

В настоящее время PE-файлы могут содержать следующие каталоги данных:

Номер Название Описание
0 IMAGE_DIRECTORY_ENTRY_EXPORT Таблица экспорта.
1 IMAGE_DIRECTORY_ENTRY_IMPORT Таблица импорта.
2 IMAGE_DIRECTORY_ENTRY_RESOURCE Таблица ресурсов.
3 IMAGE_DIRECTORY_ENTRY_EXCEPTION Таблица обработки исключений.
4 IMAGE_DIRECTORY_ENTRY_SECURITY Таблица сертификатов безопасности.
5 IMAGE_DIRECTORY_ENTRY_BASERELOC Таблица настроек адресов.
6 IMAGE_DIRECTORY_ENTRY_DEBUG Отладочная информация.
7 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE Данные, специфичные для процессора.
8 IMAGE_DIRECTORY_ENTRY_GLOBALPTR RVA глобального регистра процессора.
9 IMAGE_DIRECTORY_ENTRY_TLS Таблица локальной памяти потоков (TLS).
10 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG Таблица конфигурации загрузки.
11 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT Таблица связывания импорта.
12 IMAGE_DIRECTORY_ENTRY_IAT Таблица адресов импорта (IAT).
13 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT Таблица отложенного импорта.
14 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR Дескриптор .NET.
15 Зарезервировано.

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

5.2. Таблица экспорта

Эта таблица всегда присутствует в DLL-файлах, поскольку основным назначением динамических библиотек как раз и является экспорт символов (подпрограмм и переменных), доступных другим DLL- и EXE-файлам. Впрочем, никто не запрещает нам экспортировать символы и из EXE-файлов. Основная цель таблицы экспорта – увязать имена и/или номера экспортируемых функций с их RVA, т. е. с положением в виртуальной памяти процесса.

Таблица экспорта называется IMAGE_EXPORT_DIRECTORY и имеет следующую структуру:

Смещение (hex) Размер Тип Название Описание
00 4 DWORD Characteristics Зарезервировано, всегда равно 0.
04 4 DWORD TimeDateStamp Дата и время создания таблицы экспорта в формате Unix.
08 2 WORD MajorVersion Старшая цифра номера версии, не используется.
0A 2 WORD MinorVersion Младшая цифра номера версии, не используется.
0C 4 DWORD Name RVA ASCIIZ-строки, содержащей имя данного файла.
10 4 DWORD Base Начальный номер экспортируемых символов (больше или равен 1).
14 4 DWORD NumberOfFunctions Количество элементов в таблице адресов.
18 4 DWORD NumberOfNames Количество элементов в таблице имен и таблице номеров.
1C 4 DWORD AddressOfFunctions RVA таблицы адресов.
20 4 DWORD AddressOfNames RVA таблицы имен.
24 4 DWORD AddressOfNameOrdinals RVA таблицы номеров.

Из приведенной структуры видно, что таблица экспорта содержит указатели на три других таблицы: адресов, имен и номеров.

При поиске экспортируемого символа по его имени сначала производится бинарный поиск этого имени в таблице имен. (Хочу подчеркнуть, что имена символов чувствительны к регистру.) Если имя найдено и его номер в таблице имен равен N, то извлекается N-й элемент из таблицы номеров. Если этот номер равен K, то элемент таблицы адресов с номером (K - Base) содержит RVA данного символа.

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

Уточним строение таблицы адресов. Во-первых, она может содержать нули; это означает, что соответствующий символ не используется. Во-вторых, каждый ее элемент представляет собой либо обычный RVA символа, либо т. н. RVA перенаправления (forwarder RVA), указывающий на ASCIIZ-строку. Строка указывает на символ в другой библиотеке и имеет либо вид "DLLName.FuncName" (экспорт по имени), либо вид "DLLName#FuncNumber" (экспорт по номеру).

RVA перенаправления заменяет экспорт данного символа на экспорт некоторого другого символа из другой библиотеки. Например, KERNEL32.DLL в Windows NT содержит перенаправление функции HeapAlloc к функции NTDLL.RtlAllocateHeap. В результате программа, запрашивающая выделение памяти из кучи под Windows NT неявно обращается к библиотеке NTDLL.DLL, специфической для Windows NT. Та же самая программа под Windows 9x запросит память у KERNEL32.DLL без обращения к NTDLL.DLL, поскольку KERNEL32.DLL в Windows 9x никаких перенаправлений не содержит.

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

В качестве примера приведем программу, которая печатает номера всех экспортируемых символов, их RVA и имена, если последние есть.

#include <iostream>
using namespace std;

. . .

void _tmain(int argc, _TCHAR* argv[]) {
  LPBYTE pBase = OpenPEFile(_T("c:\\windows\\system32\\kernel32.dll"));
  if (pBase == NULL) {
    cout << "File not found!" << endl;
    return;
  }
  IMAGE_NT_HEADERS* pHeader = GetHeader(pBase);
  if (pHeader == NULL) {
    cout << "It is not a PE file!" << endl;
    return;
  }
  cout << "Ord\tRVA\tName" << endl;
  // Извлекаем параметры каталога данных экспорта.
  IMAGE_DATA_DIRECTORY& ExportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
  DWORD dwExportDirStart = ExportDataDir.VirtualAddress;
  DWORD dwExportDirEnd = dwExportDirStart + ExportDataDir.Size;
  // Получаем указатели на таблицу экспорта и все связанные с ней таблицы.
  IMAGE_EXPORT_DIRECTORY* pExportDir = (IMAGE_EXPORT_DIRECTORY*)GetFilePointer(pBase, dwExportDirStart);
  LPDWORD pAddrTable = (LPDWORD)GetFilePointer(pBase, pExportDir->AddressOfFunctions);
  LPDWORD pNameTable = (LPDWORD)GetFilePointer(pBase, pExportDir->AddressOfNames);
  LPWORD pOrdTable = (LPWORD)GetFilePointer(pBase, pExportDir->AddressOfNameOrdinals);
  // Итерация по всем элементам таблицы адресов.
  for (UINT i = 0; i < pExportDir->NumberOfFunctions; i++) {
    DWORD dwRVA = *pAddrTable++;
    // Не печатаем пропущенные элементы.
    if (dwRVA == 0)
      continue;
    cout << dec << i + pExportDir->Base << ":\t";
    // Проверяем тип очередного RVA.
    if (dwRVA >= dwExportDirStart && dwRVA < dwExportDirEnd)
      cout << (char*)GetFilePointer(pBase, dwRVA) << '\t'; // RVA перенаправления
    else
      cout << hex << dwRVA << '\t'; // обычный RVA
    // Итерация по всем элементам таблицы номеров.
    for (UINT j = 0; j < pExportDir->NumberOfNames; j++) {
      // Если номер найден, выводим соответствующее имя из таблицы имен.
      if (pOrdTable[j] == i) {
        cout << (char*)GetFilePointer(pBase, pNameTable[j]);
        break;
      }
    }
    cout << endl;
  }
  ClosePEFile(pBase);
}

5.3. Таблицы импорта

5.3.1. Структура таблицы импорта

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

Таблица импорта представляет собой массив описателей IMAGE_IMPORT_DESCRIPTOR, по одному описателю на каждую из импортируемых DLL. Массив заканчивается описателем, который полностью заполнен нулями. Каждый из описателей имеет следующую структуру:

Смещение (hex) Размер Тип Название Описание
00 4 DWORD OriginalFirstThunk RVA таблицы имен импорта (INT).
04 4 DWORD TimeDateStamp Дата и время.
08 4 DWORD ForwarderChain Индекс первого перенаправленного символа.
0C 4 DWORD Name RVA ASCIIZ-строки, содержащей имя DLL.
10 4 DWORD FirstThunk RVA таблицы адресов импорта (IAT).

Мы видим, что описатель указывает на имя библиотеки и на две таблицы: таблицу имен (INT) и таблицу адресов (IAT). Обе эти таблицы представляют собой массивы структур IMAGE_THUNK_DATA, заканчивающиеся структурой, полностью заполненной нулями. IMAGE_THUNK_DATA является 32-битовым словом (DWORD) в формате PE32 и 64-битовым словом (ULONGLONG) в формате PE32+.

При этом смысл IMAGE_THUNK_DATA зависит от старшего бита данного слова. Если этот бит установлен, то остальные 31 или 63 бита содержат номер импортируемого символа (импорт по номеру). Если же этот бит сброшен, то остальные биты задают RVA описателя импортируемого символа (импорт по имени). Старший бит обозначен как IMAGE_ORDINAL_FLAG; для его проверки удобно использовать макрос

IMAGE_SNAP_BY_ORDINAL(ptd->u1.Ordinal),

где ptd – указатель на IMAGE_THUNK_DATA.

Описатель символа называется IMAGE_IMPORT_BY_NAME. Он содержит 16-битовое слово Hint, за которым следует ASCIIZ-строка имени символа Name. Если размер итоговой структуры нечетен, то в конец добавляется еще один нулевой байт. Хочу еще раз напомнить, что имена символов чувствительны к регистру.

Хорошие сборщики заносят в поле Hint индекс символа в таблице имен библиотеки (см. выше описание таблицы экспорта). Это позволяет при загрузке быстро найти соответствующий RVA в таблице экспорта DLL. Поскольку не все сборщики правильно заполняют поле Hint, загрузчик использует следующий алгоритм. Сначала проверяется имя символа с номером Hint в таблице экспорта DLL. Если оно совпадает с именем импортируемого символа, то все в порядке. В противном случае проводится описанный выше бинарный поиск имени экспортируемого символа. При импорте по номеру символа, естественно, никакой поиск не нужен.

5.3.2. Динамическое связывание и IAT

Теперь разберемся, зачем нужны две таблицы (имен и адресов). Таблица имен INT содержится в том же каталоге, что и сама таблица импорта и никогда не изменяется. Таблица адресов IAT обычно хранится в отдельном каталоге IMAGE_DIRECTORY_ENTRY_IAT (см. выше список каталогов) и первоначально совпадает с таблицей имен.

В процессе работы программе нужны не имена или номера импортируемых символов, а их адреса в виртуальной памяти процесса. Поэтому во время загрузки производится т. н. связывание (binding) адресов. Оно состоит в следущем. После загрузки очередной динамической библиотеки в память процесса загрузчик просматривает все импортируемые из нее символы, находит соответствующие RVA в таблице экспорта DLL и заменяет в IAT элементы IMAGE_THUNK_DATA на линейные адреса соответствующих символов. В итоге, поле OriginalFirstThunk указывает на список импортируемых символов, а поле FirstThunk – на список соответствующих линейных адресов.

Интересно, что каталог IMAGE_DIRECTORY_ENTRY_IAT может в PE-файле отсутствовать (это означает, что IAT размещена в той же секции, что и таблица импорта); на правильность загрузки и связывания это не влияет.

5.3.3. Статическое связывание

Мы привели базовое описание импорта, теперь перейдем к деталям, несколько усложняющим картину. Дело в том, что помимо динамического связывания в процессе загрузки, возможно и статическое связывание PE-файла с динамическими библиотеками. Статическое связывание производится либо сборщиком, либо утилитой BIND.EXE, входящей в состав Platform SDK. При желании можно выполнить статическое связывание и с помощью функций BindImage и BindImageEx, входящих в состав IMAGEHLP.DLL.

Суть статического связывания в том, что в таблицу IAT файла прописываются линейные адреса импортируемых символов в предположении, что все DLL загружаются по прописанным в них базовым адресам (поле ImageBase необязательного заголовка). Такое связывание обычно ускоряет процесс загрузки программ, но иногда может потребоваться динамическое связывание в процессе загрузки. Это происходит в двух случаях: 1) DLL загружена в память не по ее базовому адресу; 2) версия загруженной DLL отличается от версии, с которой мы провели статическое связывание. Первый случай распознается загрузчиком легко. А как же он распознает, что загруженная DLL не та, что предполагалось? Для этого используется поле TimeDateStamp. В файле, не подвергавшемуся статическому связыванию это поле всегда равно нулю.

Существует две схемы статического связывания: старая и новая. Старая схема заносит в поле TimeDateStamp содержимое поля TimeDateStamp из заголовка файла DLL. Теперь загрузчику достаточно сравнить значения этих полей и, если они различны, произвести заново динамическое связывание.

Эта схема прекрасно работает, пока импортируемые символы не являются перенаправленными к другим библиотекам. (О перенаправлении см. описание таблицы экспорта.) Поскольку проследить цепочку перенаправлений по дате и времени создания DLL нельзя, из соображений надежности такие символы всегда связываются заново в процессе загрузки. Для этого загрузчик должен различать обычные импортируемые символы от перенаправленных. Это делается посредством поля ForwarderChain, которое содержит индекс первого перенаправленного символа в IAT. По этому индексу хранится индекс следующего перенаправленного символа и т. д. В индексе последнего перенаправленного символа хранится -1. Это, в частности, означает, что в отсутствии статического связывания или перенаправленных символов поле ForwarderChain содержит -1.

5.3.4. Таблица связывания импорта

Новая схема связывания, которая также поддерживается утилитой BIND.EXE, заносит в поля TimeDateStamp и ForwarderChain значение -1. Вся информация о статическом связывании сохраняется в отдельном каталоге данных IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT. Интересно, что этот каталог, в отличие от остальных, обычно размещается не внутри какой-либо секции, а между заголовками секций и началом первой секции.

Таблица связывания импорта представляет собой массив описателей библиотек, по одному описателю на каждую DLL. Описатель состоит из структуры IMAGE_BOUND_IMPORT_DESCRIPTOR, за которой могут следовать нуль или более структур IMAGE_BOUND_FORWARDER_REF. Завершается массив структурой IMAGE_BOUND_IMPORT_DESCRIPTOR, заполненной нулями.

Структура IMAGE_BOUND_IMPORT_DESCRIPTOR устроена так:

Смещение (hex) Размер Тип Название Описание
00 4 DWORD TimeDateStamp Дата и время создания DLL.
04 2 WORD OffsetModuleName Смещение от начала каталога до ASCIIZ-имени DLL.
06 2 WORD NumberOfModuleForwarderRefs Количество структур IMAGE_BOUND_FORWARDER_REF, следующих за данной.

За этой структурой следуют NumberOfModuleForwarderRefs структур IMAGE_BOUND_FORWARDER_REF. Каждая из этих структур описывает одну DLL, к которой есть перенаправления из данной DLL. Устроены эти структуры следующим образом:

Смещение (hex) Размер Тип Название Описание
00 4 DWORD TimeDateStamp Дата и время создания DLL.
04 2 WORD OffsetModuleName Смещение от начала каталога до ASCIIZ-имени DLL.
06 2 WORD Reserved Зарезервировано.

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

5.3.5. Таблица отложенного импорта

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

Отложенная загрузка импорта имеет, как минимум, два преимущества. Во-первых, она ускоряет загрузку программ в память. Во-вторых, она позволяет программам, использующим возможности новых версий Windows, безболезненно работать в старых версиях ОС. Для этого код программы должен в процессе выполнения проверять текущую версию ОС и, если она старая, не вызывать новых функций. Пусть, например, мы хотим использовать темы Windows XP, API которых реализованы библиотекой UXTHEME.DLL, отсутствующей в других версиях Windows. При обычной сборке программы загрузчик Windows 2000 попытается загрузить UXTHEME.DLL, не найдет ее и завершит работу по фатальной ошибке. При отложенной загрузке импорта из UXTHEME.DLL этого не произойдет, поскольку под Windows 2000 в процессе работы программы не будет ни одного обращения к этой библиотеке.

Важно понимать, что отложенная загрузка импорта не является свойством загрузчика ОС. Она реализуется добавлением к программе дополнительного кода и данных в процессе сборки. Например, для отложенного импорта библиотеки тем Windows XP мы в командной строке сборщика вместо опции /LIB:UXTHEME.LIB указываем две опции: /LIB:DELAYIMP.LIB и /DELAYLOAD:UXTHEME.DLL. Первая опция как раз и подключает необходимый дополнительный код (DELAYIMP.LIB), а вторая приказывает сборщику создать таблицу отложенного импорта.

Поскольку отложенный импорт не поддерживается загрузчиком, его структуры описаны не в файле WINNT.H, а в файлах, реализующих DELAYIMP.LIB: заголовке DELAYIMP.H и файле DELAYHLP.CPP (находятся в папке VC7/INCLUDE вашей Visual Studio).

Таблица отложенного импорта представляет собой массив описателей ImgDelayDescr, по одному описателю для каждой DLL с отложенным импортом. Массив оканчивается описателем, заполненным нулями. Эти описатели устроены так:

Смещение (hex) Размер Тип Название Описание
00 4 DWORD grAttrs Атрибуты описателя. В настоящее время используется только младший бит.
04 4 DWORD rvaDllName RVA ASCIIZ-имени DLL.
08 4 DWORD rvaHmod RVA поля типа HMODULE. Первоначально содержит 0, после загрузки DLL сюда заноситься ее описатель (handle).
0C 4 DWORD rvaIAT RVA таблицы адресов импорта. Она имеет тот же формат, что и обычная IAT.
10 4 DWORD rvaINT RVA таблицы имен импорта. Она имеет тот же формат, что и обычная INT.
14 4 DWORD rvaBoundIAT RVA необязательной таблицы связанных адресов импорта. Она имеет тот же формат, что и обычная IAT. В настоящее время совпадает с IAT, но будущие версии утилиты BIND.EXE будут помещать сюда связанную IAT.
18 4 DWORD rvaUnloadIAT RVA необязательной копии исходной IAT. Она имеет тот же формат, что и обычная IAT. В настоящее время содержит 0.
1C 4 DWORD dwTimeStamp В настоящее время содержит 0, но будущие версии утилиты BIND.EXE будут помещать сюда дату и время создания связанной DLL.

Приведенное описание требует единственного комментария. Первая версия отложенного импорта, которая появилась в Visual Studio 6.0, содержала в данной структуре не RVA, а указатели (т. е. полные виртуальные адреса). С появлением архитектуры Win64, где указатели являются 64-битовыми, пришлось вместо указателей хранить RVA (эта версия появилась в Visual Studio .NET). Чтобы определить, что содержит ImgDelayDescr, нужно проверить младший бит поля grAttrs (его маска названа dlattrRva). Если он установлен, то в структуре хранятся RVA; если сброшен, то виртуальные адреса.

5.3.6. Пример обработки таблиц импорта

Приведенный ниже пример выводит на экран всю описанную в этом разделе информацию.

#include <ctime>
#include <iostream>
#include <vector>
#include <algorithm>
#include <delayimp.h>
using namespace std;

. . .

void _tmain(int argc, _TCHAR* argv[]) {
  LPBYTE pBase = OpenPEFile(_T("c:\\windows\\system32\\user32.dll"));
  if (pBase == NULL) {
    cout << "File not found!" << endl;
    return;
  }
  IMAGE_NT_HEADERS* pHeader = GetHeader(pBase);
  if (pHeader == NULL) {
    cout << "It is not a PE file!" << endl;
    return;
  }
  // Извлекаем параметры каталога данных импорта.
  IMAGE_DATA_DIRECTORY& ImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
  if (ImportDataDir.Size != 0) {
    cout << "Import Data Directory" << endl;
    cout << "---------------------" << endl << endl;
    for (IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)GetFilePointer(pBase, ImportDataDir.VirtualAddress);
      pImportDesc->Name != 0; pImportDesc++) {
      char* name = strupr(strdup((char*)GetFilePointer(pBase, pImportDesc->Name)));
      cout << name << endl;
      free(name);
      time_t time = pImportDesc->TimeDateStamp;
      bool bBounded = false, bOldBind = false;
      if (time == 0)
        cout << "DLL not bounded" << endl;
      else if (time == -1) {
        cout << "New style bounding, see Bound Import Data Directory below" << endl;
        bBounded = true;
      } else {
        cout << "Old style bounding. Date/Time: " << asctime(gmtime(&time));
        bBounded = bOldBind = true;
      }
      IMAGE_THUNK_DATA* pINT;
      IMAGE_THUNK_DATA* pIAT;
      if (pImportDesc->OriginalFirstThunk != 0) {
        pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->OriginalFirstThunk);
        pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->FirstThunk);
      } else { // учитываем ошибку сборщика TLINK
        pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->FirstThunk);
        pIAT = NULL;
        bBounded = false;
      }
      vector forwardRefs;
      if (pImportDesc->ForwarderChain != -1 && bOldBind) {
        for (DWORD dwChain = pImportDesc->ForwarderChain; dwChain != -1;  dwChain = pIAT[dwChain].u1.Ordinal)
          forwardRefs.push_back(dwChain);
      }
      if (bBounded) {
        if (bOldBind)
          cout << "\nAddress\t\tHint\tName/Ordinal\tForwarded" << endl;
        else
          cout << "\nAddress\t\tHint\tName/Ordinal" << endl;
      } else
        cout << "\nHint\tName/Ordinal" << endl;
      for (DWORD i =0; pINT->u1.Ordinal != 0; i++) {
        if (bBounded)
          cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << '\t';
        if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
            cout << '\t' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG);
        else {
          IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal);
          cout << dec << p->Hint << '\t' << (char*)p->Name;
        }
        if (bOldBind)
          cout << (find(forwardRefs.begin(), forwardRefs.end(), i) == forwardRefs.end() ? "\tN" : "\tY");
        cout << endl;
        pINT++; pIAT++;
      }
      cout << endl;
    }
  }
  // Извлекаем параметры каталога данных связывания импорта.
  IMAGE_DATA_DIRECTORY& BoundImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT];
  if (BoundImportDataDir.Size != 0) {
    cout << "Bound Import Data Directory" << endl;
    cout << "---------------------------" << endl << endl;
    LPBYTE pBoundImportDir = GetFilePointer(pBase, BoundImportDataDir.VirtualAddress);
    for (IMAGE_BOUND_IMPORT_DESCRIPTOR* pBoundImportDesc = (IMAGE_BOUND_IMPORT_DESCRIPTOR*)pBoundImportDir;
      pBoundImportDesc->OffsetModuleName != 0; ) {
      char* name = strupr(strdup((char*)(pBoundImportDir + pBoundImportDesc->OffsetModuleName)));
      cout << name << endl;
      free(name);
      time_t time = pBoundImportDesc->TimeDateStamp;
      cout << "Date/Time: " << asctime(gmtime(&time));
      if (pBoundImportDesc->NumberOfModuleForwarderRefs == 0) {
        cout << "No forwarder refs" << endl;
        pBoundImportDesc++;
      } else {
        cout << "Forwarder refs:" << endl;
        cout << "\nName\t\tDate/Time" << endl;
        IMAGE_BOUND_FORWARDER_REF* pForwardRef = (IMAGE_BOUND_FORWARDER_REF*)(pBoundImportDesc + 1);
        for (UINT i = 0; i < pBoundImportDesc->NumberOfModuleForwarderRefs; i++, pForwardRef++) {
          char* name = strupr(strdup((char*)(pBoundImportDir + pForwardRef->OffsetModuleName)));
          time_t time = pForwardRef->TimeDateStamp;
          cout << name << '\t' << asctime(gmtime(&time));
          free(name);
        }
        pBoundImportDesc = (IMAGE_BOUND_IMPORT_DESCRIPTOR*)pForwardRef;
      }
      cout << endl;
    }
  }
  // Извлекаем параметры каталога данных отложенного импорта.
  IMAGE_DATA_DIRECTORY& DelayImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];
  if (DelayImportDataDir.Size != 0) {
    cout << "Delay Import Data Directory" << endl;
    cout << "---------------------------" << endl << endl;
    for (ImgDelayDescr* pDelayDesc = (ImgDelayDescr*)GetFilePointer(pBase, DelayImportDataDir.VirtualAddress);
      pDelayDesc->rvaDLLName != 0; pDelayDesc++) {
      if (pDelayDesc->grAttrs & dlattrRva) {
        char* name = strupr(strdup((char*)GetFilePointer(pBase, pDelayDesc->rvaDLLName)));
        cout << name << endl;
        free(name);
        cout << "Attributes:\tVisual Studio .NET" << endl;
        cout << "HMODULE:\t" << hex << *(LPDWORD)GetFilePointer(pBase, pDelayDesc->rvaHmod) << endl;
        time_t time = pDelayDesc->dwTimeStamp;
        if (time != 0)
          cout << "Date/Time:\t" << asctime(gmtime(&time));
        else
          cout << "Date/Time:\tnot set" << endl;
        cout << "\nAddress\t\tHint\tName/Ordinal" << endl;
        IMAGE_THUNK_DATA* pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaINT);
        IMAGE_THUNK_DATA* pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaIAT);
        while (pINT->u1.Ordinal != 0) {
          cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << '\t';
          if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
            cout << '\t' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG) << endl;
          else {
            IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal);
            cout << dec << p->Hint << '\t' << (char*)p->Name << endl;
          }
          pINT++; pIAT++;
        }
      } else { // Не тестировано!!!
        char* name = strupr(strdup((char*)GetFilePointer(pBase, pDelayDesc->rvaDLLName - pHeader->OptionalHeader.ImageBase)));
        cout << name << endl;
        free(name);
        cout << "Attributes:\tVisual Studio 6.0" << endl;
        cout << "HMODULE:\t" << hex << *(LPDWORD)GetFilePointer(pBase, pDelayDesc->rvaHmod - pHeader->OptionalHeader.ImageBase) << endl;
        time_t time = pDelayDesc->dwTimeStamp;
        if (time != 0)
          cout << "Date/Time:\t" << asctime(gmtime(&time));
        else
          cout << "Date/Time:\tnot set" << endl;
        cout << "\nAddress\t\tHint\tName/Ordinal" << endl;
        IMAGE_THUNK_DATA* pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaINT - pHeader->OptionalHeader.ImageBase);
        IMAGE_THUNK_DATA* pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaIAT - pHeader->OptionalHeader.ImageBase);
        while (pINT->u1.Ordinal != 0) {
          cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << '\t';
          if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
            cout << '\t' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG) << endl;
          else {
            IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal);
            cout << dec << p->Hint << '\t' << (char*)p->Name << endl;
          }
          pINT++; pIAT++;
        }
      }
      cout << endl;
    }
  }
  ClosePEFile(pBase);
}
© 2005 Юрий Лукач