关于x64/Win10中Shim机制的一次调试分析过程

有阵子给windbg写jscript插件,无意中在x64/Win10上发现一件事,执行notepad不从nt!PspProcessOpen()过,mspaint则过。跟bluerust说起这事,他瞎猜了一个说法,是不是因为mspaint有Shim机制介入,进而导致这种区别。为什么有这种区别,到了也没去深究,不是刚需。但他说的Shim机制,对于一向孤陋寡闻的我,有点意思。

Alex Ionescu早年写过一个未完的系列,我觉得就那么回事吧。

Secrets of the Application Compatilibity Database (SDB) Part 1 – Alex Ionescu [2007-05-20]
http://www.alex-ionescu.com/?p=39

Secrets of the Application Compatilibity Database (SDB) Part 2 – Alex Ionescu [2007-05-21]
http://www.alex-ionescu.com/?p=40

Secrets of the Application Compatilibity Database (SDB) Part 3 – Alex Ionescu [2007-05-26]
http://www.alex-ionescu.com/?p=41

Secrets of the Application Compatilibity Database (SDB) Part 4 – Alex Ionescu [2007-06-17]
http://www.alex-ionescu.com/?p=43

————————————————————————-

有个中国同胞就Shim机制做过逆向分析:

shimeng – wedday [2008-08-26]
http://wedday.blogspot.jp/2008/08/shimeng.html
(讲了32位的Shim机制,没讲64位的)

shimeng注入 – wedday [2008-08-27]
http://wedday.blogspot.jp/2008/08/include-ntifs.html

————————————————————————–

看得出来,是个高手,年代久远,无从联系,不知此君还有新的个人主页否?

在这个领域遍历了一圈,比较精彩的几篇是:

Leveraging the Application Compatibility Cache in Forensic Investigations – Andrew Davis, Associate Consultant, Mandiant [2012-04-17]
https://www.fireeye.com/content/dam/fireeye-www/services/freeware/shimcache-whitepaper.pdf
https://github.com/mandiant/ShimCacheParser

Shim Shady: Live Investigations of the Application Compatibility Cache – Fred House, Claudiu Teodorescu, Andrew Davis [2015-10-27]
https://www.fireeye.com/blog/threat-research/2015/10/shim_shady_live_inv.html
https://www.fireeye.com/blog/threat-research/2015/10/shim_shady_live_inv/shim-shady-part-2.html

Malicious Application Compatibility Shims – Sean Pierce [2015-11-03]
https://www.blackhat.com/docs/eu-15/materials/eu-15-Pierce-Defending-Against-Malicious-Application-Compatibility-Shims-wp.pdf

————————————————————————–

既然声称遍历了一圈,看过的肯定远比这些多,不过没必要在此一一列举,有兴趣、
有毅力者自己放狗。顺便说一句,Sysinternals的autoruns至今没有检查Shim机制相
关的东西。

下面是一些关于x64/Win10中Shim机制的调试分析过程。

无意中发现mspaint.exe会加载AcGenral.dll。

拦截对AcGenral.dll的加载:

sxe ld:AcGenral

命中时调用栈回溯如下:

在其中注意到:

顾名思义,初始化、加载Shim引擎。逆向分析其主调函数:

————————————————————————–

如果只是关心Shim机制,没必要看这一大堆,只要知道为了加载Shim引擎,
PEB->pShimData不得为NULL。起初以为是ntdll.dll初始化该值,后来确认
ntdll.dll被映射到进程空间时,该值已经就绪。

进一步调试发现,”sxe cpr”命中时,PEB已经存在,跟ntdll是否映射无关,只是不
能以符号形式访问PEB,只能用数字偏移。PEB->pShimData已经有值,由于缺少
ntdll.pdb,只能使用:

为了知道PEB->pShimData何时被赋值、被赋何值,可能需要调试父进程的代码。当时有点犯傻,第一反应是用kd进行内核态调试,拦截nt!PspAllocateProcess(),从EPROCESS中找PEB,对PEB->pShimData设数据断点。

————————————————————————–

该函数第1形参ParentProcess对应父进程,可以从中获取父进程的名字,可以针对父进程名设置条件断点,比如当父进程是cdb时断在nt!PspAllocateProcess()。

“g poi(@rsp)”之后,子进程的EPROCESS已经生成,检查确认子进程是mspaint:

检查确认父进程(当前进程)是cdb:

刷新用户态符号,查看调用栈回溯:

检查子进程的PEB:

现在$t1、$t2依次对应mspaint的EPROCESS、PEB。

此时mspaint的PEB->pShimData尚未初始化,对之设数据断点:

数据断点命中:

此时mspaint的PEB->pShimData已被初始化。

在mspaint进程空间,但”.reload /user”失败,可以用”!vad”解决之:

\Windows\System32\ntdll.dll

再次查看调用栈回溯:

像这种到了5号栈帧就突然结束的调用栈回溯,一般涉及Attach操作:

此时应该换种方式查看调用栈回溯:

上述显示表明,父进程是cdb,然后Attach到子进程mspaint。

断点命中时果然已切回cdb:

