Как dumpbin может читать таблицу экспорта, если она появляется со смещением в файле, большим, чем сам файл?

Я пишу небольшой PE-ридер, поэтому запускаю dumpbin вместе с тестовым приложением, чтобы убедиться, что значения считываются правильно. Пока все работает, кроме таблицы экспорта.

Файл, который я тестирую, является DLL. Мое приложение считывает файл как массив байтов, который передается моему классу чтения PE. Значения совпадают с выходными данными dumpbin, включая RVA и размер каталога экспортируемых данных.

        E000 [     362] RVA [size] of Export Directory

Проблема в том, что размер массива байтов составляет всего 42 496. Как вы, вероятно, можете себе представить, когда мой считыватель PE пытается прочитать значение E000 (57 344), я получаю IndexOutOfRangeException. Однако dumpbin не имеет такой проблемы и прекрасно читает каталог экспорта. И да, весь файл действительно читается в массив байтов.

Как это возможно?


person David Brown    schedule 15.10.2009    source источник


Ответы (1)


PE-файл содержит «разделы», и разделы имеют независимые базовые адреса. PE не является непрерывным образом памяти. Каждый раздел представляет собой непрерывный образ памяти.

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

Кроме того, обратите внимание на OllyDbg, бесплатный отладчик и дизассемблер с открытым исходным кодом для Windows. Возможно, это поможет вам протестировать ваше собственное программное обеспечение и может удовлетворить ту самую цель, которую вы пытаетесь выполнить, «создавая свое собственное».

Пример из вывода dumpbin /all:

SECTION HEADER #1
   .text name
    BC14 virtual size
    1000 virtual address (00401000 to 0040CC13)
    BE00 size of raw data
     400 file pointer to raw data (00000400 to 0000C1FF)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         Execute Read

В этом случае мой раздел .text начинается с RVA 1000 и продолжается до RVA CE00. Указатель файла на этот раздел равен 400. Я могу преобразовать в указатель файла любые RVA в диапазоне 1000-CDFF, вычитая 600. (Все числовые значения шестнадцатеричные.)

Всякий раз, когда вы сталкиваетесь с «RVA» (относительным виртуальным адресом), вы разрешаете его в смещение файла (или индекс в вашем массиве байтов), используя этот метод:

  1. Определите, к какому разделу относится RVA. Каждый раздел содержит RVA от виртуального адреса до размера. Разделы не пересекаются.
  2. Вычтите виртуальный адрес раздела из RVA — это даст вам смещение относительно раздела.
  3. Добавьте PointerToRawData раздела к смещению, полученному на шаге (2). Это смещение файла, соответствующее RVA.

Другой подход, который вы можете использовать, заключается в вызове MapViewOfFileEx() с флагом FILE_MAP_EXECUTE, установленным в аргументе dwDesiredAccess. Этот API будет анализировать заголовки разделов из PE-файла и считывать содержимое разделов в их расположение относительно «базы модуля».

База модуля — это базовый адрес, по которому будет загружаться PE-заголовок. При загрузке DLL с помощью LoadLibrary() функций это можно получить с помощью MODULEINFO члена lpBaseOfDll GetModuleInformation() функции.

При использовании MapViewOfFileEx() базой модуля является просто возвращаемое значение из MapViewOfFileEx().

В настройках загрузки модуля этими способами преобразование RVA в нормальное значение указателя зависит от:

  1. Сохраните базовый адрес модуля в char *
  2. Добавьте RVA в char *
  3. Приведите char * к фактическому типу данных и разыменуйте его.

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

person Heath Hunnicutt    schedule 15.10.2009
comment
Извиняюсь, пока я учусь... До сих пор я добавил логический параметр, который сообщает программе чтения PE, отображаются ли данные в памяти (для которых я установил значение false при чтении из файла). Если это не отображается в памяти, я читаю каталог экспорта по адресу, указанному PointerToRawData в разделе .edata. Однако на этот раз AddressOfFunctions указывает на E000. Какая информация мне нужна, чтобы перевести это в смещение файла? Или я что-то пропустил? - person David Brown; 15.10.2009
comment
Вы сопоставляете его с памятью с помощью MapViewOfFileEx() и передаете FILE_MAP_EXECUTE? Или вы загрузили его с помощью LoadLibraryEx() и LOAD_LIBRARY_AS_IMAGE_RESOURCE? - person Heath Hunnicutt; 15.10.2009
comment
Ни один. Я просто читаю байты из файла на диске. Я хотел бы поддерживать как PE-образы на диске, так и загруженные в память, поэтому я добавил логический параметр, чтобы указать, какой это тип образа. Если MemoryMapped равно false, я знаю, что изображение было прочитано с диска, и я могу соответствующим образом преобразовать адреса экспорта. Я просто не знаю, какая информация мне нужна для перевода. - person David Brown; 15.10.2009
comment
Я понимаю. AddressOfFunctions также является RVA. См. абзац, который я добавил к своему ответу. - person Heath Hunnicutt; 15.10.2009
comment
Я взглянул на MapViewOfFileEx, так как вы упомянули об этом, и он выглядит очень полезным в этой ситуации. Будет ли эта конкретная функция автоматически сопоставлять каталог экспорта с RVA, заданным его VirtualAddress, чтобы мне вообще ничего не нужно было переводить? - person David Brown; 15.10.2009
comment
Он не сопоставит его с RVA, а с тем, что, я думаю, вы бы назвали VA. Под VA я подразумеваю реальный виртуальный адрес, по которому загружается весь модуль. Например, если ваша DLL загружается с базовым номером модуля 04000000, то RVA 1000 станет 04001000. Однако MapViewOfFileEx — это IMO правильный путь — вместо того, чтобы иметь дело со всеми данными этого раздела, вам нужно отслеживать только одно значение, базовый адрес модуля. Имейте в виду, что если вы хотите, чтобы ваш инструмент был полезен при взломе вредоносных программ или запутанных двоичных файлов, использование API-интерфейсов ОС может оставить вас в слепой зоне. В таком случае: проанализируйте заголовки разделов. - person Heath Hunnicutt; 15.10.2009
comment
Взлом вредоносных программ или запутанных двоичных файлов не входит в мои планы на данный момент, поэтому я еще немного посмотрю на MapViewOfFileEx. Спасибо за помощь! - person David Brown; 15.10.2009
comment
Привет, с удовольствием, и я рад, что наш сохраненный ответ стал лучше из темы. :) - person Heath Hunnicutt; 15.10.2009