Формат PE — вопросы IAT

#c #format #portable-executable

#c #формат #переносимый-исполняемый

Вопрос:

Я пытаюсь написать exe-упаковщик для Windows. Я уже разработал некоторые основы. Часть, которую я должен выполнить, — это чтение «СВЯЗАННОЙ таблицы каталогов импорта» (или раздела .idata?), В основном раздела PE-файла, который содержит список DLL, которые загрузчик должен импортировать.

Мне интересно, какой лучший способ либо:

[A] выясните, где находится IAT (поскольку запуск PEView для нескольких разных .exe, похоже, показывает, что этот список может содержаться в нескольких разных местах), а затем прочитайте список

или

[B] Просто найдите способ напрямую прочитать список DLL, которые exe должен импортировать.

Есть ли способ сделать это? Есть ли какие-либо дополнительные сведения, которые люди могут порекомендовать для чтения о том, где должен быть IAT и как его читать?

Ответ №1:

Кроме того, вот PDF-файл, который поможет вам понять, как называются и организованы структуры. Некоторые полезные программы: CFF Explorer и хороший шестнадцатеричный редактор.

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

Чтобы получить относительный виртуальный адрес (адрес во время выполнения, он же RVA) IAT:

  1. Начните с базового адреса двоичного файла. Это IMAGE_DOS_HEADER структура.
  2. Следуйте по e_lfanew полю, чтобы перейти к IMAGE_NT_HEADERS структуре. Начиная со смещения 0, перейдите на 0x3c вниз и выполните разыменование, чтобы получить начало IMAGE_NT_HEADERS .
  3. Следуйте OptionalHeader инструкциям (содержащимся в IMAGE_NT_HEADERS и, следовательно, смежным с ними). Чтобы перейти к IMAGE_OPTIONAL_HEADER структуре (несмотря на ее название, она больше не является необязательной), знайте, что это третья структура в IMAGE_NT_HEADER . Чтобы перейти к OptionalHeader, добавьте 0x18 к значению, которое вы разыменовали ранее
  4. Из OptionalHeader выполните разыменование в DataDirectory. Каталог данных представляет собой массив внутри OptionalHeader, который находится внутри IMAGE_NT_HEADERS . Следуйте 24-й (если 0 является первым, как в 0, 1, 2 …) записи в массиве DataDirectory до IMAGE_DIRECTORY_ENTRY_IAT . Добавьте 0xc0 к адресу, по которому вы находитесь в данный момент, чтобы получить каталог таблицы адресов импорта

