freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

EDR的梦魇:Storm-0978使用新型内核注入技术“Step Bear”
2024-04-23 15:15:15

概述

2023 年 10 月,奇安信威胁情报中心发布了《Operation HideBear:俄语威胁者将目标瞄准东亚和北美》[1]一文,我们在文中提到攻击者的目标有着经济和技术的双重目的,经济上瞄准投资机构和比特币公司(个人),技术上对我国电感元器件制造商和生物抗体研究制药有着浓厚的兴趣,我们以中等程度的信心将其归属于 Strom-0978,并于 2023 年末对其进行持续的跟踪中捕获了一个非常奇怪的样本,经过长时间的逆向分析发现攻击者使用了一套之前从未见披露过的内核注入技术,我们将其命名为“Step Bear”,在注入中使用天堂之门和地狱之门的调用方式启动一些不常见的内核函数导致该注入技术能够绕过主流的 EDR 检测,注入流程如下:1713854829_6627596d470c6d1c7bcdd.png!small?1713854829998

  • Step1:恶意进程调用内核函数 xxxSetClassLong 向内核中的 tagCLS 额外类内存写入精心构造的 Shellcode。Shellcode 包含一段 gadgets 工具函数和多段小型 Shellcode 组成。
  • Step2:内核自动将 tagCLS 额外类内存映射到 Notepad 的只读内存块中实现代码注入。
  • Step3:使用内核版的 wordwrap 方式触发 RPC 调用链执行 Notepad 只读内存块中的 Shellcode,RPC 的调用链会先触发 Shellcode 中的 gadgets 函数,然后调用 VirtualProtect 将小型 Shellcode 的属性改为 RWX,并执行。
  • Step4:相同的方式将精心构造的 Shellcode 写入到 tagCLS 额外类内存中。
  • Step5:内核将 Shellcode 映射到浏览器进程的只读内存块中实现代码注入。
  • Step6:攻击者通过向 windows 中一个未公开的 com 窗口发送自定义的消息后触发浏览器中只读 Shellcode 的 RPC 调用链。
  • Step7:分配 RWX 类型的内存将 CRX 插件注入到浏览器中。

注入技术介绍

注入原理

