记Windows几种常用进程注入方式
之前就玩过DLL注入,但是这次突发奇想的想把这几种常用的注入方式都结合在一起,也是方便自己对某些程序的调试更加方便,也是一次代码复用的机会。
目前DLL注入大概有以下几种注入方式:
- 钩子注入
- APC回调注入
- 远程线程注入
- 木马DLL注入
- IAT表注入(不知道这个算不算)
- 输入法注入
- 注册表注入
一、钩子注入
核心函数:SetWindowsHook()
有人提到过使用全局钩子,但是个人认为不怎么推荐,因为影响范围太广,有不可预料的情况发生。所以最好精确到线程。给线程设置消息钩子后,会捕捉键盘、鼠标等消息事件。一但检测到消息发生,就会回调DLL中的函数。也就是把DLL中的导出函数当作回调函数使用,DLL自然也加载了。
首先我们根据PID获取到TID,该钩子函数需求的是TID
//通过进程PID获取线程TID DWORD GetTIDbyPID(DWORD PID) { if (PID != NULL) { DWORD dwThreadID=NULL; THREADENTRY32 te32 = { sizeof(te32) }; HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (Thread32First(hThreadSnap, &te32)) { do { if (PID == te32.th32OwnerProcessID) { dwThreadID = te32.th32ThreadID; break; } } while (Thread32Next(hThreadSnap, &te32)); } wprintf(L"ThreadId:%d\n", dwThreadID); return dwThreadID; } else return NULL; }
利用进程快照,进行线程遍历,判断线程所属,返回线程ID。
然后直接调用SetWindowsHook()方法了
//Hook 注入大法好 int HooksToInject(_TCHAR* DLLName, DWORD ProcessID) { //wprintf(L"\n%s\n%d", DLLName, ProcessID); HHOOK InjectHook = NULL; HMODULE InjectDll = LoadLibrary(DLLName); if (InjectDll != NULL) { HOOKPROC InjectMethod = (HOOKPROC)GetProcAddress(InjectDll, "InjectHook"); if (InjectMethod != NULL) { DWORD ThreadId = GetTIDbyPID(ProcessID); if (ThreadId!=NULL) { InjectHook = SetWindowsHookEx(WH_KEYBOARD, InjectMethod, InjectDll, ThreadId); if (InjectHook != NULL) { wprintf(L"%s\n", L"Hook Process Success!"); } else { return GetLastError(); } } } else { return GetLastError(); } } else { return GetLastError(); } wprintf(L"%s\n",L"Wait you unhook, press any key to unhook."); getchar(); UnhookWindowsHookEx(InjectHook); return 0;
此处我监听的是WM_KEY键盘事件 当然你也可以监听 WM_MOUSE事件。首先用本程序加载动态链接库,获取到动态链接库中,我所导出函数的地址(InjectHook函数),每个链接库系统开机后都是相同的,所以本程序获取更方便一些,下面更是相同。
二、APC回调注入
所谓APC,即Asynchronous procedure call,异步程序调用。
关键函数:QueueUserAPC()
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。
不过这个和远程注入方法都是一样的,都需要一个调试进程的权限,才可以对其他进程进行内存空间的开辟等。所以首先提升权限:
int EnablePrivilege() { //获取调试进程权限 TOKEN_PRIVILEGES tp; HANDLE hToken; if (!LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) return GetLastError(); //Get current process's token if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) return GetLastError(); tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //Update process tonkn privilrges if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) return GetLastError(); CloseHandle(hToken); return 0; }
获取进程权限令牌,获取进程自身LUID然后填充TOKEN_PRIVILEGES结构,修改TOKEN_PRIVILEGES的第一个数组Privileges[0].Attributes中的权限,然后更新权限。
其次调用QueueUserAPC()加入到回调队列。因为Loadlibrary当作回调函数使用,所以其参数需要写到被注入进程中,这就需要申请空间,也就有自身提权的必要性。
int APCCallbacktoInject(_TCHAR* DLLName, DWORD ProcessID) { //提升进程权限 if (int rtMsg = EnablePrivilege() != 0) return rtMsg; HANDLE hOProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID); if (hOProcess != NULL) { _TCHAR* pLibRemote = (_TCHAR*)VirtualAllocEx(hOProcess, NULL, 2 * wcslen(DLLName) + 1, MEM_COMMIT, PAGE_READWRITE); if (pLibRemote != NULL) { if (!WriteProcessMemory(hOProcess, pLibRemote, DLLName, 2 * wcslen(DLLName) + 1, NULL)) { PAPCFUNC pAPCFuncAddr = (PAPCFUNC)GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW"); DWORD dTid = GetTIDbyPID(ProcessID); if (dTid != NULL && pAPCFuncAddr != NULL) { HANDLE hoThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dTid); if (!QueueUserAPC(pAPCFuncAddr, hoThread, (ULONG_PTR)pLibRemote)) { wprintf(L"%s\n", L"Inject sucessful!"); } } } } } return 0; }
三、远程线程注入
关键函数:CreateRemoteThread()
利用创建远程的线程,将LoadLibrary作为函数的回调函数,进行运行以达到加载的目的。当然因为LoadLibrary的参数需要在被注入进程中,需要分配空间用于存储,自然需要调试进程的权限,提取方法见上面的栗子(北京的栗子貌似不错)中—
int RemoteToInject(_TCHAR* DLLName, DWORD ProcessID) { //DWORD ThreadId = GetTIDbyPID(ProcessID); //进程提取 if (int rt = EnablePrivilege() != 0) return rt; HANDLE hOprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID); if (hOprocess != NULL) { _TCHAR* pLibFileRemote = (_TCHAR*)VirtualAllocEx(hOprocess, NULL, 2 * wcslen(DLLName) + 1, MEM_COMMIT, PAGE_READWRITE); if (pLibFileRemote != NULL) { if (!WriteProcessMemory(hOprocess, (void*)pLibFileRemote, DLLName, 2 * wcslen(DLLName) + 1, NULL)) return GetLastError(); //Get LoadLibraryW Address PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW"); if (pfnStartAddr != NULL) { HANDLE hRemote = CreateRemoteThread(hOprocess, NULL, 0, pfnStartAddr, (PVOID)pLibFileRemote, 0, NULL); if (hRemote != NULL) { wprintf(L"%s\n", L"Inject sucessful!"); WaitForSingleObject(hRemote, INFINITE); wprintf(L"%s\n", L"The inject was killed!"); if (!VirtualFreeEx(hOprocess, pLibFileRemote, 0, MEM_RELEASE)) return GetLastError(); } else return GetLastError(); } } else return GetLastError(); } else return GetLastError(); return 0; }
其他三种,请见下回分解。目前写了一个开源项目,用于注入测试用的,代码就是文章所讲述的,可以参考完整项目:https://github.com/BackTrackCRoot/Auto-Inject-Dll/
页面下部广告