首页
社区
课程
招聘
[原创] 剖析 InfinityHook 原理 掀起一场更激烈的攻与防恶战
发表于: 2019-7-30 11:31 43944

[原创] 剖析 InfinityHook 原理 掀起一场更激烈的攻与防恶战

2019-7-30 11:31
43944

Hook 是一种可以改变程序执行流程的技术,巧妙利用 Hook 技术可以实现很多实用的操作,如:监控、过滤、拦截、修改等。

但在现代操作系统中,出现了诸多限制,导致无法轻松的进行底层Hook。其中典型例子就是 Win10 的 PatchGuard。

现在,我们将深度剖析一个神奇的技术:InfinityHook。


InfinityHook 是一个可以Hook各种系统调用、上下文切换、页面错误、DPC 等内核事件的技术。

它目前可以与 PatchGuard 同时运行,且比常规 Hook 技术具有更好隐蔽性。


InfinityHook 并不是其名意所谓的“无限 Hook”,实际意义是可以安全的对内核底层进行 Hook。

因为安全界中攻与防的对抗,产生了一种,谁先 Hook,谁 Hook 的最底层,谁最有优势的概念。

而攻与防之间互相抢夺 Hook 优势的现象,像极了一个无尽深渊,没有终点。

微软为了保护底层系统的安全,防止被第三方程序滥用,在一系列新版本操作系统中推出和升级了 PatchGuard。

而 InfinityHook 的出现打破了微软的这一保护,拥有更安全更底层的 Hook 优势。

随着时间的推移这项技术会广为人知,这意味着一场更激烈的攻防无限 Hook 战争即将开战。

故名“InfinityHook”。


想要更深入剖析 InfinityHook 的原理,就得先了解 Windows 的 Event Tracing 机制。


Windows事件跟踪(ETW)是一种高效的内核级跟踪工具,可让您将内核或应用程序定义的事件记录到日志文件中。您可以实时或从日志文件中使用事件,并使用它们来调试应用程序或确定应用程序中发生性能问题的位置。

ETW 允许您动态启用或禁用事件跟踪,允许您在生产环境中执行详细跟踪,而无需重新启动计算机或应用程序。


事件跟踪 API 分为三个不同的组件:

控制器是定义日志文件的大小和位置、启动和停止事件跟踪会话、启用提供程序以便将事件记录到会话、管理缓冲池的大小以及获取会话的执行统计信息的应用程序。


提供程序是包含事件跟踪工具的应用程序。提供程序注册后,控制器可以在提供程序中启用或禁用事件跟踪。提供者定义其启用或禁用的实现。通常,启用的提供程序会生成事件,而禁用的提供程序则不会。这使您可以向应用程序添加事件跟踪,而无需始终生成事件。

虽然 ETW 模型将控制器和提供程序分离为单独的应用程序,但应用程序可以包含这两个组件。 


首先我们要知道一点,InfinityHook 因为一些特殊的操作必须要在 Ring0 层才能实现。

而 ETW 模型的3个组件是可以在 Ring3 完成。所以我们可以参考微软开放的文档例子帮助分析。


找到微软开放的“配置和启动 NT 内核记录器会话”的代码:

https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-the-nt-kernel-logger-session


我从网页截取了关键代码贴了上来:



StartTrace 函数的声明:



分配的内存长度不仅仅只是结构体的大小,还包含了两段其他的长度。


看一下 msdn 对这个 API 的描述:

https://docs.microsoft.com/en-us/windows/win32/etw/starttrace


其中同样有一点值得关注:

This function copies the session name that you provide to the offset that the LoggerNameOffset member of Properties points to.

此函数将您提供的会话名称复制到属性的LoggerNameOffset成员指向的偏移量。

这段话说明我们拷贝到结构体之后的字符串还会在 StartTrace 内部被处理。


对在 Ring3 启动跟踪会话的信息了解的差不多了。但是问题来了,如何在 Ring0 层实现这些东西呢?

所以我们得上 IDA 分析一下这个函数的内部实现。

从 msdn 的描述里我们可以看到:

DLL

Sechost.dll on Windows 8.1 and Windows Server 2012 R2;

Advapi32.dll on Windows 8, Windows Server 2012, Windows 7, Windows Server 2008 R2, Windows Server 2008, Windows Vista and Windows XP

所以我们将 Advapi32.dll 文件用IDA打开,下载 dll 的符号文件让 IDA 自动分析。

