Когда вы открываете чужую DLL в дизассемблере или смотрите на импортируемые функции, вы быстро замечаете две разные картины: где-то функции импортируются по именам (CreateFile, MessageBox), а где-то — по числам (Ordinal 1, Ordinal 42). Второй способ выглядит загадочно, но проверить его реально — и это не требует глубоких знаний внутреннего устройства Windows. Разберёмся, как именно это сделать, и зачем вообще это нужно.
- Зачем это вообще проверять
- Что такое Ordinal в контексте PE-файла
- Способ 1: Посмотреть в PE-парсере или дизассемблере
- Способ 2: Проверить вручную по дампу
- Способ 3: Использовать dumpbin или objdump
- Способ 4: Проверить во время выполнения
- Как отличить реальный ординальный импорт от просто отсутствующего имени
- Типичные ситуации, когда встречается ординальный импорт
- Частые ошибки при определении ординального импорта
- Что делать дальше в зависимости от вашей задачи
- Итог
Зачем это вообще проверять
Импорт по ординалу — это когда программа подключает функцию из DLL не по имени, а по номеру. Раньше такой подход использовали для ускорения загрузки: не нужно сравнивать строки-имена, достаточно подставить число в таблицу. Сейчас это встречается реже, но именно по ординалу часто импортируются системные функции из kernel32.dll, ntdll.dll и user32.dll. Если вы занимаетесь реверс-инжинирингом, анализом малвари или просто пытаетесь понять, почему программа не запускается — умение быстро определить тип импорта экономит кучу времени.
Что такое Ordinal в контексте PE-файла
PE-файл (исполняемый формат Windows) хранит информацию об импортах в структуре IMAGE_IMPORT_DESCRIPTOR. Для каждой импортируемой функции есть два варианта хранения:
- По имени — в таблице указан hint (двухбайтовый индекс в таблице имён библиотеки) и само имя функции в виде ASCII-строки.
- По ординалу — старший бит поля имени установлен в 1, а младшие 15 бит (или 6 бит для PE32+) содержат непосредственно номер ординала.
Ключевой момент: если у функции старший бит равен 1, значит импорт идёт по числу, а не по строке. Это правило одинаково для 32-битных и 64-битных файлов, хотя конкретные смещения в заголовках отличаются.
Способ 1: Посмотреть в PE-парсере или дизассемблере
Самый быстрый путь — загрузить файл в инструмент, который умеет показывать структуру PE-заголовка. Вот пошагово:
- Откройте файл в CFF Explorer, PEview или PE-bear (последний бесплатный и удобный).
- Перейдите в секцию Import Directory (или Import Table).
- Найдите нужную DLL и посмотрите список импортируемых функций.
- Если вместо имени функции вы видите число в скобках — например,
[17],[0x80000011]— это импорт по ординалу.
В IDA Pro или Ghidra это видно ещё проще: в окне импорта функции с ординальным номером обычно имеют имя вида Ordinal_17 или вообще не имеют осмысленного имени. Если вы видите много таких записей — программа активно использует ординальный импорт.
Способ 2: Проверить вручную по дампу
Если под рукой нет специализированного инструмента, можно посмотреть сырые данные. Для этого нужно найти в PE-файле таблицу импортов и проверить значения полей OriginalFirstThunk и FirstThunk.
- Найдите в Optional Header поле
DataDirectory[1](Import Table RVA и Size). - Перейдите по этому RVA в файле — вы окажетесь в массиве структур
IMAGE_IMPORT_DESCRIPTOR. - Для каждой структуры посмотрите на поле
OriginalFirstThunk(илиFirstThunk, если биндинг не применён). - Пройдите по массиву thunk-значений. Каждое значение — это либо RVA на строку с именем функции, либо ординальный номер.
- Проверьте старший бит: если он установлен (значение ≥ 0x80000000 для PE32, ≥ 0x8000000000000000 для PE32+), то младшие биты содержат ординальный номер.
Пример: если вы видите значение 0x8000002A — отбрасываем старший бит, получаем 0x2A = 42. Это ординальный импорт функции номер 42 из соответствующей DLL.
Способ 3: Использовать dumpbin или objdump
Если вы работаете из командной строки, стандартные утилиты тоже справляются:
Для dumpbin (идёт с Visual Studio):
dumpbin /imports myprogram.exe
В выводе ищите строки вида:
Ordinal 17 Ordinal 42
Если вместо имён функций стоят только числа — весь импорт идёт по ординалу для этой DLL.
Для objdump (MinGW, Cygwin):
objdump -p myprogram.exe | grep -A 20 "Import Table"
Аналогично: смотрите, что указано в колонке с именами функций.
Способ 4: Проверить во время выполнения
Иногда нужно понять, использует ли запущенная программа ординальный импорт прямо в рантайме. Для этого подходят отладчики и инструменты перехвата:
- API Monitor — показывает все вызовы API с указанием, был ли вызов сделан по имени или по ординалу.
- x64dbg / x32dbg — поставьте брейкпоинт на функцию и посмотрите, как она вызывается. Если в таблице импортов адрес соответствует ординальному значению — вы увидите это в дампе памяти.
- Detours или аналогичные хуки — при перехвате вызова можно проверить, был ли он оригинально привязан по ординалу.
Как отличить реальный ординальный импорт от просто отсутствующего имени
Бывает, что реверсеры путают два разных явления:
- Импорт по ординалу — старший бит thunk-значения установлен, и это штатный механизм PE-формата.
- Безымянный импорт — имя функции отсутствует в дампе из-за повреждения файла или намеренного стирания информации, но старший бит не установлен.
Разница критическая: в первом случае программа корректно работает с ординалами, во втором — скорее всего, файл повреждён или обфусцирован. Проверить просто: посмотрите на сырое значение thunk. Если старший бит стоит — это штатный ординальный импорт.
Типичные ситуации, когда встречается ординальный импорт
| Ситуация | Почему используется ordinal | Что делать |
|---|---|---|
| Системные DLL (kernel32, ntdll, user32) | Microsoft гарантирует стабильность ординалов для критических API | Смотрите документацию по ординалам или используйте утилиты вроде ordinals.exe |
| Малварь и обфусцированный код | Скрытие реальных вызовов API от статического анализа | Динамический анализ в песочнице + отладчик |
| Старые программы (эпоха Win9x) | Тогда ординальный импорт был стандартом для скорости | Современные инструменты корректно показывают ординалы |
| .NET сборки с P/Invoke через ordinals | Редко, но бывает при явном указании EntryPoint как числа |
Проверьте атрибут DllImport в метаданных сборки |
Частые ошибки при определении ординального импорта
Ошибка 1: Путать отсутствие имени с ординальным импортом. Если в дампе пустая строка вместо имени — это не значит, что импорт по ординалу. Проверяйте старший бит thunk-значения.
Ошибка 2: Считать, что ординальный импорт — признак малвари. Многие легитимные программы используют его для системных функций. Контекст важен.
Ошибка 3: Игнорировать разницу между PE32 и PE32+. В 64-битных файлах thunk-значения 8-байтовые, и старший бит проверяется иначе (0x8000000000000000, а не 0x80000000).
Ошибка 4: Полагать, что ординальный импорт нельзя отследить. Отследить можно — через динамический анализ или по таблице экспорта DLL, где ординалы соответствуют конкретным функциям.
Что делать дальше в зависимости от вашей задачи
Если вы анализируете малварь: ординальный импорт — это попытка скрыть используемые API. Загрузите DLL в отладчик, посмотрите таблицу экспорта по ординалам (например, через dumpbin /exports ntdll.dll), и сопоставьте номера с реальными функциями. Это даст вам список вызовов без необходимости реверсить каждый вызов вручную.
Если вы отлаживаете чужую программу: ординальный импорт может быть причиной проблем, если DLL обновилась и ординалы изменились. Проверьте версию DLL, которую загружает программа, и сравните с ординалами в её экспортной таблице.
Если вы пишете свой код: избегайте ординального импорта, если не уверены в стабильности номеров в будущих версиях DLL. Используйте имена — это надёжнее и читаемее.
Итог
Определить, использует ли программа импорт по ординалу, можно четырьмя способами: через PE-парсер (CFF Explorer, PE-bear), вручную по дампу (проверка старшего бита thunk-значений), через командную строку (dumpbin, objdump) или во время выполнения (API Monitor, отладчик). Главное правило — смотрите на старший бит в thunk-таблице: если он установлен, перед вами ординальный импорт. Не путайте это с отсутствием имени из-за повреждения файла — это разные вещи, и проверка старшего бита сразу даёт ответ.
