windows中的大部分应用程序都是基于消息机制的,根据不同的消息完成不同的功能。 钩子这个机制就是由windows操作系统提供的可以用来截获和监视系统中这些消息的。
钩子又可以分为局部钩子和全局钩子。其中局部钩子是针对某个线程的,全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。
一. 全局钩子注入
首先介绍稍后用到的函数
SetWindowsHookEX 函数
//将程序定义的钩子函数安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件
//和特定线程或调用线程所在的桌面所有线程相关联
HHOOK WINAPI SetWindowsHookEX(
_In_ int idHook, //要安装的钩子程序的类型
_In_ HOOKPROC lpfn, //一个指向钩子程序的指针,如果dwThreadId=0或者指定不同进程创建线程,则
//lpfn必须指向DLL中的钩子过程,否则lpfn可以指向与当前进程关联的代码函数
_In_ HINSANCE hMod, //包含由lpfn参数指向的函数的DLL句柄,如果是当前进程则hMod必须为NULL
_In_ DWORD dwThreadId) //与钩子程序关联的线程标识符。如果为0,则与所有系统中的线程相关联
//函数成功返回钩子函数的句柄
//失败返回NULL
其中钩子类型(第一个参数idhook)分为以下几种:
全局钩子函数的代码需要在独立的DLL中,这样在一个进程执行时才可以加载这个DLL,并运行其中的钩子函数。这个加载DLL到特定进程的过程就是DLL注入
为了让DLL注入到所有进程中,设置WH_GETMESSAGE监视消息队列。windows系统基于消息驱动,所以所有进程都有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL
设置全局钩子的代码:
//设置全局钩子
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表示把当前钩子传递给钩子链中的下一个钩子。
卸载钩子代码:
//卸载钩子
BOOL UnsetGlobalHook() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}
本程序中使用的方法是在DLL中创建一块共享内存由多个进程共用。具体的实现方法如下
//共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker,"/SECTION:mydata,RWS")
使用data_seg()创建数据段,然后通过设置连接器的RWS参数设置为 可读可写可共享的共享数据段。
实现:
1.创建DLL工程项目
新建项目中选择windows桌面项目向导,选择动态链接库dll
定义dll入口函数如下
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "mydll.h"
HMODULE g_hDllModule = NULL;
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
点击生成之后在debug目录下生成dll文件
可以在其他程序中对dll进行加载调用
// Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();
HMODULE hDll = NULL;
typedef_SetGlobalHook SetGlobalHook = NULL;
typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
BOOL bRet = FALSE;
do
{
hDll = ::LoadLibrary("GlobalHook_Test.dll");
if (NULL == hDll)
{
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}
SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
if (NULL == SetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
bRet = SetGlobalHook();
if (bRet)
{
printf("SetGlobalHook OK.\n");
}
else
{
printf("SetGlobalHook ERROR.\n");
}
system("pause");
UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
if (NULL == UnsetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
UnsetGlobalHook();
printf("UnsetGlobalHook OK.\n");
}while(FALSE);
system("pause");
return 0;
}
加载完毕发现再执行其他exe文件时,使用进程查看器发现加载模块里加载了之前生成的dll文件,说明dll注入成功。
继续执行卸载钩子后发现相应的dll没有被加载,说明钩子卸载成功:
二. 远线程注入
所谓远线程注入是指一个进程在另一个进程中创建线程的技术。
函数介绍:
OpenProcess函数
//打开现有的本地进程对象
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //访问进程对象
_In_ BOOL bInheritHandle, //是否继承该进程的句柄
_In_ DWORD dwProcessId) //要打开的本地进程的PID
VirtualAllocEx函数
//在指定进程的虚拟地址空间内保留,提交或更改内存的状态
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess, //过程句柄
_In_opt_ LPVOID lpAddress, //指定要分配页面所需的起始地址的指针
_In_ SIZE_T dwSize, //要分配的内存大小,以字节为单位
_In_ DWORD flAllocationType, //内存分配类型
_In_ DWORD flProtect) //要分配的页面区域的内存保护
//如果成功,返回分配页面的基址
//失败返回NULL
flAllocationType 可取下列值:
MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存)
MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用
MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效
MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)
MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)
flProtect可取下列值:
PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访
PAGE_READWRITE 区域可被应用程序读写
PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。
PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域。
PAGE_EXECUTE_READWRITE: 区域包含可执行代码,应用程序可以读写该区域。
PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限
PAGE_NOACCESS: 任何访问该区域的操作将被拒绝
PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)
WriteProcessMemory 函数
//在指定进程中将数据写入内存区域,整个区域必须可访问
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //要修改的进程内存的句柄
_In_ LPVOID lpBaseAddress, //指向指定进程中写入数据的基地址指针
_In_ LPCVOID lpBuffer, //指向缓冲区的指针,包含要写入指定进程地址空间的数据
_In_ SIZE_T nSize,
_In_ SIZE_T *lpNumberOfBytesWritten) //指向变量的指针
//函数成功返回值不为零
//失败返回值为零
CreateRemoteThread 函数
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess, //创建线程的进程句柄
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //指向安全属性的指针
_In_ SIZE_T dwStackSize, //堆栈的初始大小
_In_ LPTHREAD_START_ROUTING lpStartAddress, //指向远程进程中线程的起始地址
_In_ LPVOID lpParameter, //传递给线程函数的变量的指针
_In_ DWORD dwCreationFlags, //控制线程创建的标志,若为0,表示线程在创建后立即运行
_Out_ LPDWORD lpThreadId) //指向接收线程标识符的变量的指针
//返回新线程句柄或者 NULL
实现原理:进程需要加载的自身原本需要的动态dll 使用的函数时LoadLibrary(lpFilename); 其中参数是要加载的dll 路径
而createRemoteThread函数需要指定多线程函数地址和多线程参数地址。因为loadLibrary函数是目标进程一定会执行的,并且把参数作为要加载的dll
所以在机器指令中相当与存在一条指令,从Loadlibrary函数入口地址开始运行,如果把这个函数地址作为要创建的多线程的地址,并把dll路径作为函数的参数,就可以利用loadLibrary加载相应的dll了
但是 windows使用基址随机化,所以每次开机系统DLL加载基址不一样,所以LoadLibrary函数基址不一样,但是有些系统DLL 要求开机之后地址是固定的,所以自己程序的LoadLibrary函数地址与其他
进程中的地址是相同的。
代码实现 :
#include<Windows.h>
#include<iostream>
#include<tchar.h>
using namespace std;
//定义函数showError
void ShowError(const char* content) {
cout << content << endl;
}
//使用CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjiectDll(DWORD dwProcessId, LPCWSTR 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;
}
//获取LoadLibrary函数地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(_T("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;
}
下面来做一个测试,打算做一个打开电脑qq就自动弹出一个框,里面有文字内容,弹窗需要写成一个DLL模块供目标进程加载。
1. 首先是根据程序名称“qq.exe”找到进程号
使用如下程序可以将QQ,exe的进程号给揪出来
#include"inject.h"
#include<tlhelp32.h>//声明快照bai函数的头文件
#include<stdio.h>
int main(int argc, char *argv[])
{
DWORD QQid; //记录qq 的进程号
PROCESSENTRY32 pe32;
//在使用这个结构之前,先设置它的大小
pe32.dwSize = sizeof(pe32);
//给系统内的所有进程拍一个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
//遍历进程快照,轮流显示每个进程的信息
BOOL bMore = ::Process32First(hProcessSnap, &pe32);
while (bMore)
{
char output[256];
const WCHAR* wc = pe32.szExeFile;
sprintf_s(output, "%ws", wc);
if (strcmp("QQ.exe", output) == 0)//如果找到进程名为abc.exe
{
QQid = pe32.th32ProcessID;
break;
//HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);//获取句柄
/*这里已经打开那个进程的句柄了*/
}
printf("next %ls\n",pe32.szExeFile);
bMore = ::Process32Next(hProcessSnap, &pe32);//寻找下一个
}
// 远线程注入 DLL
#ifndef _WIN64
BOOL bRet = CreateRemoteThreadInjectDll(QQid, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\Debug\\TestDll.dll");
#else
BOOL bRet = CreateRemoteThreadInjectDll(1144, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\x64\\Debug\\TestDll.dll");
#endif
if (FALSE == bRet)
{
printf("Inject Dll Error.\n");
}
printf("Inject Dll OK.\n");
system("pause");
return 0;
return 0;
}
2. 把dll文件做好运行效果如图
下面是dll弹窗代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include<tchar.h>
HMODULE g_hDllModule = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hModule;
::MessageBox(NULL, _T("hhh."), _T("OK"), MB_OK);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
突破sission 0 隔离:
在进行系统进程的注入时,会遇到一个叫session 0 的隔离机制,这时候需要使用更为底层的函数ZwCreateThreadEx来创建远线程,具体原理与CreateRemoteThread相同
三. APC注入
apc是 asynchronous procedure call 异步程序调用。 在windows系统中,每一个线程有一个APC队列,把我们自己定义的apc函数压入到这个队列中就可以得到执行机会
函数介绍:
QueueUserAPC 函数
DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC, //当指定线程执行可警告的等待操作时,指向应用程序提供的APC函数指针
_In_ HANDLE hThread, //线程句柄,该句柄必须有THREAD_SET_CONTEXT访问权限
_In_ ULONG_PTR dwData) //传递有pfnAPC参数指向的APC函数的单个值
函数实现:
#include"inject.h"
//定义函数showError
void ShowError(const char* content) {
printf("%s\n", content);
}
//使用CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjiectDll(DWORD dwProcessId, LPCWSTR 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;
}
//获取LoadLibrary函数地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(_T("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;
}
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(LPCWSTR pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
// 获取进程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}
// 获取第一条进程快照信息
bRet = ::Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
{
dwProcessId = pe32.th32ProcessID;
break;
}
// 遍历下一个进程快照信息
bRet = ::Process32Next(hSnapshot, &pe32);
}
return dwProcessId;
}
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *pdwThreadIdLength)
{
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
ShowError("new");
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
// 获取线程快照
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}
// 获取第一条线程快照信息
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}
// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;
} while (FALSE);
if (FALSE == bRet)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}
//APC 注入
BOOL ApcInjectDll(LPCTSTR pszProcessName, LPCTSTR pszDLLName) {
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD *pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL, hThread = NULL;
PVOID pBaseAddress = NULL;
PVOID pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDLLName);
DWORD i = 0;
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(L"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);
//释放内存
if (hProcess)
{
::CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
return bRet;
}