分析完成后,转到 StartTraceA 函数,从函数头部开始看。



首先是对参数的合法性检查。



接着是对参数2的 InstanceName 字符串内容进行比较。

如果为“NT Kernel Logger”,则将 SystemTraceControlGuid 的 Guid 赋值给 Properties->Wnode.Guid。

如果为“Circular Kernel Context Logger”, 则将 CKCLGuid 的 Guid 赋值给 Properties->Wnode.Guid。



我们看下这个 CKCLGuid 的内容,之后会用到。



首先判断“Properties->LogFileNameOffset”如果小于等于0,则跳到 LABEL_67 标签。

在图片底部可以看到 LABEL_67 标签置零了2个变量,但并没有返回退出。

说明这个函数允许 LogFileNameOffset 的值为0。


同样,我们在 msdn 查阅一下这个结构体:

https://docs.microsoft.com/en-us/windows/win32/etw/event-trace-properties


找到对这个成员的描述,截取关键内容下来:

LogFileNameOffset


Offset from the start of the structure's allocated memory to beginning of the null-terminated string that contains the log file name.

从结构的已分配内存开始到包含日志文件名的以空值终止的字符串的开头的偏移量。


If you do not want to log events to a log file (for example, you specify EVENT_TRACE_REAL_TIME_MODE only), set LogFileNameOffset to 0. If you specify only real-time logging and also provide an offset with a valid log file name, ETW will use the log file name to create a sequential log file and log events to the log file. ETW also creates the sequential log file if LogFileMode is 0 and you provide an offset with a valid log file name.

如果您不想将事件记录到日志文件中(例如,仅指定EVENT_TRACE_REAL_TIME_MODE),请将LogFileNameOffset设置为0.如果仅指定实时日志记录并提供具有有效日志文件名的偏移量,则ETW将使用日志文件名,用于创建顺序日志文件并将事件记录到日志文件中。如果LogFileMode为0,ETW还会创建顺序日志文件,并提供带有效日志文件名的偏移量。 

文章头部对 ETW 的介绍中写到:

您可以实时或从日志文件中使用事件,并使用它们来调试应用程序或确定应用程序中发生性能问题的位置。

我们可以得知, 日志文件是用于调试应用程序或定位性能问题位置的。而 InfinityHook 并不需要这些东西。

所以 LogFileNameOffset 成员可以直接置0,跳过这段继续往下分析。



接着是一些参数标志合法性检查,这些合法性检查由 Etwp 开头的 API 完成。

假定我们传递的所有参数都是正确的,所以无需跟进这些 API 进行分析。



从堆中分配一块内存空间,后面要用到。



将我们传递进来的 Properties 参数填进v27变量内。

接着我们可以看到几个字符串操作 API 。还记得刚刚在 msdn 提到的关注点吗?应该就是这里了。

第一个字符串的处理是将我们传递进来的 InstanceName 参数初始化为一个 ANSI_STRING 字符串。

再将其转换为一个 UNICODE_STRING 字符串。

转换完的 UNICODE_STRING 字符串存放在 (PUNICODE_STRING)v27 + 9 的位置。

UNICODE_STRING 结构体的大小为16,故步长为16。实际存放地址是在 v27 + 144 。

再看下 EVENT_TRACE_PROPERTIES 结构体的大小为120。

说明这个 UNICODE_STRING 字符串存放在 v27 + sizeof(EVENT_TRACE_PROPERTIES) + 24 的位置。



处理完第一个字符串后有一个判断,如果 v46 等于0,则跳到 LABEL_69 标签。

而这个 v46 就是刚刚判断 LogFileNameOffset 如果等于0所置零的变量。

所以这里可以跳过第二个字符串的处理,直接往下看。



看到了两个 Etwp 开头的API,我们先跟进第一个 API 看看。




不难看出,这个 API 对 Property 的内容影响并不大,只有两处,74和72偏移。

对比结构体定义发现,是在对 EnableFlags 成员填值。

这个成员放到后面再讲,所以这里先暂时跳过此 API。



接着来看 EtwpStartLogger 这个API。

可以很直观的看到,检查了一些成员后调用了 NtTraceControl 这个 API。

这个就是我们在 Ring0 层启动事件跟踪会话的API了。


在这 API 之下是一些与结构体内容无关的操作。所以直接跳过,返回上层继续看。



