2. PE-файлы: заголовок и заглушка DOS

2.1. Заголовок DOS

Поскольку и приложения DOS, и приложения Windows имеют расширение .EXE, все исполняемые файлы Windows используют схему двойной загрузки. Она состоит в том, что файл начинается с заголовка DOS, за которым следует заглушка (stub), т. е. небольшой EXE-файл формата DOS. При попытке загрузить файл из DOS'а исполняется заглушка, а при загрузке файла из Windows загрузчик анализирует заголовок DOS и извлекает из него смещение до настоящего заголовка исполняемого файла.

Структура заголовка DOS называется IMAGE_DOS_HEADER. Я не буду полностью описывать заголовок DOS, т. к. нас интересуют в нем только два поля, а именно:

При загрузке PE-файла мы обязаны сначала проверить сигнатуру DOS, затем найти смещение до заголовка PE, а затем проверить сигнатуру PE, расположенную в начале его заголовка. Эта сигнатура состоит из 4 байтов и равна "PE\0\0" (обозначение IMAGE_NT_SIGNATURE).

Такую же схему двойной загрузки используют и другие файлы (исполняемые файлы Win16 и OS/2 и VxD-драйверы Windows 9x), поэтому проверка правильности сигнатуры PE обязательна.

2.2. Заглушка DOS

Обычно заглушка DOS выводит на экран сообщение типа "Эта программа требует Microsoft Windows" и заканчивает работу. Однако при сборке программы мы можем указать в командной строке сборщика любой EXE-файл DOS в качестве заглушки. Это позволяет создавать "дуальные" программы, работающие и в DOS, и в Windows.

2.3. Проверка типа файла

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

IMAGE_NT_HEADERS* GetHeader(LPBYTE pBase) {
  // Проверка правильности аргументов.
  if (pBase == NULL)
    return NULL;
  // Получаем указатель на заголовок DOS.
  IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pBase;
  // Проверяем корректность указателя.
  if (IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)))
    return NULL;
  // Сначала проверяем сигнатуру DOS.
  if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    return NULL;
  // Берем указатель на заголовок PE.
  IMAGE_NT_HEADERS* pHeader = (IMAGE_NT_HEADERS*)(pBase + pDosHeader->e_lfanew);
  // Проверяем корректность указателя.
  if (IsBadReadPtr(pHeader, sizeof(IMAGE_NT_HEADERS)))
    return NULL;
  // Наконец проверяем сигнатуру PE.
  if (pHeader->Signature != IMAGE_NT_SIGNATURE)
    return NULL;
  return pHeader;
}
© 2005 Юрий Лукач