吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2939|回复: 36
上一主题 下一主题
收起左侧

[C&C++ 原创] 《PoolParty 攻击连续剧》全集

  [复制链接]
跳转到指定楼层
楼主
神奇的人鱼 发表于 2025-10-27 17:28 回帖奖励
本帖最后由 神奇的人鱼 于 2025-10-29 11:16 编辑

🎭《PoolParty 攻击连续剧》系列总览

潜入 Windows 线程池的暗面:一场关于信任与欺骗的攻防博弈

“不要唤醒工人,要让他们主动为你工作。”

在现代 Windows 安全攻防中,传统的远程线程注入(如 CreateRemoteThread)和 APC 注入早已被 EDR 严密监控。
攻击者被迫转向更隐蔽、更底层、更“合法”的执行路径——而 Windows 线程池,正是这片尚未被充分审视的新战场。

本系列《PoolParty 攻击连续剧》将带你深入 Windows 线程池的调度核心,揭示一个长期被低估的攻击面:  

线程池不仅是一个性能优化机制,更可能成为无文件代码执行的隐蔽通道


🏭 世界观:每个进程都是一座任务调度中心

我们用一套统一的隐喻贯穿全系列,便于理解与防御建模:

  • 工人 = 线程池中的工作线程  
  • 任务单 = 回调函数(Shellcode 的伪装形态)  
  • 调度中心 = 线程池管理器(ntdll!Tpp*)  
  • 快递中转站 = I/O 完成端口(IoCompletion Port)  
  • 培训中心 = WorkerFactory  
  • 内部通讯网 = ALPC  
  • 人事系统 = 作业对象(Job Object)  
  • 自动闹钟 = 定时器队列(Timer Queue)

在这座工厂里,工人从不主动找活,只等任务单被投递到队列。
而攻击者的目标,就是伪造一张看起来完全合法的任务单,让工人毫无怀疑地执行它。
对防御者而言,关键则在于:如何识别这张“合法”任务单的异常本质


🧪 什么是 PoolParty?

PoolParty 是 SafeBreach Labs 在 Black Hat EU 2023 上发布的研究成果,题为:
“The Pool Party You Will Never Forget: New Process Injection Techniques Using Windows Thread Pools”

它系统性地展示了 8 种滥用 Windows 线程池机制的进程注入技术,全部基于系统原生行为,无需创建远程线程,亦不触发传统高危 API 监控。

GitHub 仓库:https://github.com/SafeBreach-Labs/PoolParty
作者:Alon Leviev (@_0xDeku)

其核心在于两类路径:

🔹 7 种“任务单投递”战术

利用系统原生事件作为触发器,将恶意回调注入任务队列:

  • TP_WORK:伪造高优先级任务单  
  • TP_WAIT:绑定事件信号触发回调  
  • TP_IO:伪装文件 I/O 完成包  
  • TP_ALPC:冒充可信组件间通信  
  • TP_JOB:利用进程加入作业对象的事件  
  • TP_TIMER:设置延迟或周期性回调  
  • 裸完成包投递:直接向 IoCompletion 投递回调指针(极简形态)

🔴 1 种“工人改造”战术

绕过任务队列,直接干预线程创建流程:

  • StartRoutine 覆写:篡改新工人线程的初始执行逻辑

这 8 种战术共同构成了对 Windows 线程池最完整的滥用与检测矩阵


🌟 为何值得关注?(攻防双重视角)

  • 🔍 对攻击者:  

    • 高度隐蔽:执行线程来自目标进程原生线程池,无异常线程创建行为  
    • 绕过常见监控:不调用 CreateRemoteThreadQueueUserAPC 等高危 API  
    • 行为合法:所有操作均基于 Windows 内部机制(部分使用未公开但稳定的接口)  
    • 无文件、无网络、无新进程:满足高级持续性威胁(APT)的静默执行需求
  • 🛡️ 对防御者:  

    • 揭示新型无文件执行路径,推动 EDR/EDR 检测逻辑演进  
    • 提供可观测信号:如异常回调地址、非预期的 IoCompletion 投递、WorkerFactory 滥用等  
    • 促进对“合法行为中的恶意意图”的建模能力(如行为上下文、调用链完整性)  
    • 为内核级监控(如 ETW、Kernel Callback Tracing)提供新锚点

📚 系列结构

本系列共八幕,层层递进,从机制改造到行为欺骗,兼顾攻击实现与防御启示:

  1. 第一幕:StartRoutine 覆写 —— 制造一个为你而生的工人  
  2. 第二幕:TP_WORK 插入 —— 伪造一张高优先级任务单  
  3. 第三幕:TP_WAIT 插入 —— 埋下一张会自己跑的任务单  
  4. 第四幕:TP_IO 插入 —— 伪造一个不存在的快递包裹  
  5. 第五幕:TP_ALPC 插入 —— 伪造一封内部部门密电  
  6. 第六幕:TP_JOB 插入 —— 伪造一次人事调动通知  
  7. 第七幕:裸完成包投递 —— 直接塞给工人一张纸条  
  8. 第八幕:TP_TIMER 插入 + 战术自适应框架 —— 终章整合与攻防决策模型

每一篇均包含:

  • 世界观叙事(技术隐喻)  
  • 核心机制剖析(含关键数据结构与调用链)  
  • 攻击实现(C++/Win32/NTAPI 示例)  
  • 防御视角:可观测信号、检测思路、缓解建议  
  • 与其他战术的横向对比(隐蔽性、稳定性、适用场景)

🔚 致读者

本系列面向具备 Windows 内核、线程、进程、句柄等基础知识的安全研究者——无论是红队、蓝队,还是安全产品开发者。
我们不追求“一键利用”或“万能绕过”,而致力于深入机制、识别风险、构建纵深防御

因为真正的安全攻防,
不在于掌握多少技巧,
而在于理解——
系统为何信任,又该如何验证这份信任

现在,调度中心的大门已开。
欢迎来到 PoolParty


🎭 PoolParty 攻击连续剧(第一幕):

“制造一个为你而生的工人” —— 劫持线程池的入职培训手册

副标题:当任务单失效时,我们不再欺骗工人,而是直接重塑他们的灵魂。


在 Windows 的地下世界里,每一个运行中的进程都是一座精密运转的任务调度中心
这里有成群的工人(线程),在线程池的统一调度下,从任务单队列中领取指令,默默执行着系统或应用交付的使命。

长久以来,攻击者试图混入这座工厂——
他们伪造任务单(如 TP_WORKTP_TIMER),诱使现有工人执行恶意指令;
他们利用快递(I/O 完成端口)、内部通讯(ALPC)、人事变动(Job 对象)……
一切手段,只为让一个无辜的工人,在某个不经意的瞬间,执行我们的 Shellcode。

但今天,我们要讲述一个更激进的故事


🧨 不再投递任务单——我们直接篡改“入职培训手册”

想象一下:
调度中心有一个新工人培训中心,名为 WorkerFactory
每当线程池需要扩充人手,就会从这里“招募”一名新工人。
而每一位新工人上岗前,必须运行一份标准化的 “入职培训程序” —— 这个程序的入口点,就是 StartRoutine

正常流程
StartRoutine → 初始化线程上下文 → 加入线程池 → 开始轮询任务单。

我们的计划
闯入培训中心,把这份“入职培训程序”直接替换成我们的 Shellcode
从此,每一个新诞生的工人,从“出生”那一刻起,就只为我们的目标服务。

这,就是 PoolParty 家族中最激进的战术:  

WorkerFactory StartRoutine 覆写


🔧 技术实现:三步重塑工人灵魂

第一步:潜入厂区,获取通行证

auto hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);

我们首先获得目标进程的完全控制权——这是所有后续操作的前提。

第二步:定位“培训中心”,读取原始手册

