最近要求我在升级过程中跟踪一个问题。问题归结为在安装过程中捕获打开特定注册表服务项的Microsoft组件。像这样的问题经常需要实时调试来实时捕获注册表访问。我本可以在RegOpenKeyExW()上设置一个断点,并检查传递到函数中的每个请求的键,但是考虑到RegOpenKeyExW()是一个使用率很高的代码路径,所以这个方法非常耗时。此方法包括在函数上设置断点,等待插入,检查第二个参数(lpSubKey)是否与所需的注册表项(string)匹配,如果该项不匹配,则单击“g”。在找到包含我所需密钥的调用之前,我可以重复这些步骤100次。我想找个办法,只有在我的Key被碰过的时候才会设置“中断访问”。我们遇到了一个类似的问题,需要在可能有数百个文件正在使用时捕获文件系统函数处理特定文件。那么,如何实现这种类型的调试所需的自动化呢?答案是调试器脚本。
让我们来看看Windows的一个例子资源管理器使用这种方法。在这个场景中,我们将尝试捕捉资源管理器正在打开HKEY U LOCAL U MACHINESYSTEMSetup键。Windows资源管理器每秒打开多个键,因此重点是在将此特定键传递给RegOpenKeyEx()时“插入”,而不必手动遍历传递给函数的数百个可能的键。
1、确定有问题的函数
LONG WINAPI RegOpenKeyEx(
__in HKEY hKey,
__in_opt LPCTSTR lpSubKey,
__reserved DWORD ulOptions,
__in REGSAM samDesired,
__out PHKEY phkResult
);
2、创建调试器脚本
脚本逻辑是直截了当的。每次调用RegOpenKeyEx()时,调试器脚本都会中断。然后检查字符串SYSTEMSetup的第二个参数。如果找到匹配项,它将保持中断状态,否则将向运行线程返回命令。我们的剧本应该是这样的
r$t0=poi(esp+8) .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo _str }
r$t0被认为是一个伪寄存器,在这个脚本中用作变量。由于RegOpenKeyEx()的第二个参数是通过esp+8传递的,因此我们的调试器命令程序将取消对esp+8的引用,并将其移到r$t0中。现在保存lpSubKey内存地址到pseudo-register。程序的第二行简单地检查r$t0是否为NULL,如果r$t0不为NULL,则将在r$t0中保存的地址设置名为_str的别名。第三行使用MASM string运算符$spat查找与systemsetup匹配的字符串模式。如果找不到匹配项,它将打印字符串,并将命令返回给执行线程。这相当于在调试器中键入'g'。如果在屏幕上发现了字符串,并且调试器中的字符串仍被打印出来。然后我们可以在适当的上下文中调试底层问题。现在将它们保存到位于c:debuggersstrings.txt,所以我们可以从断点引用它。
3、设置断点
下一步,我们从内核调试器进入资源管理器进程
kd> !process 0 0Explorer.exe PROCESS 8340d6a8 SessionId: 1 Cid: 0270Peb: 7ffd5000 ParentCid: 01b0 DirBase: 2d48f000 ObjectTable: 90171348 HandleCount: 559. Image: explorer.exe kd> !bpid 0270 Finding wininit.exe (1)... Finding winlogon.exe (1)... Waiting forwinlogon.exe to break. This can take a couple of minutes... Break instruction exception - code 80000003(first chance) Break into process 270 set. The next break should be inthe desired process. Break instruction exception - code 80000003(first chance) ntdll!DbgBreakPoint: 77f17dfe cc int 3
然后我们在ADVAPI32!RegOpenKeyExW上设置一个断点指示它使用我们的脚本来评估SYSTEMSetup是否是在第二个参数处传入的键。如果它找到一个匹配我们的键,它将保持中断状态,否则执行将被传递回正在运行的资源管理器线程。
kd> bp ADVAPI32!RegOpenKeyExW "$$<c:\debuggers\strings.txt" kd>bl 0 e 76e8f09d 0001 (0001) ADVAPI32!RegOpenKeyExW "$$<c:debuggersstrings.txt"
下面是所有不匹配项的输出。
kd>g kd> r$t0=poi(esp+8) kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SystemCurrentControlSetServicesDnsCacheParameters kd> r$t0=poi(esp+8) kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SoftwarePoliciesMicrosoftWindows NTDnsClient
为了节省博客空间,这里删除了几个条目
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SystemCurrentControlSetServicesDnsCacheParameters kd> r$t0=poi(esp+8) kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SoftwarePoliciesMicrosoftWindows NTDnsClient kd> r$t0=poi(esp+8) kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SystemCurrentControlSetServicesDNS kd> r$t0=poi(esp+8) kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} } kd> .if ($spat("${_str}x", "*systemSetup*") == 0) { .echo _str;g } .else { .echo_str } SystemSetup ADVAPI32!RegOpenKeyExW: 76e8f09d 8bff mov edi,edi
答对 了!它检测到SystemSetup传入的字符串,并保持中断状态,允许我们开始任何必要的调试。
kd>kv ChildEBP RetAddr Args to Child 077aeae0 760dfb75 80000002 760dfb94 00000000 ADVAPI32!RegOpenKeyExW (FPO: [Non-Fpo]) WARNING: Frame IP not inany known module. Following frames may be wrong. 077aed18 760dfbb7 00000004 00000000 0000003a 0x760dfb75 077aee20 7678c1b2 00000000 00000000 00000000 0x760dfbb7 077aee24 00000000 00000000 00000000 00000000 kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo]) kd>dc 760dfb94 760dfb94 00790053 00740073006d0065 0053005c S.y.s.t.e.m..S. 760dfba4 00740065 00700075ff530000 4ce81075 e.t.u.p...S.u..L 760dfbb4 8bffffff 0ffb3bf8 0032c284 89fb3b00 .....;....2..;.. 760dfbc4 840ff47d 0000012b f875ff56 013ee857 }...+...V.u.W.>. 760dfbd4 f08b0000 840ff33b 000000f2 0000c3e9 ....;........... 760dfbe4 90909000ff8b9090 83ec8b55 458b1cec ........U......E 760dfbf4 db335308 fc458956 e8f45d89 fffff583 .S3.V.E..]...... 760dfc04 ff18758b 1e891475 fff5a9e8 89c33bff .u..u........;..
调试器脚本是另一个很酷的工具,可以添加到调试库中。我个人已经多次从这种类型的脚本中受益。再次检查调试器帮助文件以获取有关可用命令的详细信息。