攻击者在注入过程使用地狱之门(Hell's Gate)的 syscall 调用 NtUserSetClassLong,该函数底层会调用 win32kfull.sys 中的 xxxSetClassLong 函数。

1713855092_66275a74d5822991129bc.png!small?1713855093817

1713855096_66275a7861a7736697cc3.png!small?1713855096840

NtUserSetClassLong,第一个参数是刚刚创建的窗口句柄,第二个参数为偏移,一开始赋值为 0,第三个参数为指定的数据,xxxSetClassLong 函数结构如下, 该函数主要功能是将指定数据写入内核对象 tagCLS 的额外类内存中:

1713855102_66275a7e4a051546bb0ab.png!small?1713855103202

在我们双机调试的内核版本中 tagCLS 结构大小为 0xA0。

1713855107_66275a839b646e2d39138.png!small?1713855108089

这段内核内存有一个重要的属性:写入数据的这段内核空间在同一用户会话下的任意一个应用层进程中都有内存映射,如果我们此时随便打开一个进程,例如计算器,那么会在 calc 进程中的某个映射内存块中找个上述写入的指定数据,

1713855115_66275a8bc0fdf7c51fbcc.png!small?17138551172141713855175_66275ac773a11dbc05982.png!small?1713855175987

映射到应用层进程中的只读内存块大小和内容都在变化,但基址不变,换句话说攻击者只要操纵 malicious.exe 修改这段共享的内核空间数据,就可以实现针对任意进程的代码注入,但是注入的内存块属性为只读,所以攻击者接下来需要使用某种方式来触发共享内存块中的恶意代码。

触发前的准备

样本首先会执行一些初始化操作,包括加载系统dll、获取自身模块名、判断自身权限等。

会创建 Notepad 进程,获取 Notepad 进程中 edit 窗口的句柄。

1713855187_66275ad3570600f9b63dd.png!small?1713855188094

注册一个带有额外类内存的窗口类,其窗口过程为默认的 NtdllDefWindowProc,额外类内存大小为 0x16010。

1713855193_66275ad96126fc4fa19f6.png!small?1713855193876

之后会使用这个窗口类创建一个仅消息窗口,创建后会调用 NtUserSetClassLong 填充内核中共享内存块中的数据。

1713855200_66275ae070791f1badced.png!small?1713855200924

此次填充的数据仅用于定位内核共享数据块映射到 malicious.exe 中的具体地址,样本通过查找 TEB->Win32ClientInfo->phkCurrent 结构下的 AllocationBase 来确定窗口内存的基址,接着对该区域的内存与之前调用填充的数据进行逐个比较找到共享内存块的地址,找到位置后再次调用 NtUserSetClassLong 将填充数据清空。

1713855206_66275ae697cc57699fa41.png!small?1713855207680

在 Notepad 内存中搜索指定 API 地址和共享内存块的地址,用于之后填充的 Shellcode 调用。

1713855212_66275aec723ea1630c944.png!small?1713855212904

其搜索的 API 如下

1713855219_66275af3e7f6c86d2dc83.png!small?1713855221180

API 解析完毕后会在内存中解密一段 payload,我们将其称为 ShellcodeA。

1713855226_66275afa34c8935460a93.png!small?1713855226551

然后使用天堂之门(Heaven's Gate)的调用方式启动 NtUserSetClassLongPtr 向内核共享数据注入 ShellcodeA。

1713855231_66275aff55640f25e3d95.png!small?1713855231882

ShellcodeA 的结构非常复杂,ShellcodeA 中包含:gadgets 工具函数和多段小型 Shellcode 组成,我们将其统称为 ShellcodeB,具体结构如下:

1713855237_66275b05a7db1e196f46b.png!small?1713855238744

至此 ShellCodeA 已经注入到 Notepad 进程中,下面开始进入触发流程。

怎么触发?

由于攻击者实现了两次注入,两次注入的方式均是通过内核共享内存块来实现的,但是两次的触发方式则完全不同,Notepad 的触发方式较为常见,而 Chrome 中的触发方式之前从未见到。

Notepad触发

攻击者通过通过设置 EM_SETWORDBREAKPROC 来注册文本包装器回调函数,并发送 WM_LBUTTONDBLCLK 消息来触发回调函数,回调函数的参数地址则在之前通过 WM_SETTEXT 的消息进行了设置。

1713855250_66275b1260febc9b12010.png!small?1713855250817

最终触发的函数为 rpcrt4.dll 中的 I_RpcFreePipeBuffer。

1713855263_66275b1fbfe7a75ee45e2.png!small?1713855264215

I_RpcFreePipeBuffer 执行后会直接调用 Message->Handle + 0x80 位置处的函数。

1713855269_66275b256307f2911235e.png!small?1713855270780

根据 ShellcodeA 中的结构此时 Message->Handle + 0x80 的位置为 NdrServerCallAll 的地址。

1713855275_66275b2b7f5584aa8d42d.png!small?1713855277374

NdrServerCallAll 继承了 RpcFreePipeBuffer 的 Message 参数,在进行简单处理后会调用 Ndr64StubWorker。

1713855282_66275b32cf4bb426ead53.png!small?1713855283424

PRPC_MESSAGE 相关的结构如下(x86):

1713855287_66275b379e662d46aec4c.png!small?1713855288093

Ndr64StubWorker 首先通过 Ndr64pServerUnMarshal 函数解析 Message 参数并将解析后的数据传递到栈中指向的内存里。

1713855294_66275b3eb656161de20e5.png!small?1713855295260

PRPC_MESSAGE_A 是 NdrServerCallAll 的参数,会调用 Invoke 函数执行 PRPC_MESSAGE_A 中DispatchTable 的地址,

1713855301_66275b4516d8a568777c5.png!small?1713855301716

PRPC_MESSAGE_A 中 DispatchTable 的地址还是 NdrServerCallAll,至此形成套娃,会第二次进入 NdrServerCallAll,此时的参数为 PRPC_MESSAGE_B,而 PRPC_MESSAGE_B 的 DispatchTable 函数为 VirtualProtect,将共享内存块中 ShellcodeB 的内存属性改为可读可写可执行。

1713855308_66275b4c11f68788106ee.png!small?1713855311420

1713855317_66275b55dd08986e34f14.png!small?1713855319069

调用链如下:

1713855323_66275b5b9c7c9bb708c0c.png!small?1713855324203

攻击者使用了 Ndr64StubWorker 中一个从未披露的函数来执行 ShellcodeB,在调用 Invoke 函数后会顺序执行到 Ndr64pFreeParams,这个函数可以执行攻击者控制的函数地址。1713855331_66275b63745676d465195.png!small?1713855331895

由于 ShellcodeA 的特殊结构导致目前函数调用进入 NdrServerCallAll 两层嵌套的状态,所以会在两次 Invoke 函数和两次 Ndr64pFreeParams 处执行攻击者控制的代码。执行顺序分别为 Invoke_A->Invoke_B->Ndr64pFreeParams_B-> Ndr64pFreeParams_A,那么接下来会执行 Ndr64pFreeParams_B 中攻击者控制的代码,此时要调用的函数被攻击者设计成 CallWindowProcW,第一个参数 lpPrevWndFunc 被填充为 ShellcodeB 的起始地址。

1713855340_66275b6c031dc3f07556a.png!small?1713855340963

而 0x3C0 大小的 ShellcodeB 实际上有三段小型 Shellcode 组成,我们将其分为 ShellcodeC、ShellcodeD 和ShellcodeE,ShellcodeB 在执行链中的结构如下:1713855348_66275b742b930e6805d77.png!small?1713855348878

ShellcodeC 中的代码主要为 Jmp,跳转到 ShellcodeD。


1713855353_66275b79cfe50e8dab692.png!small?1713855354280

ShellcodeD 调用 RtlAddVectoredExceptionHandler 注册异常处理函数,当执行流执行到 Ndr64pFreeParams_B 之后,Ndr64pFreeParams_A 之前时,必然会触发 SEH。


1713855359_66275b7f84cc2dc3dfa68.png!small?1713855360721

异常回调函数逻辑如下:

1713855366_66275b862da17ed28ff90.png!small?1713855366911

调用 SendMessageTimeoutW 函数将 Notepad 的 edit 窗口过程修改为 RpcAsyncRegisterInfo,第二个 SendMessageTimeoutW 会向 malicious.exe 进程创建的窗口进行通信,窗口接收到消息后会将 ShellCodeA 的起始结构(Message->Handle + 0x80)的位置写入 ShellCodeE 的地址,ShellCodeE 是继 ShellcodeA 之后第二阶段的 Shellcode。

1713855374_66275b8e273cb286dc7f5.png!small?1713855374626

最终会执行到 Ndr64pFreeParams_A,攻击者设计调用的函数为 I_RpcFreePipeBuffer,SEH 回调函数中已经将(Message->Handle + 0x80)指向的地址修改为 ShellcodeE 的起始地址,所以接下来会正式进入第二阶段的逻辑,ShellcodeE 调用情况如下:


1713855379_66275b939125f74425c58.png!small?1713855380192

ShellcodeE 首先会申请可执行的内存。


1713855386_66275b9a1ed45de62d025.png!small?1713855387214

第一次与 malicious.exe 通信,主要用于 ShellcodeF 中地址的重定位。


1713855393_66275ba1a23d698a7b5dd.png!small?1713855394181

第二次与 malicious.exe 通信,通知 malicious.exe 将 ShellcodeF 注入到共享内存块中。


1713855398_66275ba67511eb0db9a14.png!small?1713855399286

注入完成后将 ShellcodeF 拷贝到刚才分配可执行内存块中,至此一次注入循环结束,注入流程图如下:


1713855405_66275bad94a1103da0d07.png!small?1713855408533

Chrome触发

ShellcodeF 的主要功能是从 Notepad 进程将恶意代码注入到 Chrome 进程中,开启第二轮注入流程,通过 Chrome_MessageWindow 寻找浏览器进程。


1713855415_66275bb7df64e534719af.png!small?1713855416330

在注入前会对当前时间进行验证,验证当前时间是否小于 64AE6B71(2023-07-12 08:59:29),如果小于则进入注入流程。


1713855420_66275bbcdeed6931d1acc.png!small?1713855421844

这意味着具体的攻击活动发生在 2023-07-12 之前,使用相同的注入方式将 Shellcode 写入共享内存块后,攻击者在浏览器进程中寻找一个未公开的隐藏窗口。1713855427_66275bc3503c4b24855fa.png!small?1713855427927

调用 NtUserMessageCall 向未公开的 com 窗口类 OleMainThreadWndClass 发送指定的消息。


1713855499_66275c0b8bc1ec3a46c8d.png!small?17138555028081713855507_66275c136827ee56c7098.png!small?1713855508224

第一个参数为未公开隐藏窗口的句柄、第二个参数为 MSG 消息,我们推测是该未知窗口的自定义消息 0x405、第三个参数数据未知、第四个参数是一个结构体的指针,指针中包含了目标浏览器进程中共享内存块的地址。我们通过调试发现浏览器进程中的 MsgWaitForMultipleObjectsEx 来等待消息事件信号。


1713856001_66275e010eb2f28432b91.png!small?1713856001626

该函数检测到事件信号并返回,随后进入消息分发过程 PeekMessageW,

1713856010_66275e0a0cf3a2c8714f7.png!small?1713856011148

PeekMessageW 会调用windows 底层的消息分发过程:NtUserPeekMessage 在进入 NtUserPeekMessage(R0) 后,根据获取的消息进入用户回调阶段 KiUserCallbackDispatcher(R3),经过如下调用链 KiUserCallbackDispatcher -> __fnDWORD -> DispatchClientMessage -> UserCallWinProcCheckWow,此时 UserCallWinProcCheckWow 的参数如下:

1713856020_66275e147933bc0903de7.png!small?1713856021133

其中第二个参数被设置为 ThreadWndProc,该函数用于进入接收消息窗口句柄的窗口回调, UserCallWinProcCheckWow 会调用此函数,进入 ThreadWndProc 之后会根据消息值进行分发,其中 0x405 会进入 OleMainThreadWndProc 函数,根据函数名推测它是 OleMainThreadWndClass 窗口类注册的默认回调函数。

1713856041_66275e29c5f699ea4737e.png!small?1713856042204

在 OleMainThreadWndProc 中在经过一次判断,进入 GetSingleThreadedHost。

1713856048_66275e3069046b528e88f.png!small?1713856049186

此时 GetSingleThreadedHost 的参数 lParam 为攻击者设置的地址,该函数在开头处会调用 [[lParam] + 0x18] 处的地址。

1713856067_66275e43e7a9bed273606.png!small?1713856068480

该地址被攻击者设置为 NdrServerCallAll,从而进入与之前 Notepad 注入相同的执行流程,调用栈如下:

1713856071_66275e479ebf5a55f26ad.png!small?1713856072352

与 Notepad 执行流程的区别在于,Notepad 是通过 I_RpcFreePipeBuffer 函数进入到 NdrServerCallAll,而浏览器进程中则是通过 combase 库中的 GetSingleThreadedHost 进入 NdrServerCallAll 进而触发浏览器共享内存块中的 RPC 链,创建 RWX 属性的内存块,最终会在浏览器内存中加载一个 CRX 插件。

1713856078_66275e4e991dda7a1ac80.png!small?1713856081548

CRX 插件的主要功能窃取浏览器中的数据,存在一个规则文件用于替换浏览器显示的信息,例如与加密货币相关的操作时,将提款请求替换为授权请求。

1713856088_66275e58143ad6c62a111.png!small?1713856088578

在 JS 代码中发现了俄文的注释信息。


1713856093_66275e5d94d300c5d8662.png!small?1713856094350

最后将窃取的数据回传到 C2 服务器上。

影响

目前主流的 APT 组织都可以称为“Loader生产者”,花重金从外包公司购买一些劣质的加载器加载通用的木马后门,随即开始攻击活动,从技术角度来看这种类型的攻击并不具备逆向分析的意义,即使把“Step Bear”放到整个恶意软件的发展史来作比较,使用如此复杂的内核注入技术运行恶意代码的在野攻击案例并不多见,其中有些技术常见于 windows 内核提权的 EXP 代码中[2],当然“Step Bear”注入技术中用了一些从未披露过的技术,例如 Ndr64pFreeParams 函数劫持执行流、未知隐藏窗口的自定义消息 0x405 的触发点,这意味着一个顶级的 windows 内核研究员利用自己对 windows 内核和 chromium 内核的独特理解设计了一套顶级的注入框架,结果对主流的 EDR 产品实现了降维打击,该人员对恶意代码的设计和编写也非常的熟练,实现注入的逻辑和触发的逻辑已经框架化,可以复用到任何攻击场景中。

从 Shellcode 的时间验证和 CRX 打包的时间可以推断 Storm-0978 开展攻击时间位于 2023 年 3 月 7 月,在这个时间线下该团伙正在使用 CVE-2023-36884 对西方国家进行攻击活动,尽管我们没有捕获到攻击入口,但是我们推测应该与之前的活动类似,使用高仿下载页面进行钓鱼活动,奇安信威胁情报中心未来会对“Operation HideBear”行动保持持续的监控。

1713856106_66275e6a0bfacd60de5f6.png!small?1713856106716

IOC

有关该组织的详细IOC请联系奇安信威胁情报中心(http://ti.qianxin.com

参考链接

[1].https://mp.weixin.qq.com/s/bXvgdEevOmuMGOKwTisqXg

[2].https://hackyboiz.github.io/202

# 注入 # 网络安全 # 内核
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录