通过 NtQueryInformationWorkerFactory,我们获取 WorkerFactory 的基本信息,尤其是:

PVOID StartRoutine = m_WorkerFactoryInformation.StartRoutine;

这个地址,就是我们要篡改的目标。

第三步:覆写培训内容,强制招募新人

  1. 修改内存保护属性(使其可写):
    VirtualProtectEx(hProcess, StartRoutine, shellcodeSize, PAGE_EXECUTE_READWRITE, &oldProtect);
  2. 将 Shellcode 直接写入 StartRoutine 所在地址(跳过常规内存分配):
    WriteProcessMemory(hProcess, StartRoutine, shellcode, shellcodeSize, nullptr);
  3. 调用 NtSetInformationWorkerFactory,将最小线程数 +1
    ULONG newMin = currentWorkerCount + 1;
    NtSetInformationWorkerFactory(hWorkerFactory, WorkerFactoryThreadMinimum, &newMin, sizeof(ULONG));

💥 瞬间:系统创建一个新线程 → 该线程立即执行 StartRoutine → 实际运行的是我们的 Shellcode!


⚖️ 优势与代价

优势 风险
执行立即:无需等待任务调度 ⚠️ 高风险:若 StartRoutine 被多次调用,可能导致崩溃
绕过任务队列依赖:即使队列满/空也能触发 ⚠️ 内存写入敏感:直接覆写代码段,易被 EDR 检测
高度隐蔽:线程行为看似“合法创建” ⚠️ 需目标可创建新线程(线程池未达上限)

📌 最佳使用场景:  

  • 其他任务单战术因队列状态失败时的 fallback 方案;  
  • 需要立即执行且能接受一定崩溃风险的高权限攻击场景。

🏗️ 代码结构:面向对象的战术封装

在我们的 PoolParty 框架中,这一战术被封装为:

class WorkerFactoryStartRoutineOverwrite : public PoolParty {
protected:
    std::shared_ptr<HANDLE> m_p_hWorkerFactory;
    WORKER_FACTORY_BASIC_INFORMATION m_WorkerFactoryInformation;

public:
    LPVOID AllocateShellcodeMemory() const override {
        return m_WorkerFactoryInformation.StartRoutine; // ← 直接复用 StartRoutine 地址!
    }

    void SetupExecution() const override {
        ULONG newMin = m_WorkerFactoryInformation.TotalWorkerCount + 1;
        NtSetInformationWorkerFactory(*m_p_hWorkerFactory, WorkerFactoryThreadMinimum, &newMin, sizeof(ULONG));
    }
};

通过继承基类 PoolParty,我们复用通用流程(句柄获取、内存写入等),仅重写关键步骤,实现战术的模块化与可扩展性


🌐 下一幕预告

在接下来的篇章中,我们将逐一揭开 PoolParty 家族的其他七种战术:  

  • 如何伪造一张“高优先级任务单”(TP_WORK)?  
  • 如何让快递员(I/O 线程)顺手执行我们的指令?  
  • 如何利用“定时闹钟”(TP_TIMER)实现持久化?

但请记住:  

最危险的攻击,不是让工人做错事,而是让他们从一开始就被设计成“错的”。


PoolParty 攻击连续剧 · 第一幕 完  


🎭 PoolParty 攻击连续剧(第二幕):

“伪造一张高优先级任务单” —— 远程 TpWork 插入的艺术

副标题:当工人还在排队等活,我们已悄悄把一张“紧急指令”塞进队列最前端。


在上一幕中,我们选择重塑工人本身——让新诞生的线程从“出生”就为我们服务。
但那是一条高风险之路:覆写代码段、依赖线程创建、可能引发崩溃。

今天,我们回归更“优雅”的传统:不碰工人,只改任务单
只不过,这一次我们伪造的不是普通工单,而是一张标有“最高优先级”的红色紧急指令——
它会被立刻执行,甚至打断当前工人的手头工作。

这,就是 PoolParty 家族中最经典、最隐蔽的战术之一:  

远程 TpWork 插入(Remote TpWork Insertion)。


📜 世界观回顾:任务单如何被处理?

在调度中心(线程池)内部,任务单被分为三个优先级队列:  

  • (Low)  
  • 正常(Normal)  
  • (High)

工人轮询时,永远优先处理高优先级队列
TP_WORK,正是 Windows 提供的标准“工作项”任务单类型——
通过 CreateThreadpoolWork() 创建,SubmitThreadpoolWork() 提交。

正常流程
应用 → 创建 TP_WORK → 提交 → 系统将其链入高优先级队列 → 工人执行回调。

我们的计划
在本地构造一个“特制任务单”
直接写入目标进程的高优先级队列头
让目标工人误以为这是自己人发的紧急指令


🔧 技术实现:伪造任务单的三重伪装

第一步:潜入厂区,获取“调度中心蓝图”

我们首先通过 GetTargetThreadPoolWorkerFactoryHandle() 获取 WorkerFactory 句柄,
再调用 NtQueryInformationWorkerFactory,读取其 StartParameter——
这实际上是指向 TP_POOL 结构体的指针,即整个线程池的“控制中枢”。

auto WorkerFactoryInfo = GetWorkerFactoryBasicInformation(*m_p_hWorkerFactory);
PVOID pTpPoolInTarget = WorkerFactoryInfo.StartParameter; // ← 调度中心核心地址

第二步:在本地伪造一张“高优先级任务单”

我们在攻击者进程中调用 CreateThreadpoolWork(),创建一个 TP_WORK 对象:  

  • 回调函数设为 m_ShellcodeAddress(即我们写入目标进程的 Shellcode 地址)  
  • 上下文设为 nullptr
auto pTpWork = CreateThreadpoolWork(
    (PTP_WORK_CALLBACK)m_ShellcodeAddress,
    nullptr,
    nullptr
);

⚠️ 注意:此时 pTpWork 还属于本地进程,不能直接提交。

第三步:篡改任务单归属,强行插入目标队列

这是最精妙的一步:
我们手动修改 TP_WORK 内部字段,使其“认贼作父”:

  1. pTpWork->Pool 指向目标进程的 TP_POOL 地址(即 StartParameter)  
  2. pTpWork->Task.ListEntryFlinkBlink 指向目标高优先级队列头  
  3. 设置 WorkState = 0x2(表示“已入队”,避免被清理)
pTpWork->CleanupGroupMember.Pool = (PFULL_TP_POOL)pTpPoolInTarget;
pTpWork->Task.ListEntry.Flink = &targetHighPriorityQueueHead;
pTpWork->Task.ListEntry.Blink = &targetHighPriorityQueueHead;
pTpWork->WorkState.Exchange = 0x2; // 已入队状态

第四步:将伪造任务单“快递”到目标进程

  1. 在目标进程分配内存:VirtualAllocEx(sizeof(TP_WORK))  
  2. 将篡改后的 TP_WORK 结构体写入:WriteProcessMemory()  
  3. 关键一步:修改目标进程的高优先级队列头,使其双向指向我们的新任务单,形成合法循环链表:
// 修改队列头的 Flink/Blink,指向我们的远程 TP_WORK
WriteProcessMemory(hTarget, &queueHead.Flink, &pRemoteTpWork->Task.ListEntry, sizeof(...));
WriteProcessMemory(hTarget, &queueHead.Blink, &pRemoteTpWork->Task.ListEntry, sizeof(...));

💥 瞬间:线程池工人下一次轮询 → 发现高优先级队列非空 → 执行 TP_WORK 回调 → 运行我们的 Shellcode!


🎯 为何选择高优先级队列?

  • 立即执行:无需等待低优先级任务完成  
  • 隐蔽性强:行为完全符合线程池正常逻辑  
  • 绕过检测:不调用 SubmitThreadpoolWork(),避免 API 监控

