原文标题:Maelstrom #5: EDR Kernel Callbacks, Hooks, and Call Stacks 原文地址:https://pre.empt.blog/2023/maelstrom-5-edr-kernel-callbacks-hooks-and-call-stacks 由于作者博客迁移的原因,原文中的图片已经失效,我在archive中找到了别人保存下来的网页:https://web.archive.org/web/20221126182533/https://pre.empt.dev/posts/maelstrom-edr-kernel-callbacks-hooks-and-callstacks/ 本文由本人用chatgbt翻译而成,Maelstrom是一个概念验证的命令与控制框架,具有Python后端和C++植入程序。
回顾到目前为止的系列内容,我们从总体上了解了命令与控制服务器(C2)的目的和意图,然后设计并实现了我们的首个植入物和服务器。如果你一直在跟进,你可能会认为你已经编写了一个C2... 这是一种常见的心态。根据我们的经验,达到这一点并不需要太多的复杂性。我们之前的所有工作都可以很容易地(而且已经被实现过!)用C#、Python、Go等编程语言在一个晚上的狂热咖啡因驱动下完成。C2的主要特征通常可以与软件工程中的一些古老的解决概念和模式相关联,比如线程管理、处理空闲进程以及确保正确执行和程序流程。 但是,当我们编写各种C2时发现,其他许多攻击性开发人员在编写自己的植入物和服务器时也发现,在代码正常运行且能够获得回传后,他们会停止在开发计算机上运行植入物,并尝试在第二台计算机上运行。这时问题开始涌现。比如“为什么我无法访问远程文件?”、“为什么我可以在某种协议下进行出站请求,但在另一种协议下不能?”、“为什么这个命令只是默默失败,没有任何解释”,还有对自己持怀疑态度并且有足够的冒名顶替症的人会问:“为什么Windows Defender没有阻止我这样做?”。 就个人而言,这是我们期待撰写的一篇文章。它将讨论在具有主动终端保护的环境中越来越常见的行为,并提供一些示例。在2022年,植入物面临着更多的审查——植入物和C2的操作者必须准备好面对或规避这种审查,而防御者必须了解其工作原理,以最大限度地发挥其能力。 在撰写本文时,我们还想澄清一些关于“它可以避开<插入公司名>的EDR”的推文。仅仅因为植入物能够执行,并不意味着终端保护对其视而不见——虽然可能会如此,但我们想演示一些这些解决方案用来识别恶意行为并引起对植入物的怀疑的技术。 简而言之,执行的证明并不等于规避的证明。
本文将涵盖以下内容:
通过本文的结束,我们将了解现代EDR如何保护免受恶意植入物的攻击,以及如何绕过这些保护措施。我们将从一个在技术上能够工作的植入物转变为对如何编写一个真正开始工作并能够实现操作员目标的植入物的认识。 正如我们多次提到的,我们并不是在创建一个可操作的C2。本系列的输出质量很差,充满了缺陷——它只足够作为一种有缺陷的概念验证,用于讨论本系列中特定项目的代码,以避免恶意行为者使用该代码。出于同样的原因,我们试图避免在本系列中讨论红队操作策略。然而,随着我们的继续,为什么与被感染用户的典型行为相融合能够奏效将变得显而易见。这是xpn 在Twitter上讨论过的内容:
找到Confluence,阅读Confluence...成为员工! — Adam Chester (@xpn) 2022年1月22日
如果你的植入物被EDR标记,对每台加入域的计算机进行NetSessionEnum
查询以查找活动会话可能不是典型的用户行为。直到它停止响应,你可能都不会知道你的植入物已经被标记。从这里开始,就是一场比赛,直到你的植入物被上传到VirusTotal ,然后你不得不重新开始。 在本文中,我们将经常提到以下程序:
The Hunting ELK (HELK):HELK是一个由Elastic Stack提供的最佳总结:
Hunting ELK或简称HELK是第一个开源的猎取平台,具有高级分析功能,如SQL声明性语言、图形化、结构化流式处理,甚至可以通过Jupyter笔记本和Apache Spark在ELK堆栈上进行机器学习。 这个项目主要用于研究,但由于其灵活的设计和核心组件,通过适当的配置和可扩展的基础设施,它可以在更大的环境中部署。
PreEmpt :一种伪EDR,具有摄取EtwTi、内存扫描器、钩子等功能。虽然它不是公开的,但在必要时会分享代码。
这两个工具将允许我们在需要时生成概念验证数据。
与《Maelstrom:编写C2植入物 》类似,我们希望有一个专门的部分来澄清一些在继续之前我们认为需要一些背景知识的主题。
终端检测与响应(Endpoint Detection and Response,简称EDR)软件使用了许多不同的首字母缩略词,不同公司的程序及其功能之间可能存在差异。为了简化起见,我们将所有仅限于静态扫描磁盘上文件的程序称为"反病毒"(anti-virus),而所有进一步扫描设备内存、查看程序运行时的行为并在发生威胁时作出响应的程序称为"EDR"。这些程序可能被称为各种名称,包括XDR、MDR或只是普通的AV。 在本系列中,我们将继续使用"EDR"这个术语。 CrowdStrike的文章“什么是终端检测和响应(EDR) ”对此有一个很好的概述:
终端检测与响应(Endpoint Detection and Response,EDR),也被称为终端检测与威胁响应(Endpoint Detection and Threat Response,EDTR),是一种终端安全解决方案,持续监控终端用户设备以侦测和应对勒索软件和恶意软件等网络威胁。Gartner的安东·楚瓦金(Anton Chuvakin) 提出了"EDR"这个术语,定义为一种解决方案,它"记录并存储终端系统级行为,使用各种数据分析技术检测可疑的系统行为,提供上下文信息,阻止恶意活动,并提供修复建议以恢复受影响的系统"。
由于这与本文相关,下一部分将介绍EDR的架构,并比较不同供应商之间的EDR行为。为了不偏离主题,我们不会涉及其他相关领域,例如反病毒软件的工作原理,基于磁盘的保护如何阻止植入执行(如果您仍在使用磁盘),以及反病毒软件和EDR在扫描文件及其行为时的实际操作方法。事实证明,那是一个完整的研究领域。
在讨论终端保护时,了解其架构可能会有所帮助。Symantec的EDR架构 大致如下: 类似的方法也可以在Windows Defender for Endpoint 中看到。基本上,安装了该产品的设备将拥有一个代理程序,其中包含多个驱动程序和进程,用于收集来自机器各个方面的遥测数据。通过本文和下一篇文章,我们将介绍其中的一些内容。
顺便提一下,在Windows环境中,微软在这方面具有先发优势。虽然目前它主要面向"大型企业"客户(至少我们认为是这样,鉴于Azure的价格!),但 微软的Defender和新的Defender MDE都可以访问微软对...自己操作系统的了解,并影响新操作系统功能的开发。从长远来看,微软Defender MDE有可能像微软Defender对反病毒市场产生影响一样,对EDR市场产生影响也不足为奇。
所有EDR的基本要点是,代理程序收集的遥测数据将被发送到云端,在那里经过各种沙箱和其他测试设备的测试,并且可以通过机器和人工操作员进一步分析其行为。 对于那些过于好奇的读者,以下链接详细介绍了特定供应商对EDR架构的方法:
不离题地说,就像不是每次红队评估都是真正的红队一样,也不是每个EDR都是真正的EDR。 以下是来自Gartner 2021年5月报告 的“Gartner Magic Quadrant ”,大致勾勒出了EDR领域的格局。值得注意的是,CrowdStrike聘请 了ReactOS 内核维护者Alex Ionescu ,这表明目前最佳的EDR解决方案充分利用了对内部Windows功能的深入了解,以最大化其性能: 现代EDR与其他同类产品的区别在于,它们依赖于实现我们将在这里讨论的方法,如自定义编写的直接行为(例如内核回调和钩子),能够快速实施新的Microsoft Windows功能并开发自定义的可靠交互和中断恶意进程的方式。 EDR供应商通常使用的另一个指标,特别是因为报告是公开的,是Mitre Enginuity 。攻击评估 的描述如下:
Mitre Enginuity ATT&CK®评估(Evals)计划将产品和服务提供商与Mitre专家一起合作,共同评估安全解决方案。Evals过程采用了一种系统化的方法 ,使用基于威胁的紫队方法,以捕捉与解决方案能力相关的关键环境信息,以便检测或防护ATT&CK知识库定义的已知对手行为。每次评估的结果都会被详细记录并公开发布。
例如,SentinelOne 的结果可以在"SentinelOne Overview "中看到。这个概述涵盖了APT场景,并标记了是否检测到了该技术,可以用作其"有效性"的追踪。然而,一些人在网上表达了他们对这种确定产品有效性的方法不够全面的感觉。 从采购角度来看EDR,有几种方法可以确定其有效性,我们在这里简要介绍一下。主要要考虑的是,有些供应商并不一定提供比反病毒软件更多的功能。与任何产品一样,请确保购买适合您业务需求的正确解决方案。
在讨论内核和用户空间模型时,将使用以下架构图,这对于任何计算机科学专业的毕业生来说都很熟悉: 绝大多数用户活动将发生在第3环,即用户模式(User Mode),而内核则在内核模式(Kernel Mode)下运行。 关于这一点的更多信息,请参阅Windows编程/用户模式与内核模式 。值得注意的是,用户模式和内核模式之间可以进行交叉操作。以下是来自先前链接的定义,总结了这些层之间的差异:
为了避免这篇文章变得比现在更长,您可以查看Windows组件概述文档 以获取有关以下图表的更多详细信息。然而,该图表简单地显示了从进程、服务等跨越到内核的Windows架构。我们很快将更详细地介绍这个问题。 使用WinAPI的应用程序将通过到运行在内核模式下的Native API (NTAPI)进行遍历。 以API Monitor为例,可以用它来查看正在执行的调用: 上述内容显示了调用CreateThread
,随后不久调用了NtCreateThreadEx
。 当调用KERNEL32.DLL中的函数(例如CreateThread
)时,它将随后调用NTDLL.DLL中相应的NTAPI函数。例如,CreateThread 调用NtCreateThreadEx 。此函数将使用系统服务号(SSN)填充RAX 寄存器。最后,NTDLL.dll将发出SYSENTER 指令。这将导致处理器切换到内核模式,并跳转到一个预定义的函数,称为系统服务调度程序。以下图像来自《Rootkits: Subverting the Windows Kernel 》一书中的Userland Hooks部分:
驱动程序是Windows的软件组件,它允许操作系统和设备之间进行通信。以下是《什么是驱动程序? 》中的一个例子:
例如,假设一个应用程序需要从设备读取一些数据。该应用程序调用操作系统实现的函数,而操作系统则调用驱动程序实现的函数。由同一家设计和制造设备的公司编写的驱动程序知道如何与设备硬件进行通信以获取数据。驱动程序从设备获取数据后,将数据返回给操作系统,然后操作系统将数据返回给应用程序。
在终端保护的情况下,驱动程序的使用有几个原因:
免责声明:在继续之前,我们强烈建议您观看REcon 2015 - Hooking Nirvana(Alex Ionescu) 。请在观看完后回到本帖。
另一个EDR常见的功能是用户态钩子DLL。通常,在进程创建时加载这些DLL,并用于通过它们代理WinAPI调用以评估其使用情况,然后重定向到正在使用的任何DLL。以VirtualAlloc
为例,流程如下: 通过钩子函数,可以通过在函数地址处放置jmp
指令来截取WinAPI调用,从而进行函数检测。这个jmp
指令将重定向调用的流程。我们将在下一节中看到这个过程的实际操作。通过钩取函数调用,作者可以:
这并不是一个详尽无遗的列表,但应该能够展示我们在运行植入程序时最常遇到的功能。 以下是一些使用用户态钩子的示例:
为了无需从头编写所有复杂的逻辑,我们将使用Hunting ELK(HELK)来访问我们的内核回调:
Hunting ELK(简称HELK)是最早的开源猎杀平台之一,具有高级分析功能,如SQL声明性语言、图形化、结构化流以及通过Jupyter笔记本和基于ELK堆栈的Apache Spark进行机器学习。该项目主要用于研究,但由于其灵活的设计和核心组件,可以在具备适当配置和可扩展基础设施的较大环境中部署。
我们还使用了“Exploring DLL Loads, Links, and Execution ”中的脚本来搜索Sysmon
日志:
内核回调 ,根据Microsoft的定义:
内核的回调机制提供了一种通用的方式,供驱动程序在满足特定条件时请求和提供通知。
基本上,它们允许驱动程序接收和处理特定事件的通知。从veil-ivy/block_create_process.cpp 中,以下是使用PsSetLoadImageNotifyRoutine
回调的实现来阻止进程创建的示例:
在这个例子中,使用了ObRegisterCallbacks
来阻止notepad
的创建。一个终端保护解决方案可能不会以这种方式使用它,但很可能会使用这种类型的回调作为遥测数据,以确定是否发生了恶意活动。 在本节中,我们将讨论PsSetLoadImageNotifyRoutine 。这个回调负责做它所说的事情:在将映像加载到进程中时发送通知。有关示例实现,请参阅《订阅内核驱动程序的进程创建、线程创建和映像加载通知 》。
为了理解PsSetLoadImageNotifyRoutine
的工作原理,我们需要确定它的触发条件。 假设以下代码:
当调用LoadLibraryA
时,该函数会注册一个回调函数来通知驱动程序发生了这个事件。为了在HELK中查看这个日志,我们使用之前提到的脚本 。 如果我们过滤main.exe
,即上述代码,我们可以看到winhttp.dll
被加载: 在Elastic中,我们还可以使用以下KQL查询语句:
event_original_message字段包含整个日志的内容。
为了了解这段代码的功能,我们可以浏览ReactOS 源代码:
通过研究这些函数,可以对加载DLL的过程有一定的了解。然而,在batsec 的论文《Bypassing Image Load Kernel Callbacks 》中指出,触发PsSetLoadImageNotifyRoutine的关键是NtCreateSection调用,而该调用随后在LdrpCreateDllSection
中被调用。因此,我们不需要花费太多时间进行调试以找到这个触发点。
在batsec的文章中,他们展示了如何使用以下代码来大量触发前面提到的事件:
以下截图也来自那篇博文:
batsec发现通过调用NtCreateSection
,可以在不实际加载DLL的情况下发送事件。同样,可以通过更新LDR_DATA_TABLE_ENTRY 结构对欺骗进行一定程度的武器化/操纵,以执行其他操作:
在这个例子中,我们将毫无理由地使用CertEnroll.dll
:
现在我们只需要逐步遍历结构并填写所需的信息。
填写结构的其余部分:
这是它的实际应用: (在原文中是一个动图) 在上述示例中,可以看到CertEnroll.dll
加载在spoof-load.exe
进程中。请记住,这并没有真正加载。这里发生的唯一事情是传递了一个DLL的字符串。然后我们告诉加载程序,DLL的基地址就是shellcode的基地址: (在原文中是一个动图) 从这种技术来看,有两个明显的用例:
在这里,我们不会重新发明轮子,这个概念在《Bypassing Image Load Kernel Callbacks 》中有很好的解释。基本上,为了防止回调触发,需要重新编写一个完整的加载器。该研究的结论是DarkLoadLibrary :
实质上,DarkLoadLibrary
是LoadLibrary
的一种实现,它不会触发映像加载事件。它还具有许多额外的功能,可以在恶意软件开发过程中更加方便。
这个库的概念验证用法来自于DLL Shenanigans 。 让我们来检查一下: 然后执行上述3个命令:
为了避免调用被识别为注册回调的NtCreateSection
,使用NtAllocateVirtualMemory
或VirtualAlloc
进行部分映射,就像在MapSections() 中看到的那样。
显然,PsSetLoadImageNotifyRoutine
并不是唯一的回调函数,还有许多其他回调函数可供使用。《Kernel Callback Functions 》提供了一个(非详尽!)的列表:
其中一个强大的回调函数是PsSetCreateProcessNotifyRoutineEx()
,因为进程创建的通知对于系统遥测来说将是致命的。截至撰写本文时,我们还没有注意到在这个领域有任何研究。尽管坦率地说,我们还没有进行过深入的调查。
在本节中,我们将介绍一些流行但基础的Hooking技术。
在查看一些库之前,让我们先看两个例子 - x86平台下的手动Hook和NtSetProcessInformation回调。
使用Windows API Hooking 作为x86示例(更容易演示),我们可以将代码调整为类似以下的形式:
让我们逐步解释这段代码... 首先,MessageBoxA
函数位于User32.dll
库中,因此我们需要加载该库:
接下来,我们需要获取USER32!MessageBoxA
函数的地址:
有了这个地址,现在可以读取函数的字节:
这将读取函数调用的前6个字节,稍后将更新这些字节以执行对新函数的push
,从而实现jmp
操作。 字节码:
现在,需要构建补丁。可以按以下方式完成:
由此产生的十六进制表示为:
使用defuse.ca 进行反汇编,上述代码可以翻译为汇编语言:
请注意,推送的0x00BD1212
是我们要跳转到的函数的地址,而不是调用USER32!MessageBoxA
:
此时,补丁已经准备好了。它将使用一个推送指令替换前6个字节,以跳转到新地址。 接下来要做的是实际写入这个新地址:
然后,在反汇编中可以看到:
添加了一个jmp
指令,跳转到新的函数。允许运行后,将调用被Hook的函数,并打印出参数:
运行结果如下:
在REcon 2015年的"Hooking Nirvana"(Alex Ionescu) 中,Alex Ionescu 展示了如何使用带有ProcessInstrumentationCallback
标志的NtSetProcessInformation 来进行进程仪器化。在这次演讲中,Alex演示了通过回调函数进行hook的方法。 设置回调非常简单:
回调函数的包含方式如下:
CALLBACK_FUNCTION_GOES_HERE
是要用作回调的函数,然后ProcessInstrumentationCallback
是:
另一个要注意的点是,通过将回调设置为NULL
,可以删除发送的任何回调。这在modexp 的《Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams 》中有记录。Secrary 在此基础上进行了进一步的工作,并在Secrary的博客《Hooking via InstrumentationCallback 》中进行了介绍。Alex Ionescu的原始代码可以在HookingNirvana 存储库中找到。 借用Secrary的钩子可以访问函数和返回值,给出以下汇编代码:
钩子(Hook) :在编写好汇编代码后,我们还需要编写被汇编代码调用的函数,以接收所有提供的寄存器值并返回它们对应的函数名称:
然后,在main
函数中,调用了SymInitialize 函数,然后设置了仪器化(instrumentation):
通过运行这个完整的示例,我们现在可以看到所有函数名称和返回代码。 这种技术可以更新以获取对参数的访问,进行完整的分析,但是在这个初步的概念验证中,我们没有觉得有必要深入研究这个。 最后,提到一下这种技术可以用于枚举给定函数调用的系统服务号(System Service Number,SSN)。这在 Paranoid Ninja 的 EtwTi-Syscall-Hook 和 Release v0.8 - Warfare Tactics 中有记录,其中钩子的代码要小得多(代价是功能较少):
以及相应的汇编代码:
在2019年,Cneelis 发表了《Red Team Tactics: Combining Direct System Calls and sRDI to bypass AV/EDR 》,随后发布了SysWhispers 。
SysWhispers提供了红队人员在核心内核映像(ntoskrnl.exe)中生成任意系统调用的头文件/汇编对的能力。这些头文件还包括必要的类型定义。
然后,modexp 提供了一个更新 ,纠正了版本1的一个缺点,并带来了SysWhispers2 。
SysWhispers2中的具体实现是对@modexpblog的代码的变体。一个区别是每次生成时函数名称的哈希值都是随机的。之前曾发布 过这个技术的@ElephantSe4l 还有另一个基于C++17的实现 ,也值得一看。
主要变化是引入了base.c ,这是绕过用户模式钩子并直接调用系统调用 的结果,用于红队工作。 再次提到,KlezVirus 制作了SysWhispers3 :
使用方法与SysWhispers2非常相似,但有以下几个例外:
有关这些特性的更详细解释,请参阅博文《SysWhispers is dead, long live SysWhispers! 》。
这只是一套SysCall技术,还有另一种基于Heavens Gate的技术。 详细了解这些不同技术,请参阅《Gatekeeping Syscalls 》。 甚至还有更多的技术:
总结一下: 通过能够转入内核模式,我们可以在用户模式钩子看不见的情况下进行操作。所以,让我们构建一些东西。 在我们的示例中,我们将使用MinHook :
这是一个用于Windows的精简的x86/x64 API Hooking库。
所以,这将是一个被加载到进程中的DLL,然后钩取功能,并根据其行为做出一些决策。以下是DllMain
的示例代码:
当DLL_PROCESS_ATTACH
是加载原因时,我们创建一个新线程,并将其指向我们的"main"函数。这是我们初始化MinHook并设置一些钩子的地方:
MH_Initialize()
是一个必需的调用,所以我们从这里开始。接下来,我们创建三个钩子:
使用MH_CreateHookApi()
函数来创建钩子:
创建一个钩子需要以下四个要素:
NtAllocateVirtualMemory_Hook()
是用于替换原始函数的函数:
函数的声明与函数的typedef
完全相同:
这样做是为了避免在钩子之间出现类型不匹配的问题。 在NtAllocateVirtualMemory_Hook
函数中,我们只检查保护类型是否为PAGE_EXECUTE_READWRITE
(RWX
),因为这通常是恶意活动的迹象(通常情况下)。如果匹配,我们只是打印出发现了某些内容。 然后,我们有一个阻塞(blocking)的概念。这意味着如果BLOCKING
为真,则函数直接返回。如果为假,则返回原始函数的指针,允许函数按照用户的期望执行。 在NtProtectVirtualMemory
函数中,我们只检查是否更改了PAGE_EXECUTE_READ
的保护类型,因为这是常见的保护类型,用于避免RWX(可执行、可读、可写)内存分配:
在NtWriteVirtualMemory
函数中,没有进行额外的检查:
在这个例子中,我们有一个PE文件,它只是调用LoadLibraryA
加载DLL,然后运行一个伪造的注入操作:
运行这段代码后,我们可以看到检测到的调用(在非阻塞模式下)。 在屏幕截图中,我们可以看到: 切换到可执行读取(RX)权限的操作
这就是我们计划检测的内容。那么,如何绕过这些检测呢?由于社区的大量开发,实际上很容易实现。但在此之前,我们需要讨论用户空间(User-land)和内核空间(Kernel-land)。
在这个例子中,我们将使用Tartarus Gate 。我们只需要在wmain函数中进行一次调用:
然后,在Payload
中更改有效载荷:
检查加载的模块: DLL已加载...... 运行代码并在线程创建处设置断点(因为有效载荷是垃圾数据): 上述代码显示了MinHook的初始化,然后启用了钩子。在这两者之间,有一次切换到可执行读取(RX)权限的操作。然而,它发生在钩子设置之前。所以这很可能是MinHook或CRT(C Runtime)执行的某些操作。我们没有花时间进一步检查这个问题。
正如我们所述,这并不是对每种潜在技术的全面评估。另一种可能值得探索的技术是向量异常处理(Vectored Exception Handling,VEH) 。其中两个例子是ethicalchaos 在“进程内无补丁的AMSI绕过 ”和Countercept 的“CallStackSpoofer ”中提到的。 和内核回调一样,VEH是一个活跃的研究领域,其中有很多内容值得探索,远超过我们在这篇文章中的时间和篇幅所能涵盖的范围。
进程的另一个被审查的组件是通过线程 的调用堆栈(Call Stack)进行的。如《在WinDbg中查看调用堆栈 》所述,调用堆栈定义如下:
调用堆栈是导致程序计数器当前位置的函数调用链。调用堆栈的顶部函数是当前函数,下一个函数是调用当前函数的函数,依此类推。显示的调用堆栈基于当前程序计数器,除非你更改寄存器上下文。有关如何更改寄存器上下文的更多信息,请参阅更改上下文 。
由于调用堆栈可以帮助确定线程的意图,它经常受到审查以确定其有效性。在本节中,我们想展示如何使用调用堆栈来确定恶意行为(以一个简单的例子为例),然后讨论处理这种行为的攻击策略。 在这里,我们有一个名为Vulpes的植入程序: 如果我们查看进程(10792)的线程,我们会看到一堆以难以捉摸的TpReleaseCleanupGroupMembers 开始的线程: 这在进程中非常常见,这里有一个chrome.exe的例子: 然后是RuntimeBroker.exe
的例子: 这是对攻击者来说一个重要的侧记。如果所讨论的植入程序依赖于伪装成其他东西,那么这一点需要考虑在内。例如,如果植入程序的运行基于Chrome等浏览器,那么HTTP请求的处理方式应该相同,并且应该模仿线程的入口点和调用堆栈。 回到Vulpes植入程序,调用堆栈主要是TpReleaseCleanupGroupMembers
,这是正常的。然而,如果我们检查一些线程,下面是负责WinHTTP的线程: 这里是由该进程启动的一个通用线程: 有几个其他的线程,但我们将重点关注第二个例子,因为这是一个在许多进程中都会找到的线程的调用堆栈。让我们看看如何以编程方式读取线程堆栈,以及伪造的线程基址可能会显得可疑。 这是入口点:
在上面的例子中,我们有两个线程ID:
有了这些信息,我们需要编写一个函数来枚举线程的调用堆栈。可以使用以下代码来实现:
通过使用DbgHelp 库,我们可以使用以下函数:
将代码指向正常线程堆栈: 这与我们之前看到的相符。将其更改为指向有问题的线程: 现在显示了一个我们之前没有看到的不同线程堆栈。观察第3个栈帧,它是CreateTimeQueueTimer ,正如以下文章中所描述的休眠混淆技术:
作为免责声明,该技术的全部功劳归功于Peter Winter-Smith 。 因此,从编程的角度来看,很容易找出线程的调用堆栈。让我们将其扩展为一些完全基础的内容,以便我们可以开始工作。 首先,我们将定义一个硬编码的预期函数列表,这些函数我们之前看到过,可以用作完整性检查:
然后,我们创建一个空的向量,用于跟踪我们找到的所有符号名称:
现在,不仅仅是打印,我们将所有符号名称都添加到一个向量中:
运行代码并找到所有符号后,我们来看看这两个向量是否匹配:
将其指向正常的线程: 我们得到了CLEAN
消息。然后,我们来看看有问题的线程:
显然,这段代码并不适用于实际生产环境,编写这种逻辑的细节非常具有挑战性。然而,一些EDR供应商已经开始采用这种技术。随着对混淆和蒙蔽终端保护的研究不断增加,这对于蓝队和红队来说都是一个有用的技术。 说到红队,关于修复这个线程问题的研究已经在进行中。 这种技术最初由Peter Winter-Smith 首次推广,他在这个领域经常出现,然后由mgeeky 在ThreadStackSpoofer 中重新解释。然而,这个概念验证将返回地址设置为0,从而消除了对用于shellcode注入的内存地址的引用。
这是一个Thread Stack Spoofing技术的示例实现,旨在逃避对检查的线程调用堆栈中shellcode帧的引用的恶意代码分析员、AV和EDR的检测。其思想是隐藏线程调用堆栈中对shellcode的引用,从而伪装包含恶意代码的分配。
如果我们移除Vulpes中的休眠遮蔽,调用堆栈将如下所示: 这种技术旨在通过将返回地址存储到一个变量中,将返回地址设置为0,然后恢复返回地址来掩盖这些地址。 以下是来自上述代码库的一个快速示例代码:
在最近的一个项目中,William Burgess 创建了CallStackSpoofer ,将这一技术推向更高的水平,完全伪造堆栈。 参见:Spoofing Call Stacks to confused EDRs 通过使用预定义的堆栈向量,该项目能够模拟:
以下是WMI 的预定义容器 示例:
通过实施这种类型的技术,将极大地增加了实施我们之前展示的调用堆栈完整性检查的难度(尽管我们的演示中使用的是硬编码的值,但观点仍然成立)。
这是一篇相当长的帖子,我们试图为EDR(终端检测与响应)机制提供一些清晰的解释,以便不仅能够识别恶意活动,而且能够预防它。在此过程中,我们讨论了常见的问题和一些增强措施,以保护系统免受绕过攻击的影响。 在这个过程中,我们试图更多地揭示“X绕过EDR”的故事情节,虽然攻击可能暂时逃避检测,但通常还是会留下活动的日志记录。 下一集将讨论ETW(事件跟踪)和AMSI(反恶意软件扫描接口)!
param (
[string]$Loader
=
"",
[string]$dll
=
""
)
$eventId
=
7
$logName
=
"Microsoft-Windows-Sysmon/Operational"
$Yesterday
=
(Get
-
Date).AddHours(
-
1
)
$events
=
Get
-
WinEvent
-
FilterHashtable @{logname
=
$logName;
id
=
$eventId ;StartTime
=
$Yesterday;}
foreach($event
in
$events)
{
$msg
=
$event.Message.ToString()
$image
=
($msg|Select
-
String
-
Pattern
'Image:.*'
).Matches.Value.Replace(
"Image: "
, "")
$imageLoaded
=
($msg|Select
-
String
-
Pattern
'ImageLoaded:.*'
).Matches.Value.Replace(
"ImageLoaded: "
, "")
if
($image.ToLower().contains($Loader.ToLower())
-
And $imageLoaded.ToLower().Contains($dll.ToLower()))
{
Write
-
Host Image Loaded $imageLoaded
}
}
param (
[string]$Loader
=
"",
[string]$dll
=
""
)
$eventId
=
7
$logName
=
"Microsoft-Windows-Sysmon/Operational"
$Yesterday
=
(Get
-
Date).AddHours(
-
1
)
$events
=
Get
-
WinEvent
-
FilterHashtable @{logname
=
$logName;
id
=
$eventId ;StartTime
=
$Yesterday;}
foreach($event
in
$events)
{
$msg
=
$event.Message.ToString()
$image
=
($msg|Select
-
String
-
Pattern
'Image:.*'
).Matches.Value.Replace(
"Image: "
, "")
$imageLoaded
=
($msg|Select
-
String
-
Pattern
'ImageLoaded:.*'
).Matches.Value.Replace(
"ImageLoaded: "
, "")
if
($image.ToLower().contains($Loader.ToLower())
-
And $imageLoaded.ToLower().Contains($dll.ToLower()))
{
Write
-
Host Image Loaded $imageLoaded
}
}
static OB_CALLBACK_REGISTRATION obcallback_registration;
static OB_OPERATION_REGISTRATION oboperation_callback;
static PVOID registry
=
NULL;
static UNICODE_STRING altitude
=
RTL_CONSTANT_STRING(L
"300000"
);
/
/
1
: kd > dt nt!_EPROCESS ImageFileName
/
/
+
0x5a8
ImageFileName : [
15
] UChar
static const unsigned
int
imagefilename_offset
=
0x5a8
;
auto drv_unload(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
ObUnRegisterCallbacks(registry);
}
OB_PREOP_CALLBACK_STATUS
PreOperationCallback(
_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
if
(strcmp(BLOCK_PROCESS, (char
*
)PreInfo
-
>
Object
+
imagefilename_offset)
=
=
0
) {
if
((PreInfo
-
>Operation
=
=
OB_OPERATION_HANDLE_CREATE))
{
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE)
=
=
PROCESS_TERMINATE)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_TERMINATE;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_READ)
=
=
PROCESS_VM_READ)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_READ;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION)
=
=
PROCESS_VM_OPERATION)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_OPERATION;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE)
=
=
PROCESS_VM_WRITE)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_WRITE;
}
}
}
return
OB_PREOP_SUCCESS;
}
VOID
PostOperationCallback(
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION PostInfo
)
{
UNREFERENCED_PARAMETER(RegistrationContext);
UNREFERENCED_PARAMETER(PostInfo);
}
extern
"C"
auto DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
-
> NTSTATUS {
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject
-
>DriverUnload
=
drv_unload;
auto status
=
STATUS_SUCCESS;
static OB_CALLBACK_REGISTRATION ob_callback_register;
static OB_OPERATION_REGISTRATION oboperation_registration;
oboperation_registration.Operations
=
OB_OPERATION_HANDLE_CREATE;
oboperation_registration.ObjectType
=
PsProcessType;
oboperation_registration.PreOperation
=
PreOperationCallback;
oboperation_registration.PostOperation
=
PostOperationCallback;
ob_callback_register.Altitude
=
altitude;
ob_callback_register.Version
=
OB_FLT_REGISTRATION_VERSION;
ob_callback_register.OperationRegistrationCount
=
1
;
ob_callback_register.OperationRegistration
=
&oboperation_registration;
status
=
ObRegisterCallbacks(&ob_callback_register, ®istry);
if
(!NT_SUCCESS(status)) {
DbgPrint(
"failed to register callback: %x \r\n"
,status);
}
return
status;
}
static OB_CALLBACK_REGISTRATION obcallback_registration;
static OB_OPERATION_REGISTRATION oboperation_callback;
static PVOID registry
=
NULL;
static UNICODE_STRING altitude
=
RTL_CONSTANT_STRING(L
"300000"
);
/
/
1
: kd > dt nt!_EPROCESS ImageFileName
/
/
+
0x5a8
ImageFileName : [
15
] UChar
static const unsigned
int
imagefilename_offset
=
0x5a8
;
auto drv_unload(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
ObUnRegisterCallbacks(registry);
}
OB_PREOP_CALLBACK_STATUS
PreOperationCallback(
_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
if
(strcmp(BLOCK_PROCESS, (char
*
)PreInfo
-
>
Object
+
imagefilename_offset)
=
=
0
) {
if
((PreInfo
-
>Operation
=
=
OB_OPERATION_HANDLE_CREATE))
{
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE)
=
=
PROCESS_TERMINATE)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_TERMINATE;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_READ)
=
=
PROCESS_VM_READ)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_READ;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION)
=
=
PROCESS_VM_OPERATION)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_OPERATION;
}
if
((PreInfo
-
>Parameters
-
>CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE)
=
=
PROCESS_VM_WRITE)
{
PreInfo
-
>Parameters
-
>CreateHandleInformation.DesiredAccess &
=
~PROCESS_VM_WRITE;
}
}
}
return
OB_PREOP_SUCCESS;
}
VOID
PostOperationCallback(
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION PostInfo
)
{
UNREFERENCED_PARAMETER(RegistrationContext);
UNREFERENCED_PARAMETER(PostInfo);
}
extern
"C"
auto DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
-
> NTSTATUS {
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject
-
>DriverUnload
=
drv_unload;
auto status
=
STATUS_SUCCESS;
static OB_CALLBACK_REGISTRATION ob_callback_register;
static OB_OPERATION_REGISTRATION oboperation_registration;
oboperation_registration.Operations
=
OB_OPERATION_HANDLE_CREATE;
oboperation_registration.ObjectType
=
PsProcessType;
oboperation_registration.PreOperation
=
PreOperationCallback;
oboperation_registration.PostOperation
=
PostOperationCallback;
ob_callback_register.Altitude
=
altitude;
ob_callback_register.Version
=
OB_FLT_REGISTRATION_VERSION;
ob_callback_register.OperationRegistrationCount
=
1
;
ob_callback_register.OperationRegistration
=
&oboperation_registration;
status
=
ObRegisterCallbacks(&ob_callback_register, ®istry);
if
(!NT_SUCCESS(status)) {
DbgPrint(
"failed to register callback: %x \r\n"
,status);
}
return
status;
}
int
main()
{
HMODULE hModule
=
LoadLibraryA(
"winhttp.dll"
);
printf(
"WinHTTP: 0x%p\n"
, hModule);
return
0
;
}
int
main()
{
HMODULE hModule
=
LoadLibraryA(
"winhttp.dll"
);
printf(
"WinHTTP: 0x%p\n"
, hModule);
return
0
;
}
process_name :
"main.exe"
and
event_id:
7
and
ImageLoaded: winhttp.dll
process_name :
"main.exe"
and
event_id:
7
and
ImageLoaded: winhttp.dll
Image loaded:
RuleName:
-
UtcTime:
2022
-
04
-
29
18
:
50
:
10.780
ProcessGuid: {
3ebcda8b
-
3362
-
626c
-
a200
-
000000004f00
}
ProcessId:
6716
Image: C:\Users\admin\Desktop\main.exe
ImageLoaded: C:\Windows\System32\winhttp.dll
FileVersion:
10.0
.
19041.1620
(WinBuild.
160101.0800
)
Description: Windows HTTP Services
Product: Microsoft® Windows® Operating System
Company: Microsoft Corporation
OriginalFileName: winhttp.dll
Hashes: SHA1
=
4F2A9BB575D38DBDC8DBB25A82BDF1AC0C41E78C
,MD5
=
FB2B6347C25118C3AE19E9903C85B451,SHA256
=
989B2DFD70526098366AB722865C71643181F9DCB8E7954DA643AA4A84F3EBF0
,IMPHASH
=
0597CE736881E784CC576C58367E6FEA
Signed: true
Signature: Microsoft Windows
SignatureStatus: Valid
User: PUNCTURE\admin
Image loaded:
RuleName:
-
UtcTime:
2022
-
04
-
29
18
:
50
:
10.780
ProcessGuid: {
3ebcda8b
-
3362
-
626c
-
a200
-
000000004f00
}
ProcessId:
6716
Image: C:\Users\admin\Desktop\main.exe
ImageLoaded: C:\Windows\System32\winhttp.dll
FileVersion:
10.0
.
19041.1620
(WinBuild.
160101.0800
)
Description: Windows HTTP Services
Product: Microsoft® Windows® Operating System
Company: Microsoft Corporation
OriginalFileName: winhttp.dll
Hashes: SHA1
=
4F2A9BB575D38DBDC8DBB25A82BDF1AC0C41E78C
,MD5
=
FB2B6347C25118C3AE19E9903C85B451,SHA256
=
989B2DFD70526098366AB722865C71643181F9DCB8E7954DA643AA4A84F3EBF0
,IMPHASH
=
0597CE736881E784CC576C58367E6FEA
Signed: true
Signature: Microsoft Windows
SignatureStatus: Valid
User: PUNCTURE\admin
BOOL
FakeImageLoad()
{
HANDLE hFile;
SIZE_T stSize
=
0
;
NTSTATUS ntStatus
=
0
;
UNICODE_STRING objectName;
HANDLE SectionHandle
=
NULL;
PVOID BaseAddress
=
NULL;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES objectAttributes
=
{
0
};
RtlInitUnicodeString(
&objectName,
DLL_TO_FAKE_LOAD
);
InitializeObjectAttributes(
&objectAttributes,
&objectName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
ntStatus
=
NtOpenFile(
&hFile,
0x100021
,
&objectAttributes,
&IoStatusBlock,
5
,
0x60
);
ntStatus
=
NtCreateSection(
&SectionHandle,
0xd
,
NULL,
NULL,
0x10
,
SEC_IMAGE,
hFile
);
ntStatus
=
NtMapViewOfSection(
SectionHandle,
(HANDLE)
0xFFFFFFFFFFFFFFFF
,
&BaseAddress,
NULL,
NULL,
NULL,
&stSize,
0x1
,
0x800000
,
0x80
);
NtClose(SectionHandle);
}
int
main()
{
for
(
INT
i
=
0
; i <
10000
; i
+
+
)
{
FakeImageLoad();
}
return
0
;
}
BOOL
FakeImageLoad()
{
HANDLE hFile;
SIZE_T stSize
=
0
;
NTSTATUS ntStatus
=
0
;
UNICODE_STRING objectName;
HANDLE SectionHandle
=
NULL;
PVOID BaseAddress
=
NULL;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES objectAttributes
=
{
0
};
RtlInitUnicodeString(
&objectName,
DLL_TO_FAKE_LOAD
);
InitializeObjectAttributes(
&objectAttributes,
&objectName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
ntStatus
=
NtOpenFile(
&hFile,
0x100021
,
&objectAttributes,
&IoStatusBlock,
5
,
0x60
);
ntStatus
=
NtCreateSection(
&SectionHandle,
0xd
,
NULL,
NULL,
0x10
,
SEC_IMAGE,
hFile
);
ntStatus
=
NtMapViewOfSection(
SectionHandle,
(HANDLE)
0xFFFFFFFFFFFFFFFF
,
&BaseAddress,
NULL,
NULL,
NULL,
&stSize,
0x1
,
0x800000
,
0x80
);
NtClose(SectionHandle);
}
int
main()
{
for
(
INT
i
=
0
; i <
10000
; i
+
+
)
{
FakeImageLoad();
}
return
0
;
}
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY,
*
PLDR_DATA_TABLE_ENTRY;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY,
*
PLDR_DATA_TABLE_ENTRY;
UNICODE_STRING uFullPath;
UNICODE_STRING uFileName;
WCHAR
*
dllPath
=
L
"C:\\Windows\\System32\\CertEnroll.dll"
;
WCHAR
*
dllName
=
L
"CertEnroll.dll"
;
RtlInitUnicodeString(&uFullPath, dllPath);
RtlInitUnicodeString(&uFileName, dllName);
UNICODE_STRING uFullPath;
UNICODE_STRING uFileName;
WCHAR
*
dllPath
=
L
"C:\\Windows\\System32\\CertEnroll.dll"
;
WCHAR
*
dllName
=
L
"CertEnroll.dll"
;
RtlInitUnicodeString(&uFullPath, dllPath);
RtlInitUnicodeString(&uFileName, dllName);
status
=
NtQuerySystemTime(&pLdrEntry2
-
>LoadTime);
status
=
NtQuerySystemTime(&pLdrEntry2
-
>LoadTime);
pLdrEntry2
-
>LoadReason
=
LoadReasonDynamicLoad;
pLdrEntry2
-
>LoadReason
=
LoadReasonDynamicLoad;
SIZE_T bufSz
=
sizeof(buf);
LPVOID pAddress
=
VirtualAllocEx(hProcess,
0
, bufSz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(pAddress, buf, sizeof(buf));
SIZE_T bufSz
=
sizeof(buf);
LPVOID pAddress
=
VirtualAllocEx(hProcess,
0
, bufSz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(pAddress, buf, sizeof(buf));
pLdrEntry2
-
>BaseNameHashValue
=
UnicodeToHash(uFileName, FALSE);
pLdrEntry2
-
>BaseNameHashValue
=
UnicodeToHash(uFileName, FALSE);
pLdrEntry2
-
>ImageDll
=
TRUE;
pLdrEntry2
-
>LoadNotificationsSent
=
TRUE;
pLdrEntry2
-
>EntryProcessed
=
TRUE;
pLdrEntry2
-
>InLegacyLists
=
TRUE;
pLdrEntry2
-
>InIndexes
=
TRUE;
pLdrEntry2
-
>ProcessAttachCalled
=
TRUE;
pLdrEntry2
-
>InExceptionTable
=
FALSE;
pLdrEntry2
-
>OriginalBase
=
(ULONG_PTR)pAddress;
pLdrEntry2
-
>DllBase
=
pAddress;
pLdrEntry2
-
>SizeOfImage
=
6969
;
pLdrEntry2
-
>TimeDateStamp
=
0
;
pLdrEntry2
-
>BaseDllName
=
uFileName;
pLdrEntry2
-
>FullDllName
=
uFullPath;
pLdrEntry2
-
>ObsoleteLoadCount
=
1
;
pLdrEntry2
-
>Flags
=
LDRP_IMAGE_DLL | LDRP_ENTRY_INSERTED | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED;
pLdrEntry2
-
>ImageDll
=
TRUE;
pLdrEntry2
-
>LoadNotificationsSent
=
TRUE;
pLdrEntry2
-
>EntryProcessed
=
TRUE;
pLdrEntry2
-
>InLegacyLists
=
TRUE;
pLdrEntry2
-
>InIndexes
=
TRUE;
pLdrEntry2
-
>ProcessAttachCalled
=
TRUE;
pLdrEntry2
-
>InExceptionTable
=
FALSE;
pLdrEntry2
-
>OriginalBase
=
(ULONG_PTR)pAddress;
pLdrEntry2
-
>DllBase
=
pAddress;
pLdrEntry2
-
>SizeOfImage
=
6969
;
pLdrEntry2
-
>TimeDateStamp
=
0
;
pLdrEntry2
-
>BaseDllName
=
uFileName;
pLdrEntry2
-
>FullDllName
=
uFullPath;
pLdrEntry2
-
>ObsoleteLoadCount
=
1
;
pLdrEntry2
-
>Flags
=
LDRP_IMAGE_DLL | LDRP_ENTRY_INSERTED | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED;
pLdrEntry2
-
>DdagNode
=
(PLDR_DDAG_NODE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LDR_DDAG_NODE));
if
(!pLdrEntry2
-
>DdagNode)
{
return
-
1
;
}
pLdrEntry2
-
>NodeModuleLink.Flink
=
&pLdrEntry2
-
>DdagNode
-
>Modules;
pLdrEntry2
-
>NodeModuleLink.Blink
=
&pLdrEntry2
-
>DdagNode
-
>Modules;
pLdrEntry2
-
>DdagNode
-
>Modules.Flink
=
&pLdrEntry2
-
>NodeModuleLink;
pLdrEntry2
-
>DdagNode
-
>Modules.Blink
=
&pLdrEntry2
-
>NodeModuleLink;
pLdrEntry2
-
>DdagNode
-
>State
=
LdrModulesReadyToRun;
pLdrEntry2
-
>DdagNode
-
>LoadCount
=
1
;
pLdrEntry2
-
>DdagNode
=
(PLDR_DDAG_NODE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LDR_DDAG_NODE));
if
(!pLdrEntry2
-
>DdagNode)
{
return
-
1
;
}
pLdrEntry2
-
>NodeModuleLink.Flink
=
&pLdrEntry2
-
>DdagNode
-
>Modules;
pLdrEntry2
-
>NodeModuleLink.Blink
=
&pLdrEntry2
-
>DdagNode
-
>Modules;
pLdrEntry2
-
>DdagNode
-
>Modules.Flink
=
&pLdrEntry2
-
>NodeModuleLink;
pLdrEntry2
-
>DdagNode
-
>Modules.Blink
=
&pLdrEntry2
-
>NodeModuleLink;
pLdrEntry2
-
>DdagNode
-
>State
=
LdrModulesReadyToRun;
pLdrEntry2
-
>DdagNode
-
>LoadCount
=
1
;
int
__stdcall HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
printf(
"\n[ HOOKED MESSAGEBOXA ]\n"
);
printf(
"-> Arguments:\n"
);
printf(
" 1. lpText: %s\n"
, lpText);
printf(
" 2. lpCaption: %s\n"
, lpCaption);
printf(
" 3. uType: %ld\n"
, uType);
return
1
;
}
void PrintHexA(char
*
data,
int
sz)
{
printf(
" -> "
);
for
(
int
i
=
0
; i < sz; i
+
+
)
{
printf(
"\\x%02hhX"
, data[i]);
}
printf(
"\n"
);
}
int
main()
{
SIZE_T lpNumberOfBytesRead
=
0
;
HMODULE hModule
=
nullptr;
FARPROC pMessageBoxAFunc
=
nullptr;
char pMessageBoxABytes[BYTES_REQUIRED]
=
{};
void
*
pHookedMessageBoxFunc
=
&HookedMessageBoxA;
hModule
=
LoadLibraryA(
"user32.dll"
);
if
(!hModule)
{
return
-
1
;
}
pMessageBoxAFunc
=
GetProcAddress(hModule,
"MessageBoxA"
);
printf(
"-> Original MessageBoxA: 0x%p\n"
, pMessageBoxAFunc);
if
(ReadProcessMemory(GetCurrentProcess(), pMessageBoxAFunc, pMessageBoxABytes, BYTES_REQUIRED, &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] ReadProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
printf(
"-> MessageBoxA Hex:\n"
);
PrintHexA(pMessageBoxABytes, BYTES_REQUIRED);
printf(
"-> Hooked MessageBoxA: 0x%p\n"
, pHookedMessageBoxFunc);
char patch[BYTES_REQUIRED]
=
{
0
};
memcpy_s(patch,
1
,
"\x68"
,
1
);
memcpy_s(patch
+
1
,
4
, &pHookedMessageBoxFunc,
4
);
memcpy_s(patch
+
5
,
1
,
"\xC3"
,
1
);
printf(
"-> Patch Hex:\n"
);
PrintHexA(patch, BYTES_REQUIRED);
if
(WriteProcessMemory(GetCurrentProcess(), (LPVOID)pMessageBoxAFunc, patch, sizeof(patch), &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] WriteProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
MessageBoxA(NULL,
"AAAAA"
,
"BBBBB"
, MB_OK);
return
0
;
}
int
__stdcall HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
printf(
"\n[ HOOKED MESSAGEBOXA ]\n"
);
printf(
"-> Arguments:\n"
);
printf(
" 1. lpText: %s\n"
, lpText);
printf(
" 2. lpCaption: %s\n"
, lpCaption);
printf(
" 3. uType: %ld\n"
, uType);
return
1
;
}
void PrintHexA(char
*
data,
int
sz)
{
printf(
" -> "
);
for
(
int
i
=
0
; i < sz; i
+
+
)
{
printf(
"\\x%02hhX"
, data[i]);
}
printf(
"\n"
);
}
int
main()
{
SIZE_T lpNumberOfBytesRead
=
0
;
HMODULE hModule
=
nullptr;
FARPROC pMessageBoxAFunc
=
nullptr;
char pMessageBoxABytes[BYTES_REQUIRED]
=
{};
void
*
pHookedMessageBoxFunc
=
&HookedMessageBoxA;
hModule
=
LoadLibraryA(
"user32.dll"
);
if
(!hModule)
{
return
-
1
;
}
pMessageBoxAFunc
=
GetProcAddress(hModule,
"MessageBoxA"
);
printf(
"-> Original MessageBoxA: 0x%p\n"
, pMessageBoxAFunc);
if
(ReadProcessMemory(GetCurrentProcess(), pMessageBoxAFunc, pMessageBoxABytes, BYTES_REQUIRED, &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] ReadProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
printf(
"-> MessageBoxA Hex:\n"
);
PrintHexA(pMessageBoxABytes, BYTES_REQUIRED);
printf(
"-> Hooked MessageBoxA: 0x%p\n"
, pHookedMessageBoxFunc);
char patch[BYTES_REQUIRED]
=
{
0
};
memcpy_s(patch,
1
,
"\x68"
,
1
);
memcpy_s(patch
+
1
,
4
, &pHookedMessageBoxFunc,
4
);
memcpy_s(patch
+
5
,
1
,
"\xC3"
,
1
);
printf(
"-> Patch Hex:\n"
);
PrintHexA(patch, BYTES_REQUIRED);
if
(WriteProcessMemory(GetCurrentProcess(), (LPVOID)pMessageBoxAFunc, patch, sizeof(patch), &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] WriteProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
MessageBoxA(NULL,
"AAAAA"
,
"BBBBB"
, MB_OK);
return
0
;
}
hModule
=
LoadLibraryA(
"user32.dll"
);
if
(!hModule)
{
return
-
1
;
}
hModule
=
LoadLibraryA(
"user32.dll"
);
if
(!hModule)
{
return
-
1
;
}
pMessageBoxAFunc
=
GetProcAddress(hModule,
"MessageBoxA"
);
pMessageBoxAFunc
=
GetProcAddress(hModule,
"MessageBoxA"
);
if
(ReadProcessMemory(GetCurrentProcess(), pMessageBoxAFunc, pMessageBoxABytes, BYTES_REQUIRED, &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] ReadProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
if
(ReadProcessMemory(GetCurrentProcess(), pMessageBoxAFunc, pMessageBoxABytes, BYTES_REQUIRED, &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] ReadProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
\x8B\xFF\x55\x8B\xEC\x83
char patch[BYTES_REQUIRED]
=
{
0
};
memcpy_s(patch,
1
,
"\x68"
,
1
);
memcpy_s(patch
+
1
,
4
, &pHookedMessageBoxFunc,
4
);
memcpy_s(patch
+
5
,
1
,
"\xC3"
,
1
);
char patch[BYTES_REQUIRED]
=
{
0
};
memcpy_s(patch,
1
,
"\x68"
,
1
);
memcpy_s(patch
+
1
,
4
, &pHookedMessageBoxFunc,
4
);
memcpy_s(patch
+
5
,
1
,
"\xC3"
,
1
);
\x68\x12\x12\xBD\x00\xC3
0
:
68
12
12
bd
00
push
0xbd1212
5
: c3 ret
0
:
68
12
12
bd
00
push
0xbd1212
5
: c3 ret
void
*
pHookedMessageBoxFunc
=
&HookedMessageBoxA;
void
*
pHookedMessageBoxFunc
=
&HookedMessageBoxA;
if
(WriteProcessMemory(GetCurrentProcess(), (LPVOID)pMessageBoxAFunc, patch, sizeof(patch), &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] WriteProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
if
(WriteProcessMemory(GetCurrentProcess(), (LPVOID)pMessageBoxAFunc, patch, sizeof(patch), &lpNumberOfBytesRead)
=
=
FALSE)
{
printf(
"[!] WriteProcessMemory: %ld\n"
, GetLastError());
return
-
1
;
}
00BB1212
jmp HookedMessageBoxA (
0BB1A80h
)
00BB1212
jmp HookedMessageBoxA (
0BB1A80h
)
int
__stdcall HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
printf(
"\n[ HOOKED MESSAGEBOXA ]\n"
);
printf(
"-> Arguments:\n"
);
printf(
" 1. lpText: %s\n"
, lpText);
printf(
" 2. lpCaption: %s\n"
, lpCaption);
printf(
" 3. uType: %ld\n"
, uType);
return
1
;
}
int
__stdcall HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
printf(
"\n[ HOOKED MESSAGEBOXA ]\n"
);
printf(
"-> Arguments:\n"
);
printf(
" 1. lpText: %s\n"
, lpText);
printf(
" 2. lpCaption: %s\n"
, lpCaption);
printf(
" 3. uType: %ld\n"
, uType);
return
1
;
}
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;
InstrumentationCallbackInfo.Version
=
0
;
InstrumentationCallbackInfo.Reserved
=
0
;
InstrumentationCallbackInfo.Callback
=
CALLBACK_FUNCTION_GOES_HERE;
HANDLE hProcess
=
(HANDLE)
-
1
;
HMODULE hNtdll
=
GetModuleHandleA(
"ntdll"
);
if
(hNtdll
=
=
nullptr)
{
return
FALSE;
}
_NtSetInformationProcess pNtSetInformationProcess
=
reinterpret_cast<_NtSetInformationProcess>(GetProcAddress(hNtdll,
"NtSetInformationProcess"
));
if
(pNtSetInformationProcess
=
=
nullptr)
{
return
FALSE;
}
NTSTATUS Status
=
pNtSetInformationProcess(hProcess, (PROCESS_INFORMATION_CLASS)ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof(InstrumentationCallbackInfo));
if
(NT_SUCCESS(Status))
{
return
TRUE;
}
else
{
return
FALSE;
}
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;
InstrumentationCallbackInfo.Version
=
0
;
InstrumentationCallbackInfo.Reserved
=
0
;
InstrumentationCallbackInfo.Callback
=
CALLBACK_FUNCTION_GOES_HERE;
HANDLE hProcess
=
(HANDLE)
-
1
;
HMODULE hNtdll
=
GetModuleHandleA(
"ntdll"
);
if
(hNtdll
=
=
nullptr)
{
return
FALSE;
}
_NtSetInformationProcess pNtSetInformationProcess
=
reinterpret_cast<_NtSetInformationProcess>(GetProcAddress(hNtdll,
"NtSetInformationProcess"
));
if
(pNtSetInformationProcess
=
=
nullptr)
{
return
FALSE;
}
NTSTATUS Status
=
pNtSetInformationProcess(hProcess, (PROCESS_INFORMATION_CLASS)ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof(InstrumentationCallbackInfo));
if
(NT_SUCCESS(Status))
{
return
TRUE;
}
else
{
return
FALSE;
}
InstrumentationCallbackInfo.Callback
=
CALLBACK_FUNCTION_GOES_HERE;
InstrumentationCallbackInfo.Callback
=
CALLBACK_FUNCTION_GOES_HERE;
.code
PUBLIC asmCallback
EXTERN Hook:PROC
asmCallback PROC
push rax ;
return
value
push rcx
push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15
; without this it crashes :)
sub rsp,
1000h
mov rdx, rax
mov rcx, r10
call Hook
add rsp,
1000h
pop R15
pop R14
pop R13
pop R12
pop RSP
pop RSI
pop RDI
pop RBP
pop RBX
pop rcx
pop rax
jmp R10
asmCallback ENDP
end
.code
PUBLIC asmCallback
EXTERN Hook:PROC
asmCallback PROC
push rax ;
return
value
push rcx
push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15
; without this it crashes :)
sub rsp,
1000h
mov rdx, rax
mov rcx, r10
call Hook
add rsp,
1000h
pop R15
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-8-28 14:47
被Max_hhg编辑
,原因: 格式优化