将堆内存的 Property 内容,置回到参数 Property 中。然后跳到 LABEL_58 标签。



释放堆内存,返回成功与否。


至此,StartTraceA 这个 API 逆向完成。

我们知道了部分参数的填写规则和关键 API。


下一步我们开始写 InfinityHook 的实现代码。



然后定义一个变量为其申请一块内存空间,并对其置零。

为了保险起见,以防底层 API 还会对结构体更后面的内存进行操作,所以申请的大小填 PAGE_SIZE。


然后初始化一个变量名为 InstanceName 的 UNICODE_STRING 字符串。


接着我们开始填写 pProperty 内容。


pProperty->Wnode.BufferSize:填写我们为其申请内存的大小 PAGE_SIZE。

pProperty->Wnode.ClientContext:

根据 msdn 的描述,此成员为时间戳的分辨率。可以为3个值:

我们这里填写为3值(CPU 循环计数器),因为它的实现很简单,仅使用了一条汇编指令,后面会讲。

pProperty->Wnode.Flags:根据 msdn 的描述,必须包含 WNODE_FLAG_TRACED_GUID 以指示该结构包含事件跟踪信息。

pProperty->Wnode.Guid:

从上面的 IDA 中取出来改为代码则是:


pProperty->BufferSize:每个事件的缓冲区,直接填4字节即可,因为我们不需要实际接收事件内容。

pProperty->LogFileMode:上面说过,我们不需要 ETW 写入日志文件,所以设置为 EVENT_TRACE_BUFFERING_MODE。

pProperty->MinimumBuffers:为事件跟踪会话的缓冲池分配的最小缓冲区数,最小为2,直接填2,。

pProperty->MaximumBuffers:

这个成员 msdn 描述的有点矛盾。

既说必须大于等于 MinimumBuffers,又说 LogFileMode 设置了 EVENT_TRACE_BUFFERING_MODE 可以不用设置此值。

虽然应该可以忽略这个成员,但这里为了保险起见,我们设置和 MinimumBuffers 一样的值。

pProperty->InstanceName:这个为我们刚刚扩充的成员,填写初始化的 UNICODE_STRING 字符串 InstanceName 即可。


所以Property的成员填写代码是这样的。



然后就是控制 CKCL 事件跟踪会话。


NtTraceControl,msdn 并没有给出他的声明。结合谷歌搜索、GitHub 项目和 IDA 分析,可以得到的声明为:


其中参数一可以是5个常量中的任意一个:


这5个常量在此 API 内部对应5个 Etwp 开头的 API,分别代表:启动跟踪、停止跟踪、查询跟踪、更新跟踪、刷新跟踪。

InfinityHook 只需要:启动、停止和更新。


值得注意的是,我们需要在更新跟踪的时候,设置 pProperty->EnableFlags 标志,以此来过滤我们想要的事件。

本文将以 Hook Syscall 中的 NtOpenProcess 作为例子进行讲解。

所以这里设置 EVENT_TRACE_FLAG_SYSTEMCALL 标志,以拦截到所有的 Syscall 事件。


我们将以上内容封装为一个函数,所以整个函数的代码是这样的:


接着调用这个函数,启动并更新 CKCL 事件跟踪对象:


至此,CKCL 已成功启动和更新。



下面,我们需要找到一些内核全局地址,来方便我们进行之后的操作。


EtwpDebuggerData,是一张存放所有 ETW 信息的表。

通过IDA搜索可以发现有这个变量符号,但是查看交叉引用却发现IDA并没有分析出哪里使用了这个变量。

所以只能通过暴搜的方法去找到这个变量的地址。


通过多个系统版本的内核文件进行 IDA 分析,可以发现两条信息。



那我们首先取到内核基地址,解析PE头找到“.data”和".rdata"区段的起始地址和区段大小,然后根据特征码进行暴搜。



接着在 EtwpDebuggerData + 0x10 处取到 EtwpDebuggerDataSilo 指针。

再从 EtwpDebuggerDataSilo + 0x10 的位置取到 CkclWmiLoggerContext 指针。

这两个数据的硬编码偏移在所有系统上都一样,并没有发生改变。

EtwpDebuggerDataSilo 和 CkclWmiLoggerContext,是直接参考的 GitHub 上的项目,

因为在IDA中找了很久都没有找到关于 EtwpDebuggerDataSilo 的信息,