📌 注意:此战术依赖对 TP_POOLTP_WORK 内部结构的精确理解——
微软未公开这些结构,需通过逆向或符号文件还原(如你代码中的 FULL_TP_POOL / FULL_TP_WORK)。


🏗️ 代码结构:精准操控任务队列

PoolParty 框架中,这一战术被封装为:

class RemoteTpWorkInsertion : public PoolParty {
protected:
    std::shared_ptr<HANDLE> m_p_hWorkerFactory;

public:
    void HijackHandles() override {
        m_p_hWorkerFactory = GetTargetThreadPoolWorkerFactoryHandle();
    }

    void SetupExecution() const override {
        // 1. 读取目标 TP_POOL
        // 2. 本地创建 TP_WORK,回调 = m_ShellcodeAddress
        // 3. 篡改 TP_WORK 内部指针,指向目标 TP_POOL 和高优先级队列
        // 4. 写入目标进程,并修补队列头形成循环链表
    }
};

整个过程不调用任何远程线程创建 API,仅通过内存写入和链表操作,实现“静默投递”。


⚖️ 与“StartRoutine 覆写”的对比

维度 StartRoutine 覆写 TpWork 插入
攻击面 线程创建流程 任务调度队列
执行时机 新线程创建时 工人下一次轮询(高优先级立即)
内存操作 覆写代码段(高风险) 写入数据结构(低风险)
崩溃概率 高(若多次调用) 极低(结构合法)
EDR 规避 困难(代码段写入) 较易(仅数据写入)

结论
TpWork 插入是“优雅的欺骗”,StartRoutine 覆写是“暴力的重塑”
前者适合持久化与隐蔽渗透,后者适合紧急执行与 fallback。


🌐 下一幕预告

在第三幕中,我们将探索一种更“被动”的战术:
如何埋下一张“触发式任务单”(TP_WAIT)?
——它不会立即执行,而是静静等待某个事件(如句柄信号)发生,才悄然启动。

届时,我们将看到:  

最危险的指令,往往藏在最安静的等待之中


PoolParty 攻击连续剧 · 第二幕 完  


🎭 PoolParty 攻击连续剧(第三幕):

“埋下一张会自己跑的任务单” —— 用事件触发的 TP_WAIT 诡计

副标题:最危险的指令,往往藏在最安静的等待之中。


在前两幕中,我们或重塑工人灵魂(StartRoutine 覆写),或伪造高优先级任务单(TpWork 插入)。
但这些战术都有一个共同点:主动出击——要么强制创建线程,要么直接塞入队列。

今天,我们要讲述一种更狡猾、更被动的策略:
我们不催促工人干活,而是在调度中心埋下一颗“定时信标”
它静静沉睡,直到某个外部信号(如一个事件被触发)出现,
才瞬间激活,将我们的指令悄无声息地投递到工人手中

这,就是 PoolParty 家族中最具“异步艺术感”的战术:  

远程 TP_WAIT 插入(Remote TpWait Insertion)。


🕳️ 世界观升级:引入“快递中转站”与“信号塔”

为了理解这一战术,我们需要在调度中心之外,引入两个新设施:

  1. I/O 完成端口(IoCompletion)
    → 相当于快递中转站,所有异步 I/O 操作完成后,都会在此投递一个“包裹”(完成包)。

  2. 事件对象(Event)
    → 相当于一座信号塔,当它被“点亮”(SetEvent),就会向中转站发送一个通知。

TP_WAIT,正是 Windows 提供的一种等待信号并执行回调的机制。
正常情况下,应用会调用 SetThreadpoolWait(),让线程池在事件触发时执行指定函数。

正常流程
应用注册 TP_WAIT → 等待 Event → Event 被 Set → 线程池执行回调。

我们的诡计
在目标进程伪造一个 TP_WAIT
将其与一个我们控制的本地事件绑定
并通过 ZwAssociateWaitCompletionPacket,让事件触发时自动向目标的“快递中转站”投递包裹
最终由线程池工人取出包裹,执行我们的 Shellcode


🔧 技术实现:四步构建“信号触发链”

第一步:在本地创建“特制 TP_WAIT”

我们在攻击者进程中调用 CreateThreadpoolWait(),创建一个 TP_WAIT 对象:  

  • 回调函数设为 m_ShellcodeAddress(目标进程中的 Shellcode 地址)  
  • 上下文为 nullptr
auto pTpWait = CreateThreadpoolWait(
    (PTP_WAIT_CALLBACK)m_ShellcodeAddress,
    nullptr,
    nullptr
);

这个对象内部包含一个关键结构:WaitPkt(等待完成包)和 Direct(APC 执行上下文)。

第二步:将 TP_WAIT 与 TP_DIRECT 写入目标进程

  1. 在目标进程分配内存,写入完整的 TP_WAIT 结构体:
    auto pRemoteTpWait = VirtualAllocEx(..., sizeof(FULL_TP_WAIT));
    WriteProcessMemory(hTarget, pRemoteTpWait, pTpWait, ...);
  2. 单独提取 pTpWait->Direct(即 TP_DIRECT 结构),也写入目标进程:
    auto pRemoteTpDirect = VirtualAllocEx(..., sizeof(TP_DIRECT));
    WriteProcessMemory(hTarget, pRemoteTpDirect, &pTpWait->Direct, ...);

📌 为什么需要 TP_DIRECT
因为 ZwAssociateWaitCompletionPacket 要求提供一个 APC 上下文(即 TP_DIRECT*),用于在完成包投递后执行回调。

第三步:创建本地事件,并绑定到目标“快递中转站”

  1. 创建一个命名事件(如 PoolParty_Signal):
    auto hEvent = CreateEvent(nullptr, FALSE, FALSE, L"PoolParty_Signal");
  2. 调用 ZwAssociateWaitCompletionPacket,建立三者关联:
    ZwAssociateWaitCompletionPacket(
       pTpWait->WaitPkt,          // 完成包(包裹内容)
       *m_p_hIoCompletion,        // 目标的 I/O 完成端口(快递中转站)
       hEvent,                    // 本地事件(信号塔)
       pRemoteTpDirect,           // APC 上下文(执行指令)
       pRemoteTpWait,             // TP_WAIT 对象(任务单)
       0, 0, nullptr
    );

💡 关键点
此 API 是微软未公开的内核接口,允许将任意事件目标进程的 I/O 完成端口绑定。
一旦事件被触发,系统会自动向目标的 IoCompletion 投递一个完成包,其中包含我们的回调上下文。

第四步:点亮信号塔,触发执行

SetEvent(hEvent);

💥 瞬间:  

  • 事件被触发 →  
  • 系统向目标进程的 IoCompletion 投递完成包 →  
  • 线程池工人从快递中转站取出包裹 →  
  • 执行 TP_DIRECT 中的 APC →  
  • 最终调用 TP_WAIT 的回调 → 运行我们的 Shellcode

整个过程无需远程线程创建、无需任务队列写入,仅依赖 Windows 内核的异步通知机制。


🎯 战术优势:隐蔽、异步、跨进程

优势 说明
完全异步 执行时机由攻击者控制(SetEvent 时刻)
跨进程绑定 本地事件可触发远程进程的回调
绕过 API 监控 未调用 SubmitThreadpoolWorkQueueUserAPC
利用合法机制 ZwAssociateWaitCompletionPacket 是系统内部合法 API

📌 适用场景:  

  • 需要延迟执行条件触发的 payload;  
  • 目标进程已启用 I/O 完成端口(常见于服务、网络应用);  
  • 希望避免直接内存写入任务队列(如 EDR 监控链表操作)。

🏗️ 代码结构:异步工作项的抽象

