等待线程劫持:线程执行劫持的更隐蔽版本

admin 2025年4月16日15:29:01评论3 views字数 20268阅读67分33秒阅读模式

【翻译】WAITING THREAD HIJACKING A STEALTHIER VERSION OF THREAD EXECUTION HIJACKING

要点

  • 进程注入(Process Injection)是攻击者工具箱中的重要技术之一。在不断的猫鼠游戏中,攻击者试图发明能够绕过防御的新实现方式,使用创新方法和鲜为人知的 API。
  • 通过以非典型方式组合常见的构建模块,Check Point Research 能够创建一个已知方法——线程执行劫持(Thread Execution Hijacking)的更加隐蔽的版本。

引言

进程注入是攻击者使用的重要技术[1]之一。我们几乎可以在每一个恶意软件中找到它的变种实现。它服务于以下目的:

  • 防御规避:在不同进程的掩护下隐藏恶意模块
  • 干扰现有进程:读取它们的内存,钩取使用的 API 等
  • 权限提升

我们之前关于进程注入[2]博客[3]中,我们解释了这个主题的基础知识以及检测和防御的基本思路。我们还提出了一种名为Thread Name-Calling[4]的新技术——滥用 Thread Name API,该 API 原本用于为运行中的线程分配名称。该技术允许使用没有写入权限的句柄向进程写入数据。远程执行是通过异步过程调用 (APC) 的新 API[5]实现的,请求 Special User APC。借助这些构建模块,我们能够注入有效载荷并运行它,而不被大多数测试的 EDR(终端检测与响应系统) 注意到。虽然远程写入是使用意想不到的 API 实现的,但执行并不完全新颖,因为它是APC 注入[6]的一种变体。

在当前研究中,我们的目标相同:隐蔽地注入到正在运行的进程中。这次我们从相反的方向解决问题。我们使用常见的分配和写入原语来实现意外的代码执行。此外,我们展示了如何扩展实现并混淆调用的 API 序列,以干扰可用于检测的行为特征。所描述的技术拦截等待线程的流程并滥用它来执行植入物——这就是为什么我们将其称为等待线程劫持 (Waiting Thread Hijacking, WTH)。它可以被视为经典线程劫持[7]的演进。与旧方法不同,WTH 避免使用触发大多数警报的 API,如SuspendThread / ResumeThread`SetThreadContext`[8]

它涉及具有以下访问权限的句柄:

  • 对于目标进程:PROCESS_VM_OPERATIONPROCESS_VM_READPROCESS_VM_WRITE
  • 对于目标线程:THREAD_GET_CONTEXT

