- Как найти скрытые функции в .dll-файлах через анализ PE-заголовка — практическое руководство
- Почему функции прячутся в .dll
- Что нужно знать о PE-заголовке
- Как найти скрытые функции — пошагово
- Как отличить настоящую скрытую функцию от мусора
- Частые ошибки, которые ломают анализ
- Когда использовать какой инструмент
- Сценарии выбора: что делать в разных ситуациях
- Как лучше делать — практические рекомендации
- Что делать дальше
Как найти скрытые функции в .dll-файлах через анализ PE-заголовка — практическое руководство
Ты работаешь с чужим кодом. Может, это старый плагин, который никто не документировал. Или ты разбираешься в бинарнике, чтобы понять, почему программа ведёт себя странно. Ты открыл .dll-файл в Hex-редакторе — и ничего не понял. Или ты загрузил его в IDA Pro, а там куча импортов, но не хватает ключевой функции, которая, по логике, должна быть. Ты не один. Так бывает каждый день. И вот тут — не в коде, не в рантайме, а в самом заголовке файла — начинается настоящая охота.
PE-заголовок — это не просто «метаданные». Это карта, по которой Windows знает, где искать функции, какие библиотеки подгружать, где лежит код, а где — данные. И если кто-то спрятал функцию — она не исчезла. Она просто не в списке экспорта. А значит, её нужно искать не там, где все ищут.
Почему функции прячутся в .dll
Скрытые функции — это не всегда вредоносный код. Иногда это:
- Отладочные утилиты, оставленные разработчиками и не удалённые из релиза;
- Функции для внутреннего использования, которые не должны быть доступны сторонним приложениям;
- Резервные интерфейсы, которые включаются при определённых условиях (например, при наличии лицензионного ключа);
- Обфусцированные части кода, чтобы затруднить анализ;
- Функции, которые были удалены из экспорта, но оставлены в коде для обратной совместимости.
Если ты видишь, что программа вызывает какую-то функцию — но её нет в списке экспорта — значит, она вызывается по адресу. А значит, её можно найти. И не через дебаггер, а через статический анализ PE-заголовка.
Что нужно знать о PE-заголовке
PE (Portable Executable) — это формат исполняемых файлов в Windows. Он состоит из нескольких частей:
- MS-DOS заголовок — устаревший, но обязателен. Служит для совместимости. Его можно игнорировать.
- PE-сигнатура — 4 байта:
50 45 00 00(«PE\0\0»). Это твой первый маркер, что файл — действительно PE. - COFF Header — содержит информацию о архитектуре (x86, x64), количестве секций, времени сборки.
- Optional Header — ключевой блок. Здесь лежат указатели на важные структуры: таблицу экспорта, импорта, ресурсов, релокаций и т.д.
- Секции —
.text,.data,.rdata,.rsrcи другие. Код, данные, ресурсы.
Ты не будешь разбирать весь PE-файл. Тебя интересует только одно: директивы, которые указывают на расположение таблицы экспорта. Но не только её. Ты ищешь не только то, что экспортируется, а то, что есть.
Как найти скрытые функции — пошагово
Предположим, у тебя есть файл plugin.dll. Ты открыл его в Detect It Easy (или в PE-bear, или в CFF Explorer). Вот что ты делаешь:
- Найди Optional Header. В CFF Explorer — в левой панели: Optional Header → Data Directories. Там 16 записей. Тебя интересует запись №1: Export Table. Если её нет — файл не экспортирует функции вообще. Но это не значит, что их нет.
- Проверь RVA (Relative Virtual Address) экспорта. Допустим, там написано:
0x00002000. Это адрес относительно начала образа в памяти. Ты переходишь к секции, которая содержит этот адрес. Обычно это.rdataили.text. - Посмотри на размер экспорта. Если он равен 0 — значит, экспорта нет. Но если он не нулевой, а ты всё равно не видишь нужную функцию — возможно, она удалена из списка, но осталась в коде.
- Проверь секцию .text. Это твой основной источник кода. Ты ищешь в ней все функции, которые начинаются с
sub_илиfunc_— но это не всё. Ты ищешь вызовыcallк адресам, которые не входят в импорт. - Найди все адреса, на которые есть ссылки. В IDA Pro — нажми
Shift+F12, чтобы найти все строки. Ищи строки, похожие на названия функций:"GetSecretKey","InternalDebug". Потом иди к этим строкам — и смотри, откуда к ним идут переходы. Часто скрытые функции вызываются черезjmp [addr]илиcall [reg]. - Проверь таблицу импорта. Иногда функции не экспортируются, но импортируются из других .dll. Если ты видишь, что
plugin.dllимпортируетkernel32.dllиuser32.dll, но не импортируетapihelper.dll, а в коде есть вызовы к функциям изapihelper.dll— значит, функции подгружаются динамически черезLoadLibrary+GetProcAddress. Это частый приём для скрытия.
Как отличить настоящую скрытую функцию от мусора
В коде — тысячи функций. Большинство — это компиляторный мусор, статические функции, внутренние вспомогательные процедуры. Как понять, что ты нашёл что-то ценное?
| Признак | Скорее всего — мусор | Скорее всего — скрытая функция |
|---|---|---|
| Имя функции | sub_401230, fun_001 |
InitializeLicense, UnlockFeatureX |
| Количество аргументов | 0 или 1 (обычно внутренние вспомогательные) | 2–5 (типично для интерфейсных функций) |
| Вызовы API | Нет вызовов к системным функциям | Есть RegSetValue, VirtualAlloc, CreateFile |
| Структура кода | Простой цикл или копирование байтов | Много ветвлений, проверки флагов, вызовы других функций |
| Ссылки на строки | Нет или только шаблоны вроде "Error %d" |
Есть уникальные строки: "Feature disabled. Contact admin." |
| Размер | Меньше 32 байт | Больше 128 байт |
Если функция соответствует хотя бы трём пунктам из правой колонки — она стоит внимания. Особенно если она вызывается в коде, но не экспортируется.
Частые ошибки, которые ломают анализ
Я видел, как люди тратят часы, потому что делали одно и то же:
- Смотрят только на Export Table. Это как искать ключ в кармане, потому что там его «должны» держать. А он лежит в шкафу.
- Игнорируют динамический импорт. Если в коде есть вызов
GetProcAddress(LoadLibrary("helper.dll"), "Init")— функция не в экспортеplugin.dll, а вhelper.dll. Нужно анализировать оба файла. - Путают RVA и файловый смещение. RVA — это адрес в памяти. Файловое смещение — это позиция в файле. Чтобы перейти от RVA к смещению, нужно найти секцию, которая его содержит, и вычесть
VirtualAddress - PointerToRawData. Ошибка здесь — и ты ищешь функцию в другом месте. - Думают, что если функция не экспортируется — она не используется. Нет. Она может вызываться через указатель, через виртуальную таблицу (VMT), через динамическую загрузку. В C++-библиотеках это стандартная практика.
- Не проверяют ресурсы. Иногда скрытые функции — это не код, а встроенные скрипты или бинарные данные, которые загружаются и выполняются через
VirtualAlloc+memcpy. Проверяй секцию.rsrc.
Когда использовать какой инструмент
Ты не обязан использовать только один инструмент. Каждый имеет свои сильные стороны.
- CFF Explorer — лучший для быстрого просмотра PE-заголовка. Удобно смотреть структуру, RVA, размеры. Нет анализа кода — только метаданные.
- IDA Pro — если ты хочешь разобраться в логике. Позволяет переименовывать функции, строить графы вызовов, искать строки. Лучше всего для глубокого анализа.
- Radare2 / Ghidra — если ты работаешь в терминале или не можешь позволить себе IDA. Ghidra — бесплатный, мощный, но медленный. Radare2 — быстрый, но требует знания команд.
- PE-bear — отличный выбор для новичков. Интуитивный интерфейс, подсветка структур, экспорт в CSV. Не заменит IDA, но отлично подойдёт для первичного сканирования.
Мой рабочий путь: сначала — PE-bear или CFF Explorer. Смотрю, есть ли экспорты, размеры, секции. Потом — IDA. Если не могу открыть в IDA (слишком большой файл, защита) — Ghidra. Если нужно быстро — Radare2 с aaa и afl.
Сценарии выбора: что делать в разных ситуациях
Вот как действовать, если ты в той или иной ситуации:
- Ситуация: ты видишь, что программа вызывает неизвестную функцию, но в экспорте её нет.
→ Сначала ищи в .text секции все вызовыcallс адресами вне импорта. Ищи строки, связанные с этой функцией. Потом смотри, есть ли она в других .dll, которые загружаются динамически. - Ситуация: ты анализируешь лицензионный плагин, и он не работает без ключа.
→ Ищи в коде строки вроде"License key invalid"или"Activation required". Обрати внимание на функции, которые вызываются перед проверкой. Часто там есть скрытые функции вродеCheckLicenseInternal— они не экспортируются, но вызываются внутри. - Ситуация: ты хочешь понять, почему приложение не запускается на Windows 7, хотя на Windows 10 — работает.
→ Проверь, какие API вызываются. Если есть вызовы кWindows.FoundationилиWindows.UI— это UWP-функции, которых нет в Win7. Они могут быть вызваны черезLoadLibrary+GetProcAddress— и скрыты от обычного анализа. - Ситуация: ты нашёл подозрительную функцию, но не понимаешь, как она работает.
→ Не пытайся сразу понять логику. Сначала посмотри, какие библиотеки она импортирует. Потом — какие файлы/регистры она читает. Это даст тебе контекст. Потом — уже логику.
Как лучше делать — практические рекомендации
Вот что работает на практике:
- Всегда начинай с PE-заголовка. Это твой «план этажа». Без него ты блуждаешь в темноте.
- Сравни несколько версий .dll. Если у тебя есть старая и новая версия — сравни RVA экспорта, размеры секций, строки. Изменения — это твои подсказки.
- Ищи не только по именам, но и по паттернам. Например, если ты знаешь, что функция должна возвращать 64-битный хеш — ищи код, который делает
mov rax, [rsp+8]+retпосле сложных вычислений. Это часто скрытые хеширующие функции. - Запоминай адреса. Если ты нашёл функцию по RVA 0x1234 — запиши её. Потом в дебаггере ты сможешь поставить брейкпоинт на этот адрес — даже если она не экспортируется.
- Не бойся ручного анализа. Иногда автоматические анализаторы (типа Ghidra) «запутываются» в обфусцированном коде. Ручной просмотр 10–20 строк может дать больше, чем 2 часа автоматического анализа.
Что делать дальше
Ты нашёл скрытую функцию. Что дальше?
- Если это библиотека, которую ты разрабатываешь — добавь документацию.
- Если это чужой код — подумай: безопасно ли использовать эту функцию? Может, она не предназначена для внешнего вызова — и её поведение может измениться в следующей версии.
- Если ты анализируешь вредоносное ПО — запиши адрес, параметры, поведение. Это может быть ключ к обнаружению других вредоносных компонентов.
- Если ты хочешь использовать функцию в своём коде — не вызывай её напрямую. Напиши обёртку, которая проверяет наличие функции через
GetProcAddressи обрабатывает ошибки. Иначе твоя программа упадёт при обновлении.
Скрытые функции — это не тайна. Это просто неудобно спрятанная информация. Ты не взламываешь систему. Ты читаешь карту. И если ты знаешь, где искать — ты всегда найдёшь то, что нужно.
Начни с CFF Explorer. Открой .dll. Найди Optional Header. Посмотри на Export Table. Потом — на .text. Найди первую функцию, которая не в экспорте, но вызывается. Проверь её на соответствие критериям из таблицы. И сделай это сегодня. Не завтра. Потому что только практика превращает знания в навык.
Информация, представленная в этой статье, носит ознакомительный характер. Анализ исполняемых файлов может нарушать лицензионные соглашения или законы об авторском праве. Используй полученные знания только в рамках законных задач: отладки, исследований безопасности, обратной совместимости. Принимать решения о применении найденных функций следует с учётом юридических и этических норм.