你的代码中,RemoteTpWaitInsertion 继承自 AsynchronousWorkItemInsertion(可能是 PoolParty 的异步子类),体现了对“事件-完成包-回调”模式的封装:

void SetupExecution() const override {
    // 1. 创建本地 TP_WAIT(回调 = Shellcode)
    // 2. 将 TP_WAIT 和 TP_DIRECT 写入目标进程
    // 3. 创建命名事件
    // 4. 调用 ZwAssociateWaitCompletionPacket 绑定事件 → IoCompletion → TP_WAIT
    // 5. SetEvent 触发
}

这种设计将触发逻辑执行逻辑解耦,为后续扩展(如定时触发、文件句柄触发)奠定基础。


⚖️ 与前两幕的战术对比

战术 触发方式 执行延迟 风险 隐蔽性
StartRoutine 覆写 强制创建线程 立即 高(覆写代码)
TpWork 插入 写入高优先级队列 极短(下一轮询)
TpWait 插入 外部事件触发 可控(SetEvent 时) 极低 极高

结论
TpWait 是“潜伏者”的首选——它不急于行动,只等一声令下,便悄然完成使命。


🌐 下一幕预告

在第四幕中,我们将探索一种更“底层”的战术:
如何利用 I/O 完成端口本身作为投递通道(TP_IO)?
——我们将伪造一个“虚假 I/O 操作完成”事件,让工人误以为这是磁盘或网络的响应,从而执行我们的代码。

届时,我们将看到:  

当快递员收到一个不存在的包裹,他依然会认真拆开


PoolParty 攻击连续剧 · 第三幕 完  


🎭 PoolParty 攻击连续剧(第四幕):

“伪造一个不存在的快递包裹” —— 利用 TP_IO 欺骗工人拆箱

副标题:当工人以为自己在处理磁盘响应,其实拆开的是我们埋下的炸弹。


在前三幕中,我们或重塑工人(StartRoutine 覆写),或伪造任务单(TpWork),或设置信号陷阱(TpWait)。
但这些战术,或多或少都依赖“调度中心内部”的逻辑。

今天,我们要从外部世界发起攻击——
我们假装自己是一个磁盘驱动器,向调度中心的“快递中转站”(I/O 完成端口)投递一个虚假的 I/O 完成通知
工人收到后,会像处理普通文件读写一样,认真执行回调——
而这个回调,正是我们的 Shellcode。

这,就是 PoolParty 家族中最具“欺骗性”的战术之一:  

远程 TP_IO 插入(Remote TpIo Insertion)。


📦 世界观扩展:I/O 完成端口 = 快递中转站

在现代 Windows 进程中,尤其是高性能服务(如 IIS、SQL Server、游戏服务器),
I/O 完成端口(IoCompletion Port)被广泛用于高效处理磁盘、网络等异步操作。

正常流程:  

  1. 应用发起异步写文件(WriteFile + OVERLAPPED)  
  2. 磁盘驱动完成操作后,向 IoCompletion 投递一个“完成包”  
  3. 线程池工人从中转站取出包裹,执行 TP_IO 回调函数

整个过程对应用透明,高效且可扩展。

我们的诡计
我们不真的等待磁盘响应,而是主动“伪造一个完成事件”
让系统误以为某个文件 I/O 已完成,从而触发我们的回调


🔧 技术实现:四步伪造“磁盘快递”

第一步:创建一个临时文件(作为“伪装载体”)

我们在本地创建一个临时文件(如 C:\Windows\Temp\poolparty.tmp),并以 FILE_FLAG_OVERLAPPED 模式打开——
这是启用异步 I/O 的前提。

auto hFile = CreateFile(
    L"poolparty.tmp",
    GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    nullptr,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
    nullptr
);

📌 注意:文件内容不重要,它只是一个“信使外壳”。


第二步:在本地构造“特制 TP_IO”

调用 CreateThreadpoolIo(),创建一个 TP_IO 对象:  

  • 回调函数设为 m_ShellcodeAddress(目标进程中的 Shellcode 地址)  
  • 文件句柄为刚创建的 hFile
auto pTpIo = CreateThreadpoolIo(hFile, (PTP_WIN32_IO_CALLBACK)m_ShellcodeAddress, nullptr, nullptr);

CreateThreadpoolIo 并不会立即启动 I/O,我们需要手动模拟“I/O 已发起”状态

pTpIo->CleanupGroupMember.Callback = m_ShellcodeAddress; // 修复回调指针
++pTpIo->PendingIrpCount; // 模拟有一个挂起的 I/O 请求

💡 为什么需要 PendingIrpCount
线程池在处理完成包时,会检查该计数。若为 0,会忽略回调。
我们通过递增它,让系统“相信”有一个 I/O 正在等待完成。


第三步:将 TP_IO 写入目标进程,并绑定到其 I/O 完成端口

  1. 在目标进程分配内存,写入完整的 TP_IO 结构体:

    auto pRemoteTpIo = VirtualAllocEx(..., sizeof(FULL_TP_IO));
    WriteProcessMemory(hTarget, pRemoteTpIo, pTpIo, ...);
  2. 关键一步:调用 ZwSetInformationFile,将本地文件句柄关联到目标进程的 I/O 完成端口

    FILE_COMPLETION_INFORMATION info;
    info.Port = *m_p_hIoCompletion;          // 目标的 IoCompletion 句柄
    info.Key  = &pRemoteTpIo->Direct;        // APC 执行上下文(指向 TP_IO)
    
    ZwSetInformationFile(hFile, ..., &info, ..., FileReplaceCompletionInformation);

🌟 魔法所在
此操作告诉内核:“当这个文件的 I/O 完成时,请把完成包投递到目标进程的快递中转站”
即便文件是我们在本地创建的,完成通知却会出现在远程进程中!


第四步:发起写操作,触发“虚假完成”

我们向临时文件写入一段无意义数据(如一首诗):

WriteFile(hFile, "The PoolParty has begun...", ..., &overlapped);

由于文件是异步打开的,WriteFile 会立即返回,
而内核会在 I/O 完成后,自动向目标进程的 IoCompletion 投递完成包

💥 瞬间:  

  • 目标进程的线程池工人从中转站取出包裹 →  
  • 解析 Key 字段,找到 TP_IODirect 上下文 →  
  • 执行回调 → 运行我们的 Shellcode

整个过程完全模拟合法 I/O 行为,连内核都“信以为真”。


🎯 战术优势:天然合法、难以区分

优势 说明
行为完全合法 使用标准异步 I/O 机制,无非常规 API 调用
绕过行为检测 EDR 很难区分“真实文件写入”与“攻击性 I/O”
无需远程线程 仅通过文件系统与完成端口联动触发
适用于高安全环境 常见于服务进程,本身就有大量 I/O 操作作掩护

📌 适用场景:  

  • 目标进程已绑定 I/O 完成端口(如数据库、Web 服务器);  
  • 需要高度隐蔽、长期潜伏的 payload 投递;  
  • 作为对抗 EDR 的“白利用”(Living-off-the-Land)战术。

🏗️ 代码结构:异步 I/O 的精准操控

在你的框架中,RemoteTpIoInsertion 继承自 AsynchronousWorkItemInsertion,体现了对“外部事件 → 完成端口 → 回调”链路的统一抽象:

void SetupExecution() const override {
    // 1. 创建临时异步文件
    // 2. 构造 TP_IO,修复回调并模拟 PendingIrpCount
    // 3. 写入目标进程
    // 4. 通过 ZwSetInformationFile 将文件绑定到目标 IoCompletion
    // 5. WriteFile 触发 I/O 完成
}

这种设计将文件 I/O线程池回调无缝衔接,展现了对 Windows 异步模型的深度掌控。


⚖️ 与前三幕战术的对比