使用的 API:

  • NtQuerySystemInformation(参数为SystemProcessInformation
  • GetThreadContext
  • ReadProcessMemory
  • VirtualAllocEx
  • WriteProcessMemory
  • VirtualProtectEx

考虑的目标是具有中等完整性级别[9]的 64 位进程。

执行远程代码

为了防止远程代码执行,杀毒软件和 EDR 尝试监控与已知技术相关的 API(监控函数的例子可以在 Mr-Un1k0d3r 的仓库中找到:https://github.com/Mr-Un1k0d3r/EDRs[10])。限制的实现因产品而异。虽然一些产品可能容忍远程内存分配和写入,但尝试运行植入物肯定会导致被检测。触发执行的选项非常有限,所有用于此目的的 API 都受到严密监视。这就是为什么找到一种新的、意外的执行方式是攻击者和红队成员在尝试实现代码注入时面临的最大挑战之一。

基本限制是代码在线程[11]上运行。这给我们留下两种可能性:要么在目标进程中创建新线程,要么使用一些现有线程。运行代码的另一种方式是纤程[12](例如:ImmoralFiber,BlackHat Asia 2024[13]),尽管在底层它们仍然由线程管理[14]。虽然使用纤程可以帮助隐藏本地进程内的执行,但在实现远程执行方面帮助不大。为了使用它们在远程进程中运行代码,目标必须已经有现有的纤程。这大大缩小了可能目标的列表,使得这种方法在大多数实际场景中无法使用。

远程执行注入代码的常见方法:

  • 远程线程创建
  • 将函数添加到现有线程的 APC 队列
  • 直接操作现有线程的上下文(GetThreadContext/SetThreadContext)。这包括使用`RtlRemoteCall`[15] API(因为它在底层调用GetThreadContext/SetThreadContext)。
  • 安装钩子
  • 替换定义的回调函数

让我们逐一分析每个选项。

创建远程线程很简单,涉及使用CreateRemoteThread[16]系列中的一个 API。虽然我们可以以各种方式应用它[17],调用这些函数的高级或低级版本,甚至使用原始系统调用,但最终结果不会很隐蔽。创建新线程会生成内核回调,使 EDR 能够快速获知可疑意图(通过`PsSetCreateThreadNotifyRoutine`[18]/`Ex`[19])。

这导致我们得出结论,完全放弃创建新线程,并以某种方式利用现有线程会更有效。最直接和方便的方法是使用 APC,正如在Thread Name-Calling 博客[20]中详细解释的那样。然而,这种方法也有缺点。在这种情况下,内核回调不会被触发,但它会生成ETW 事件[21],杀毒软件/EDR 产品可以监听这些事件。其次,必须以THREAD_SET_CONTEXT访问权限打开线程句柄。打开句柄的事件也会生成内核回调(可以通过内核模式中的ObRegisterCallbacks[22]监控),因此我们应该避免可疑标志,并最小化请求的访问权限。

直接操作现有线程是另一种古老且众所周知的方法的一部分,称为线程执行劫持[23](Thread Execution Hijacking)。它涉及在目标进程中找到一个线程,暂停它,更改其上下文以将执行流重定向到我们自己的代码,然后恢复它。与 APC 注入一样,它需要线程句柄以THREAD_SET_CONTEXT权限打开,还需要另一个可疑标志:THREAD_SUSPEND_RESUME。另一个问题是,与此方法相关的 API 通常受到监控,这将使我们被 EDR 阻止。如果我们放弃所有需要修改线程上下文的函数,我们只剩下对目标进程内存的简单写入。这看起来不多,但创造性地使用它仍然可以实现代码执行。

这类方法中的一个基本思路是 API 钩子。我们可以预测进程中有一些函数将在某个时刻被调用。如果我们拦截它们,我们可以让我们的代码一起运行。实现钩子有多种不同选项:内联钩子以及 IAT(导入地址表)或 EAT(导出地址表)钩子。然而,安装它们需要覆盖已加载 DLL 的一部分,这通常会引发警报。

更具创造性的方法基于进程可能使用在特定事件上执行的各种回调函数的事实。如果我们覆盖指向回调函数的指针,我们可以强制执行我们的代码。有许多出版物详细介绍了这一组中的不同方法。一些例子包括:

  • 覆盖 TLS 回调[24]
  • 覆盖内核回调表[25]
  • 覆盖 GUI 回调(例如:odzhan 博客[26] #1)
  • 覆盖 ETW 回调(例如:odzhan 博客[27] #2)
  • PROPagate – 使用 Windows 子类化运行代码(hexacorn 博客[28] #1,hexacorn 博客[29] #2)

Alon Leviev 在他关于[Thread "Pool-Party"](https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/ "Thread "Pool-Party"")的出版物中描述的技术是这一组的一个有趣且新颖的补充。他的想法基于 Windows 大量使用线程池[30]的事实。通过获取目标的 Worker Factory 句柄,我们可以操作其结构以链接到我们的植入物并强制其执行。例如,我们可以覆盖 Worker Factory 结构中的起始地址,将其重定向到我们的 shellcode,然后触发其运行。也可以通过手动编写其结构并将其附加到 Worker Factory 队列来在远程进程中安装新的工作项。

在我们将要介绍的解决方案中,Alon 论文中提到的一些观察结果也将被使用。然而,整个技术要简单得多,基于操作工厂产生的线程之一,而不是工厂本身。

等待线程劫持解决了哪些问题?

等待线程劫持(Waiting Thread Hijacking)是一种受到经典线程执行劫持[31]概念和关于线程池[32]研究的启发的方法。然而,它的使用并不严格限于属于线程池的线程。

让我们思考一下经典线程执行劫持的最大问题是什么。

  • 首先,我们必须暂停远程线程,然后恢复它。这可能会在目标应用程序中造成一些同步问题。但最重要的是,它迫使我们使用SuspendThread/ResumeThread API,以及具有THREAD_SUSPEND_RESUME访问权限的线程句柄。这些是可疑指标,肯定会被 EDR 观察到。
    • 如果我们能找到一个进程,其中这些操作在目标端自动完成,我们就能避免这个障碍...
  • 其次,我们需要将暂停线程的执行重定向到植入物。在这种技术的经典版本中,使用SetThreadContext(或其低级等效项`NtSetContextThread`[33])。重定向是通过修改指令指针完成的。直接设置线程上下文非常可疑,且噪声大(生成两个不同的 ETW 事件)[34]。它很可能会被杀毒软件/EDR 标记。此外,它需要句柄具有THREAD_SET_CONTEXT访问权限,这是我们想要避免的。
    • 是否可能在不手动修改其上下文的情况下更改线程的执行流?
  • 最后,返回原始执行流不会无缝衔接。我们需要在某处存储原始线程上下文,并在 shellcode 退出时恢复它。目标进程的稳定性可能面临风险。
    • 是否可能在不需要再次设置线程上下文的情况下返回原始执行?

事实证明,这些限制可以通过一些简单的技巧来克服。

缓冲区溢出利用中使用的一个老式步骤是用我们自己的地址替换函数的返回地址。一个有效的漏洞利用可能涉及其他原语,如分配可执行内存,或添加执行权限。为了规避这一点,发明了像DCP(动态代码禁止)[35]这样的限制。这些也是反漏洞利用软件将监控的典型事件。然而,如果可执行内存是通过外部进程在目标内分配的,DCP 策略不适用。事实证明,如果替换返回地址是在外部完成的,这一事件也可能不被注意到。在当前技术中,我们利用了这一点。

不过,我们需要非常谨慎地选择在哪些情况下可以覆盖返回地址。由于我们必须避免手动暂停/恢复线程,我们将使用那些通过自身特性让我们达到相同效果的线程。也就是说,等待线程,我们可以在不破坏整个应用程序的情况下操作它们的返回地址。这样的线程可以通过线程池轻松找到。

线程池基础

线程池的概念[36]自 Windows 早期版本就已存在,其现代形式在 Vista 中引入。API 的详细信息及其演变在MSDN 上有描述[37]

线程池是优化线程创建和任务处理的方式。与每次有新事件要处理时创建新线程不同,我们有一组现成的可重用线程。线程池架构的一部分是工作队列和等待线程的存在。

通过使用 Process Explorer 或System Informer[38]等工具检查进程,您可以观察线程池机制的工作。大多数默认 Windows 进程由多个线程组成,其中许多处于等待状态。这些线程的调用栈包含以Tp*为前缀的函数 - 代表线程池(Thread Pool)。

等待线程劫持:线程执行劫持的更隐蔽版本
图 1 - ProcessExplorer 视图。属于池的线程的调用栈示例,在 WrQueue 中等待。

利用线程池

在任何现代 Windows 系统上,我们都能找到大量具有等待线程的进程[39],这些线程由线程池管理。它们可以提供构建块来创建更隐蔽版本的线程执行劫持。

我们可以利用一个处于休眠状态的等待线程,而不是暂停然后恢复现有线程之一。这些线程将在等待的事件上自动重新激活,给我们带来我们想要实现的效果,而无需调用外部 API。

另一个需要解决的问题与更改线程上下文有关。这次我们不会直接操作指令指针。在考虑的情况下,我们处理的是在系统调用上等待其完成的线程。最后执行的函数只是 NTDLL 中相应的系统调用包装器。知道这些包装器使用非常简单的布局,我们确信栈上的最后一个元素是返回地址。因此,我们可以简单地获取线程的上下文,读取栈指针,然后用指向我们自己代码的指针替换返回地址。

执行这些操作所需的只是具有`THREAD_GET_CONTEXT`[40]的线程句柄,以及具有读/写权限的进程句柄(PROCESS_VM_READ | PROCESS_VM_WRITE[41])。

虽然涉及暂停/恢复和手动上下文设置的线程干扰将被大多数 EDR 阻止,但这种方法允许避开常见触发器并实现代码执行。

识别合适的线程

理论上,任何等待线程,无论等待原因如何,都可能被劫持。但当然,我们不想在被攻击的应用程序中引入同步问题。一个安全的选择是选择等待原因为WrQueue的线程。这个原因表明线程正在等待KQUEUE对象,这是一个用于管理 IRP 队列的内核对象。

要找到它们,我们使用带参数SystemProcessInformation的本机函数NtQuerySystemInformation,允许我们枚举所有进程和线程并提取有用信息。对于每个线程,它检索结构`SYSTEM_THREAD_INFORMATION`[42]。字段ThreadState允许检查线程当前是否正在等待,而WaitReason提供了更多关于原因的信息。

WrQueue等待原因的存在表明我们正在处理两种可能情况之一。

  1. 等待发生在由 kernelbase → GetQueuedCompletionStatus调用的NtRemoveIoCompletion内部:
等待线程劫持:线程执行劫持的更隐蔽版本

图 2 - 函数GetQueuedCompletionStatus内部。高亮显示的行显示了系统调用包装器的返回地址。

  1. 等待发生在由 ntdll → TppWorkerThread调用的NtWaitForWorkViaWorkerFactory内部
等待线程劫持:线程执行劫持的更隐蔽版本
图 3 - 函数TppWorkerThread内部。高亮显示的行显示了系统调用包装器的返回地址。

每张图片中原始返回地址以灰色高亮显示。

在这两种情况下,情况都有利于劫持。线程在系统调用包装函数内的系统调用完成上等待。这些包装器不创建栈帧。栈顶部的第一个参数是返回地址,指向调用者的函数。

确保圆满结局

实现注入方法时要记住的重要事项之一是确保在执行我们的代码后目标应用程序不会崩溃。

在等待线程劫持中,我们用自己的地址替换原始地址。因此,一旦等待完成,返回地址将从栈中获取,并被跟随。由于现在它被覆盖为指向我们的 shellcode 的指针,这将导致植入物的执行。然而,我们中断了函数的正常执行流程,因此目标进程的稳定性存在风险。防止最终崩溃的一种方法是使 shellcode 永不返回 - 例如,通过使其陷入无限Sleep循环。但这不是最佳解决方案。例如,我们将错过处理线程原本等待的一些事件。虽然这可能不会导致严重问题,但仍然不是最优的。

幸运的是,使用适当的存根(stub)恢复原始执行流程相对简单。

模板:

[SAVED_RET_PTR] ; space where the original return pointer will be savedpushf           ; store original flags, and values of registers:push rax push rcxpush rdxpush rbxpush rbppush rsipush rdipush r8push r9push r10push r11push r12push r13push r14push r15call shellcode_main ; call our main shellcode functionpop r15             ;restore original flags, and values of registers:pop r14pop r13pop r12pop r11pop r10pop r9pop r8pop rdipop rsipop rbppop rbxpop rdxpop rcxpop raxpopfjmp qword ptr ds:[SAVED_RET_PTR] ;jump to the original return address[shellcode_main] ; the vital part of the shellcode

存根(stub)从一个指针大小的空闲空间开始。这是注入器在替换之前保存先前返回地址的地方。代码从之后开始。它保存标志位和所有可能包含稍后继续正常执行所需的重要数据的通用寄存器。在保存上下文后,我们现在可以执行负责任何计划活动的 shellcode(在概念验证 PoC 的情况下,传统上是弹出 calc.exe)。主函数返回后,我们恢复之前保存的寄存器和标志位,然后跳转到原始返回地址。

让我们看看它的实际运行情况。

步骤 1 - 当等待完成时,执行即将跟随返回地址。在我们的例子中,栈上的地址已被替换。

等待线程劫持:线程执行劫持的更隐蔽版本

图 4 - 在系统调用包装器内部:NtWaitForWorkViaWorkerFactory。系统调用完成后,函数将从栈中获取第一个条目(已被覆盖),并将其用作返回地址。

步骤 2 - shellcode 开始执行,首先保存所有标志位和寄存器。它们将在最后被恢复,以恢复原始值。

等待线程劫持:线程执行劫持的更隐蔽版本

图 5 - 植入的 shellcode 中存根的视图。右侧面板显示 shellcode 执行前寄存器的值。

步骤 3 - shellcode 完成了执行。寄存器被恢复到与开始时相同的状态。shellcode 末尾的跳转将带我们回到原始返回地址。

等待线程劫持:线程执行劫持的更隐蔽版本

图 6 - 植入的 shellcode 中存根的视图。右侧面板显示 shellcode 执行后寄存器的值。

步骤 4 - 执行返回到原始函数。

等待线程劫持:线程执行劫持的更隐蔽版本
图 7 - 植入完成后,执行流程返回到系统调用包装器通常返回的位置。

图 7 - 植入完成后,执行流程返回到系统调用包装器通常返回的位置。

实现

有了所有这些构建模块,让我们看看最终的实现。

等待线程劫持:线程执行劫持的更隐蔽版本

图 8 - 显示等待线程劫持(Waiting Thread Hijacking)所有主要步骤的动画。

写入(和可能的分配)以标准方式完成,使用VirtualAllocEx + WriteProcessMemory。在 shellcode 植入目标后,我们继续设置其执行。枚举进程中的所有线程,我们扫描当前处于等待模式的线程,选择其中一个,并覆盖其返回地址。一旦线程切换回活动模式,它将恢复执行,返回到被替换的地址,该地址现在指向植入物。执行植入物后,线程应无缝返回其常规职责。

代码片段:

bool check_ret_target(LPVOID ret){    HMODULE mod = get_module_by_address((LPVOID)ret);if (mod == NULL) {        std::cout << "Pointer not in any recognized module.n";return false;    }if (mod == GetModuleHandleA("ntdll.dll") ||         mod == GetModuleHandleA("kernelbase.dll") ||         mod == GetModuleHandleA("kernel32.dll"))    {return true;    }    std::cout << "Pointer not in ntdll/kernel32.n";return false;}bool run_injected(DWORD pid, ULONGLONG shellcodePtr, KWAIT_REASON wait_reason){    std::cout << "Enumerating threads of PID: " << pid << "n";    std::map<DWORD, thread_info> threads_info;if (!pesieve::util::fetch_threads_info(pid, threads_info)) {return false;    }    HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pid);if (!hProcess) return false;    CONTEXT ctx = { 0 };    ULONGLONG suitable_ret_ptr = 0;    ULONGLONG suitable_ret = 0;    std::cout << "Threads: " << threads_info.size() << std::endl;for (auto itr = threads_info.begin(); itr != threads_info.end(); ++itr) {        thread_info& info = itr->second;if (!info.is_extended) return false;if (info.ext.state == Waiting) {            std::cout << "TID: " << info.tid << std::hex << " : wait reason: " << std::dec << info.ext.wait_reason << "n";if (info.ext.wait_reason != wait_reason || !read_context(info.tid, ctx)) {continue;            }            ULONGLONG ret = read_return_ptr<ULONGLONG>(hProcess, ctx.Rsp);            std::cout << "RET: " << std::hex << ret << "n";if (!suitable_ret_ptr) {if (!check_ret_target((LPVOID)ret)) {                    std::cout << "Not supported ret target. Skipping!n";continue;                }                suitable_ret_ptr = ctx.Rsp;                suitable_ret = ret;                std::cout << "tUsing as a target!n";break            }        }else {            std::cout << "TID: " << itr->first << "is NOT waiting, State: " << info.ext.state << "n";        }    }    bool is_injected = false;if (suitable_ret_ptr) {        // overwrite the shellcode with the jump back        SIZE_T written = 0;if (ntapi::WriteProcessMemory(hProcess, (LPVOID)shellcodePtr, &suitable_ret, sizeof(suitable_ret), &written) && written == sizeof(suitable_ret)) {            std::cout << "Shellcode ptr overwritten! Written: " << written << " n";        }else {            std::cout << "Failed to overwrite shellcode jmp back: " << std::hex << GetLastError() << "n";return false;        }if (!protect_memory(pid, (LPVOID)shellcodePtr, sizeof(g_payload), PAGE_EXECUTE_READ)) {            std::cerr << "Failed making memory executable!n";return false;        }        shellcodePtr += 0x8; // after the saved return...        std::cout << "Trying to overwrite: " << std::hex << suitable_ret_ptr << " -> " << suitable_ret << " with: " << shellcodePtr << std::endl;if (ntapi::WriteProcessMemory(hProcess, (LPVOID)suitable_ret_ptr, &shellcodePtr, sizeof(shellcodePtr), &written) && written == sizeof(shellcodePtr)) {            std::cout << "Ret overwritten!n";            is_injected = true;        }    }    CloseHandle(hProcess);return is_injected;}bool execute_injection(DWORD processID){    LPVOID shellcodePtr = NULL;    {        HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION, FALSE, processID);if (!hProcess) return false;        shellcodePtr = ntapi::VirtualAllocEx(hProcess, nullptr, sizeof(g_payload), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);        CloseHandle(hProcess);if (!shellcodePtr) return false;    }    {        HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, processID);if (!hProcess) return false;        SIZE_T written = 0;        bool isOk = ntapi::WriteProcessMemory(hProcess, (LPVOID)shellcodePtr, g_payload, sizeof(g_payload), &written);        CloseHandle(hProcess);if (!isOk) return false;    }return run_injected(processID, (ULONG_PTR)shellcodePtr, WrQueue);}

PoC 的完整源代码可在 GitHub 上获取[43]

演示

Windows 11 24H2 上的演示

https://youtu.be/CZIR4gq4jQ0

源代码:

https://github.com/hasherezade/waiting_thread_hijacking[44]

执行流程混淆

一些产品基于行为特征进行检测,这些特征由连续调用的 API 序列组成。虽然对远程进程的单次写入可能会被放行,但如果同一进程进行其他有趣的调用,可能会形成注入技术的标记。

扰乱以进程为中心的行为特征的一种方法是应用简单的混淆,并将我们技术的每个步骤分割到不同的子进程中。

在这个模型中,具有读写访问权限的内存分配由第一个进程执行。然后将分配的地址存储在环境变量中并传递给下一个子进程,该子进程用内容填充新的内存区域。然后,最后的子进程将分配内存的访问权限更改为读 - 执行。最后一个子进程替换等待线程的返回指针,将其重定向到先前写入的 shellcode。

如果我们愿意接受小的并发风险,我们也可以将执行分为三个步骤而不是四个步骤。使用等待线程劫持,植入物的执行不是即时的,而是取决于接收到的事件。因此,在堆栈上的指针已经被替换后,可以将 shellcode 设为可执行作为最后一步。

使用SPLIT_STEPS标志编译 PoC 时,可以获得具有此混淆方法的增强演示。

结论

等待线程劫持依赖于对远程进程的写入。然而,一些终端检测和响应 (EDR) 系统可能会阻止任何远程写入的尝试,从而有效地阻止这种技术。

为了克服这些限制,最好使用非常规写入(如 Thread Name-Calling 博客中所示)。但这里出现了先有鸡还是先有蛋的问题:要做到这一点,我们需要远程执行一些 API。因此,这违背了这种技术的目的,即隐蔽执行。像 NINA[45] 和 GhostWriting[46] 这样的技术试图在不使用 APC(异步过程调用)的情况下解决写入问题,但它们有自己的权衡。例如,它们使用 SetThreadContext[47],这仍然可被许多 EDR 系统检测和阻止。

另一方面,有些产品对远程执行方法非常严格,但对内存分配和写入则更为宽松。在这种情况下,WTH 仍然可以有效地隐藏植入代码运行的时间点,尽管使用了常见的写入原语,但仍能保持未被检测。

不同的 EDR 有不同的工作机制,通过多样化技术库,我们可以提高红队行动中的成功率。使用 WTH,我们能够绕过一些阻止 Thread Name-Calling 的 EDR。然而,也出现了相反的效果。没有完美的注入技术;每种技术都有其优缺点,正如往常一样,我们被迫在一个可疑指标和另一个之间进行权衡。

这种技术不使用罕见的 API 或复杂的结构。由于这种简单性,它易于实现,但通过静态分析样本(例如使用 YARA 规则)更难检测。所有使用的 API 在良性可执行文件中也很常见。捕获它的最佳方法是监视行为,立即停止远程写入,或检查它们通向何处。

参考资料

已知进程注入技术的汇编:

  • https://i.blackhat.com/USA-19/Thursday/us-19-Kotler-Process-Injection-Techniques-Gotta-Catch-Them-All-wp.pdf[48]
  • https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process[49]
  • https://attack.mitre.org/techniques/T1055/[50]
  • https://www.ired.team/offensive-security/code-injection-process-injection[51]

关于经典线程劫持:

  • https://www.ired.team/offensive-security/code-injection-process-injection/injecting-to-remote-process-via-thread-hijacking[52]

关于线程劫持的衍生技术:

  • https://infosecwriteups.com/t-rop-h-thread-hijacking-without-executable-memory-allocation-d746c102a9ca[53]

关于 SetThreadContext 及其触发的警报:

  • https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d[54]

关于等待线程和线程池:

  • https://scorpiosoftware.net/2022/03/21/threads-threads-and-more-threads/[55]
  • https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools[56]
  • https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=6[57]

使用线程池进行注入:

  • https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/[58]
  • https://i.blackhat.com/EU-23/Presentations/EU-23-Leviev-The-Pool-Party-You-Will-Never-Forget.pdf[59]
  • https://vvinoth.com/post/threadpools/[60]

另一种利用等待线程的技术:

  • https://www.unknowncheats.me/forum/anti-cheat-bypass/261176-silentjack-ultimate-handle-hijacking-user-mode-multi-ac-bypass-eac-tested.html[61]

参考资料

[1] 

攻击者使用的重要技术: https://attack.mitre.org/techniques/T1055

[2] 

我们之前关于进程注入: https://attack.mitre.org/techniques/T1055/

[3] 

博客: https://research.checkpoint.com/2024/thread-name-calling-using-thread-name-for-offense/

[4] 

Thread Name-Calling: https://research.checkpoint.com/2024/thread-name-calling-using-thread-name-for-offense/

[5] 

异步过程调用 (APC) 的新 API: https://repnz.github.io/posts/apc/user-apc/#ntqueueapcthreadex2-some-new-friends-in-the-fast-ring

[6] 

APC 注入: https://attack.mitre.org/techniques/T1055/004

[7] 

线程劫持: https://attack.mitre.org/techniques/T1055/003/

[8] 

SetThreadContexthttps://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d

[9] 

中等完整性级别: https://jsecurity101.medium.com/better-know-a-data-source-process-integrity-levels-8338f3b74990

[10] 

https://github.com/Mr-Un1k0d3r/EDRs: https://github.com/Mr-Un1k0d3r/EDRs

[11] 

线程: https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=4

[12] 

纤程: https://www.ired.team/offensive-security/code-injection-process-injection/executing-shellcode-with-createfiber

[13] 

BlackHat Asia 2024: https://github.com/JanielDary/ImmoralFiber

[14] 

由线程管理: https://learn.microsoft.com/en-us/windows/win32/procthread/fibers

[15] 

RtlRemoteCallhttps://www.alex-ionescu.com/rtlremotecall/

[16] 

CreateRemoteThread: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadscreateremotethread

[17] 

以各种方式应用它: https://aliongreen.github.io/posts/remote-thread-injection.html

[18] 

PsSetCreateThreadNotifyRoutinehttps://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine

[19] 

Exhttps://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutineex

[20] 

Thread Name-Calling 博客: https://research.checkpoint.com/2024/thread-name-calling-using-thread-name-for-offense/

[21] 

ETW 事件: https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-

[22] 

ObRegisterCallbacks: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks

[23] 

线程执行劫持: https://attack.mitre.org/techniques/T1055/003/

[24] 

覆盖 TLS 回调: https://attack.mitre.org/techniques/T1055/005/

[25] 

覆盖内核回调表: https://attack.mitre.org/techniques/T1574/013/

[26] 

odzhan 博客: https://web.archive.org/web/20240316160018/https://modexp.wordpress.com/2019/04/25/seven-window-injection-methods/

[27] 

odzhan 博客: https://web.archive.org/web/20240324163515/https://modexp.wordpress.com/2020/04/08/red-teams-etw/

[28] 

hexacorn 博客: https://www.hexacorn.com/blog/2017/10/26/propagate-a-new-code-injection-trick/

[29] 

hexacorn 博客: https://www.hexacorn.com/blog/2017/11/03/propagate-a-new-code-injection-trick-64-bit-and-32-bit/

[30] 

线程池: https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=6

[31] 

经典线程执行劫持: https://attack.mitre.org/techniques/T1055/003/

[32] 

线程池: https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=6

[33] 

NtSetContextThreadhttps://ntdoc.m417z.com/ntsetcontextthread

[34] 

噪声大(生成两个不同的 ETW 事件): https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d

[35] 

DCP(动态代码禁止): https://www.ired.team/offensive-security/defense-evasion/acg-arbitrary-code-guard-processdynamiccodepolicy

[36] 

线程池的概念: https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools

[37] 

MSDN 上有描述: https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools

[38] 

System Informer: https://systeminformer.com/

[39] 

大量具有等待线程的进程: https://scorpiosoftware.net/2022/03/21/threads-threads-and-more-threads/

[40] 

THREAD_GET_CONTEXThttps://learn.microsoft.com/en-us/windows/win32/procthread/thread-security-and-access-rights

[41] 

PROCESS_VM_READ | PROCESS_VM_WRITE: https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights

[42] 

SYSTEM_THREAD_INFORMATIONhttps://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/thread.htm

[43] 

PoC 的完整源代码可在 GitHub 上获取: https://github.com/hasherezade/waiting_thread_hijacking

[44] 

https://github.com/hasherezade/waiting_thread_hijacking: https://github.com/hasherezade/waiting_thread_hijacking

[45] 

NINA: https://undev.ninja/nina-x64-process-injection/

[46] 

GhostWriting: https://blog.sevagas.com/IMG/pdf/code_injection_series_part5.pdf

[47] 

SetThreadContext: https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d

[48] 

https://i.blackhat.com/USA-19/Thursday/us-19-Kotler-Process-Injection-Techniques-Gotta-Catch-Them-All-wp.pdf: https://i.blackhat.com/USA-19/Thursday/us-19-Kotler-Process-Injection-Techniques-Gotta-Catch-Them-All-wp.pdf

[49] 

https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process: https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process

[50] 

https://attack.mitre.org/techniques/T1055/: https://attack.mitre.org/techniques/T1055/

[51] 

https://www.ired.team/offensive-security/code-injection-process-injection: https://www.ired.team/offensive-security/code-injection-process-injection

[52] 

https://www.ired.team/offensive-security/code-injection-process-injection/injecting-to-remote-process-via-thread-hijacking: https://www.ired.team/offensive-security/code-injection-process-injection/injecting-to-remote-process-via-thread-hijacking

[53] 

https://infosecwriteups.com/t-rop-h-thread-hijacking-without-executable-memory-allocation-d746c102a9ca: https://infosecwriteups.com/t-rop-h-thread-hijacking-without-executable-memory-allocation-d746c102a9ca

[54] 

https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d: https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d

[55] 

https://scorpiosoftware.net/2022/03/21/threads-threads-and-more-threads/: https://scorpiosoftware.net/2022/03/21/threads-threads-and-more-threads/

[56] 

https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools: https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools

[57] 

https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=6: https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=6

[58] 

https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/: https://www.safebreach.com/blog/process-injection-using-windows-thread-pools/

[59] 

https://i.blackhat.com/EU-23/Presentations/EU-23-Leviev-The-Pool-Party-You-Will-Never-Forget.pdf: https://i.blackhat.com/EU-23/Presentations/EU-23-Leviev-The-Pool-Party-You-Will-Never-Forget.pdf

[60] 

https://vvinoth.com/post/threadpools/: https://vvinoth.com/post/threadpools/

[61] 

https://www.unknowncheats.me/forum/anti-cheat-bypass/261176-silentjack-ultimate-handle-hijacking-user-mode-multi-ac-bypass-eac-tested.html: https://www.unknowncheats.me/forum/anti-cheat-bypass/261176-silentjack-ultimate-handle-hijacking-user-mode-multi-ac-bypass-eac-tested.html

原文始发于微信公众号(securitainment):等待线程劫持:线程执行劫持的更隐蔽版本

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月16日15:29:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   等待线程劫持:线程执行劫持的更隐蔽版本https://cn-sec.com/archives/3964902.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息