概述
-
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函数。
NtUserSetClassLong,第一个参数是刚刚创建的窗口句柄,第二个参数为偏移,一开始赋值为0,第三个参数为指定的数据,xxxSetClassLong函数结构如下, 该函数主要功能是将指定数据写入内核对象tagCLS的额外类内存中:
在我们双机调试的内核版本中tagCLS结构大小为0xA0。
这段内核内存有一个重要的属性:写入数据的这段内核空间在同一用户会话下的任意一个应用层进程中都有内存映射,如果我们此时随便打开一个进程,例如计算器,那么会在calc进程中的某个映射内存块中找个上述写入的指定数据,
映射到应用层进程中的只读内存块大小和内容都在变化,但基址不变,换句话说攻击者只要操纵malicious.exe修改这段共享的内核空间数据,就可以实现针对任意进程的代码注入,但是注入的内存块属性为只读,所以攻击者接下来需要使用某种方式来触发共享内存块中的恶意代码。
触发前的准备
样本首先会执行一些初始化操作,包括加载系统dll、获取自身模块名、判断自身权限等。
之后会创建notepad进程,获取notepad进程中edit窗口的句柄。
注册一个带有额外类内存的窗口类,其窗口过程为默认的NtdllDefWindowProc,额外类内存大小为0x16010。
之后会使用这个窗口类创建一个仅消息窗口,创建后会调用NtUserSetClassLong填充内核中共享内存块中的数据。
此次填充的数据仅用于定位内核共享数据块映射到malicious.exe中的具体地址,样本通过查找TEB->Win32ClientInfo->phkCurrent结构下的AllocationBase来确定窗口内存的基址,接着对该区域的内存与之前调用填充的数据进行逐个比较找到共享内存块的地址,找到位置后再次调用NtUserSetClassLong将填充数据清空。
在notepad内存中搜索指定API地址和共享内存块的地址,用于之后填充的shellcode调用。
其搜索的API如下
API解析完毕后会在内存中解密一段payload,我们将其称为shellcodeA。
然后使用天堂之门(Heaven’s Gate)的调用方式启动NtUserSetClassLongPtr向内核共享数据注入shellcodeA。
ShellcodeA的结构非常复杂,shellcodeA中包含:gadgets工具函数和多段小型shellcode组成,我们将其统称为shellcodeB,具体结构如下:
至此ShellCodeA已经注入到notepad进程中,下面开始进入触发流程。
怎么触发?
Notepad触发
攻击者通过通过设置EM_SETWORDBREAKPROC来注册文本包装器回调函数,并发送WM_LBUTTONDBLCLK消息来触发回调函数,回调函数的参数地址则在之前通过WM_SETTEXT的消息进行了设置。
最终触发的函数为为rpcrt4.dll中的I_RpcFreePipeBuffer。
I_RpcFreePipeBuffer执行后会直接调用Message->Handle + 0x80位置处的函数
根据ShellcodeA中的结构此时Message->Handle + 0x80的位置为NdrServerCallAll的地址。
NdrServerCallAll继承了RpcFreePipeBuffer的Message参数,在进行简单处理后会调用Ndr64StubWorker
PRPC_MESSAGE相关的结构如下(x86)
Ndr64StubWorker首先通过Ndr64pServerUnMarshal函数解析Message参数并将解析后的数据传递到栈中指向的内存里。
PRPC_MESSAGE_A是NdrServerCallAll的参数,会调用Invoke函数执行PRPC_MESSAGE_A中DispatchTable的地址,
PRPC_MESSAGE_A中DispatchTable的地址还是NdrServerCallAll,至此形成套娃,会第二次进入NdrServerCallAll,此时的参数为PRPC_MESSAGE_B,而PRPC_MESSAGE_B的DispatchTable函数为VirtualProtect,将共享内存块中shellcodeB的内存属性改为可读可写可执行。
调用链如下:
攻击者使用了Ndr64StubWorker中一个从未披露的函数来执行ShellcodeB,在调用Invoke函数后会顺序执行到Ndr64pFreeParams,这个函数可以执行攻击者控制的函数地址。
由于ShellcodeA的特殊结构导致目前函数调用进入NdrServerCallAll两层嵌套的状态,所以会在两次Invoke函数和两次Ndr64pFreeParams处执行攻击者控制的代码。执行顺序分别为Invoke_A->Invoke_B->Ndr64pFreeParams_B-> Ndr64pFreeParams_A,那么接下来会执行Ndr64pFreeParams_B中攻击者控制的代码,此时要调用的函数被攻击者设计成CallWindowProcW,第一个参数lpPrevWndFunc被填充为ShellcodeB的起始地址。
而0x3C0大小的ShellcodeB实际上有三段小型shellcode组成,我们将其分为ShellcodeC、shellcodeD和shellcodeE,ShellcodeB在执行链中的结构如下:
ShellcodeC中的代码主要为Jmp,跳转到ShellcodeD
ShellcodeD调用RtlAddVectoredExceptionHandler注册异常处理函数,当执行流执行到Ndr64pFreeParams_B之后,Ndr64pFreeParams_A之前时,必然会触发SEH,
异常回调函数逻辑如下:
调用SendMessageTimeoutW函数将notepad的edit窗口过程修改为RpcAsyncRegisterInfo,第二个SendMessageTimeoutW会向malicious.exe进程创建的窗口进行通信,窗口接收到消息后会将ShellCodeA的起始结构(Message->Handle + 0x80)的位置写入ShellCodeE的地址,ShellCodeE是继shellcodeA之后第二阶段的Shellcode。
最终会执行到Ndr64pFreeParams_A,攻击者设计调用的函数为I_RpcFreePipeBuffer,SEH回调函数中已经将(Message->Handle + 0x80)指向的地址修改为ShellcodeE的起始地址,所以接下来会正式进入第二阶段的逻辑,ShellcodeE调用情况如下:
ShellcodeE首先会申请可执行的内存。
第一次与malicious.exe通信,主要用于ShellcodeF中地址的重定位。
第二次与malicious.exe通信,通知malicious.exe将ShellcodeF注入到共享内存块中。
注入完成后将ShellcodeF拷贝到刚才分配可执行内存块中,至此一次注入循环结束,注入流程图如下:
Chrome触发
ShellcodeF的主要功能是从Notepad进程将恶意代码注入到Chrome进程中,开启第二轮注入流程,通过Chrome_MessageWindow寻找浏览器进程。
在注入前会对当前时间进行验证,验证当前时间是否小于64AE6B71(2023-07-12 08:59:29),如果小于则进入注入流程。
这意味着具体的攻击活动发生在2023-07-12之前,使用相同的注入方式将shellcode写入共享内存块后,攻击者在浏览器进程中寻找一个未公开的隐藏窗口。
调用NtUserMessageCall向未公开的com窗口类OleMainThreadWndClass发送指定的消息。
第一个参数为未公开隐藏窗口的句柄、第二个参数为MSG消息,我们推测是该未知窗口的自定义消息0x405、第三个参数数据未知、第四个参数是一个结构体的指针,指针中包含了目标浏览器进程中共享内存块的地址。我们通过调试发现浏览器进程中的MsgWaitForMultipleObjectsEx来等待消息事件信号。
该函数检测到事件信号并返回,随后进入消息分发过程PeekMessageW,
PeekMessageW 会调用windows 底层的消息分发过程:NtUserPeekMessage在进入 NtUserPeekMessage(R0) 后,根据获取的消息进入用户回调阶段KiUserCallbackDispatcher(R3),经过如下调用链KiUserCallbackDispatcher -> __fnDWORD -> DispatchClientMessage -> UserCallWinProcCheckWow,此时UserCallWinProcCheckWow的参数如下:
其中第二个参数被设置为ThreadWndProc,该函数用于进入接收消息窗口句柄的窗口回调,UserCallWinProcCheckWow会调用此函数,进入ThreadWndProc 之后会根据消息值进行分发,其中0x405 会进入OleMainThreadWndProc 函数,根据函数名推测它是OleMainThreadWndClass 窗口类注册的默认回调函数。
在OleMainThreadWndProc 中在经过一次判断,进入GetSingleThreadedHost,
此时GetSingleThreadedHost 的参数lParam 为攻击者设置的地址,该函数在开头处会调用[[lParam] + 0x18] 处的地址。
在JS代码中发现了俄文的注释信息。
最后将窃取的数据回传到C2服务器上。
影响
从shellcode的时间验证和CRX打包的时间可以推断Storm-0978开展攻击时间位于2023年3月-7月,在这个时间线下该团伙正在使用CVE-2023-36884对西方国家进行攻击活动,尽管我们没有捕获到攻击入口,但是我们推测应该与之前的活动类似,使用高仿下载页面进行钓鱼活动,奇安信威胁情报中心未来会对“Operation HideBear”行动保持持续的监控。
总结
IOC
参考链接
[2].https://hackyboiz.github.io/2023/10/30/pwndorei/newjeans-hyper-v-pt5/
点击阅读原文至ALPHA 7.0
即刻助力威胁研判
原文始发于微信公众号(奇安信威胁情报中心):EDR的梦魇:Storm-0978使用新型内核注入技术“Step Bear”