Если вы хотите просмотреть список DLL и адреса их функций, есть некоторая справочная информация:

  • 3 важных поля в структуре IMAGE_IMPORT_DESCRIPTOR: OriginalFirstThunk (RVA таблицы поиска импорта), Name (RVA имени Dll ASCII с нулевым завершением) и FirstThunk (RVA IAT / массива линейных адресов, созданных загрузчиком).
  • Необходимы два массива, потому что один представляет собой массив имен импортированных подпрограмм (ILT), а другой — массив адресов импортированных подпрограмм (IAT). Процедуры, импортированные из библиотеки DLL, могут быть импортированы по их имени или порядковому номеру. Чтобы определить, является ли процедура порядковым импортом, проверьте флаг, установленный в порядковом поле структуры IMAGE_THUNK_DATA в массиве ILT.
  • Каждая функция, импортируемая модулем во время загрузки, будет представлена структурой IMAGE_THUNK_DATA. как исходный первый элемент, так и первый элемент указывают на массив IMAGE_THUNK_DATA . Однако структура IMAGE_THUNK_DATA является объединением, которое содержит другую структуру IMAGE_IMPORT_BY_NAME . Это важно знать, потому что исходный первый блок использует структуру IMAGE_IMPORT_BY_NAME, а Первый блок использует поле функции во внутреннем объединении структуры IMAGE_THUNK_DATA .
  • RVA, указанные на диске, не позволят вам просматривать файл, потому что RVA представляет адрес при загрузке двоичного файла в память; чтобы просмотреть двоичный файл на диске, вам нужно будет преобразовать значения RVA в правильную форму. Формула проста; HMODULE RVA = линейный адрес элемента PE. HMODULE также известен как базовый адрес. Но для получения базового адреса на самом деле требуется несколько длительный алгоритм, который зависит от того, какое значение RVA, о котором вы говорите, на самом деле. Чтобы получить значение базового адреса для данного RVA, чтобы вычислить линейный адрес элемента PE на диске:

    1. Получите заголовок раздела; для этого пройдите по списку разделов (таких как .data, .text и т. Д.), Пока не найдете раздел, в котором RVA, о котором идет речь, находится в пределах текущего раздела.Виртуальный адрес и текущая секция.Виртуальный адрес currentSection.size.

      1.1) Сначала найдите количество разделов в заголовке файла в структуре NT_HEADERS. Это 2 байта после 2-байтового номера компьютера в заголовке файла. * Чтобы сделать это вручную: добавьте 0x6 к значению, разыменованному из e_lfanew; таким образом, переместите 0x3c со смещения 0, разыменуйте значение и 0x6 к нему. Затем считайте два байта и интерпретируйте как целое число.

      1.2) Найдите местоположение первого раздела; он примыкает к OptionalHeader . Помните, что внутри OptionalHeader находится массив DataDirectories . Длина OptionalHeader составляет 216 байт плюс 2 слова в конце, которые обозначают его окончание; поэтому возьмите 224 в шестнадцатеричном формате (0xe0) и добавьте его к значению, разыменованному в 0x3c с самого начала, чтобы получить местоположение первого раздела.

      1.3) Чтобы найти заголовок раздела, в котором находится ваш RVA, постоянно выполняйте этот тест относительно текущего раздела, в котором вы находитесь. Если тест не пройден, перейдите к следующему разделу. Если вы перебираете все разделы и обнаруживаете, что достигли конечных нулевых слов, то файл должен быть поврежден или вы допустили ошибку. Тест заключается в следующем: сравните RVA, который вы хотите преобразовать в полезный указатель, с виртуальным адресом раздела; RVA должен быть >= виртуальному адресу раздела и < сумма виртуального адреса и виртуального размера раздела. Виртуальный адрес раздела можно найти, добавив 12 к адресу раздела. И виртуальный размер раздела может быть найден на 8 по адресу раздела. Подводя итог: передайте if — (section.VirtualAddress section.VirtualSize) > RVA >= section.VirtualAddress . * Для перехода к следующему разделу длина описания раздела равна 0x28; вы можете просто добавить 0x28 к указателю текущего раздела, чтобы перейти к следующему. Последний раздел представляет собой нулевой байт, обозначающий конец.

    2. Из полученного заголовка раздела выполните следующие действия: (Базовый адрес RVA) — (SectionHeader.VirtualAddress — sectionhHeader.Указатель на данные). * Виртуальный адрес SectionHeader находится на расстоянии 12 байт от самого SectionHeader, как рассчитано выше. Указатель torawdata находится на расстоянии 20 от заголовка раздела.

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

Это был полный рот. Если вы хотите резюмировать, вам следует прочитать страницы 257-60 главы 5 (Таблицы подключений вызовов) в Арсенале руткитов, но для более понятной графики ознакомьтесь с openrce.org ссылка на pdf, которую я дал вверху.

Однако, чтобы сделать это, начните с самого начала…:

  1. Перейдите к OptionalHeader, как описано выше. OptionalHeader содержит массив элементов (DataDirectory) IMAGE_DATA_DIRECTORY в качестве последнего элемента. Вторым элементом в этом массиве является IMAGE_DIRECTORY_ENTRY_IMPORT, который определяет местоположение IAT. Итак, чтобы уточнить, IMAGE_NT_HEADER содержит массив OptionalHeader, который содержит массив DataDirectory. Последняя запись в этом массиве будет обнулена.
  2. Из разыменования OptionalHeader в IMAGE_DIRECTORY_ENTRY_IMPORT. Следующим словом является размер каталога импорта. От смещения в файле перейдите на 0x68 вниз.
  3. Это значение представляет собой RVA каталога импорта, который представляет собой массив структур типа IMAGE_IMPORT_DESCRIPTOR (по одной для каждой библиотеки DLL, импортируемой модулем), поля последнего из которых равны нулю. Третье слово в IMAGE_IMPORT_DESCRIPTOR содержит указатель FirstThunk на IMAGE_THUNK_DATA.
  4. Используя алгоритм, описанный выше, преобразуйте RVA каталога импорта в полезный указатель и используйте следующий алгоритм для итерации по массиву каталогов импорта.

    4.1) Для importDescriptor преобразуйте поле name RVA в указатель, чтобы получить имя. Это может быть null

    4.2) Чтобы получить имя и адрес каждой импортируемой подпрограммы, получите как записи OriginalFirstThunk, так и записи FirstThunk RVA в дескрипторе импорта. Каждый из OFT и FT может быть нулевым, что указывает на то, что он пустой, поэтому проверьте это.

    4.3) Преобразуйте OFT RVA в указатели; OFT соответствует ILT, а FT соответствует IAT. Либо ILT, либо IAT могут иметь значение null, что указывает на то, что они пусты.

    4.4) Получить имя функции, импортированной из указателя ILT, и адрес функции из указателя IAT. Чтобы перейти к следующей импортированной функции, помните, что ILT и IAT являются массивами; следующий элемент находится на постоянном расстоянии.

    4.5) Убедитесь, что полученные новые значения указателей ILT и IAT не равны нулю; если они не равны нулю, повторите. Если любой из них равен нулю, вы попали в конец списка функций, импортированных для этой библиотеки dll; дескриптор импорта также является повторяющимся массивом, поэтому смещение к следующей импортированной библиотеке dll является постоянным. По сути, вы перебираете библиотеки DLL, и для каждой библиотеки dll вы перебираете функции, импортированные таким образом. 19

