前言
注入技术可以方便的对目标进程空间进行修改,将shellcode和dll注入到目标进程执行。dll注入使用最广泛。因为dll 不需要像shellcode一样需要获取kernel32.dll加载基址并根据导出表获取导出函数的地址。如果dll注入成功,则表示dll已经成功加载到目标进程空间中,导入表、导出表、重定位表均已经修改完毕,dll中的代码可以正常执行,正式由于的力量的简单易用,一些特殊程序常常使用这种技术。
一、常见的dll注入技术
1.全局钩子注入
2.远程线程钩子(利用CreateRemoteThread和LoadLibarary函数的相似性)
3.突破Session 0隔离的远程线程注入(利用底层函数ZwCreateThreadEx)
4.利用APC的机制
二、使用步骤
1.全局钩子注入
代码如下(示例):
实现过程
为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子。因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动的,所以所有进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL。
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
设置WH_GETMESSAGE全局钩子具体实现的代码如下所示。
// 设置全局钩子
BOOL SetGlobalHook()
{
g_hHook =:SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (NULL == g_hHook)
{
return FALSE;
}
return TRUE;
}
// 钩子回调函数
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return:CallNextHookEx(g_hHook, code, wParam, lParam);
}
// 卸载钩子
BOOL UnsetGlobalHook()
{
if (g_hHook)
{
:UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}
2.远程线程注入
实现过程
// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char *pszDllFileName)
{
HANDLE hProcess = NULL;
DWORD dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
// 打开注入进程,获取进程句柄
hProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
return FALSE;
}
// 在注入进程中申请内存
dwSize = 1 +:lstrlen(pszDllFileName);
pDllAddr =VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr)
{
ShowError("VirtualAllocEx");
return FALSE;
}
// 向申请的内存中写入数据
if (FALSE ==WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr =GetProcAddress(:GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA");
return FALSE;
}
// 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
HANDLE hRemoteThread =CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) pFuncProcAddr, pDllAddr, 0, NULL);
if (NULL == hRemoteThread)
{
ShowError("CreateRemoteThread");
return FALSE;
}
// 关闭句柄
:CloseHandle(hProcess);
return TRUE;
}
注意:注意,如果注入失败的话,那么可以尝试以管理员身份运行程序。由于OpenProcess函数的缘故,在打开高权限进程时,程序会因权限不足而无法打开进程,获取进程句柄。如果对一些系统进程进行注入测试,就会发现一个问题,即不能成功注入到一些系统服务进程。这是由于系统存在SESSION 0隔离的安全机制,传统的远线程注入DLL方法并不能突破SESSION 0隔离。接下来,继续介绍突破 SESSION 0 隔离的远线程注入,向系统服务进程注入DLL。
3.突破Session 0隔离的远程线程注入(利用ZwCreateThreadEx函数)
ZwCreateThreadEx函数可以突破SESSION 0隔离,将DLL成功注入到SESSION 0隔离的系统服务进程中。其中,由于ZwCreateThreadEx在ntdll.dll中并没有声明,所以需要使用GetProcAddress从ntdll.dll中获取该函数的导出地址。
// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char *pszDllFileName)
{
// 打开注入进程,获取进程句柄(略)
// 在注入进程中申请内存(略)
// 向申请的内存写入数据(略)
// 加载 ntdll.dll
HMODULE hNtdllDll =LoadLibrary("ntdll.dll");
if (NULL == hNtdllDll)
{
ShowError("LoadLirbrary");
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr =GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA");
return FALSE;
}
// 获取ZwCreateThreadEx函数地址
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx):GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL == ZwCreateThreadEx)
{
ShowError("GetProcAddress_ZwCreateThread");
return FALSE;
}
// 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
ShowError("ZwCreateThreadEx");
return FALSE;}
// 关闭句柄(略)
return TRUE;
}
注意:如果在DLL中想通过调用MessageBox弹窗提示来判断是否DLL注入成功,那么就会大失所望了。由于会话隔离,在系统服务程序里不能显示程序窗体,也不能用常规方式创建用户进程。通常情况下,可以通过查看进程加载模块的信息(包括模块名称、模块路径等),来判断出进程中是否存在可疑模块。
4.APC注入
代码如下:
实现过程
APC注入的具体实现代码如下所示。
// APC注入
BOOL ApcInjectDll(char *pszProcessName, char *pszDllName)
{
// 变量 (略)
do
{
// 根据进程名称获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (0 >= dwProcessId)
{
bRet = FALSE;
break;
}
// 根据PID获取所有相应的线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (FALSE == bRet)
{
bRet = FALSE;
break;
}
// 打开注入进程
hProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}
// 在注入进程空间申请内存
pBaseAddress =VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pBaseAddress)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}
// 向申请的空间中写入DLL路径数据
WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
if (dwRet!= dwDllPathLen)
{
ShowError("WriteProcessMemory");
bRet = FALSE;
break;
}
// 获取 LoadLibraryA 地址
pLoadLibraryAFunc =GetProcAddress(:GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc)
{
ShowError("GetProcessAddress");
bRet = FALSE;
break;
}
// 遍历线程, 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
// 打开线程
hThread =OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread)
{// 插入APC
:QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
// 关闭线程句柄
CloseHandle(hThread);
hThread = NULL;
}
}
bRet = TRUE;
} while (FALSE);
// 释放内存 (略)
return bRet;
}
注意:APC注入原理是利用当线程被唤醒时APC中的注册函数会执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。为了增加APC执行的可能性,应向目标进程中所有的线程都插入APC。
如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,则可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃的问题。
总结
与之前介绍的远线程注入类似,注入操作通常实现的是加载DLL的功能,因此可以通过查看进程加载模块的信息,来判断进程是否注入到了其他模块。