第七章 隐藏技术---DLL劫持
一、原理#
当一个可执行文件运行时,Windows加载器将可执行模块映射到进程的地址空间中,加载器分析可执行模块的输入表,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中。 由于输入表中只包含DLL名而没有它的路径名,因此加载程序必须在磁盘上搜索DLL文件。搜索DLL文件的顺序如下所示:
程序所在目录
系统目录
16位系统目录
Windows目录
当前目录
PATH环境变量中的各个目录
利用搜索路径的这个特点,先伪造一个与系统同名的DLL,提供同样的输出表,并使每个输出函数转向真正的系统DLL。程序调用系统DLL时会先调用当前程序所在目录下的伪造的DLL,完成相关功能后,再跳到系统DLL同名函数里执行。这个功能就是DLL劫持。
为了使程序在加载了劫持的DLL后还能正常执行,劫持DLL导出函数的名称和功能必须要与原来的DLL相同。可以有两种方式来调用原来DLL的导出函数。一种是直接转发DLL函数,另一种是调用DLL函数。
1、直接转发DLL函数#
在所有的预处理指令中,#Pragma指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。可以通过下面的指令完成函数转发的操作。
#pragma comment(linker,"/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]")
使用/EXPORT选项,可以从程序中导出函数,以便其他程序可以调用该函数,它也可以导出数据。其中,entryname是调用程序要使用的函数或数据项的名称。ordinal在导出表中指定范围在1~65535之间的索引;如果没有指定ordinal,则链接器将分配一个。NONAME关键字只将函数导出位序号,并且没有entryname。DATA关键字指定导出项为数据项,用户程序中的数据项必须用extern __declspec(dllimport)来声明。
例如,假设导出函数为MessageBoxATest,若直接转发user32.dll中的MessageBoxA导出函数,那么编译指令代码如下:
#pragma comment(linker,"/EXPORT:MessageBoxATest=user32.MessageBoxA")
当调用MessageBoxATest函数的时候,系统会将其直接转发给user32.dll模块中的MessageBoxA导出函数去执行。
2、调用DLL函数#
通过LoadLibrary和GetProcAddress函数来加载DLL并获取DLL的导出函数地址,然后跳转执行。
extern "C" void __declspec(naked) MessageBoxATest()
{
PVOID pAddr;
HMODULE hDll;
hDll = ::LoadLibrary("c:\\Windows\\System32\\user32.dll");
if(NULL!=hDll)
{
pAddr=::GetProcAdress(hDll,"MessageBoxA");
if(pAddr){
__ams jmp pAddr;
}
::FreeLibrary(hDll);
}
}
使用关键字__declspec(naked)告诉编译器不要对函数进行优化。注意naked特性仅适用于x86和ARM,并不用于x64。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端