战术 触发源 是否需远程内存写入 是否依赖目标状态 隐蔽性
StartRoutine 覆写 强制创建线程 是(覆写代码段) 需能创建线程
TpWork 插入 伪造任务单 是(写入队列) 需有 WorkerFactory
TpWait 插入 本地事件 是(写入 TP_WAIT) 需有 IoCompletion 极高
TpIo 插入 本地文件 I/O 是(写入 TP_IO) 需有 IoCompletion 极高 + 行为合法

结论
TP_IO 是“伪装大师”——它不制造异常,只利用系统本就信任的通道,完成致命一击。


🌐 下一幕预告

在第五幕中,我们将深入 Windows 内核通信机制,探索:
如何通过 ALPC(高级本地过程调用)投递任务单(TP_ALPC)?
——我们将伪造一个“内部部门通讯请求”,让调度中心误以为这是来自可信组件的指令。

届时,我们将看到:  

最危险的命令,往往来自“自己人”的频道


PoolParty 攻击连续剧 · 第四幕 完  


🎭 PoolParty 攻击连续剧(第五幕):

“伪造一封内部部门密电” —— 利用 ALPC 欺骗调度中心

副标题:最危险的命令,往往来自“自己人”的频道。


在前四幕中,我们或重塑工人(StartRoutine 覆写),或伪造任务单(TpWork),或设置信号陷阱(TpWait),或伪装磁盘快递(TpIo)。
但这些战术,大多依赖外部事件或文件系统。

今天,我们要潜入 Windows 最核心的内部通讯网络——
ALPC(Advanced Local Procedure Call),
这是系统组件之间(如 CSRSS、LSASS、服务管理器)进行高效、安全通信的“加密专线”。

正常用途
当一个服务需要请求另一个服务执行操作(如启动进程、查询令牌),
它会通过 ALPC 端口发送一条结构化消息,接收方线程池自动处理。

我们的诡计
我们创建一个“冒牌 ALPC 服务端口”
将其绑定到目标进程的 I/O 完成端口
再以“客户端”身份发送一条消息
让目标工人误以为这是来自可信系统组件的内部指令

这,就是 PoolParty 家族中最具“内鬼气质”的战术:  

远程 TP_ALPC 插入(Remote TpAlpc Insertion)。


📡 世界观升级:ALPC = 调度中心的内部加密专线

想象调度中心内部有一套独立于公网的内部通讯系统:  

  • 每个关键部门(如人事、安保、I/O 管理)都有自己的加密频道(ALPC 端口);  
  • 消息格式严格,权限验证严密;  
  • 所有通讯自动由线程池工人处理,无需人工干预。

正常流程:  

  1. 部门 A 创建 ALPC 服务端口(如 \Sessions\1\PoolParty_Service)  
  2. 部门 B 连接该端口并发送消息  
  3. 消息被投递到部门 A 的 I/O 完成端口  
  4. 工人取出消息,执行 TP_ALPC 回调

我们的计划
我们冒充“新成立的内部部门”
注册一个看似合法的 ALPC 端口
并将回调指向我们的 Shellcode
再自己给自己发一条“启动指令”


🔧 技术实现:五步伪造“内部密电”

第一步:创建临时 ALPC 端口(用于构造 TP_ALPC)

我们先调用 NtAlpcCreatePort() 创建一个临时端口(无需命名),
仅用于调用 TpAllocAlpcCompletion() —— 这是 Windows 内部用于注册 ALPC 回调的函数。

auto hTempPort = NtAlpcCreatePort(nullptr, nullptr);
auto pTpAlpc = TpAllocAlpcCompletion(hTempPort, (PTP_ALPC_CALLBACK)m_ShellcodeAddress, ...);

📌 注意TpAllocAlpcCompletion 并非公开 API,通常需通过 ntdll 导出或动态解析。


第二步:创建命名 ALPC 服务端口(真正的“钓鱼频道”)

我们创建一个全局可见的命名端口(如 \PoolParty_Command):

UNICODE_STRING portName = L"\\PoolParty_Command";
OBJECT_ATTRIBUTES objAttr = { .ObjectName = &portName };
ALPC_PORT_ATTRIBUTES portAttr = { .MaxMessageLength = 328, .Flags = 0x20000 };

auto hAlpcPort = NtAlpcCreatePort(&objAttr, &portAttr);

这个端口将作为“诱饵”,等待“客户端”连接。


第三步:将 TP_ALPC 写入目标进程

我们在目标进程中分配内存,并将伪造的 TP_ALPC 结构体写入:

auto pRemoteTpAlpc = VirtualAllocEx(..., sizeof(FULL_TP_ALPC));
WriteProcessMemory(hTarget, pRemoteTpAlpc, pTpAlpc, ...);

该结构体内部包含回调地址(即 Shellcode)和 ALPC 上下文。


第四步:将 ALPC 端口绑定到目标的 I/O 完成端口

这是最关键的一步:
调用 NtAlpcSetInformation(),将命名 ALPC 端口关联到目标进程的 I/O 完成端口

ALPC_PORT_ASSOCIATE_COMPLETION_PORT info;
info.CompletionPort = *m_p_hIoCompletion;   // 目标的快递中转站
info.CompletionKey  = pRemoteTpAlpc;        // 指向我们的 TP_ALPC

NtAlpcSetInformation(hAlpcPort, AlpcAssociateCompletionPortInformation, &info, ...);

💡 魔法所在
此操作告诉内核:“所有发往此 ALPC 端口的消息,请投递到目标进程的 IoCompletion”
即便端口由我们在本地创建,消息处理却发生在远程进程


第五步:以客户端身份连接并发送消息(触发执行)

我们调用 NtAlpcConnectPort(),作为“客户端”连接到刚创建的命名端口,
并在连接请求中附带一段消息数据(如一首诗):

ALPC_MESSAGE msg;
msg.PortHeader.DataLength = poem.length();
msg.PortHeader.TotalLength = sizeof(PORT_MESSAGE) + poem.length();
memcpy(msg.PortMessage, poem.data(), poem.length());

NtAlpcConnectPort(&portName, ..., (PPORT_MESSAGE)&msg, ...);

💥 瞬间:  

  • 内核收到连接请求 →  
  • 向目标进程的 IoCompletion 投递 ALPC 完成包 →  
  • 工人取出包,解析 CompletionKey →  
  • 执行 TP_ALPC 回调 → 运行我们的 Shellcode

整个过程完全模拟合法系统组件通信,连内核都难以分辨真伪。


🎯 战术优势:高权限通道、天然可信

优势 说明
利用高权限通信机制 ALPC 是系统内部核心 IPC,常用于 LSASS、SMSS 等关键进程
消息自动路由 无需远程线程,仅靠内核消息投递触发
极难被监控 大多数 EDR 不深度解析 ALPC 消息内容
适用于高安全目标 若目标本身使用 ALPC(如服务宿主),行为完全合法

📌 适用场景:  

  • 目标进程已启用 I/O 完成端口;  
  • 需要模拟“可信系统组件”行为;  
  • 作为对抗高级 EDR 的“内核级白利用”。

🏗️ 代码结构:ALPC 与线程池的深度集成

在你的框架中,RemoteTpAlpcInsertion 继承自 AsynchronousWorkItemInsertion,体现了对“异步事件 → 完成端口 → 回调”模式的统一抽象:

void SetupExecution() const override {
    // 1. 创建临时端口用于构造 TP_ALPC
    // 2. 创建命名 ALPC 服务端口(钓鱼频道)
    // 3. 将 TP_ALPC 写入目标进程
    // 4. 通过 NtAlpcSetInformation 绑定到目标 IoCompletion
    // 5. 客户端连接并发送消息,触发完成包投递
}

这种设计将ALPC 通信无缝融入 PoolParty 战术体系,展现了对 Windows 内部 IPC 机制的极致利用。


