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