之前逆向分析过一个能够过360启动项监控的远控木马的工作原理(具体细节可以参考https://articles.zsxq.com/id_w2ue4o2bs2ju.html),关注到系统的组策略中的脚本(启动/关机)是一个很奇怪的点,360的启动项列举并不会列出这一项,所以这是一个很好的权限维持的点。
之前分析的木马是通过模拟消息的方式进行填写,这种方式太过于笨重,一直希望可以有一种简单的方式去操作这里。直到我翻遍了windows关于组策略操作相关文档(https://learn.microsoft.com/en-us/windows/win32/api/gpedit/nn-gpedit-igrouppolicyobject),也没有找到相关内容。网上有人给出了一些相关的回答(https://simplecodesoftware.com/articles/how-to-set-up-group-policy-scripts-programmatically),但是具体的操作办法都是需要先写文件,然后再去更新组策略。
那么问题来了,相关的组策略实际上到底存储在哪里?为什么更新需要这么复杂?相关的过程能不能被利用?本文就上述问题展开解答。
路径:C:\Windows\System32\GroupPolicy\Machine\Scripts注册表项1: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts
注册表项2: \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State
通过分析发现 C:\Windows\System32\GroupPolicy\Machine\Scripts
路径下的内容仅仅只控制界面显示。
而删除scripts.ini文件后,界面上就看不到相关的表项了。
但是注册表相关的内容是依然存在的。
此时运行gpscript.exe /startup
,会发现此配置是依然可以执行的。
结论: 文件内容部分只负责界面展示,注册表项只负责执行
利用这里进行权限维持的困境是:1. 直接写文件会被拦截,2. 直接写注册表也肯定会被拦截。结合此困境,我们就特别想去搞清楚如下问题: 谁负责写的文件;谁更新的注册表;更新注册表和写文件的是否是同一个程序,如果不是同一个,他们之间是怎样通信的?
首先attack到mmc.exe进程上去,在 zwWriteFile
上下断点,保存在界面上保存相关的配置,就可以跟踪到 scrptadm.dll:CScriptsSnapIn::ScriptDlgProc
函数,这是一个消息处理函数,他会调用CScriptsSnapIn::OnApplyNotify
进行保存按钮消息的处理。
这里可以清楚的看到调用 CScriptsSnapIn::SaveToIniFile
将相关的配置保存在 scripts.ini
文件中。
但是此时还没有进行注册表写入操作,我们继续跟踪,发现调用了 gpedit.dll:CGroupPolicyObject::Save
函数进行了信息的保存,在其中调用了 GPAPI.dll:RefreshPolicyForPrincipal
继续分析 GPAPI.dll:RefreshPolicyForPrincipal
,发现最后会进行一个rpc远程过程调用,之后结束全部的流程。
这里的RPC发送的请求包就不再具体分析了,也没有必要。因为GPAPI.dll:RefreshPolicyForPrincipal
是一个导出函数,我们是可以直接进行调用的,不需要自己手工构造RPC请求。
但是到此依然没有找到写注册表的元凶,那我们接下来监控注册表写入事件,看具体哪个服务处理了此RPC请求,经过监控发现对应的进程是 C:\Windows\system32\svchost.exe -k netsvcs -p -s gpsvc
,对应的服务是gpsvc
;
继续分析一下gpsvc.dll, 看到函数ProcessLocalGPO
中对文件路径进行了拼接,然后读取相关的文件内容:
最后调用了相关的函数进行了注册表更新。
至此组策略更新的相关的工作过程已经基本上分析完整了,下面用一个图简单的描述一下。
想写入组策略的启动脚本,我们至少有两个地方可以进行操作,写入文件或者写入注册表,但是这两个位置都毫无可能性,因为都被拦截的死死的。
通过上述分析,如果我们可以向gpsvc
服务发起RPC请求,就可以实现注册表的写入,这个注册表写入肯定不会被拦截,因为hips并不清楚RPC请求的发起者是谁,它只能看到服务写入了一个注册表。
但是文件路径是被写死的,由于无法控制文件内容,依然无法实现写入任意的启动项,这里我感觉其实是无解的
但是如果有了system权限,问题就解决了。这个路径是存储在gpsvc.dll的.rdata
节上的,只需要patch这个路径,就可以控制注册表写入了。
我们接下来使用 CE
来进行这个操作,首先找到进程,然后搜索字符串%SystemRoot%\System32\GroupPolicy
,并将其内容修改为%ProgramData%\test
。
然后复制%SystemRoot%\System32\GroupPolicy
目录下的文件到%ProgramData%\test
目录,并修改scripts.ini
文件内容为要执行的程序,之后执行命令行程序 gpupdate /force
,就会看到更新成功了。
然后重启也是可以成功运行的。
这里测试的时候发现只执行gpupdate
是无法成功的,进行跟踪调试发现区别是调用RefreshPolicyForPrincipal
的时候参数是不同的,普通情况下传入的参数是0,这里传入的是1。
由于此函数未公开,只能写死这几个参数。
下面就自己编写代码来实现整个过程,首先要找到gpsvc.dll的基址
之后读取.rdata的内存,找到字符串的位置。
然后修改内存属性,修改内存。
最后调用组策略刷新函数,强制组策略刷新。
注意这里写内存的操作会被360的核晶拦截,识别为进程注入。不开核晶是不会拦截的,毕竟只是写内存操作,没有创建线程等行为。
https://articles.zsxq.com/id_w2ue4o2bs2ju.html
https://gist.github.com/rajbos/49f70f4e2b9765da05f0526225de2450
https://simplecodesoftware.com/articles/how-to-set-up-group-policy-scripts-programmatically
我们的知识星球"安全的矛与盾"是一个既讲攻击也讲防御,开放的、前沿的安全技术分享社区。在这里你不仅可以学习到最新的攻击方法与逃避检测的技术,也可以学到最全面的安全防御体系,了解入侵检测、攻击防护系统的原理与实践。站在攻与防不同的视角看问题,提高自己对安全的理解和深度,做到: 知攻、知守、知进退;有矛、有盾、有安全。
更多的干货内容,更深入的技术交流,尽在知识星球“安全的矛与盾”,欢迎大家扫码加入!有问题请咨询微信 Manliness_man