DLL注入是把指定的DLL加载到另一个进程的内存空间中去。
DLL注入技术:1、通过远程线程注入
2、通过全局消息钩子注入
3、通过注册表注入
4、通过输入法注入
5、驱动注入
6、在启动进程时挂起主线程,写入指令后再恢复线程
7、DLL劫持
8、修改PE文件导入表
9、使用NtMapViewOfSection注入
10、使用SetThreadContext注入
通过远程线程注入:1、用OpenProcess打开要注入进程的句柄
2、用VirtualAllocEx在远程进程中申请一段内存,长度为DLL路径长度+1(多出来的一字节用于存放\0)
3、用WriteProcessMemory将DLL的路径远程写入申请的内存中
4、用CreateRemoteThread将LoadLibraryA作为线程启动函数,参数为DLL的路径,远程创建线程
5、用CloseHandle关闭线程句柄
6、调用DllMain函数,在函数里完成要做的事。
实现代码:
BOOL InjectDll(DWORD dwProcessID, char* dllPath){//参数:目标进程ID、DLL路径
FARPROC FuncAddr = NULL;
HMODULE hdll = LoadLibrary(TEXT("Kernel32.dll"));//加载DLL
if (hdll != NULL){
FuncAddr = GetProcAddress(hdll, "LoadLibraryA");//获取LoadLibraryA函数地址
if (FuncAddr == NULL)return FALSE;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION, FALSE, dwProcessID);//获取进程句柄
if (hProcess == NULL)return FALSE;
DWORD dwSize = strlen(dllPath) + 1;
LPVOID RemoteBuf = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);//远程申请内存
DWORD dwRealSize;
if (WriteProcessMemory(hProcess, RemoteBuf, dllPath, dwSize, &dwRealSize))//远程写内存
{
DWORD dwThreadId;
HANDLE hRemoteThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)FuncAddr, RemoteBuf, 0, &dwThreadId);//创建远程线程
if (hRemoteThread == NULL)
{
VirtualFreeEx(hProcess, RemoteBuf, dwSize, MEM_COMMIT);
CloseHandle(hProcess);
return FALSE;
}
//释放资源
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);
VirtualFreeEx(hProcess, RemoteBuf, dwSize, MEM_COMMIT);
CloseHandle(hProcess);
return TRUE;
}
else
{
VirtualFreeEx(hProcess, RemoteBuf, dwSize, MEM_COMMIT);
CloseHandle(hProcess);
return FALSE;
}
}
通过全局消息钩子注入
通过设置全局消息钩子来实现dll注入,然后窗体有相关消息请求会自动加载注入dll,在入口处做处理。
dll注入期间注入程序不可以退出,否则dll内核句柄有可能被释放
源码:
DLL调用
/*
HMODULE h = LoadLibrary(L"xx.dll");
_SetHook SetHook = (_SetHook)GetProcAddress(h,"SetHook");
_UnHook UnHook = (_UnHook)GetProcAddress(h,"UnHook");
SetHook();
Sleep(10000);
UnHook();
CloseHandle(h);
*/
//DLL相关代码
#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#pragma comment(lib,"psapi.lib")
#pragma data_seg("Yrrehs")
HHOOK HT = NULL;
#pragma data_seg()
HINSTANCE DLLhinst = NULL;
LRESULT CALLBACK CProc(int nCode,WPARAM wParam,LPARAM lParam){
return CallNextHookEx(HT,nCode,wParam,lParam);
}
//安装钩子
extern "C" __declspec(dllexport) BOOL SetHook(){
HT = SetWindowsHookEx(WH_CALLWNDPROC,CProc,DLLhinst,0);
if(HT == NULL){
return false;
}
return true;
}
//卸载钩子
extern "C" __declspec(dllexport) BOOL UnHook(){
BOOL HM_BOOL = FALSE;
if(HT != NULL){
HM_BOOL = UnhookWindowsHookEx(HT);
}
return HM_BOOL;
}
//获得进程名
wchar_t* GetProcessName(DWORD processID){
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,processID);
wchar_t *procName = new wchar_t[MAX_PATH];
GetModuleFileNameEx(hProcess,NULL,procName,MAX_PATH);
CloseHandle(hProcess);
return procName;
}
//获得进程名
wchar_t* GetProcessName(wchar_t *FileName){
size_t len = wcslen(FileName);
size_t i = len-1;
for(;i>=0;i--){
if(FileName[i] == L'\\'){
break;
}
}
wchar_t *temp = FileName + i + 1;
return temp;
}
BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReason,LPVOID lpvReserved){
DLLhinst = hinstDll;
if(DLL_PROCESS_ATTACH == fdwReason){
wchar_t *procName = GetProcessName(GetCurrentProcessId());
if(_wcsicmp(L"xxx.exe",GetProcessName(procName))==0){
//XXXXXX
}
}
if(DLL_PROCESS_DETACH == fdwReason){
}
return TRUE;
}
通过注册表注入
在系统中每一个进程加载User32.dll时,会受到DLL_PROCESS_ATTACH通知,当User32.dll对其进行处理时,会取得注册表键值HKEY_LOCAL_MACHINE\Software\Microsoft\windowsNT\CurrentVersion\Windows\AppInit_Dlls,并调用LoadLibrary来载入这个字符串指定的每个DLL。被调用的DLL会在系统调用它们的DllMain函数,并将参数fdwReason的值设为DLL_PROCESS_ATTACH时,对自己进行初始化。所以我们在这个键值中添加我们的Dll路径,即可实现注入。
注入流程:
1、打开注册表键值如下:HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\
2、在上面的注册表项中操作AppInit_DLLs键值,在该键值中添加自己的DLL的全路径加dll名,多个DLL以逗号或者空格分开(因此在文件名中应该尽量不要存在空格),该键值只有第一个dll文件名可以包含路径,后面的都不能包含,因此最好将dll放在系统路径,这样可以不用包含路径也能被加载。
3、在该注册表项中添加键值LoadAppInit_DLLs,类型为DWORD,并将其值置为1。
关于注册表操作的API:
RegOpenKeyEx 打开注册表键值
RegQueryValueEx 查询键值
RegSetValueEx 设置键值
RegCloseKey 关闭键值
源码:
//打开键值
nReg = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
m_szRegPath,
0,
KEY_ALL_ACCESS,
&hKey);
if(nReg != ERROR_SUCCESS)
{
return FALSE;
}
//查询键值
DWORD dwReadType;
DWORD dwReadCount;
TCHAR szReadBuff[1000] = {0};
nReg = RegQueryValueEx(hKey,
_T("AppInit_DLLs"),
NULL,
&dwReadType,
(BYTE*)&szReadBuff,
&dwReadCount);
if(nReg != ERROR_SUCCESS)
{
return FALSE;
}
//是否dll名称已经在内容中
tstring strCmpBuff;
strCmpBuff = szReadBuff;
if (!strCmpBuff.find(InjectFilePath))
{
return FALSE;
}
//有字符串就加入空格
if (0 != _tcscmp(szReadBuff,_T("")))
{
_tcscat_s(szReadBuff,_T(" "));
}
_tcscat_s(szReadBuff,InjectFilePath);
//把dll路径设置到注册表中
nReg = RegSetValueEx(hKey,
_T("AppInit_DLLs"),
0,
REG_SZ,
(CONST BYTE*)szReadBuff,
(_tcslen(szReadBuff)+1)*sizeof(TCHAR));
完成注册表注入之后,并不是希望所有程序都运行DLL里面的内容,需要在DLL中过滤窗口名称,让指定窗口名称的EXE文件运行DLL里的线程。所需API如下所示:
CreateThread 创建线程
Sleep 睡眠
EnumWindows 遍历窗口
GetWindowsText 得到窗口名称
GetCurrentProcessId 得到当前进程ID
GetWindowThreadProcessId 由HWND获得进程ID
为了实现此功能,需要在注入的DLL中创建线程,并在线程中执行遍历窗口函数,我们需要先获取窗口名称,与我们想运行的EXE名称进行对比,并进行进程ID对比,因为不光只有一个EXE文件的运行实例,经过这些过滤后,可以在指定的EXE文件中运行代码。
源码:
BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam)
{
TCHAR str[MAXBYTE] = {0};
//得到窗口名称
GetWindowText(hwnd,str,sizeof(str));
//是否名称是计算器
if(0 == _tcscmp(str,_T("计算器")))
{
//由于存在可能多个计算器,需要过滤线程ID
//得到本身线程的ID
DWORD dwCurrentProcessId = GetCurrentProcessId();
DWORD dwFindCurrentProcessId = 0;
//得到窗口线程ID
GetWindowThreadProcessId(hwnd,&dwFindCurrentProcessId);
//比较
if (dwCurrentProcessId == dwFindCurrentProcessId)
{
*(PDWORD)lParam = 1;
return FALSE;
}
}
return TRUE;
}
DWORD ThreadProc(LPVOID lParam)
{
//等待1秒时间以便于让windows创建窗口
Sleep(1000);
DWORD dwFind = 0;
//遍历窗口,过滤窗口名称
EnumWindows(lpEnumFunc,(LPARAM)&dwFind);
if (!dwFind) return 0;
// 运行代码
return 0;
}
BOOL InitInstance()
{
DWORD dwThreadId;
m_hThread = ::CreateThread(NULL, NULL,
(LPTHREAD_START_ROUTINE)ThreadProc,
this, NULL,&dwThreadId);
return TRUE;
}