DLL注入
DLL注入指的是向其他进程插入DLL文件,是通过强制让其他进程调用LoadLibrary()API实现的. DLL注入和一般的DLL加载的区别在于,目标是其它进程还是自身进程.
当DLL被加载到进程后,会自动调用DllMain(),可以通过往DllMain()函数写入代码来实现相应的功能.
DLL注入的方式有以下三种
-
windows消息勾取
-
远程线程注入
-
使用注册表
下面看一下这三种注入方式的原理,实现以及调试方法.
Windows消息勾取
Windows消息勾取的原理
Windows是消息驱动系统,可以通过发送消息,处理消息来实现不同的功能.消息发送的过程大致可以分为以下几步.
graph TD
触发事件 --> 封装消息 --> 系统消息队列 --> 应用程序消息队列 --> 应用程序 --> 应用程序处理函数
钩子可以用来偷看或截取消息,设钩则是在的流程中设置一个中间点,信息都会先通过这里.
Windows消息勾取是在 系统消息队列--->应用程序消息队列 设置钩子,而消息钩子会提前于应用程序得到消息.对于特定的消息,会先将钩子处理函数所在的DLL注入到对应的进程中,并调用钩子处理函数对消息进行处理.
graph TD
系统消息队列 --> 钩子1 --> 钩子2 --> ... --> 应用程序消息队列
多个钩子组成的链条被称为沟链.通过调用SetWindowsHookEx()API即可设置需要的钩子.
Windows消息钩子的实现
DLL库
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
当DLL被加载到进程中,会先调用DllMain(),并且dwReason为DLL_PROCESS_ATTACH,根据switch分支语句,将DLL的句柄赋给全局变量g_hInstance.
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD,//键盘钩子
KeybordProc,//处理函数地址
g_hInstance, //DLL句柄
0);//目标是所有进程
}
这里在导出函数HookStart()中调用SetWindowsHookEx()API,主要是为了方便.
LRESULT CALLBACK KeybordProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szPath[MAX_PATH] = { 0 };//文件路径
CHAR* p = nullptr;
if (nCode >= 0)
{
if (!(lParam & 0x80000000))//释放键盘按键时
{
GetModuleFileNameA(nullptr, szPath, MAX_PATH);
//获取当前进程加载模块的文件的完整路径
p = strrchr(szPath, '\\');//获得进程名称
//比较进程名称
if (!strcmp(DEF_PROCESS_NAME, p+1))
{
return 1;//不将消息传递给应用程序
}
}
}
//如果不是notepad.exe或者nCode<0,则将消息传递给下一个钩子或者应用程序
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
KeyBoardProc是回调函数,当勾取到消息的时候,系统会自动调用这个函数.
HookMain
//定义的名称
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
//定义的类型
typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();
int main()
{
HMODULE hDll = nullptr;//dll句柄
PFN_HOOKSTART HookStart = nullptr;//开始勾取函数
PFN_HOOKSTOP HookStop = nullptr;//停止勾取函数
//加载DLL
hDll = LoadLibraryA(DEF_DLL_NAME);
//获得导出函数地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
//开始勾取
HookStart();
//等待结束
printf("press 'q' to quit\n");
while (getchar() != 'q');
//停止勾取
HookStop();
return 0;
}
Windows消息勾取的调试
- 打开OD,加载notepad.exe并运行.
- 打开HookMain.exe,设置钩子.
- 点击Options选项中的Debugging options,在Events中选中Break On New Module(DLL),即在加载新的DLL时暂停.
- 在打开的记事本中使用键盘输入
- KeyHook.dll被注入到notepad.exe的进程中
- 在View选项中选择Executable modules,这样就可以看到被注入的dll
- 要注意在加载KeyHook.dll时,可能会先加载其他的dll,此时只要按f9就可以了
远程线程注入
远程线程注入的原理
远程线程注入简单的说,就是调用CreateRemoteThread(),在其他的进程中创建一条线程,执行LoadLibrary(),将特定的DLL加载到进程中间.
远程线程注入的实现
int main(int argc,char* argv[])
{
if (argc != 3)
return 1;
if (InjectDll((DWORD)atol(argv[1]),argv[2]))
printf("Success!!\n");
else
printf("Failed!!\n");
return 0;
}
通过命令行参数获得进程的PID和要在注入的dll路径.(注意dll路径要写完整,要不然目标进程无法找到dll,即使显示注入成功,实际dll并没有注入)
//获取目标进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
if (hProcess == nullptr)
{
return FALSE;
}
通过传入的PID,调用OpenProcess()API,得到进程的句柄,以便之后用来操作进程.
//在目标进程中分配空间
dwBufSize = (strlen(szDllPath)+1);
lpRemoteBuf = VirtualAllocEx(hProcess, nullptr, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
//在分配的空间中写入dll路径
WriteProcessMemory(hProcess, lpRemoteBuf, (LPCVOID)szDllPath, dwBufSize, nullptr);
在目标进程中分配空间,并将dll路径写.注意这边是对目标进程进行操作.
//获得LoadLibrary地址
hMod = GetModuleHandleA("kernel32.dll");
lpThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
因为kernel32.dll在进程中的地址都是相同的,因此LoadLibrary()API的地址也是相同的,在原进程中获得的API地址等于目标进程的地址.
//创建远程线程
CreateRemoteThread(
hProcess,
nullptr,
0,
lpThreadProc,//线程入口地址
lpRemoteBuf, //参数
0,
nullptr
);
这里先看一下LoadLibrary()和ThreadProc()的定义:
HMODULE LoadLibrary(
LPCSTR lpLibFileName
);
//返回值为4字节的dll模块句柄
//参数为指针类型
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
//返回值为4字节的退出码
//参数为指针类型
由此可见,LoadLibrary()和ThreadProc()的结构几乎是完全一样的,所以可以将LoadLibrary()API当作线程处理函数,当作CreateRemoteThread()的第四个参数传递, 第五个参数也同时变为dll的路径.
远程线程注入的调试
调试方法于Windows消息勾取的调试方法相同,可以查看之前写的内容.
注册表注入
注册表注入的实现和原理
打开注册表编辑器regedit.exe,进入以下路径
将要注入的DLL路径写入AppInit_DLLs,并且将LoadAppInit_DLLs的值设为1.重启后,指定的DLL就会被注入到进程程中.
原因是:当进程加载User32.dll时,会读取AppInit_DLLs和LoadAppInit_DLLs,若有值,就会调用LoadLibrary()加载指定的DLL.
以上内容来自于阅读逆向工程核心原理第21和23章的内容,为了整理内容和实践书中的代码就写下了这篇博客.