我想制作自己的 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(但在允许的范围内)
启动程序时,我会说有一个“最小包”的库。尽管您调用
LoadLibraryA了 ,系统仍会为您提供已预加载的库的地址。那些。如果你打了两次电话,LoadLibraryA库将被加载到相同的地址。我想指出几点:
那些。即使你理论上加载了三个或四个重要的库 kernel32.dll user32.dll gdi32.dll ntdll.dll(它们总是在内存中)——它们将 a)根据它们的图像库定位;b)将加载十几个依赖库。由于系统库是先加载的,所以它们会占用自己的稳定地址,而其他人会缺少这些地址。
进一步..我会推荐这样的算法,首先调用
GetModuleHandleA,如果这个函数返回地址-这意味着库已经加载,不能再次加载(下面的例外)。对于系统库(以及可执行模块的导入部分的库) - 该功能将毫无问题地工作。如果您“嵌入” - 那么我建议纯粹调用此函数进行验证。LoadLibraryA与 不同,函数GetModuleHandleA增加了库使用计数器,即 如果您的程序或并行“线程”(线程)进行调用FreeLibrary- 那么在 c 的情况下LoadLibraryA- 库将不会被卸载,因为 将有一个额外的标签,它被使用,因为GetModuleHandleA- 不会有这样的行为。您可以使用一对而不是LoadLibrary一FreeLibrary对GetModuleHandleA/LoadLibraryA如果不需要卸载库。我没有注意到有人卸载了库,我认为这仅适用于插件库。PS 地址空间的稳定性充满了病毒通过注入带有指向稳定定位系统函数的链接的原始代码进行渗透的可能性。因此,在 Windows 操作系统中,从 vista 开始(“六”,无需安装额外的包,在早期的有条件的 XP 中可以),已经有ASLR技术允许您随机化加载库的地址空间。我没有深入研究,我不能说它有多有效。到目前为止(2020 年),该技术不适用于 windows 系统库。系统库的地址是专门选择的,以便内核可以有效地适应内存,也许这就是这种行为的原因。通过设置dynamicbase标志为新程序启用 ASLR 。并且在 VS2019 中默认启用。对于较旧的程序,ASLR 被禁用以实现向后兼容性。也许将来 ASLR 会更有效地工作。