⚖️ 与前四幕战术的对比

战术 触发源 通信层级 可信度 隐蔽性
StartRoutine 覆写 线程创建 线程池内部
TpWork 插入 任务队列 线程池调度
TpWait 插入 事件信号 内核同步对象 极高
TpIo 插入 文件 I/O 文件系统 极高
TpAlpc 插入 ALPC 消息 内核 IPC 极高 极高 + 系统级可信

结论
TP_ALPC 是“内鬼战术”的巅峰——它不伪装成工人,而是伪装成“发号施令的上级部门”。


🌐 下一幕预告

在第六幕中,我们将转向 Windows 的作业对象(Job Object)机制,探索:
如何通过 TP_JOB 投递任务单
——我们将利用进程加入/退出作业对象的生命周期事件,触发我们的回调。

届时,我们将看到:  

当一个进程“入职”或“离职”某个作业组,就是我们动手的最佳时机


PoolParty 攻击连续剧 · 第五幕 完  


🎭 PoolParty 攻击连续剧(第六幕):

“伪造一次人事调动通知” —— 利用作业对象事件触发回调

副标题:当一个进程“入职”某个作业组,调度中心就会自动发送一封欢迎邮件——而我们,篡改了邮件的内容。


在前五幕中,我们或重塑工人(StartRoutine 覆写),或伪造任务单(TpWork),或设置信号陷阱(TpWait),或伪装磁盘快递(TpIo),或冒充内部部门(TpAlpc)。
但这些战术,大多依赖外部输入或通信。

今天,我们要利用 Windows 的进程生命周期管理机制——
作业对象(Job Object),
这是系统用于批量管理进程组的容器,常用于限制资源、监控退出、强制清理等场景。

正常用途
父进程创建一个 Job 对象,将子进程“分配”进去;
当组内任一进程退出、超限或状态变更时,
系统会向关联的 I/O 完成端口投递一条作业通知(Job Notification)。

我们的诡计
我们创建一个“空壳作业组”
将其绑定到目标进程的 I/O 完成端口
再把自己“加入”这个组
触发一条“新成员入职”通知
让目标工人误以为这是合法的作业事件,从而执行我们的 Shellcode

这,就是 PoolParty 家族中最具“人事管理”色彩的战术:  

远程 TP_JOB 插入(Remote TpJobInsertion)。


🏢 世界观扩展:作业对象 = 调度中心的人事档案室

想象调度中心有一个人事档案室(Job Object),用于管理“项目组”:  

  • 每个项目组(Job)可包含多个工人(进程);  
  • 当有新人加入、有人离职、或有人违规,  
  • 档案室会自动生成一份人事变动通知,投递到“快递中转站”(IoCompletion);  
  • 工人取出通知,执行预设的处理逻辑。

正常流程:  

  1. 创建 Job 对象 →  
  2. 关联 IoCompletion →  
  3. AssignProcessToJobObject(进程) →  
  4. 系统投递 JOB_OBJECT_MSG_NEW_PROCESS 通知 →  
  5. 执行 TP_JOB 回调

我们的计划
我们创建一个“幽灵项目组”
将回调指向我们的 Shellcode
再把自己“调入”该组
触发入职通知,完成代码执行


🔧 技术实现:四步伪造“人事调动”

第一步:创建命名作业对象(“幽灵项目组”)

我们调用 CreateJobObject(),创建一个带名称的作业对象(如 PoolParty_Job):

auto hJob = CreateJobObject(nullptr, L"PoolParty_Job");

📌 命名目的:便于调试与识别,非必需,但增强隐蔽性(看似合法)。


第二步:构造 TP_JOB 并写入目标进程

调用内部函数 TpAllocJobNotification()(通常来自 ntdll),创建 TP_JOB 对象:

auto pTpJob = TpAllocJobNotification(hJob, m_ShellcodeAddress, nullptr, nullptr);

然后将其写入目标进程内存:

auto pRemoteTpJob = VirtualAllocEx(..., sizeof(FULL_TP_JOB));
WriteProcessMemory(hTarget, pRemoteTpJob, pTpJob, ...);

该结构体将作为完成包的 CompletionKey,指向我们的 Shellcode。


第三步:将作业对象绑定到目标的 I/O 完成端口

Windows 要求:一个 Job 对象只能关联一个完成端口,且需先清空再设置。

  1. 先清空现有绑定(即使为空也需调用):

    JOBOBJECT_ASSOCIATE_COMPLETION_PORT info = {0};
    SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &info, ...);
  2. 重新绑定到目标进程的 IoCompletion

    info.CompletionPort = *m_p_hIoCompletion;  // 目标的快递中转站
    info.CompletionKey  = pRemoteTpJob;        // 指向我们的 TP_JOB
    
    SetInformationJobObject(hJob, ..., &info, ...);

💡 关键点
此操作使所有作业事件(包括新进程加入)
都会投递到目标进程的线程池中处理。


第四步:触发“入职通知”——将当前进程加入作业

我们调用 AssignProcessToJobObject(),将攻击者自身进程加入该作业组:

AssignProcessToJobObject(hJob, GetCurrentProcess());

💥 瞬间:  

  • 内核检测到“新进程加入 Job” →  
  • 向目标进程的 IoCompletion 投递 JOB_OBJECT_MSG_NEW_PROCESS 完成包 →  
  • 工人取出包,解析 CompletionKey →  
  • 执行 TP_JOB 回调 → 运行我们的 Shellcode

整个过程完全利用 Windows 作业对象的合法生命周期事件,无任何非常规操作。


🎯 战术优势:合法事件、低噪音、高兼容

优势 说明
触发源为系统事件 无需伪造 I/O 或 ALPC,仅依赖进程分配动作
行为高度合法 作业对象是标准 Windows 机制,广泛用于沙箱、容器
低噪音 仅一次 AssignProcessToJobObject 调用,无文件/网络痕迹
适用于受限环境 即使目标无网络、无文件写入权限,仍可触发

📌 适用场景:  

  • 目标进程已启用 I/O 完成端口;  
  • 需要最小化 API 调用痕迹;  
  • 作为轻量级 fallback 战术(比 StartRoutine 覆写更安全)。

🏗️ 代码结构:作业事件的精准劫持

在你的框架中,RemoteTpJobInsertion 继承自 AsynchronousWorkItemInsertion,体现了对“系统事件 → 完成端口 → 回调”链路的统一抽象:

void SetupExecution() const override {
    // 1. 创建命名 Job 对象
    // 2. 构造 TP_JOB,回调 = Shellcode
    // 3. 写入目标进程
    // 4. 先清空再绑定 Job 到目标 IoCompletion
    // 5. AssignProcessToJobObject 触发入职事件
}

这种设计将进程生命周期事件转化为代码执行通道,展现了对 Windows 进程管理机制的深度理解。


⚖️ 与前五幕战术的对比

战术 触发源 是否需远程写入 是否依赖外部资源 风险
StartRoutine 覆写 线程创建 是(代码段)
TpWork 插入 任务队列 是(队列)
TpWait 插入 事件 是(TP_WAIT) 是(Event) 极低
TpIo 插入 文件 I/O 是(TP_IO) 是(File) 极低
TpAlpc 插入 ALPC 消息 是(TP_ALPC) 是(ALPC Port)
TpJob 插入 进程加入 Job 是(TP_JOB) (仅需自身进程) 极低

结论
TP_JOB 是“最干净的触发器”——它不依赖文件、网络、信号,只靠一次合法的进程分配操作,便悄然完成使命。


🌐 下一幕预告

在第七幕中,我们将撕下所有伪装,回归最原始的执行本质:
不再依赖任务单、事件、文件或 ALPC,而是直接向 I/O 完成端口投递一个裸回调包