Комментарии:

1. Мне не нравится, насколько вы процитировали ответ Аарона Клотца, не уточнив, что было цитатой и каким было ваше дополнение. Вы должны привести его ответ, а затем сказать, что бы вы добавили / изменили, а не просто копировать и вставлять его ответ в свой собственный.

2. не делайте такого редактирования, если вы считаете, что что-то полезно, чем добавить в качестве нового ответа, я исправил это, но не буду в будущем

3. @CareyGregory Да, проработав над этим весь день, мне как бы не хотелось его удалять, просто потому, что ответ другого парня уже был в моем. Я чувствую, что более важно иметь то, что он написал там (здесь тоже ценная информация), поэтому я отмечу то, что он написал в моем ответе, как его ответ.

4. Я подумал о том, что вы сказали, и я предполагаю, что ответ, который я предоставил, был совершенно другим; в этом отношении это должен быть собственный ответ. То, что начиналось как разъяснение, вылилось в полномасштабное приключение, я рад, что вы указали на это в ретроспективе. Я буду осторожен в будущем.

5. Хорошо продуманные, подробные вторые ответы часто делают SO действительно полезным. Намного лучше!

Ответ №2:

Да, вы можете найти IAT, просмотрев заголовки исполняемого файла. Посмотрите на winnt.h объявления заголовков.

Для получения отличной информации о том, как находить информацию в заголовках, см. Серию статей Мэтта Пьетрека в журнале MSDN «Углубленный обзор формата переносимых исполняемых файлов Win32», части I и II.

Вы также можете получить фактическую спецификацию Microsoft PE отсюда.

TL; DR: В основном последовательность поиска выглядит следующим образом:

  1. Начните с базового адреса двоичного файла. Это IMAGE_DOS_HEADER структура.
  2. Следуйте по e_lfanew полю, чтобы перейти к IMAGE_NT_HEADERS структуре.
  3. Следуйте OptionalHeader инструкциям, чтобы перейти к IMAGE_OPTIONAL_HEADER структуре (несмотря на ее название, она больше не является необязательной).
  4. Перейдите DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] к массиву IMAGE_IMPORT_DESCRIPTOR структур. Для каждой импортированной библиотеки DLL есть одна запись. Последняя запись в этом массиве будет обнулена.
  5. Name Поле в каждой записи является RVA, которое указывает на имя библиотеки DLL. FirstThunk Поле представляет собой RVA, которое указывает на IAT этой библиотеки DLL, которая представляет собой массив IMAGE_THUNK_DATA структур.

Комментарии:

1. Этот ответ не объясняет, что такое RVA, как сформулировать из него полезный указатель и как выполнить итерацию по фактической таблице указателей в самом IAT; это только объясняет, как добраться до IAT. Вопрос пользователя включает в себя необходимость прочитать список. Последний ответ многословен и вряд ли будет прочитан кем-либо, кто использует Visual Studio или что-либо еще, где ручной обход его с помощью смещений и вычислений, подобных обратному проектированию, неуместен. В этом контексте краткое описание того, как выполнить итерацию с помощью некоторого кода на C , стало бы полезным дополнением.