Совпадают ли адреса WinApi функций в разных PE?
Я хочу сделать свой загрузчик dll,который позволит загружать dll в процесс из массива байтов,но нашел готовый вариант.И мне не понятна одна деталь.Обо все по порядку..
Первым делом мы проецируем все секции в целевой процесс в соответствии с выравниванием.
Вторым делом создаем область памяти в целевом процессе в которую запишем данные,необходимые для запуска dll из памяти и функцию которая будет заполнять таблицу импорта,релокации и которую мы в свою очередь запустим в отдельном потоке.
Собственно сам загрузчик:https://github.com/BenjaminSoelberg/ReflectivePELoader .
А момент который мне не понятен,когда мы заполняем структуру loaderdata ,мы, в поля указателей на функции передаем адреса функций из программы,которой загружаем dll:
typedef HMODULE(__stdcall* pLoadLibraryA)(LPCSTR);
typedef FARPROC(__stdcall* pGetProcAddress)(HMODULE, LPCSTR);
LoaderParams.fnLoadLibraryA = LoadLibraryA;
LoaderParams.fnGetProcAddress = GetProcAddress;
Ведь адреса загрузки этих функций,не всегда будут одинаковые в нашем загрузчике и целевом процессе.Библиотеки могут быть в разных процессах загружены по разным адресам.Тем не менее данный загрузчик работает как часики на x32 программах.
UPD. Решил покопаться в отладчике,и правда,пока все библиотеки что сравнивал имеют одинаковый адрес загрузки.Как x32 так и x64(но не x64 с x32).Неужели они всегда одинаковые?Насколько я знал dll может загрузиться по произвольному адресу(но в пределах допустимого)
Ответы (1 шт):
При запуске программы, я бы сказал что существует "минимальный пакет" библиотек. Несмотря на то, что вы вызвали LoadLibraryA, вам всёравно система отдаёт адрес той библиотеки, которая уже предзагружена. Т.е. если вы сделаете два раза вызов LoadLibraryA - библиотека загрузится по одинаковому адресу.
Дальше я бы отметил несколько особенностей:
- У каждой библиотеки или модуля есть "рекомендуемый адрес загрузки" - называется image base (в PE-coff заголовке). При первой загрузке библиотеки, она загружается именно по этому адресу, если он не занят другой библиотекой или модулем. Именно это приводит к "стабильности" адресов библиотек. При одинаковой последовательности загрузки библиотек - они окажутся по одинаковым адресам.
- Есть "верхняя область" для загрузки системный библиотек.
- Библиотека может подгрузить ещё библиотеки которые ей нужны, в процессе инициализации (могут как динамическим связыванием так и програмно)
- Некоторые драйвера могут внедрять свою библиотеку в каждый запущенный процесс
- У каждого процесса своё адресное пространство, называется виртуальная память, память адресных пространств не пересекается (не конфликтует, пересекатся может - но этим управляет ОС и вы с прикладного уровня это не можете проверить), несистемные библиотеки могут быть загружены по разным адресам для разных программ.
Т.е. даже если теоретически загрузить три-четыре важных библиотеки kernel32.dll user32.dll gdi32.dll ntdll.dll (они всегда есть в памяти) - то они а) будут расположены по своему image base; б)подгрузят ещё с десяток зависимых библиотек. Так как системные библиотеки грузятся первыми - то займут стабильно-свои адреса, которые недостануться другим.
Дальше.. Я бы рекомендовал такой алгоритм, вначале делаете вызов GetModuleHandleA, если эта функция вернула адрес - это будет означать что библиотека уже загружена, и её можно негрузить повторно (исключения ниже). Для системных библиотек (А так же для библиотек import секции вашего исполняемого модуля) - функция будет работать без проблем. Если вы "внедряетесь" - тогда я эту функцию рекомендую вызывать чисто для проверки. Функция LoadLibraryA, в отличие от GetModuleHandleA, увеличивает счётчик использования библиотек, т.е. если ваша программа или паралельная "нить" (thread) сделает вызов FreeLibrary - то в случае с LoadLibraryA - библиотека не будет выгружена, т.к. будет стоять дополнительно метка что она используется, для GetModuleHandleA - такого поведения не будет. Можно вместо пары LoadLibrary-FreeLibrary использовать пару GetModuleHandleA/LoadLibraryA если выгрузка библиотеки не нужна. Я незамечал что бы библиотеки кто-то выгружал, думаю это касается исключительно библиотек-плагинов.
P.S. Стабильность адресного пространства чревата возможностью проникновения вирусов c помощью внедрения сырого кода с ссылкой на стабильно-расположеные системные функции. Поэтому в ОС Windows начиная с vista ("шестёрка", без установки доп пакетов, в более ранних можно с XP условно) уже существует технология ASLR позволяющая рандомизировать адресное пространство загруженых библиотек. Не изучал досконально, не могу сказать на сколько она эффективна. Пока что (2020 год) на системные библиотеки windows эта технология не распространяется. Адреса системных библиотек специально подобраны так, что бы ядро эффективно поместилось в памяти, возможно это причина такого поведения. ASLR активируется для новых программ установкой флага dynamicbase и по-умолчанию задействована в VS2019. Для старых программ ASLR отключена для обратной совместимости. Возможно в будущем ASLR будет работать более эффективно.