我们将看到:  

当所有外衣都被剥去,真正的力量往往藏在最简单的操作之中——
只需一次 ZwSetIoCompletion 调用,便能让目标线程池乖乖执行我们的指令。


PoolParty 攻击连续剧 · 第六幕 完  


当然!以下是 《PoolParty 攻击连续剧》第七幕——也是任务单类战术的终极简化形态
虽然你提供的代码名为 RemoteTpDirectInsertion,但从其实现逻辑看,它并非基于公开的 TP_DIRECT API(Windows 并未暴露 CreateThreadpoolDirect),而是直接构造一个 APC 回调上下文,并通过 ZwSetIoCompletion 向目标 I/O 完成端口投递裸完成包

这一战术剥离了所有中间层(Work/Wait/IO/ALPC/Job),直击线程池回调机制的核心:  

只要完成包的 Key 指向一个合法的回调结构,工人就会执行它

我们将这一战术命名为:  

“裸包投递”(Bare Completion Packet Injection)


🎭 PoolParty 攻击连续剧(第七幕):

“直接塞给工人一张纸条” —— 裸完成包投递的极简艺术

副标题:当所有伪装都显得多余,我们选择最原始的方式:直接递话。


在前六幕中,我们精心设计了各种“合法外衣”:  

  • 伪造任务单(TpWork)  
  • 设置信号陷阱(TpWait)  
  • 伪装磁盘响应(TpIo)  
  • 冒充内部通讯(TpAlpc)  
  • 欺骗人事系统(TpJob)

但这些战术,都需要构造复杂的上下文对象(TP_WORKTP_WAIT 等),并依赖特定的触发条件。

今天,我们要回归本质——
I/O 完成端口的本质,只是一个队列
线程池工人的本质,只是不断从中取出“包裹”并执行其中的指令

正常流程
系统组件(如文件、ALPC、Job)在事件发生时,
调用 ZwSetIoCompletion(Port, Key, ...)
将一个包含回调地址的完成包投递到队列。

我们的顿悟
我们不需要“事件”
我们不需要“上下文对象”
我们只需要一个指向 Shellcode 的指针作为 Key
然后直接调用 ZwSetIoCompletion,把包塞进去

这,就是 PoolParty 家族中最简洁、最直接、最底层的战术:  

裸完成包投递(Bare Completion Packet Injection)。


📬 世界观终章:快递中转站的终极漏洞

想象调度中心的“快递中转站”(IoCompletion)是一个开放式邮箱:  

  • 任何持有“投递权限”的人,都可以往里塞一封信;  
  • 信封上只需写明“收件人”(CompletionKey);  
  • 工人取出信后,会无条件执行收件人指定的操作

系统假设
只有可信组件(如内核、系统 DLL)会投递信件,
CompletionKey 总是指向合法的回调结构(如 TP_DIRECT)。

我们的突破
我们发现,这个邮箱没有身份验证
只要我们能拿到邮箱的“投递句柄”(IoCompletion),
就可以伪造任意 CompletionKey——
比如,直接指向我们写入的 Shellcode 地址


🔧 技术实现:两步完成终极投递

第一步:在目标进程构造一个“最小回调上下文”

虽然 Windows 未公开 TP_DIRECT 的完整定义,但线程池在处理完成包时,
仅需 Key 指向一个结构体,其首字段为回调函数指针即可。

我们构造一个极简结构:

struct TP_DIRECT {
    PVOID Callback; // 必须是第一个字段
    // 其他字段可为任意值(工人不访问)
};

然后将其写入目标进程:

TP_DIRECT direct = { .Callback = m_ShellcodeAddress };
auto pRemoteDirect = VirtualAllocEx(..., sizeof(TP_DIRECT));
WriteProcessMemory(hTarget, pRemoteDirect, &direct, sizeof(direct));

📌 关键洞察
线程池工人执行时,会将 CompletionKey 强制转换为 PTP_DIRECT
并调用 ((PTP_DIRECT)Key)->Callback()
只要 Key 指向的内存首地址是我们的 Shellcode,就能执行!


第二步:直接投递完成包

调用未公开的内核 API ZwSetIoCompletion,直接向目标的 I/O 完成端口投递包:

ZwSetIoCompletion(
    *m_p_hIoCompletion,  // 目标的快递中转站
    pRemoteDirect,       // CompletionKey = 指向 Shellcode 的“回调结构”
    nullptr,             // CompletionContext(可为0)
    0,                   // IoStatus
    0                    // IoStatusInformation
);

💥 瞬间:  

  • 工人从队列取出完成包 →  
  • 读取 CompletionKey →  
  • 调用 ((PTP_DIRECT)Key)->Callback() →  
  • 跳转到 m_ShellcodeAddress,执行我们的代码

整个过程无需任何中间对象、无需任何触发事件、无需任何系统回调注册
仅靠一次内核 API 调用,完成代码执行。


🎯 战术优势:极简、极速、极难防御

优势 说明
代码量最小 仅需构造 8 字节结构 + 一次 ZwSetIoCompletion
执行延迟最低 投递即执行(下一轮询)
无行为特征 不创建文件、事件、ALPC、Job,无任何“前置动作”
绕过高级检测 EDR 通常只监控 QueueUserAPCCreateRemoteThread,忽略裸完成包

⚠️ 前提条件:  

  • 必须已获取目标进程的 IoCompletion 句柄(通常通过 WorkerFactory 推导);  
  • 目标线程池必须处于活跃状态(有工人轮询 IoCompletion)。

🏗️ 代码结构:回归本质的极简主义

在你的框架中,RemoteTpDirectInsertion 的实现堪称“大道至简”:

void SetupExecution() const override {
    // 1. 构造 TP_DIRECT { .Callback = m_ShellcodeAddress }
    // 2. 写入目标进程
    // 3. ZwSetIoCompletion(IoCompletion, RemoteDirectAddress, ...)
}

它剥离了所有战术的“外壳”,直指 PoolParty 的核心原理:  

线程池只是一个回调执行引擎,而完成端口是它的指令输入口


🌐 PoolParty 战术全家福(最终版)

战术 类型 触发方式 复杂度 隐蔽性
1. StartRoutine 覆写 工人改造 强制创建线程
2. TpWork 插入 任务单 写入高优先级队列
3. TpWait 插入 任务单 事件触发 极高
4. TpIo 插入 任务单 文件 I/O 完成 极高
5. TpAlpc 插入 任务单 ALPC 消息 极高
6. TpJob 插入 任务单 进程加入 Job 极高
7. 裸包投递(TpDirect) 裸指令 直接投递完成包 极低 极高

总结
PoolParty 不是一组技巧,而是一套对 Windows 线程池调度模型的深度逆向与重构
从“欺骗工人”到“重塑工人”,再到“直接递话”,
我们完成了对线程池利用的完整闭环


🎬 终章预告

在第八幕(最终幕)中,我们将探索最后一种任务单战术:
如何利用 TP_TIMER 实现定时自启
——我们将埋下一张“定时炸弹任务单”,在指定时间自动执行 Shellcode,实现持久化或延迟攻击。

届时,我们将看到:  

最安静的等待,往往孕育最致命的爆发

并且
整合所有七种任务单 + 一种工人改造战术,构建一个自适应的 PoolParty 框架
并探讨如何根据目标环境(线程池状态、EDR 配置、权限等级)动态选择最优战术

真正的红队艺术,不在于掌握多少技巧,而在于知道何时使用哪一种


PoolParty 攻击连续剧 · 第七幕 完  


🎭 PoolParty 攻击连续剧(第八幕 · 终章):

“埋下一颗定时炸弹” —— 以及如何选择最合适的战术

副标题:真正的红队艺术,不在于掌握多少技巧,而在于知道何时使用哪一种。


