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

Я пытаюсь написать упаковщик exe для окон. У меня есть некоторые из основ, разработанных до сих пор. Тем не менее, часть, которой я занимаюсь, - это чтение «Таблицы каталогов BOUND IMPORT» (или раздела .idata?), в основном раздела PE-файла, который содержит список DLL, которые загрузчик должен импортировать.

Мне интересно, как лучше всего:

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

OR

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

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


person Dan    schedule 06.10.2011    source источник


Ответы (2)


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

Отличную информацию о том, как найти информацию в заголовках, см. в серии статей Мэтта Питрека в журнале MSDN Magazine, "Углубленный взгляд на формат переносимых исполняемых файлов 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.
person Aaron Klotz    schedule 06.10.2011
comment
Этот ответ не объясняет, что такое RVA, как сформулировать из него полезный указатель и как выполнить итерацию по фактической таблице указателей в самой IAT; это только объясняет, как добраться до IAT. Вопрос пользователя включает в себя необходимость прочитать список. Последний ответ многословен и вряд ли будет прочитан кем-либо, кто использует визуальную студию или что-то еще, где ручное перемещение по смещениям и расчеты, подобные обратному инжинирингу, неуместны. В этом контексте краткое описание того, как повторить это с помощью некоторого кода C++, было бы полезным дополнением. - person Adam Miller; 05.11.2012

Кроме того, вот 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. 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 имени ASCII-библиотеки, оканчивающейся нулем) и FirstThunk (RVA IAT/массив линейных адресов, созданный загрузчик).
  • Необходимы два массива, поскольку один представляет собой массив имен импортированных подпрограмм (ILT), а другой — массив адресов импортированных подпрограмм (IAT). Подпрограммы, импортированные из DLL, можно импортировать по имени или порядковому номеру. Чтобы узнать, является ли подпрограмма порядковым импортом, проверьте флаг, установленный в поле Ordinal структуры IMAGE_THUNK_DATA в массиве ILT.
  • Каждая функция, импортируемая модулем во время загрузки, будет представлена ​​структурой IMAGE_THUNK_DATA. как Original First Thunk, так и First Thunk указывают на массив IMAGE_THUNK_DATA. Однако структура IMAGE_THUNK_DATA представляет собой объединение, которое содержит другую структуру, IMAGE_IMPORT_BY_NAME. Это важно знать, поскольку исходный первый преобразователь использует структуру IMAGE_IMPORT_BY_NAME, а первый преобразователь использует поле Function во внутреннем объединении структуры IMAGE_THUNK_DATA.
  • RVA, указанные на диске, не позволят вам просмотреть файл, потому что RVA представляет адрес, когда двоичный файл загружается в память; чтобы просмотреть двоичный файл на диске, вам нужно будет преобразовать значения RVA в правильную форму. Формула проста; HMODULE + RVA = линейный адрес PE-элемента. HMODULE также известен как базовый адрес. Но для получения базового адреса на самом деле требуется несколько длинный алгоритм, который зависит от того, какое значение RVA вы имеете в виду на самом деле. Чтобы получить значение базового адреса для данного RVA, чтобы вычислить линейный адрес PE-элемента на диске:

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

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

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

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

    2. Из полученного заголовка раздела выполните следующее: (baseAddress+RVA) - (sectionHeader.virtualAddress - sectionhHeader.PointerToRawData). * Виртуальный адрес sectionHeader находится на расстоянии 12 байт от самого sectionHeader, как рассчитано выше. PointerToRawData находится на расстоянии 20 единиц от заголовка раздела.

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

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

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

  1. Перейдите к опциональному заголовку, как описано выше. Необязательный заголовок содержит массив (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 преобразуйте поле имени RVA в указатель, чтобы получить имя. Это может быть нуль

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

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

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

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

person Adam Miller    schedule 05.11.2012
comment
Меня не устраивает, как много вы цитировали ответ Аарона Клотца, не уточнив, что было цитатой, а что было вашим дополнением. Вы должны процитировать его ответ, а затем сказать, что бы вы добавили/изменили, а не просто копировать и вставлять его ответ в свой собственный. - person Carey Gregory; 05.11.2012
comment
не редактируйте так, если вы считаете, что что-то полезное, чем добавляйте в качестве нового ответа, я исправил это, но не буду в будущем - person NullPoiиteя; 05.11.2012
comment
@CareyGregory Да, проработав над этим весь день, мне как-то не хотелось его удалять, просто потому, что ответ, что другой парень, уже был в моем. Я чувствую, что лучше иметь то, что он написал там (здесь это тоже ценная информация), поэтому я отмечу то, что он написал в моем ответе, как его ответ. - person Adam Miller; 05.11.2012
comment
Я подумал о том, что вы сказали, и полагаю, что ответ, который я дал, был совершенно другим; если на то пошло, это должен быть собственный ответ. То, что началось как разъяснение, закончилось полномасштабным приключением, я рад, что вы указали на это задним числом. Я буду осторожен в будущем. - person Adam Miller; 05.11.2012
comment
Хорошо продуманные, тщательные вторые ответы часто делают SO действительно полезным. Намного лучше! - person Carey Gregory; 05.11.2012
comment
Спасибо! Я ценю это :) - person Adam Miller; 05.11.2012