#c #format #portable-executable
#c #формат #переносимый-исполняемый
Вопрос:
Я пытаюсь написать exe-упаковщик для Windows. Я уже разработал некоторые основы. Часть, которую я должен выполнить, — это чтение «СВЯЗАННОЙ таблицы каталогов импорта» (или раздела .idata?), В основном раздела PE-файла, который содержит список DLL, которые загрузчик должен импортировать.
Мне интересно, какой лучший способ либо:
[A] выясните, где находится IAT (поскольку запуск PEView для нескольких разных .exe, похоже, показывает, что этот список может содержаться в нескольких разных местах), а затем прочитайте список
или
[B] Просто найдите способ напрямую прочитать список DLL, которые exe должен импортировать.
Есть ли способ сделать это? Есть ли какие-либо дополнительные сведения, которые люди могут порекомендовать для чтения о том, где должен быть IAT и как его читать?
Ответ №1:
Кроме того, вот PDF-файл, который поможет вам понять, как называются и организованы структуры. Некоторые полезные программы: CFF Explorer и хороший шестнадцатеричный редактор.
Мой ответ отличается от приведенного выше тем, что он описывает способ вручную выполнить то, что было описано выше, в исполняемом файле, который все еще находится на диске.
Чтобы получить относительный виртуальный адрес (адрес во время выполнения, он же RVA) IAT:
- Начните с базового адреса двоичного файла. Это
IMAGE_DOS_HEADER
структура. - Следуйте по
e_lfanew
полю, чтобы перейти кIMAGE_NT_HEADERS
структуре. Начиная со смещения 0, перейдите на 0x3c вниз и выполните разыменование, чтобы получить начало IMAGE_NT_HEADERS . - Следуйте
OptionalHeader
инструкциям (содержащимся в IMAGE_NT_HEADERS и, следовательно, смежным с ними). Чтобы перейти кIMAGE_OPTIONAL_HEADER
структуре (несмотря на ее название, она больше не является необязательной), знайте, что это третья структура в IMAGE_NT_HEADER . Чтобы перейти к OptionalHeader, добавьте 0x18 к значению, которое вы разыменовали ранее - Из 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 на диске:
-
Получите заголовок раздела; для этого пройдите по списку разделов (таких как .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 к указателю текущего раздела, чтобы перейти к следующему. Последний раздел представляет собой нулевой байт, обозначающий конец.
-
Из полученного заголовка раздела выполните следующие действия: (Базовый адрес RVA) — (SectionHeader.VirtualAddress — sectionhHeader.Указатель на данные). * Виртуальный адрес SectionHeader находится на расстоянии 12 байт от самого SectionHeader, как рассчитано выше. Указатель torawdata находится на расстоянии 20 от заголовка раздела.
- Полученное значение представляет собой фактический указатель на данные, требуемые / представленные RVA. Вы можете использовать его, чтобы найти фактическое расположение в файле нужных вам данных.
-
Это был полный рот. Если вы хотите резюмировать, вам следует прочитать страницы 257-60 главы 5 (Таблицы подключений вызовов) в Арсенале руткитов, но для более понятной графики ознакомьтесь с openrce.org ссылка на pdf, которую я дал вверху.
Однако, чтобы сделать это, начните с самого начала…:
- Перейдите к OptionalHeader, как описано выше. OptionalHeader содержит массив элементов (DataDirectory) IMAGE_DATA_DIRECTORY в качестве последнего элемента. Вторым элементом в этом массиве является IMAGE_DIRECTORY_ENTRY_IMPORT, который определяет местоположение IAT. Итак, чтобы уточнить, IMAGE_NT_HEADER содержит массив OptionalHeader, который содержит массив DataDirectory. Последняя запись в этом массиве будет обнулена.
- Из разыменования OptionalHeader в IMAGE_DIRECTORY_ENTRY_IMPORT. Следующим словом является размер каталога импорта. От смещения в файле перейдите на 0x68 вниз.
- Это значение представляет собой RVA каталога импорта, который представляет собой массив структур типа IMAGE_IMPORT_DESCRIPTOR (по одной для каждой библиотеки DLL, импортируемой модулем), поля последнего из которых равны нулю. Третье слово в IMAGE_IMPORT_DESCRIPTOR содержит указатель FirstThunk на IMAGE_THUNK_DATA.
-
Используя алгоритм, описанный выше, преобразуйте 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: В основном последовательность поиска выглядит следующим образом:
- Начните с базового адреса двоичного файла. Это
IMAGE_DOS_HEADER
структура. - Следуйте по
e_lfanew
полю, чтобы перейти кIMAGE_NT_HEADERS
структуре. - Следуйте
OptionalHeader
инструкциям, чтобы перейти кIMAGE_OPTIONAL_HEADER
структуре (несмотря на ее название, она больше не является необязательной). - Перейдите
DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
к массивуIMAGE_IMPORT_DESCRIPTOR
структур. Для каждой импортированной библиотеки DLL есть одна запись. Последняя запись в этом массиве будет обнулена. Name
Поле в каждой записи является RVA, которое указывает на имя библиотеки DLL.FirstThunk
Поле представляет собой RVA, которое указывает на IAT этой библиотеки DLL, которая представляет собой массивIMAGE_THUNK_DATA
структур.
Комментарии:
1. Этот ответ не объясняет, что такое RVA, как сформулировать из него полезный указатель и как выполнить итерацию по фактической таблице указателей в самом IAT; это только объясняет, как добраться до IAT. Вопрос пользователя включает в себя необходимость прочитать список. Последний ответ многословен и вряд ли будет прочитан кем-либо, кто использует Visual Studio или что-либо еще, где ручной обход его с помощью смещений и вычислений, подобных обратному проектированию, неуместен. В этом контексте краткое описание того, как выполнить итерацию с помощью некоторого кода на C , стало бы полезным дополнением.