这次实验是在WIN7 X86系统上进程,使用的编译器是VS2017。
所谓的DLL注入,其实就是在其他的进程中把我们编写的DLL加载进去。如下图所示:
而加载Dll的API就是LoadLibrary,它的参数是保存要加载的DLL的路径的地址。所以DLL注入的核心就是把要注入的DLL的路径写到目标进程中,然后在目标进程中调用LoadLibrary函数,并且指定参数为保存了DLL路径的地址。
要实现DLL注入,首先就要创建一个用来注入的DLL。在VS2017中要生成一个DLL项目,只需要向下图这样创建一个DLL工程就好。
在生成的文件中,有个dllmain.cpp,打开以后内容如下: 当DLL的状态发生变化的时候,就会调用DllMain函数。而传递的ul_reason_for_call这个参数代表了4种不同的状态变化的情况,我们就可以根据这四种不同的状态根据需要来写出相应的代码,就会让注入的DLL执行我们需要的功能。
ul_reason_for_call的值 | 代表的状态 |
---|---|
DLL_PROCESS_ATTACH | Dll刚刚映射到进程空间中 |
DLL_THREAD_ATTACH | 进程中有新线程创建 |
DLL_THREAD_DETACH | 进程中有新线程销毁 |
DLL_PROCESS_DETACH | Dll从进程空间中接触映射 |
不过在实现DLL注入的时候用的DLL几乎都是在Dll刚刚映射到进程空间的时候就执行相关的代码。比如像下面这样,创建一个新线程来执行代码,这里在桌面打开一个文件来并写入加载这个DLL的进程的完成路径名。由于是独占方式打开,此时如果多个线程同时打开这个文件,CreateFile就会出错,错误码就会是32,根据这个来对线程进行休眠,等其他线程使用完了,再次打开文件进行操作。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>
#include <Shlobj.h>
#pragma comment(lib, "shell32.lib")
#define FILE_NAME "result.txt"
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
HANDLE hFile = NULL;
CHAR szDesktopFile[MAX_PATH] = { 0 }; //保存系统桌面路径
CHAR szFullFilePath[MAX_PATH] = { 0 }; //保存完成的加载DLL文件的文件路径
DWORD dwRetLen = 0, dwFileLen = 0;
BOOL bRet = TRUE;
//获取桌面路径
bRet = SHGetSpecialFolderPath(NULL, szDesktopFile, CSIDL_DESKTOP, TRUE);
if (bRet)
{
strcat(szDesktopFile, "\\");
strcat(szDesktopFile, FILE_NAME);
while (TRUE)
{
hFile = CreateFile( szDesktopFile,
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) //打开文件错误
{
if (GetLastError() == 32) //错误码是不是其他进程正在使用这个文件,是的话等待一会在继续打开
{
Sleep(200);
continue;
}
else break;
}
else
{
GetModuleFileName(NULL, szFullFilePath, MAX_PATH); //获取加载DLL的进程的完整路径
dwFileLen = strlen(szFullFilePath);
szFullFilePath[dwFileLen] = '\r'; //由于是在WIN7运行,换行符是\r\n
szFullFilePath[dwFileLen + 1] = '\n';
SetFilePointer(hFile, 0, NULL, FILE_END);
WriteFile(hFile, szFullFilePath, dwFileLen + 2, &dwRetLen, NULL);
if (hFile) CloseHandle(hFile);
break;
}
}
}
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
if (hThread) CloseHandle(hThread);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
点击生成解决方案以后就可以在项目目录下找到相应的DLL文件,如下图。这个文件就是用来注入到其他进程的DLL。
由于要编写的代码中,只有注入功能不同,但是其他的辅助功能。比如,提权,获取进程PID等等是一样的,为了避免重复就先在这给出代码的框架。后面的不同注入技术只需根据需要加进去就好。注意,如果想要提权成功,需要用管理员权限运行代码。
#include <cstdio>
#include <Windows.h>
#include <TlHelp32.h>
#define PROCESS_NAME "taskmgr.exe" //要注入的进程名,这个是任务管理器的进程名
#define DLL_NAME "InjectDll.dll" //要注入的DLL的名称
BOOL InjectDll(DWORD dwPid, CHAR szDllName[]); //注入DLL
DWORD GetPID(PCHAR pProName); //根据进程名获取PID
VOID ShowError(PCHAR msg); //打印错误信息
BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName); //提升进程权限
int main()
{
CHAR szDllPath[MAX_PATH] = { 0 }; //保存要注入的DLL的路径
DWORD dwPID = 0; //保存要注入的进程的PID
// 提升当前进程令牌权限
if (!EnbalePrivileges(GetCurrentProcess(), SE_DEBUG_NAME))
{
printf("权限提升失败\n");
}
dwPID = GetPID(PROCESS_NAME);
if (dwPID == 0)
{
printf("没有找到要注入的进程\n");
goto exit;
}
GetCurrentDirectory(MAX_PATH, szDllPath); //获取程序的目录
strcat(szDllPath, "\\");
strcat(szDllPath, DLL_NAME); //与DLL名字拼接得到DLL的完整路径
printf("要注入的进程名:%s PID:%d\n", PROCESS_NAME, dwPID);
printf("要注入的DLL的完整路径%s\n", szDllPath);
if (InjectDll(dwPID, szDllPath))
{
printf("Dll注入成功\n");
}
exit:
system("pause");
return 0;
}
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
return bRet;
}
DWORD GetPID(PCHAR pProName)
{
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bRet = FALSE;
DWORD dwPID = 0;
if (hSnap == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot process %d\n", GetLastError());
goto exit;
}
pe32.dwSize = sizeof(pe32);
bRet = Process32First(hSnap, &pe32);
while (bRet)
{
if (lstrcmp(pe32.szExeFile, pProName) == 0)
{
dwPID = pe32.th32ProcessID;
break;
}
bRet = Process32Next(hSnap, &pe32);
}
CloseHandle(hSnap);
exit:
return dwPID;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, GetLastError());
}
BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken))
{
ShowError("OpenProcessToken");
goto exit;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
if (!LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue))
{
ShowError("LookupPrivilegeValue");
goto exit;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL))
{
ShowError("AdjustTokenPrivileges");
goto exit;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
bRet = TRUE;
goto exit;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
ShowError("ERROR_NOT_ALL_ASSIGNED");
goto exit;
}
}
exit:
return bRet;
}
这种注入方式可以说是最常用的注入方式了,它的核心就是调用Windows提供的CreateRemoteThread函数。该函数可以在其他的进程空间中创建一个新的线程进行执行,该函数在文档中的定义如下:-
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId);
参数 | 说明 |
---|---|
hProcess | 要创建线程的进程句柄 |
lpThreadAttributes | 新线程的安全描述符 |
dwStackSize | 堆栈起始大小,为0表示默认大小 |
lpStartAddress | 表示要运行线程的起始地址 |
lpParameter | 保存要传递给线程参数的地址 |
dwCreationFlags | 控制线程创建的标志,为0表示创建后立即执行 |
lpThreadId | 指向接收线程标识符变量的指针。为NULL表示不返回线程标识符 |
其中的关键三个参数分别是:
(1)hProcess用来指定在哪个进程中创建新线程。
(2)lpStartAddress用来指定将进程中的哪个地址开始作为新线程运行的起始地址。
(3)lpParameter保存的也是一个地址,这个地址中保存的就是新线程要用到的参数。
那也就是说只要我们指定了一个地址给lpStartAddress,那么我们就可以在其他进程中创建一个线程来执行程序。而再看加载DLL的LoadLibrary函数在文档中的定义如下:
HMODULE WINAPI LoadLibrary(__in LPCTSTR lpFileName);
可以看到,这个函数同样也只需要一个参数,这个参数是一个地址,而这个地址中保存的是我们要加载的DLL的名称的字符串。
根据这些,不难想到,只要我们可以获取新进程中的LoadLibrary函数的地址以及包含有要加载的DLL的字符串的地址就可以通过CreateRemoteThread函数来成功开起一个线程执行LoadLibrary函数来加载我们的DLL。
那么现在的问题就是如何获得LoadLibrary函数的地址以及保存有要加载的DLL路径的字符串的地址。
对于LoadLibrary函数,由于它是在常用的系统DLL,也就是KERNEL32.dll中,所以这个DLL是可以按照它的ImageBase成功装载到每个进程的空间中。这样的话Kernel32.dll在每个进程中的起始地址是一样的,那么LoadLibrary函数的地址也就会一样。那么我们就可以在本进程中查找LoadLibrary函数的地址,并且完全可以相信,在要注入DLL的进程中LoadLibrary的地址也是这个。
至于DLL名称的字符串,我们可以通过在进程中申请一块可以将DLL完整路径写入的内存,并在这个内存中将DLL的完整路径写入,将写入到注入进程DLL完整路径的内存地址作为参数就可以实现进程的注入。
具体代码如下:
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
HANDLE hProcess = NULL, hRemoteThread = NULL;
HMODULE hKernel32 = NULL;
DWORD dwSize = 0;
LPVOID pDllPathAddr = NULL;
PVOID pLoadLibraryAddr = NULL;
// 打开注入进程,获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
goto exit;
}
// 在注入进程中申请可以容纳DLL完成路径名的内存空间
dwSize = 1 + strlen(szDllName);
pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (!pDllPathAddr)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
goto exit;
}
// 把DLL完整路径名写入进程中
if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
bRet = FALSE;
goto exit;
}
hKernel32 = LoadLibrary("kernel32.dll");
if (hKernel32 == NULL)
{
ShowError("LoadLibrary");
bRet = FALSE;
goto exit;
}
// 获取LoadLibraryA函数地址
pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibraryAddr == NULL)
{
ShowError("GetProcAddress ");
bRet = FALSE;
goto exit;
}
//创建远程线程进行DLL注入
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pLoadLibraryAddr,
pDllPathAddr, 0, NULL);
if (hRemoteThread == NULL)
{
ShowError("CreateRemoteThread");
bRet = FALSE;
goto exit;
}
exit:
if (hKernel32) FreeLibrary(hKernel32);
if (hProcess) CloseHandle(hProcess);
if (hRemoteThread) CloseHandle(hRemoteThread);
return bRet;
}
上面的方法虽然可以方便的注入DLL,但是在WIN7、WIN10系统上,会由于SESSION 0隔离机制从而导致只能成功注入普通的用户进程,如果注入系统进程就会导致失败。
而经过逆向分析发现,使用Kernel32.dll中的CreateRemoteThread进行注入的时候,程序会走到ntdll.dll中的ZwCreateThreadEx函数进行执行。这是一个未导出的函数,所以需要手动获取函数地址来进行调用,相比于CreateRemoteThread更加底层。这个函数在64位和32位系统中的函数声明也不相同,在32位中的声明如下:
typedef DWORD(WINAPI *pFnZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
而在64位中的声明如下:-
typedef DWORD(WINAPI *pFnZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
根据逆向分析的结果,在内核6.0(WIN7, WIN10)等系统上调用CreateRemoteThread的时候,当程序走到ZwCreateThreaEx的时候它第7个参数,也就是CreateThreadFlags会被设置为1,如下图:
它会导致线程创建的时候就被挂起,随后查看要运行的进程所在的会话层之后再决定是否要恢复线程的运行。所以要破解这种情况只需要将第7个参数设为0就可以,相应代码如下:
typedef DWORD(WINAPI *pFnZwCreateThreadEx)(PHANDLE, ACCESS_MASK, LPVOID,
HANDLE, LPTHREAD_START_ROUTINE,
LPVOID, BOOL, DWORD, DWORD, DWORD, LPVOID);
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
HANDLE hProcess = NULL, hRemoteThread = NULL;
HMODULE hKernel32 = NULL, hNtDll = NULL;
DWORD dwSize = 0;
LPVOID pDllPathAddr = NULL;
PVOID pLoadLibraryAddr = NULL;
pFnZwCreateThreadEx ZwCreateThreadEx = NULL;
// 打开注入进程,获取进程句柄
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
goto exit;
}
// 在注入进程中申请可以容纳DLL完成路径名的内存空间
dwSize = 1 + strlen(szDllName);
pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (!pDllPathAddr)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
goto exit;
}
// 把DLL完成路径名写入进程中
if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
bRet = FALSE;
goto exit;
}
hKernel32 = LoadLibrary("kernel32.dll");
if (hKernel32 == NULL)
{
ShowError("LoadLibrary kernel32");
bRet = FALSE;
goto exit;
}
// 获取LoadLibraryA函数地址
pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibraryAddr == NULL)
{
ShowError("GetProcAddress LoadLibraryA");
bRet = FALSE;
goto exit;
}
hNtDll = LoadLibrary("ntdll.dll");
if (hNtDll == NULL)
{
ShowError("LoadLibrary ntdll");
bRet = FALSE;
goto exit;
}
ZwCreateThreadEx = (pFnZwCreateThreadEx)GetProcAddress(hNtDll, "ZwCreateThreadEx");
if (!ZwCreateThreadEx)
{
ShowError("GetProcAddress ZwCreateThreadEx");
bRet = FALSE;
goto exit;
}
ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,
hProcess, (LPTHREAD_START_ROUTINE)pLoadLibraryAddr,
pDllPathAddr, 0, 0, 0, 0, NULL);
if (hRemoteThread == NULL)
{
ShowError("ZwCreateThreadEx");
bRet = FALSE;
goto exit;
}
exit:
if (hKernel32) FreeLibrary(hKernel32);
if (hNtDll) FreeLibrary(hNtDll);
if (hProcess) CloseHandle(hProcess);
if (hRemoteThread) CloseHandle(hRemoteThread);
return bRet;
}
在Windows系统中,每个线程都会维护一个自己的APC队列,这个APC队列中保存了要求线程执行的一些APC函数。对于用户模式的APC队列,当线程处在可警告状态时,就会执行这些APC函数。而要往APC队列中增加APC函数,需要通过QueueUserAPC函数来实现,这个函数在文档中的定义如下:
DWORD WINAPI QueueUserAPC(
__in PAPCFUNC pfnAPC,
__in HANDLE hThread,
__in ULONG_PTR dwData);
参数 | 说明 |
---|---|
pfnAPC | 当满足条件时,要执行的APC函数的地址 |
hThread | 指定增加APC函数的线程句柄 |
dwData | 要执行的APC函数参数地址 |
可以看到pfnAPC和dwData这两个参数和CreateRemoteThread中的lpStartAddress和lpParameter的作用是一样的。不过这里是对线程进行操作,一个进程有多个线程。所以为了确保程序正确运行,所以需要遍历所有线程,查看是否是要注入的进程的线程,依次获得句柄插入APC函数。具体代码如下:
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
HANDLE hProcess = NULL, hThread = NULL, hSnap = NULL;
HMODULE hKernel32 = NULL;
DWORD dwSize = 0;
PVOID pDllPathAddr = NULL;
PVOID pLoadLibraryAddr = NULL;
THREADENTRY32 te32 = { 0 };
// 打开注入进程,获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
goto exit;
}
// 在注入进程中申请可以容纳DLL完成路径名的内存空间
dwSize = 1 + strlen(szDllName);
pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (!pDllPathAddr)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
goto exit;
}
// 把DLL完成路径名写入进程中
if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
bRet = FALSE;
goto exit;
}
hKernel32 = LoadLibrary("kernel32.dll");
if (hKernel32 == NULL)
{
ShowError("LoadLibrary");
bRet = FALSE;
goto exit;
}
// 获取LoadLibraryA函数地址
pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibraryAddr == NULL)
{
ShowError("GetProcAddress");
bRet = FALSE;
goto exit;
}
//获得线程快照
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (!hSnap)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
goto exit;
}
//遍历线程
te32.dwSize = sizeof(te32);
if (Thread32First(hSnap, &te32))
{
do
{
//这个线程的进程ID是不是要注入的进程的PID
if (te32.th32OwnerProcessID == dwPid)
{
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
if (hThread)
{
QueueUserAPC((PAPCFUNC)pLoadLibraryAddr, hThread, (ULONG_PTR)pDllPathAddr);
CloseHandle(hThread);
hThread = NULL;
}
else
{
ShowError("OpenThread");
bRet = FALSE;
goto exit;
}
}
} while (Thread32Next(hSnap, &te32));
}
exit:
if (hKernel32) FreeLibrary(hKernel32);
if (hProcess) CloseHandle(hProcess);
if (hThread) CloseHandle(hThread);
return bRet;
}
这种注入方式主要是通过修改注册表中HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows中的AppInit_DLLs和LoadAppInit_Dlls,如下图:
只要将AppInit_DLLs设置为要注入的DLL的路径并且将LoadAppInit_DLLs的值改成1。那么,当程序重启的时候,所有加载user32.dll的进程都会根据AppInit_Dlls中的DLL路径加载指定的DLL。
所以这种DLL注入的实现代码如下:
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
HKEY hKey = NULL;
CHAR szAppKeyName[] = { "AppInit_DLLs" };
CHAR szLoadAppKeyName[] = { "LoadAppInit_DLLs" };
DWORD dwLoadAppInit = 1; //设置LoadAppInit_DLLs的值
//打开相应注册表键
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
{
ShowError("RegOpenKeyEx");
bRet = FALSE;
goto exit;
}
//设置AppInit_DLLs为相应的DLL路径
if (RegSetValueEx(hKey, szAppKeyName, 0, REG_SZ, (PBYTE)szDllName, strlen(szDllName) + 1) != ERROR_SUCCESS)
{
ShowError("RegSetValueEx");
bRet = FALSE;
goto exit;
}
//将LoadAppInit_DLLs的值设为1
if (RegSetValueEx(hKey, szLoadAppKeyName, 0, REG_DWORD, (PBYTE)&dwLoadAppInit, sizeof(dwLoadAppInit)) != ERROR_SUCCESS)
{
ShowError("RegSetValueEx");
bRet = FALSE;
goto exit;
}
exit:
return bRet;
}
运行程序以后,会发现相应的键值已经被设置。
Windows系统中的大多数应用都是基于消息机制的,也就是说它们都有一个消息过程函数,可以根据收到的不同消息来执行不同的代码。基于这种消息机制,Windows维护了一个OS message queue以及为每个程序维护着一个application message queue。当发生各种事件的时候,比如敲击键盘,点击鼠标等等,操作系统会从OS message queue将消息取出给到相应的程序的application message queue。
而OS message queue和application message queue的中间有一个称为钩链的结果如下:
在这个钩链中保存的就是设置的各种钩子函数,而这些钩子函数会比应用程序还早接收到消息并对消息进行处理。所以程序员可以通过在钩子中设置钩子函数,而要设置钩子函数就需要使用SetWindowHookEx来将钩子函数安装到钩链中,函数在文档中的定义如下:
HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
参数 | 说明 |
---|---|
idHook | 要安装的钩子类型,为了挂全局钩子,这里选择WH_GETMESSAGE。表示的是安装一个挂钩过程,它监视发送到消息队列的消息 |
lpfn | 表示的是钩子的回调函数。如果dwThreadId为0,则lpfn指向的钩子过程必须指向DLL中的钩子过程 |
hMod | 包含由lpfn参数执行的钩子过程的DLL句柄 |
dwThreadId | 与钩子过程关联的线程标识符,如果为0则表示与所有线程相关联。 |
如果函数成功,则返回钩子过程的句柄,否则为NULL。
根据上面的介绍可以得知,想要创建一个全局钩子,就必须在DLL文件中创建。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件的进程地址空间中,使它可以调用钩子函数进行处理。
所以只要在系统中安装了全局钩子,那么只要进程接收到可以发出钩子的消息,全局钩子的DLL就会被系统自动或者强行加载到进程空间中,这就可以实现DLL注入。
而这里之所以设置为WH_GETMESSAGE,是因为这种类型的钩子会监视消息队列,又因为Windows系统是基于消息驱动的,所以所有的进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子。
由于设置全局钩子的代码需要在DLL文件中完成,所以首先需要新建一个InjectDll.cpp。 随后在文件中写入如下设置全局钩子的函数:
extern HMODULE g_hDllModule;
// 设置全局钩子
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);
}
这里只是简单的调用CallNextHookEx函数表示将当前钩子传递给钩链中的下一个钩子,第一个参数要指定当前钩子的句柄。如果直接返回0,则表示中断钩子传递,这就实现了对钩子进行拦截。 而g_hDllModule则是在DLL加载的时候被赋值的。
当钩子不再使用,可以卸载掉全局钩子,这样此时已经包含钩子回调函数的DLL模块的进程就会释放DLL模块。卸载钩子的代码如下:
// 卸载钩子
BOOL UnSetGlobalHook()
{
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}
上面的全局钩子的设置,钩子回调函数的实现以及全局钩子的卸载都需要使用到全局钩子的句柄。为了让任意一个独立的进程中对句柄的修改都可以影响到其他进程,就需要在DLL中使用共享内存的,来保证将DLL中加载到多个进程以后,一个进程对它的修改可以影响到其他进程。设置共享内存的方式如下:
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
而为了调用设置钩子和卸载钩子的函数,就需要创建一个.def文件来将两个函数导出。
此时使用PEID查看InjectDll.dll可以看到导出表有如下的导出函数: 接下来只要在代码中将DLL引入并或者对应的函数对它们进行调用就好。
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
BOOL bRet = TRUE;
HMODULE hDll = NULL;
pFnSetGlobalHook SetGlobalHook = NULL;
pFnUnSetGlobalHook UnSetGlobalHook = NULL;
hDll = LoadLibrary(szDllName);
if (hDll == NULL)
{
ShowError("LoadLibrary");
bRet = FALSE;
goto exit;
}
SetGlobalHook = (pFnSetGlobalHook)GetProcAddress(hDll, "SetGlobalHook");
if (SetGlobalHook == NULL)
{
ShowError("GetProcAddress SetGlobalHook");
bRet = FALSE;
goto exit;
}
if (!SetGlobalHook())
{
printf("钩子安装失败\n");
bRet = FALSE;
goto exit;
}
printf("钩子安装成功,按回车卸载钩子\n");
system("pause");
UnSetGlobalHook = (pFnUnSetGlobalHook)GetProcAddress(hDll, "UnSetGlobalHook");
if (UnSetGlobalHook == NULL)
{
ShowError("GetProcAddress UnSetGlobalHook");
bRet = FALSE;
goto exit;
}
if (UnSetGlobalHook())
{
printf("已将全局钩子卸载\n");
}
exit:
return bRet;
}
将编译好的exe文件和dll文件放到同一路径中,运行exe以后会在桌面生成一个result.txt文件。打开文件以后会看到里面的内容是被注入的进程的完整的路径名。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/HM2Qgfy27PG2fxOUTfFQ7A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。