Как определить, будет ли exe загружать DLL статически или динамически, посмотрев на заголовок PE-файла? - PullRequest
1 голос
/ 22 апреля 2020

Как видно из заголовка, как определить, будет ли exe загружать DLL статически или динамически, посмотрев на заголовок PE-файла?

Другими словами, как определить, является ли DLL частью исполняемого файла? , или будет вызываться загрузчиком?

Спасибо

1 Ответ

3 голосов
/ 22 апреля 2020

Позвольте мне сначала уточнить некоторые термины, чтобы избежать путаницы.

Все, что выполняется в DLL, по определению динамически c. Но DLL может быть статически или динамически привязана к исполняемому файлу.

При связывании stati c EXE связывается с библиотекой импорта DLL (фактически это файл .LIB, который создается вместе с DLL). Соответствующие прототипы функций DLL в заголовочных файлах обычно объявляются с помощью __declspe c (dllimport). Это приводит к тому, что EXE-файл заполняется заглушками для каждого символа DLL, которые заполняются загрузчиком Windows во время выполнения. Это облегчается тем, что конечный EXE имеет структуру раздела импорта в своих заголовках PE, в которой перечислены все библиотеки DLL, которые должны быть разрешены загрузчиком Windows во время выполнения, и их имена символов c (например, функции). Windows затем выполняет всю грязную работу, чтобы найти и загрузить эти библиотеки DLL и адреса символов, на которые ссылаются c, прежде чем EXE начнет выполнение основного потока в его точке входа. Если Windows не удается найти какие-либо библиотеки DLL или адреса символов, на которые ссылаются c, EXE не запустится.

При использовании привязки Dynami c EXE явно вызывает код для загрузки DLL (-ов). ) и разрешить символьные c адреса. Это делается с помощью двух функций API KERNEL32: LoadLibrary () и GetProcAddress (). Если это делает EXE, не будет никакого связанного раздела импорта, описывающего эту DLL и ее символы, и загрузчик Windows с радостью загрузит EXE, не зная ни одной указанной DLL. Затем в приложении определяется, как обрабатывать успех или неудачу LoadLibrary () и / или GetProcAddress ().

Стоит отметить, что в этот момент могут быть предоставлены такие библиотеки, как C -Runtime в форме DLL (динамическая c библиотека) или stati c форма (stati c библиотека). Если вы свяжетесь с одной из этих библиотек статически , в заголовке PE встроенного EXE-файла не будет раздела импорта DLL и не будет разрешено заглушки функций для выполнения во время выполнения для этой библиотеки. Вместо заглушек эти символы (функции и / или переменные данных) становятся частью EXE. Библиотечные функции и / или данные Stati c копируются в EXE и присваиваются компоновщику относительные адреса; ничем не отличается, если бы эти символы были реализованы непосредственно EXE-файлом. Кроме того, не будет никакого разрешения LoadLibrary () или GetProcAddress () ни явным образом (загрузчиком Windows), ни явно в коде для этих функций, поскольку они будут непосредственно присутствовать и содержаться в конечном EXE-файле. В качестве примечания можно использовать символы отладки в этом случае, чтобы попытаться различить реализованные функции EXE и реализованные функции библиотеки (если вас это волнует), но это сильно зависит от настроек, используемых для построения как EXE, так и состояния c библиотека.

С уточненной терминологией позвольте мне попытаться ответить на ваш вопрос! :) Позвольте мне также добавить, что я не собираюсь go описывать особенности связанных и несвязанных символов импорта для раздела импорта модуля, потому что это различие не имеет ничего общего с исходным вопросом и больше связано с ускорением работы. сделано загрузчиком Windows. Однако если вас интересуют эти подробности, вы можете прочитать в PE COFF спецификации Microsoft .

Чтобы узнать, является ли EXE статически связанным с DLL, вы Вы можете либо проанализировать PE-заголовки самостоятельно, чтобы найти раздел импорта DLL, либо использовать один из десятков инструментов, чтобы сделать это за вас, например, Dependency Walker . Например, если вы загрузите свой EXE-файл в Dependency Walker, вы увидите список всех статически связанных DLL-библиотек в верхней левой панели под самим EXE-файлом. Если какая-либо из этих DLL не найдена во время выполнения, программа не сможет загрузиться. В правой панели, верхней таблице, вы увидите символы (например, функции), на которые ссылаются в EXE для выбранной DLL. Все эти символы должны быть дополнительно найдены для загрузки EXE. Нижняя таблица просто показывает все символы, экспортируемые из DLL, на которые ссылаются или нет.

Если EXE-файл использует dynamici c привязку (также называемую ручная привязка ) для данной библиотеки DLL, для этой библиотеки DLL не будет раздела импорта и, следовательно, вы не увидите ссылки на такие инструменты, как Dependency Walker. НО , вы можете щелкнуть по KERNEL32.DLL в Dependency Walker (все EXE-файлы будут иметь эту зависимость, хотя есть исключения из этого правила, к которым я не буду обращаться здесь) и найти ссылки на LoadLibrary () и GetProcAddress (). К сожалению, большинство EXE-файлов ссылаются на эти функции в шаблонном коде, таком как C -Runtime, так что это не скажет вам слишком много.

Если вы хотите углубиться в попытку выяснить, какие библиотеки DLL загружаются вручную Приложение, первое, что нужно попробовать, это найти эту строку имени DLL, выполнив поиск в DLL по имени DLL. Обратите внимание, что строка имени DLL не обязательно должна заканчиваться на «.DLL», так как LoadLibrary () автоматически принимает это расширение, если оно не предусмотрено. Стандартным инструментом для поиска строк в двоичном модуле является Sysinternals Strings . Это прекрасно работает для модулей, которые не пытаются скрыть , что они делают.

С учетом сказанного, запутанный код (найденный в распаковщиках, вирусах и т. П.) Может запутывать или шифровать имена DLL а также функции, на которые ссылаются. Код может даже решить разрешить LoadLibrary () и GetProcAddress () во время выполнения, чтобы еще больше помешать попыткам выяснить, что они делают. Лучше всего в таких ситуациях использовать такой инструмент, как Sysinternals Process Monitor или отладчик с подробным ведением журнала, чтобы следить за загрузкой библиотек DLL во время работы программы. Вы также можете использовать дизассемблер (такой как IDA), чтобы попытаться собрать воедино то, что делает код. Чтобы выяснить, какие символы DLL используются, вы можете запустить EXE в отладчике и дождаться первоначального прерывания в точке входа. Затем добавьте точку останова в первой инструкции в KERNEL32.GetProcAddress. Запустите программу. При достижении этой точки останова аргументы стека будут содержать символ, пытающийся быть разрешенным.

Как вы можете видеть, если приложение разрешает символы DLL вручную (динамическое связывание c), процесс выяснения того, что Ссылки на библиотеки DLL не так просты.

...