4. PE-файлы: секции

4.1. Заголовки секций

Сразу после заголовка PE в файле располагается массив заголовков секций. Его размер задается полем NumberOfSections заголовка файла. Каждый заголовок секции состоит из 0x28 байт и имеет следующую структуру:

Смещение (hex) Размер Тип Название Описание
00 8 CHAR[8] Name Название секции.
08 4 DWORD Misc.VirtualSize Размер секции в памяти. Если это значение больше SizeOfRawData, то секция дополняется в памяти нулевыми байтами.
0C 4 DWORD VirtualAddress RVA секции в памяти.
10 4 DWORD SizeOfRawData Размер секции в файле. Всегда кратен FileAlignment из необязательного заголовка. Если секция содержит только неинициализированные данные, то это поле равно нулю.
14 4 DWORD PointerToRawData Смещение в файле до начала данных секций. Всегда кратно FileAlignment из необязательного заголовка. Если секция содержит только неинициализированные данные, то это поле равно нулю.
18 4 DWORD PointerToRelocations В исполняемых файлах это поле всегда равно нулю.
4 DWORD PointerToLinenumbers В исполняемых файлах это поле всегда равно нулю.
20 2 WORD NumberOfRelocations В исполняемых файлах это поле всегда равно нулю.
22 2 WORD NumberOfLinenumbers В исполняемых файлах это поле всегда равно нулю.
24 4 DWORD Characteristics Атрибуты секции.

Прокомментируем некоторые поля.

Название секции

Название секции содержит от 0 до 8 ASCII-символов. Вместо константы 8 можно использовать определение IMAGE_SIZEOF_SHORT_NAME. Если длина названия меньше 8 символов, то оно дополняется нулевыми байтами. Если оно состоит ровно из 8 символов, то завершающего нулевого байта нет. Важно отметить, что название секции, вообще говоря, никак не соотносится с ее содержимым. Каждый компилятор использует свое собственное соглашение о именовании секций, поэтому полагаться на название секции при ее анализе нельзя. Единственно надежным способом определить, что содержит данная секция, является анализ ее атрибутов и содержащихся в ней каталогов данных.

Атрибуты секции

32-битовое поле Characteristics содержит набор флагов, описывающих содержимое данной секции. Секции исполняемого файла могут иметь следующие атрибуты:

Название Значение Описание
IMAGE_SCN_CNT_CODE 0x00000020 Секция содержит исполняемый код.
IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 Секция содержит инициализированные данные.
IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 Секция содержит неинициализированные данные.
IMAGE_SCN_MEM_DISCARDABLE 0x02000000 Секция может быть удалена из памяти после создания процесса.
IMAGE_SCN_MEM_NOT_CACHED 0x04000000 Секция не может кэшироваться.
IMAGE_SCN_MEM_NOT_PAGED 0x08000000 Секция не может выгружаться в файл подкачки.
IMAGE_SCN_MEM_SHARED 0x10000000 Все копии данного файла могут иметь один общий экземпляр этой секции. По-видимому, используется только для секций данных динамических библиотек.
IMAGE_SCN_MEM_EXECUTE 0x20000000 Секцию можно исполнять как программный код.
IMAGE_SCN_MEM_READ 0x40000000 Секцию можно читать.
IMAGE_SCN_MEM_WRITE 0x80000000 В секцию можно писать.

4.2. Содержимое секций

Сами секции располагаются в файле после всех заголовков секций. Каждая секция выравнена на границу FileAlignment из необязательного заголовка.

При анализе содержимого секций следует учитывать, что это содержимое может быть разнородным. Например, одна секция может содержать и исполняемый код, и таблицу импорта.

4.3. Доступ к секциям

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

IMAGE_FIRST_SECTION(pHeader),

где pHeader – указатель на заголовок PE, возвращаемый функцией GetHeader.

Для перебора всех заголовков секций удобно использовать такой цикл:

  IMAGE_SECTION_HEADER* pSectHeader = IMAGE_FIRST_SECTION(pHeader);
  for (UINT i = 0; i < pHeader->FileHeader.NumberOfSections; i++, pSectHeader++) {
    // pSectHeader указывает на заголовок i-й секции.
    . . .
  }

Теперь мы можем привести алгоритм, позволяющий пересчитать любой RVA в смещение относительно начала файла. Нам потребуются две функции. Первая ищет секцию, содержащую данный RVA, а вторая преобразует RVA в указатель на соответствующую ему позицию в файле.

IMAGE_SECTION_HEADER* GetSection(IMAGE_NT_HEADERS* pHeader, DWORD dwRVA) {
  // Проверка правильности аргументов.
  if (pHeader == NULL)
    return NULL;
  // Перебор всех заголовков секций.
  IMAGE_SECTION_HEADER* pSectHeader = IMAGE_FIRST_SECTION(pHeader);
  for (UINT i = 0; i < pHeader->FileHeader.NumberOfSections; i++, pSectHeader++) {
    // Если RVA находится внутри секции, то возвращаем указатель на ее заголовок.
    if (dwRVA >= pSectHeader->VirtualAddress && dwRVA < pSectHeader->VirtualAddress + pSectHeader->Misc.VirtualSize)
      return pSectHeader;
  }
  return NULL; // секция не найдена
}

LPBYTE GetFilePointer(LPBYTE pBase, DWORD dwRVA) {
  // Проверка правильности аргументов.
  if (pBase == NULL)
    return NULL;
  // Ищем секцию, содержащий данный RVA.
  IMAGE_SECTION_HEADER* pSectHeader = GetSection(GetHeader(pBase), dwRVA);
  if (pSectHeader == NULL) // Если секция не найдена,
    return pBase + dwRVA; // то RVA равно смещению в файле,
  // иначе вычисляем смещение относительно начала секции в файле.
  return pBase + pSectHeader->PointerToRawData + (dwRVA - pSectHeader->VirtualAddress);
}

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

#include <iostream>
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;
  }
  cout << "Dir\tRVA\tSize\tFilePtr" << endl;
  IMAGE_DATA_DIRECTORY* pDataDir = pHeader->OptionalHeader.DataDirectory;
  for (UINT i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++, pDataDir++) {
    if (pDataDir->Size != 0) {
      ULONG nFilePtr = (ULONG)(ULONG_PTR)GetFilePointer(pBase, pDataDir->VirtualAddress);
      cout << dec << i << ":\t" << hex << pDataDir->VirtualAddress << '\t'
        << pDataDir->Size << '\t' << nFilePtr << endl;
    }
  }
  ClosePEFile(pBase);
}
© 2005 Юрий Лукач