Когда вы открываете любой исполняемый файл (.exe или .dll) в PE-редакторе или отладчике, вы видите список функций, которые эта программа вызывает из других библиотек. Например, CreateFileW из kernel32.dll или recv из ws2_32.dll. За это отвечает Import Table — механизм, который позволяет программе не тащить в себе весь код Windows, а «добирать» нужные функции прямо в момент запуска.
Понимание того, как физически расположены эти данные в файле, критически важно для реверс-инжиниринга, написания своих загрузчиков (лоадеров) или анализа вредоносного ПО. Если вы пытаетесь пропатчить программу или понять, с чем она взаимодействует, вам нужно знать, куда смотреть.
- Зачем вообще нужна эта таблица
- Анатомия Import Table: как это устроено внутри
- 1. Точка входа: Import Directory
- 2. IAT (Import Address Table) — сердце импорта
- 3. INT (Import Name Table)
- Сравнение: Импорт по имени vs Импорт по ординалу
- Что это позволяет делать на практике
- 1. Анализ поведения (Reverse Engineering)
- 2. IAT Hooking (Перехват функций)
- 3. Скрытие импорта (API Hashing)
- Частые ошибки при разборе импортов
- Как лучше действовать в разных ситуациях
- Итог и рекомендации
Зачем вообще нужна эта таблица
Представьте, что каждая программа должна была включать в себя весь код функций для работы с сетью, файлами и окнами. Размер простейшего «Hello World» вырос бы с нескольких килобайт до сотен мегабайт. Поэтому используется динамическая линковка.
Таблица импорта — это, по сути, «список покупок». Программа говорит операционной системе: «Мне для работы нужны вот эти DLL и конкретные функции из них». ОС загружает эти библиотеки в память, находит адреса функций и записывает их в программу, чтобы та могла по ним прыгать.
Анатомия Import Table: как это устроено внутри
Важно понимать: в PE-файле нет одной-единственной «таблицы импорта». Это целая иерархия структур. Если вы будете искать просто «Import Table», вы запутаетесь. На самом деле процесс идет по цепочке.
1. Точка входа: Import Directory
Все начинается с заголовка PE (Optional Header). В нем есть поле DataDirectory[1], которое указывает на Import Directory Table. Это массив структур IMAGE_IMPORT_DESCRIPTOR.
Каждый такой дескриптор описывает одну DLL. Если программа использует пять разных библиотек, в этом массиве будет пять записей, а последняя будет пустой (нулевой), чтобы ОС знала, где список закончился.
Внутри IMAGE_IMPORT_DESCRIPTOR самое важное — это три указателя:
- OriginalFirstThunk: указывает на имена функций (нужно для линковщика и анализатора).
- FirstThunk: указывает на таблицу адресов (IAT), куда ОС запишет реальные адреса функций при запуске.
- Name: указатель на строку с названием DLL (например, «user32.dll»).
2. IAT (Import Address Table) — сердце импорта
IAT — это то, с чем программа работает в реальности. Когда код вызывает функцию MessageBox, он не идет искать её в DLL каждый раз. Он делает косвенный переход по адресу, который хранится в IAT.
Как это работает в динамике:
- На диске в IAT лежат не адреса функций, а указатели на структуры
IMAGE_THUNK_DATA. - Когда Windows загружает файл, загрузчик (Ldr) проходит по списку импорта.
- Он находит нужную DLL в памяти, ищет в ней адрес функции.
- Заменяет временный указатель в IAT на реальный адрес функции в памяти.
3. INT (Import Name Table)
Пока IAT хранит адреса, INT хранит имена функций. Это нужно для того, чтобы загрузчик знал, что именно искать в DLL. Если функция импортируется по имени, там будет указатель на строку. Если по порядковому номеру (ordinal), там будет просто число.
Сравнение: Импорт по имени vs Импорт по ординалу
Не все функции имеют текстовые имена. Иногда разработчики библиотек используют индексы (ординалы). Это ускоряет поиск и позволяет скрыть истинное назначение функции.
| Критерий | Импорт по имени (ByName) | Импорт по ординалу (By Ordinal) |
|---|---|---|
| Что хранится в INT | Указатель на строку (например, «GetSystemTime») | Числовой индекс (например, 12) |
| Читаемость | Высокая. Сразу понятно, что делает код. | Низкая. Нужно смотреть таблицу экспорта DLL. |
| Скорость загрузки | Чуть медленнее (нужен поиск строки). | Быстрее (прямой доступ по индексу). |
| Стабильность | Высокая. Имя редко меняется между версиями. | Рискованно. Номер функции может измениться в новой версии DLL. |
Что это позволяет делать на практике
Знание структуры импорта дает вам несколько мощных инструментов при работе с бинарными файлами:
1. Анализ поведения (Reverse Engineering)
Если вы видите в импортах InternetOpenW, HttpSendRequestW и WriteFile, вы можете с уверенностью сказать, что программа что-то скачивает из сети и сохраняет на диск, даже не читая ни одной строчки ассемблерного кода.
2. IAT Hooking (Перехват функций)
Поскольку программа вызывает функцию через адрес в IAT, вы можете заменить этот адрес на адрес своей собственной функции.
Сценарий: Вы хотите, чтобы программа вместо того, чтобы записывать данные в реальный файл, писала их в ваш лог. Вы находите в IAT адрес WriteFile и меняете его на адрес своей функции MyWriteFile. Программа даже не заметит подмены.
3. Скрытие импорта (API Hashing)
Авторы вредоносного ПО не хотят, чтобы аналитик видел список функций. Поэтому они удаляют стандартную таблицу импорта и используют GetProcAddress и LoadLibrary. Вместо имен функций они хранят хеши. Программа в рантайме обходит экспорт DLL, считает хеши всех функций и, найдя совпадение, вызывает нужную.
Частые ошибки при разборе импортов
Многие новички путаются в терминах и структурах. Вот где чаще всего возникают проблемы:
- Путаница между FirstThunk и OriginalFirstThunk. На диске они часто выглядят одинаково. Но в памяти
OriginalFirstThunkостается списком имен, аFirstThunk(IAT) превращается в список реальных адресов. Если вы пытаетесь читать имена функций из IAT запущенного процесса, вы увидите просто случайные числа (адреса памяти). - Игнорирование Forwarders. Иногда DLL не содержит функцию, а «перенаправляет» запрос в другую библиотеку (например,
kernel32.dllможет перенаправить вntdll.dll). Если вы вручную ищете функцию в первой библиотеке и не находите её, проверьте, не является ли запись «форвардером». - Попытка найти импорты в упакованных файлах. Если файл упакован (UPX, VMProtect), стандартная таблица импорта будет пустой или содержать только две функции:
LoadLibraryиGetProcAddress. Это нормально. Реальный импорт восстанавливается распаковщиком в памяти во время выполнения.
Как лучше действовать в разных ситуациях
В зависимости от вашей задачи, подход к работе с импортами будет разным:
Ситуация А: Вам нужно быстро понять, что делает неизвестный файл.
👉 Решение: Используйте инструменты вроде PE-bear или CFF Explorer. Смотрите раздел «Import Directory». Ищите функции, связанные с сетью, реестром, файловой системой и созданием процессов.
Ситуация Б: Вы пишете патч для программы и хотите изменить её логику.
👉 Решение: Используйте IAT Hooking. Не пытайтесь менять код самой функции в DLL (это затронет все программы в системе), меняйте адрес вызова в IAT конкретного исполняемого файла.
Ситуация В: Вы анализируете вредоносный код, и импортов почти нет.
👉 Решение: Ищите в коде вызовы GetProcAddress. Скорее всего, программа динамически подгружает функции. Поставьте брейкпоинт на эту функцию в отладчике, и вы увидите, какие именно API она запрашивает в реальном времени.
Итог и рекомендации
Таблица импорта — это мост между статичным файлом на диске и живым процессом в памяти. Чтобы уверенно с ней работать, запомните эту цепочку:
Optional Header → Import Directory → DLL Descriptor → IAT/INT.
Краткий чек-лист для практики:
- Для статического анализа смотрите
OriginalFirstThunk(имена функций). - Для анализа в памяти или перехвата функций работайте с
FirstThunk(IAT). - Если видите функции по ординалам — ищите таблицу экспорта соответствующей DLL.
- Если импортов подозрительно мало — ищите динамический поиск функций через хеши или
GetProcAddress.
Лучший способ закрепить это — взять любой простой системный файл (например, calc.exe), открыть его в PE-bear и вручную проследить путь от заголовка до конкретной строки с названием функции в секции данных.