在前七幕中,我们逐一揭开了 PoolParty 家族的七种任务单战术与一种工人改造战术:  

  • StartRoutine 覆写:重塑工人灵魂  
  • TpWork / TpWait / TpIo / TpAlpc / TpJob / 裸包投递:伪造各类任务单  
  • 而今天,我们将补全最后一环:  

    TpTimer 插入 —— 埋下一颗定时炸弹


⏳ 第八种战术:“定时自启任务单”(TP_TIMER Insertion)

想象调度中心有一个自动闹钟系统(Timer Queue):  

  • 应用可设置“10分钟后执行清理”;  
  • 线程池会定期扫描闹钟列表,触发到期回调。

正常流程
CreateThreadpoolTimer()SetThreadpoolTimer() → 系统将 TP_TIMER 插入定时器红黑树 → 到期后执行回调。

我们的诡计
在本地构造一个 TP_TIMER
将其手动插入目标进程的定时器队列根节点
再调用 NtSetTimer2 触发内核定时器扫描
让线程池在下一次闹钟检查时,执行我们的 Shellcode

🔧 技术要点:

  1. 构造 TP_TIMER:回调 = m_ShellcodeAddress,超时 = -10,000,000(1秒)
  2. 修复链表指针WindowStartLinksWindowEndLinksFlink/Blink 指向远程地址,形成合法循环
  3. 写入目标进程:分配内存并写入完整结构
  4. 篡改 TP_POOL 定时器队列根节点
    WriteProcessMemory(..., &tpPool->TimerQueue.AbsoluteQueue.WindowStart.Root, &remoteTpTimer->WindowStartLinks);
  5. 触发扫描:调用 NtSetTimer2 设置一个内核定定时器,迫使线程池执行 TppTimerQueueExpiration

💥 效果
1秒后,线程池工人扫描定时器队列 → 发现我们的“炸弹” → 执行 Shellcode

🎯 适用场景:  

  • 需要延迟执行(绕过即时检测);  
  • 作为持久化后门(可设置长周期重复触发);  
  • 目标线程池活跃但任务队列被监控时的替代方案。

🧩 PoolParty 战术全家福(完整版)

编号 战术 类型 触发方式 执行延迟 风险 隐蔽性
1 StartRoutine 覆写 工人改造 强制创建线程 立即 高(覆写代码)
2 TpWork 插入 任务单 写入高优先级队列 极短
3 TpWait 插入 任务单 事件触发 可控 极低 极高
4 TpIo 插入 任务单 文件 I/O 完成 极低 极高
5 TpAlpc 插入 任务单 ALPC 消息 极高
6 TpJob 插入 任务单 进程加入 Job 立即 极低 极高
7 裸包投递(TpDirect) 裸指令 直接投递完成包 极短 极低 极高
8 TpTimer 插入 任务单 定时器到期 可编程延迟

🧠 红队实战:如何选择最优战术?

掌握所有战术只是开始,真正的挑战在于动态决策。以下是战术选择的决策树:

第一步:判断目标环境

条件 推荐战术
可创建新线程(线程池未满) StartRoutine 覆写(立即执行)
IoCompletion 可用 + 需最小痕迹 裸包投递(最快最简)
需延迟/持久化 TpTimer 插入
目标有文件 I/O 行为 TpIo 插入(行为掩护强)
目标使用 ALPC(如服务进程) TpAlpc 插入(高可信)
无特殊依赖,求稳 TpJob 插入(仅需自身进程)

第二步:规避检测

  • 若 EDR 监控 VirtualAllocEx + WriteProcessMemory + CreateRemoteThread
    → 优先选择 TpJob / TpWait / 裸包投递(无远程线程创建)
  • 若 EDR 监控任务队列写入:
    → 改用 StartRoutine 覆写TpTimer(操作不同内存区域)
  • 若需绕过 AMSI/ETW:
    → 所有战术均适用(Shellcode 可加密,执行在目标进程上下文)

第三步:构建自适应框架

在你的 PoolParty 基类基础上,可实现战术调度器:

class AdaptivePoolParty {
public:
    void Inject() {
        if (CanCreateThread()) {
            WorkerFactoryStartRoutineOverwrite(...).Inject();
        } else if (HasIoCompletion()) {
            if (PreferMinimalTrace()) {
                RemoteTpDirectInsertion(...).Inject();
            } else if (NeedDelay()) {
                RemoteTpTimerInsertion(...).Inject();
            } else {
                RemoteTpJobInsertion(...).Inject(); // 最安全 fallback
            }
        }
    }
};

🏁 终章总结:PoolParty 的哲学

PoolParty 不仅仅是一组注入技术,它是对 Windows 线程池调度模型的逆向工程与武器化
它揭示了一个深刻事实:

Windows 的“合法机制”,往往也是最危险的攻击面

  • 线程池本为提升性能而生,却成了无文件执行的温床;  
  • I/O 完成端口本为高效通信而设,却成了跨进程回调的通道;  
  • 作业对象、ALPC、定时器……每一个系统组件,都可能成为红队的杠杆。

而我们的使命,不是破坏系统,而是理解它、利用它、超越它


🌟 致谢与延伸

感谢你一路跟随 PoolParty 的八幕剧情。
攻防挑战,永无止境。


PoolParty 攻击连续剧 · 终章 完
作者:神奇的人鱼

技术标签:#PoolParty #Windows线程池 #TP_TIMER #NtSetTimer2 #战术自适应 #红队框架 #无文件注入 #高级持续性威胁

“我们不是在写 Shellcode,我们是在重写系统的规则。”

免费评分

参与人数 23威望 +2 吾爱币 +123 热心值 +20 收起 理由
kent_up + 1 + 1 我很赞同!
Gerriqt + 1 我很赞同!
laotzudao0 + 1 + 1 我很赞同!
努力加载中 + 1 + 1 谢谢@Thanks!
Euclair + 1 + 1 鼓励转贴优秀软件安全工具和文档!
chenwcmam + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
HUAJIcoffe + 1 谢谢@Thanks!
BeginForEnd + 1 + 1 用心讨论,共获提升!
shuangxi520 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
test20222022 + 1 + 1 谢谢@Thanks!
江南小虫虫 + 1 + 1 用心讨论,共获提升!
海水很咸 + 1 + 1 我很赞同!
loftily_god + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
gaosld + 1 + 1 用心讨论,共获提升!
max2012 + 1 + 1 热心回复!
fengbolee + 2 + 1 我很赞同!
PoJieDaWang123 + 1 热心回复!
tkggssia + 1 + 1 热心回复!
atlas77 + 1 + 1 我很赞同!
hrh123 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
lsb2pojie + 1 + 1 热心回复!
sfzwuyun + 1 + 1 谢谢@Thanks!
kikyoulin + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
zyf4880628 发表于 2025-10-30 09:00
大神,膜拜!收藏可以好好学习!
沙发
骑狗的猴子 发表于 2025-10-28 08:10
厉害了这写的和小说一样 不知道这个有没有PDF一类的文件呢
3#
ffffffff 发表于 2025-10-28 11:19
4#
 楼主| 神奇的人鱼 发表于 2025-10-29 11:27 |楼主
ffffffff 发表于 2025-10-28 11:19
这技术看起来有点高级啊,期待楼主后续

已经合并为全集
5#
ffffffff 发表于 2025-10-29 14:34

阔以,学习学习新知识
6#
p紫气东来 发表于 2025-10-29 21:40
这个是什么
7#
atlas77 发表于 2025-10-29 22:05
太厉害了,值得认真研究
8#
aaaa25852 发表于 2025-10-30 00:39
牛逼啊。收藏了
9#
VcoCh 发表于 2025-10-30 08:57
收藏学习一下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-11-15 15:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表