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

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

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

http://www.alex-ionescu.com/?p=39

http://www.alex-ionescu.com/?p=40

http://www.alex-ionescu.com/?p=41

http://www.alex-ionescu.com/?p=43

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

http://wedday.blogspot.jp/2008/08/shimeng.html

http://wedday.blogspot.jp/2008/08/include-ntifs.html

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

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

https://www.fireeye.com/content/dam/fireeye-www/services/freeware/shimcache-whitepaper.pdf

https://github.com/mandiant/ShimCacheParser

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

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。

cdb.exe -noinh -snul -hd -o -xe ld:ntdll mspaint.exe

拦截对AcGenral.dll的加载:

sxe ld:AcGenral

命中时调用栈回溯如下:

ntdll!NtMapViewOfSection+0x14

ntdll!LdrpMinimalMapModule+0xde

ntdll!LdrpMapDllWithSectionHandle+0x14

ntdll!LdrpMapDllNtFileName+0x18a

ntdll!LdrpMapDllSearchPath+0x1de

ntdll!LdrpProcessWork+0x66

ntdll!LdrpLoadDllInternal+0x13e

ntdll!LdrpLoadDll+0x107

ntdll!LdrpLoadShimEngine+0x194

ntdll!LdrpInitShimEngine+0x157

ntdll!LdrpInitializeProcess+0x1cda

ntdll!_LdrpInitialize+0x4e393

ntdll!LdrpInitialize+0x3b

ntdll!LdrInitializeThunk+0xe

在其中注意到:

LdrpLoadShimEngine

LdrpInitShimEngine

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

LdrpInitializeProcess ( PCONTEXT Context, ... )

{

...

if ( !UseWOW64 )

{

/*

* > dt ntdll!_PEB pShimData

* +0x2d8 pShimData : Ptr64 Void

*/

ShimData = PEB->pShimData;

}

...

if ( ShimData != NULL && ... )

{

PEB->AppCompatInfo = NULL;

/*

* 初始化SHIM引擎

*/

LdrpInitShimEngine( ShimData );

}

...

}

(上段因排版限制有删减,请在PC上用浏览器看TXT原文)

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

dq @$peb+@@(#FIELD_OFFSET(nt!_PEB,pShimData)) l 1

dt ntdll!_PEB pShimData @$peb

dx @$peb->pShimData

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

dq @$peb+0x2d8 l 1

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

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

跟踪后知道:

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

前面拦截nt!PspAllocateProcess(),根本目的是拦截子进程PEB的创建,进一步逆向得知,可以直接拦截nt!MmCreatePeb(),对之设置条件断点,当目标进程是mspaint时断在nt!MmCreatePeb()。

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

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

KERNEL32!BasepQueryAppCompat

KERNEL32!BasepGetAppCompatData

KERNEL32!BasepInitAppCompatData

KERNEL32!IsTSAppCompatEnabled

KERNEL32!IsRegTSAppCompatEnabled

KERNEL32!BasepCheckAppCompat

KERNEL32!BaseGenerateAppCompatData

设断:

bp KERNEL32!BasepQueryAppCompat

bp KERNEL32!BasepGetAppCompatData

bp KERNEL32!BasepInitAppCompatData

bp KERNEL32!IsTSAppCompatEnabled

bp KERNEL32!IsRegTSAppCompatEnabled

bp KERNEL32!BasepCheckAppCompat

bp KERNEL32!BaseGenerateAppCompatData

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

KERNEL32!BasepQueryAppCompat

KERNELBASE!CreateProcessInternalW+0x27b3

KERNELBASE!CreateProcessW+0x66

KERNEL32!CreateProcessWStub+0x53

cmd!ExecPgm+0x244

KERNEL32!BasepGetAppCompatData

KERNELBASE!CreateProcessInternalW+0x2d03

KERNELBASE!CreateProcessW+0x66

KERNEL32!CreateProcessWStub+0x53

cmd!ExecPgm+0x244

KERNEL32!BaseGenerateAppCompatData

KERNEL32!BasepGetAppCompatData+0x391

KERNELBASE!CreateProcessInternalW+0x2d03

KERNELBASE!CreateProcessW+0x66

KERNEL32!CreateProcessWStub+0x53

cmd!ExecPgm+0x244

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,发现如下进程也加载了它:

dwm.exe

winlogon.exe

svchost.exe (要进一步检查对应的服务)

RuntimeBroker.exe

explorer.exe

cmd.exe

cdb.exe

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

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

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

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

wwahost.exe

microsoftedge.exe

microsoftedgecp.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的映射,看到调用栈回溯:

apphelp!RtlFileMapMapView+0x137

apphelp!AslFileMappingEnsureMappedAs+0x35

apphelp!SdbOpenDatabaseEx+0x8c

apphelp!SdbInitDatabaseEx+0xa8

apphelp!ApphelpCreateAppcompatData+0xa8

KERNEL32!BaseGenerateAppCompatData+0x5f

KERNEL32!BasepGetAppCompatData+0x391

KERNELBASE!CreateProcessInternalW+0x2d03

KERNELBASE!CreateProcessW+0x66

KERNEL32!CreateProcessWStub+0x53

cmd!ExecPgm+0x244

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

至此知道:

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

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

看了一圈,估计是这个意思。一个程序是否需要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机制科普,最后也无明确的指导性结论,但个人觉得有一些通用逆向分析思路藏于其中。这些思考过程不是最优的,但这种基于当下知识储备展开的逐步迭代、推进,显得格外真实,供读者借鉴。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180427G0SLWZ00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券