Когда ты открываешь DLL и видишь в таблице экспорта пару стандартных функций — не спешить делать выводы. Практически каждый реверсер знает, что в PE-файле есть достаточно мест, где можно спрятать реальный функционал или указать на него неочевидным образом. Разберём, как именно искать скрытые функции через анализ PE-заголовка, без углубления в академическую теорию — только то, что реально работает на практике.
- Почему функции вообще скрывают в DLL
- Что реально даёт PE-заголовок в этом контексте
- Шаг 1: Смотрим на экспорт, но правильно
- Шаг 2: Сверяем таблицу экспорта с RVA-адресами
- Шаг 3: Разбираюсь с секциями — ищу аномалии
- Шаг 4: Проверяю TLS callbacks
- Шаг 5: Анализирую таблицу импорта как подсказку
- Шаг 6: Ресурсная секция — не только иконки
- Инструменты, которые реально помогают
- Типичные ошибки при поиске скрытых функций
- Что делать в зависимости от вашей ситуации
- Практический алгоритм действий
- На что обращать внимание — короткий чеклист
- Заключение
Почему функции вообще скрывают в DLL
Сразу определимся, с чем имеем делом. Скрытые функции — это не обязательно вредоносный код. Это может быть:
- отладочный функционал, который разработчик забыл или не захотел убрать;
- внутренние механики, не предназначенные для внешнего использования;
- намеренно скрытый код — например, реализация недокументированных возможностей;
- результат работы обфускатора или протектора, который намеренно искажает структуру файла.
В любом случае, стандартный способ — посмотреть таблицу экспорта — показывает далеко не всё. Нужно копать глубже в структуру PE.
Что реально даёт PE-заголовок в этом контексте
PE-заголовок — это не просто «шапка» файла. Это карта, по которой загрузчик Windows ориентируется в том, где что лежит. И именно в ней часто находятся следы скрытого функционала.
Ключевые структуры, на которые смотрим:
- IMAGE_EXPORT_DIRECTORY — основная таблица экспорта. Но даже здесь не всё очевидно.
- IMAGE_IMPORT_DIRECTORY — таблица импорта. Показывает, откуда DLL подтягивает функции, и иногда это подсказка.
- IMAGE_RESOURCE_DIRECTORY — секция ресурсов. Там могут быть встроенные строки, диалоги, даже дополнительные исполняемые данные.
- Section headers — описания секций. По ним видно, где код, где данные, а где что-то подозрительное.
- TLS callbacks — функции, которые выполняются до точки входа. Идеальное место для скрытого кода.
Шаг 1: Смотрим на экспорт, но правильно
Первое, что делает большинство — открывает Dependency Walker или CFF Explorer и смотрит список экспортируемых функций. Это нормально, но недостаточно.
Вот на что обращаю внимание:
- Экспорт по ординалам без имён. Если функция экспортируется только по числовому ординалу и не имеет строкового имени — это классический признак намеренного сокрытия. Так делают и легальные разработчики (например, в системных DLL), и малварь.
- Имена-плейсхолдеры. Функции с именами вроде
sub_10001A30,unknown,nullsub— кандидаты на более пристальный разбор. - Несоответствие количества функций и размера кода. Если в DIRECTORY_ENTRY_EXPORT указано 50 функций, а в кодовой секции места явно больше — часть кода не экспортируется явно.
Шаг 2: Сверяем таблицу экспорта с RVA-адресами
Это ключевой момент, который часто пропускают. В IMAGE_EXPORT_DIRECTORY есть три массива:
- AddressOfFunctions — RVA самих функций.
- AddressOfNames — RVA строк с именами функций.
- AddressOfNameOrdinals — массив ординалов, связывающий имя с функцией.
Что делаю на практике: сверяю количество записей в AddressOfFunctions с AddressOfNames. Если в AddressOfFunctions записей больше — значит, есть функции без имён. Это прямой сигнал.
Далее смотрю на RVA-адреса в AddressOfFunctions. Если несколько адресов указывают в одну и ту же область памяти или в область, которая явно не является кодом (например, в секцию ресурсов) — это повод разбираться детальнее.
Шаг 3: Разбираюсь с секциями — ищу аномалии
Section headers — это то, что сразу показывает структуру файла на уровне памяти. Вот конкретные признаки, которые настораживают:
- Секция с характериками IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE (RWX). Это не всегда подозрительно, но в сочетании с другими признаками — классика упаковщиков.
- Секция с нестандартным именем — например,
.ndata,.rsrcв необычном месте, или вообще без имени. - Размер секции в памяти значительно больше размера на диске. Это признак того, что код распаковывается в рантайме.
- Несколько секций с исполняемым кодом. В норме одна (
.textилиCODE). Если их несколько — возможно, часть кода спрятана.
Шаг 4: Проверяю TLS callbacks
TLS (Thread Local Storage) callbacks — это функции, которые вызываются загрузчиком до вызова DllMain. Это значит, что код в них выполняется раньше, чем что-либо ещё, и его легко пропустить при статическом анализе.
Где искать: в IMAGE_DATA_DIRECTORY с индексом IMAGE_DIRECTORY_ENTRY_TLS. Если запись не пустая — смотрю на TLS directory, а именно на поле AddressOfCallbacks.
На практике: если вижу TLS callback, который делает что-то кроме инициализации переменных — например, вызывает другие функции, работает с файлами или сетью — это почти наверняка скрытый функционал.
Шаг 5: Анализирую таблицу импорта как подсказку
Импорт — это не только про то, что DLL сама использует. Это ещё и подсказка о том, что она может делать скрыто.
Смотрю на следующее:
- Импорт функций из нестандартных DLL. Например, если обычная библиотека подключает
WinHttpOpen,CryptEncryptилиCreateRemoteThread— это говорит о сетевой активности, шифровании или инъекции. - Импорт по ординалам вместо имён. В таблице импорта есть OriginalFirstThunk и FirstThunk. Если значения — это ординалы, а не RVA на строки с именами — возможна намеренная обфускация.
- Подозрительные комбинации.
VirtualAlloc+WriteProcessMemory+CreateRemoteThread— классическая цепочка для инъекций.
Шаг 6: Ресурсная секция — не только иконки
IMAGE_RESOURCE_DIRECTORY — это кладезь информации. Помимо иконок и диалогов, там могут быть:
- встроенные исполняемые файлы (RT_RCDATA);
- зашифрованные или закодированные данные;
- строки, которые используются скрытыми функциями;
- конфигурационные блоки.
Если в ресурсах вижу блок данных размером в сотни килобайт, а сам DLL при этом небольшая — это повод извлечь и проанализировать этот блок отдельно.
Инструменты, которые реально помогают
Не буду перечислять двадцать утилит — только то, чем сам пользуюсь и что даёт конкретный результат в контексте поиска скрытых функций:
| Инструмент | Что даёт для нашей задачи | Когда использовать |
|---|---|---|
| CFF Explorer | Полная картина PE-заголовка, все data directories, секции, экспорт/импорт | Первичный осмотр любого PE-файла |
| PE-bear | Визуальный анализ структуры, удобно сравнивать секции и видеть аномалии | Быстрый визуальный осмотр |
| IDA Pro / Ghidra | Декомпиляция, поиск функций по сигнатурам, анализ графа вызовов | Глубокий анализ после первичного осмотра |
| PEview | Быстрый просмотр PE-структур без лишнего | Когда нужно за 30 секунд понять структуру |
| pefile (Python) | Скриптовый анализ, автоматизация поиска аномалий | Массовая проверка файлов или автоматизация |
Типичные ошибки при поиске скрытых функций
Вот реальные промахи, которые регулярно вижу (и сам совершал):
- Смотрю только на экспорт. Это самое очевидное, но скрытый функционал почти никогда не лежит на поверхности в экспортной таблице.
- Игнорирую ординальный экспорт. Если функция доступна только по ординалу — это не значит, что она не важна. Напротив, часто так прясут самое интересное.
- Не проверяю TLS. Просто забывают. А между тем это один из самых надёжных способов спрятать код.
- Не сверяю RVA с физическими смещениями. Если файл был модифицирован после компиляции, смещения могут не совпадать, и анализ будет некорректным.
- Доверяю имени секции. Секцию можно назвать как угодно. Важны характеристики (Characteristics), а не имя.
Что делать в зависимости от вашей ситуации
Если вы исследуете легальную библиотеку и ищете недокументированный функционал: начните с экспорта по ординалам, затем проверьте импорт на предмет необычных вызовов. Далее — ресурсная секция. Скорее всего, недокументированные функции либо экспортируются без имён, либо спрятаны в TLS.
Если вы анализируете подозрительный файл: сначала проверьте секции на RWX и аномалии размера. Потом TLS callbacks. Затем ресурсы — там может быть встроенный шеллкод. Экспорт смотрите в последнюю очередь, потому что малварь часто вообще не экспортирует ничего полезного.
Если вы делаете массовую проверку файлов: автоматизируйте через pefile или аналогичную библиотеку. Скрипт должен проверять: количество экспортируемых функций без имён, наличие TLS, RWX-секции, аномалии в ресурсах. Это отсеет 90% безобидных файлов и оставит те, что стоит посмотреть вручную.
Практический алгоритм действий
Вот пошаговый порядок, который я использую на практике:
- Открываю файл в CFF Explorer или PE-bear — получаю общую картину.
- Смотрю Section Headers — ищу RWX-секции, аномалии в размерах, нестандартные имена.
- Проверяю IMAGE_EXPORT_DIRECTORY — сравниваю количество функций и имён, ищу ординальный экспорт.
- Смотрю TLS callbacks — если есть, перехожу в IDA/Ghidra и анализирую каждую.
- Проверяю импорт — ищу подозрительные комбинации функций.
- Анализирую ресурсы — извлекаю блоки RT_RCDATA и проверяю их содержимое.
- Если нашёл что-то подозрительное — перехожу к дизассемблированию и декомпиляции конкретных функций.
На что обращать внимание — короткий чеклист
- Экспорт без имён (ординальный экспорт)
- Несоответствие количества функций и имён в экспорте
- RWX-секции или несколько исполняемых секций
- Размер секции в памяти больше размера на диске
- Наличие TLS callbacks
- Подозрительные импорты (сеть, шифрование, инъекции)
- Большие блоки в ресурсной секции
- Нестандартные имена секций или их отсутствие
Заключение
PE-заголовок — это не формальность, а реальная карта того, что происходит внутри DLL. Скрытые функции редко удаётся спрятать бесследно — они всегда оставляют следы в структуре файла: несоответствия в таблицах, лишние секции, TLS callbacks, подозрительные импорты.
Главное — не ограничиваться просмотром экспорта. Системный подход: секции → экспорт → TLS → импорт → ресурсы → дизассемблирование. Такой порядок покрывает практически все известные способы сокрытия функционала в PE-файлах.
Если файл проходит все проверки без аномалий — скорее всего, скрытых функций нет. Если же что-то бросается в глаза — начните с TLS и ординального экспорта, именно там прячут чаще всего.
