Как найти скрытые функции в .dll-файлах через анализ PE-заголовка — практическое руководство

Как найти скрытые функции в .dll-файлах через анализ PE-заголовка — практическое руководство

Ты работаешь с чужим кодом. Может, это старый плагин, который никто не документировал. Или ты разбираешься в бинарнике, чтобы понять, почему программа ведёт себя странно. Ты открыл .dll-файл в Hex-редакторе — и ничего не понял. Или ты загрузил его в IDA Pro, а там куча импортов, но не хватает ключевой функции, которая, по логике, должна быть. Ты не один. Так бывает каждый день. И вот тут — не в коде, не в рантайме, а в самом заголовке файла — начинается настоящая охота.

PE-заголовок — это не просто «метаданные». Это карта, по которой Windows знает, где искать функции, какие библиотеки подгружать, где лежит код, а где — данные. И если кто-то спрятал функцию — она не исчезла. Она просто не в списке экспорта. А значит, её нужно искать не там, где все ищут.

Почему функции прячутся в .dll

Скрытые функции — это не всегда вредоносный код. Иногда это:

  • Отладочные утилиты, оставленные разработчиками и не удалённые из релиза;
  • Функции для внутреннего использования, которые не должны быть доступны сторонним приложениям;
  • Резервные интерфейсы, которые включаются при определённых условиях (например, при наличии лицензионного ключа);
  • Обфусцированные части кода, чтобы затруднить анализ;
  • Функции, которые были удалены из экспорта, но оставлены в коде для обратной совместимости.

Если ты видишь, что программа вызывает какую-то функцию — но её нет в списке экспорта — значит, она вызывается по адресу. А значит, её можно найти. И не через дебаггер, а через статический анализ PE-заголовка.

Что нужно знать о PE-заголовке

PE (Portable Executable) — это формат исполняемых файлов в Windows. Он состоит из нескольких частей:

  1. MS-DOS заголовок — устаревший, но обязателен. Служит для совместимости. Его можно игнорировать.
  2. PE-сигнатура — 4 байта: 50 45 00 00 («PE\0\0»). Это твой первый маркер, что файл — действительно PE.
  3. COFF Header — содержит информацию о архитектуре (x86, x64), количестве секций, времени сборки.
  4. Optional Header — ключевой блок. Здесь лежат указатели на важные структуры: таблицу экспорта, импорта, ресурсов, релокаций и т.д.
  5. Секции.text, .data, .rdata, .rsrc и другие. Код, данные, ресурсы.

Ты не будешь разбирать весь PE-файл. Тебя интересует только одно: директивы, которые указывают на расположение таблицы экспорта. Но не только её. Ты ищешь не только то, что экспортируется, а то, что есть.

Как найти скрытые функции — пошагово

Предположим, у тебя есть файл plugin.dll. Ты открыл его в Detect It Easy (или в PE-bear, или в CFF Explorer). Вот что ты делаешь:

  1. Найди Optional Header. В CFF Explorer — в левой панели: Optional HeaderData Directories. Там 16 записей. Тебя интересует запись №1: Export Table. Если её нет — файл не экспортирует функции вообще. Но это не значит, что их нет.
  2. Проверь RVA (Relative Virtual Address) экспорта. Допустим, там написано: 0x00002000. Это адрес относительно начала образа в памяти. Ты переходишь к секции, которая содержит этот адрес. Обычно это .rdata или .text.
  3. Посмотри на размер экспорта. Если он равен 0 — значит, экспорта нет. Но если он не нулевой, а ты всё равно не видишь нужную функцию — возможно, она удалена из списка, но осталась в коде.
  4. Проверь секцию .text. Это твой основной источник кода. Ты ищешь в ней все функции, которые начинаются с sub_ или func_ — но это не всё. Ты ищешь вызовы call к адресам, которые не входят в импорт.
  5. Найди все адреса, на которые есть ссылки. В IDA Pro — нажми Shift+F12, чтобы найти все строки. Ищи строки, похожие на названия функций: "GetSecretKey", "InternalDebug". Потом иди к этим строкам — и смотри, откуда к ним идут переходы. Часто скрытые функции вызываются через jmp [addr] или call [reg].
  6. Проверь таблицу импорта. Иногда функции не экспортируются, но импортируются из других .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. Найди первую функцию, которая не в экспорте, но вызывается. Проверь её на соответствие критериям из таблицы. И сделай это сегодня. Не завтра. Потому что только практика превращает знания в навык.

Информация, представленная в этой статье, носит ознакомительный характер. Анализ исполняемых файлов может нарушать лицензионные соглашения или законы об авторском праве. Используй полученные знания только в рамках законных задач: отладки, исследований безопасности, обратной совместимости. Принимать решения о применении найденных функций следует с учётом юридических и этических норм.

Оцените статью
PEFile — Безопасность и технологии простым языком