仅仅找到了一个可能是 CkclWmiLoggerContext 的指针,是动态分配的,

鄙人逆向能力有限,实在没能跟到 EtwpDebuggerDataSilo 在哪,

如果有大佬研究出来了,欢迎在评论区分享,谢谢!


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-7-6 03:03 被Sprite雪碧编辑 ,原因:
收藏
免费 42
支持
分享
打赏 + 12.00雪花
打赏次数 3 雪花 + 12.00
 
赞赏  漆黑火焰魔法使   +1.00 2023/01/08 感谢分享~
赞赏  VCKFC   +1.00 2019/12/28
赞赏  黑手鱼   +10.00 2019/11/22 优秀
最新回复 (70)
雪    币: 1564
活跃值: (3572)
能力值: ( LV13,RANK:420 )
在线值:
发帖
回帖
粉丝
2
nice~支持
2019-7-30 11:39
0
雪    币: 5194
活跃值: (9722)
能力值: ( LV9,RANK:181 )
在线值:
发帖
回帖
粉丝
3
好文,学习!
2019-7-30 11:41
0
雪    币: 6584
活跃值: (4541)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
4
 毕竟攻防无绝对,技术无黑白。
没有攻与防之间的战斗,哪里来绝对的安全
2019-7-30 12:01
0
雪    币: 712
活跃值: (121)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
666666
2019-7-30 13:50
0
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
6
火钳刘明
2019-7-30 15:17
0
雪    币: 6124
活跃值: (4666)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
7
19xx可能来不及了,下一个版本有望被patchguard修复。如果影响力不大的话可能会放过(怎么可能)
2019-7-30 16:02
0
雪    币: 635
活跃值: (1016)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
mark
2019-7-30 17:09
0
雪    币: 914
活跃值: (2468)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
9
lei了lei了
2019-7-30 17:20
0
雪    币: 204
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
无敌啊。
2019-7-30 18:05
0
雪    币: 9217
活跃值: (1886)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
11
发出来了,可以的,先回复一下,之后再看
2019-7-30 19:49
0
雪    币: 66
活跃值: (2746)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
既然发出来了  就等着被修复吧
2019-7-30 20:48
0
雪    币: 9217
活跃值: (1886)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
等大佬们搞其他事件的Hook
2019-7-31 00:12
0
雪    币: 43
活跃值: (388)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
14
类似的技术我已经用很久了,终于有人发出来了。这套技术还可以用于注入代码到进程运行。
2019-7-31 00:39
0
雪    币: 4709
活跃值: (1575)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
15
lei了lei了,mark
2019-7-31 02:22
0
雪    币: 688
活跃值: (3630)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
16
2019-7-31 05:13
0
雪    币: 7885
活跃值: (2285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
分析很到位,赞
2019-7-31 07:35
0
雪    币: 429
活跃值: (418)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
18
2019-7-31 07:50
0
雪    币: 15191
活跃值: (16857)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
19
这个流批!讲的很详细
2019-7-31 10:40
0
雪    币: 284
活跃值: (3604)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
20
牛逼666
2019-7-31 12:00
0
雪    币: 161
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
牛逼
2019-7-31 12:29
0
雪    币: 388
活跃值: (707)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
22
nice,支持,思路不错!
2019-7-31 12:52
0
雪    币: 7092
活跃值: (2988)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
牛逼
2019-7-31 13:58
0
雪    币: 3407
活跃值: (1242)
能力值: ( LV13,RANK:335 )
在线值:
发帖
回帖
粉丝
24
这个bug的产生应该是部门之间的协作出现了问题:PatchGuard 不让hook,改检查的地方都检查了,内核数据结构,代码段,做好了放下了。现在ETW要上,要实现功能,还要灵活配置,就留下了这样的接口,然而并没有考虑到是否充当了保护罩让hook通过了PatchGuard 检查。或者想到了,没有通知PatchGuard 。或者通知了PatchGuard ,却无力推动PatchGuard 修复。我所在的一个2000人开发的公司都推不动,何况微软呢。
2019-7-31 15:19
1
雪    币: 3407
活跃值: (1242)
能力值: ( LV13,RANK:335 )
在线值:
发帖
回帖
粉丝
25
这也给挖洞提供了一个思路。杀软不让病毒执行却为了管理方便,留了一个后门给自己自动更新等等
2019-7-31 15:24
1
游客
登录 | 注册 方可回帖
返回
//