至此知道:

为了搞清楚SHIM初始化,必须逆向分析KERNELBASE!CreateProcessInternalW()。这里一定要记住,子进程的PEB->pShimData是由父进程填充的,假设用cdb调试mspaint,”sxe cpr”断下来时mspaint的PEB->pShimData已被填充,无法调试这个填充过程。可
以用kd调试cmd,也可以用cdb附加到cmd,然后在cmd里启动mspaint。

前面拦截nt!PspAllocateProcess(),根本目的是拦截子进程PEB的创建,进一步逆向
找到:

————————————————————————–

可以直接拦截nt!MmCreatePeb(),对之设置条件断点,当目标进程是mspaint时断在

kd调试经常需要”.reload /user”,还是用cdb附加到cmd调试比较方便。

在KERNELBASE!CreateProcessInternalW()中看到若干与SHIM相关的导入函数,来自
kernel32.dll。”x KERNEL32!*AppCompat*”可以看到更多相关符号,比如:

设断:

mspaint启动过程中依次看到如下几种调用栈回溯:

mspaint、notepad、calc都会触发BasepQueryAppCompat、BasepGetAppCompatData,但notepad、calc不会触发BaseGenerateAppCompatData,这与mspaint不同。

前面已知父进程的KERNELBASE!CreateProcessInternalW()会调用ntdll!NtWriteVirtualMemory()去写子进程的PEB->pShimData,检查父进程附近代码
得知:

实测mspaint、notepad、calc都有上述流程。对比mspaint、notepad、calc的SHIM数
据,发现它们都是0xfb8字节,可以解码其中一部分:

————————————————————————–

mspaint.xml如下:

————————————————————————–

用Process Explorer搜索apphelp.dll,发现如下进程也加载了它:

检查cmd的SHIM数据,对比mspaint、cmd:

至此可以自定义几个用于IDA逆向分析的结构:

————————————————————————–

写入子进程的SHIM数据由父进程调用KERNEL32!BasepGetAppCompatData()产生:

————————————————————————–

BasepGetAppCompatData()对如下三个EXE做了特别对待:

用wt命令对比分析mspaint、notepad在KERNEL32!BasepGetAppCompatData()中的流程,确认mspaint的*a10、*a11有值,notepad则无,这些数据将来会复制到ShimData中。
BasepGetAppCompatData()的*a10等于NULL时,导致notepad不会调用
BaseGenerateAppCompatData()。需要进一步分析CreateProcessInternalW(),以了解BasepGetAppCompatData()的*a10何时不为NULL。*a10是由BasepQueryAppCompat()
填充的:

————————————————————————–

顺着BasepQueryAppCompat()找到CompatCacheLookupAndWriteToProcess(),mspaint和notepad在后者的流程会分叉。

分析CompatCacheLookupAndWriteToProcess(),越来越混乱。至此仍然无法解释为什么mspaint有Shim机制介入,而notepad没有,这个配置到底在哪里?

观察到mspaint会加载apphelp.dll、映射sysmain.sdb,猜测cmd在创建mspaint时也会映射sysmain.sdb,也就是说mspaint实际涉及2次sysmain.sdb。猜测x64/Win10不分32、64位,只有一份sysmain.sdb,AppPatch64子目录是个摆设。

监控cmd对sysmain.sdb的映射,看到调用栈回溯:

cmd创建mspaint时果然先映射了一次sysmain.sdb。

至此知道:

尽管确认cmd创建mspaint时,自己会映射一次sysmain.sdb,但这不是最早的分叉点。
CompatCacheLookupAndWriteToProcess()所暗示的”Compatibility Cache”是什么东西,从哪里来的?

放狗搜”Windows Compatibility Cache”,找到这些内容:

————————————————————————–

这3个键值都是REG_BINARY类型。

看了一圈,估计是这个意思。一个程序是否需要Shim机制介入,归根结底还是sysmain.sdb在起作用。如果某程序第一次执行时被判定需要Shim机制介入,会在”Compatibility Cache”里缓存相应信息。当系统重启、关闭时,”Compatibility Cache”里缓存的信息被序列化后存入AppCompatCache子键下。程序
执行时,先在”Compatibility Cache”里找,找不到了再去sysmain.sdb里找,以此判定是否需要Shim机制介入。

我用下面这个工具看sysmain.sdb:

Windows Shim Database (SDB) Parser (shims) – David Tomczak
https://tzworks.net/prototype_page.php?proto_id=33

在其中找到:

————————————————————————–

与之前从内存中转储得来的mspaint.xml做个对比,关键内容相符合。

最初没有特别明确的目标,只是想用调试器看看,如果能找到一些关键点,即使当下不深究,将来需要时也可以提纲挈领式地继续。后来工作安排有变动,不打算看Shim机制了,但已经有过一些调试分析,不忍舍弃,留于此间。本文未做Shim机制科普,最后也无明确的指导性结论,但个人觉得有一些通用逆向分析思路藏于其中。这些思考过程不是最优的,但这种基于当下知识储备展开的逐步迭代、推进,显得格外真实,
供读者借鉴。



发表评论