最近张银奎大侠出新书《格蠹汇编-软件调试案例集锦》 迫不及待买回来看了下,确实是好书,行如流水,看如小说,有慎怕一下看完了之想,学习之余写下读书笔记便于以后查询。
1.编辑博客时,编辑的内容在浏览器进程里,如果发表失败,网页上造成博文丢失,只要浏览器没有关闭可以从浏览器中来找回文章。
->附加浏览器进程s -u 0 L800000 "当年在交大"s是搜索 u是搜索Unicode字符串(a是ascii字符串) 0是开始地址 L800000是搜素的元素个数 这里是unicode所以元素单位是dword 搜索的范围就是 4*800000个byte。
->!address列出用户态的所有区域,可以看到 State 00010000 MEM_FREE 信息 所以可以把0改成10000 缩小点范围。
-> du 001b5942 L1000 du查看Unicode字符串,可以看到完整文章。d是显示内存。因为unicode编码,所以这个时候把内存中的文章写入文件会造成乱码,需要在开头加入 ff fe。
->eb 001b5942-2 ff fe其中e是修改内存b是byte单位。修改后可以用
-> db 001b5942-2观察下内存 然后将内存堆保存到文件,
->.writemem c:\test.txt 001b5942-2 L2000 最后找回博文。
2.当windows出问题时Bug Check会触发蓝屏崩溃,中断到内核调试器中,比如 BugCheck C000021A, {e1c52ce0, c0000034, 0, 0} 在WinDbg帮助中可以查到0xC000021A的解释,这里需要注意搜索0xC000021A不是搜索C000021A。第一个参数e1c52ce0是有关进程的信息,比如名称。第二个参数是错误码用!error c0000034 可以查看解释。windows支持延时删除,很多时候文件已经加载,需要重新启动来删除文件,比如杀毒软件删除病毒。2个方法,第一种 注册表中 Key:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager Value:PendingFileRenameOperations值下面建立一个延迟删除列表,系统在下次启动的时候,CSRSS.EXE进程将根据这个列表对文件进行延迟更新或者延迟删除操作。第二种方法 MoveFileEx 设置 MOVEFILE_DELAY_UNTIL_REBOOT 标志。全部进程列表 !process 0 0在内核调试才能用,内核调试是系统为对象有很多进程,用户态调试是进程为对象只有一个。
3.系统启动过程:输入用户名密码,WinLogon将其传递给LSASS进行验证,通过验证创建访问令牌对象
->WinLogon启动HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon的UserInit下指定的程序,默认为C:\Windows\system32\userinit.exe, 木马可以添加在这里让系统运行它。
->UserInit进程执行登录和初始化脚本,然后启动shell表建中定义的Shell程序,默认是Explorer.exe。出现的问题是没有开始任务栏,这是Explorer除了问题,检查shell的值正确,检查UserInit的值被修改,修改回来,重启出现了任务栏。
4.设置JIT(及时调试机制) 注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug 其中Auto为1直接运行调试器 为0表示不直接调试器 先弹出对话框,可以选择调试或关闭。在管理员权限下运行WinDbg -I (大写i)可以把WinDbg注册为JIT调试器。当广告插件引起Explorer进程崩溃时,会进入WinDbg,
->kn 100 k是栈回溯 n是每行前显示栈帧的序号,100是显示多少条。kPL也可以查看栈信息,P是把参数和函数原型的格式显示出来,需要私有符号,大P是显示在一行,小p是每个参数显示一行,L屏蔽掉与上下文相关的源文件信息,以节约篇幅
->.frame /c c切换到栈帧号为c的栈帧, .frame显示当前局部上下文,/c显示寄存器信息 这里切换到了CoGetClassObject的函数栈帧
STDAPI CoGetClassObject(
REFCLSID rclsid, //创建的Com对象的类标识符(CLSID)
DWORD dwClsContext, //指向接口IUnknown的指针
COSERVERINFO* pServerInfo, //运行可执行代码的上下文
REFIID riid, //创建的Com对象的接口标识符
LPVOID* ppvObj); //用来接收指向Com对象接口地址的指针变量
->kv 1 其中v是显示前三个参数以及FPO(栈指针省略)信息和调用协议,这里的参数是不准确的,有些函数没有这么多参数 或 使用快速调用协议FASTCALL 用到寄存器传参。对应的 kb是只显示前三个参数。这里得到第一个参数01f1b434 类型是REFCLSID 这个类型没有导出 查询头文件看到它是IID类型 ->dt _GUID 01f1b434 dt是显示数据类型和按照类型显示内存中的数据 _GUID是结构的类型 得到COM对象的类ID({bbca9f81-8f4f-11d2-90ff-0080c83d3571}),在注册表 HKEY_CLASSES_ROOT\CLSID下找到这个GUID 得到模块名wc98pp.dll
->lm vm wc98pp 查看模块的详细信息,其中lm是显示一个简单的模块列表, v是显示详细信息, m是对模块名过滤, wc98pp就是模块名,使用匹配符 w*,就会列出w开头模块的详细信息,!lmi也是得到模块详细信息,但一次只能查询一个模块。从信息看到 无版本 无厂家 无产品名称 加上 Google一下,得知是广告插件,删除电脑上所有的wc98pp.dll和注册表中相关的信息,恢复正常。
5.win7系统不断重启,提示关键的一些系统服务被关闭,可能是SvcHost进程意外终止。SvcHost进程崩溃会产生dumpfile,重启电脑F8,选择修复计算机,进入win7的WinRE系统,选择命令行,dir *.mdmp /s 找到dumpfile文件,插入U盘复制文件,在正常电脑分析。
->Windbg打开转储文件 显示(280.2a4): Stack buffer overflow - code c0000409 (first/second chance not available) 说明280号进程的2a4线程发生栈溢出,~*查看所有线程,发现当前线程就是发生溢出的线程,执行kn观察栈回溯,
发现几个Wer开头的函数,说明进程终止前调用了WER措施,从而产生了dumpfile。栈中显示
...
08 009af5f0 74ee700a kernel32!UnhandleExceptionFilter+0x1af
09 009af942 74ed1674 umpo!__report_gsfailure+0xce
0a 009afb30 006a002e umpo!UmpoAlpcSendPowerMessage+0x88
WARNING: Frame IP not in any known module. Following frames may be wrong.
0b 009afb3c 00000000 0x6a002e
...
0b号栈没有符号说明,可能是正确数据覆盖,UmpoAlpcSendPowerMessage中调用了__report_gsfailure,此函数是在Cookies的溢出检查(简称GS机制)中报告错误的函数,说明U函数中有CS机制,反汇编U函数可以看到
mov eax,dword ptr [XXXX!__security_cookie]
xor eax,ebp
mov dword ptr [ebp-4],eax
返回时有:
call XXXX!__security_check_cookie
->U函数的EBP是009afb30,dd 009afb30-4 l4 得到 009afb2c 00640064 00640064 006a002e 00670070 其中Cookie值为00640064,父函数的EBP为00640064,函数返回地址006a002e,这些值看起来不太像内存地址,像ASCII码。反汇编U函数发现问题代码,变量区长度是0x204.
->db 009afb30-0x204 l210 显示长度0x210=0x204+4(cookie)+4(父ebp)+4(返回地址) 结果中看到一个很长的文件名覆盖了Cookie父EBP和返回地址,注册表搜索此文件名,改小长度,重启正常了。
6. 继续上章系统电源崩溃的问题,采用内核调试的方法。在WinRE中用bcdedit开启内核调试,配置参照:http://blog.csdn.net/whatday/article/details/7290147 重启后中断到windbg
->!process 看到 Image:svchost.exe,和上一章推断一样断点异常来自承载系统服务的svchost进程,断点指令与正在调试的服务崩溃问题相关。
->k 栈回溯,但是没有模块符号信息,因为每个进程的用户态空间不一样,而且平凡切换,为了节约时间,调试器不记录每个用户态空间有哪些模块,如果要观察用户态内容,首先要确保当前进程是要观察的进程,进程的切换.process /i 9328a530 其中9328a530是进程EPROCESS结构地址, 另外要让调试器重新加载用户模块信息,.reload /user 执行一下就可以了 在执行k命令 栈回溯有信息了。最终都是到了UnhandleExceptionFilter函数
-> uf KERNELBASE!UnhandledExceptionFilter反汇编得到如下伪码:
if (ExInfo->ExceptionRecord.ExceptionCode == 0C0000409h && BaseIsKernelDebuggerPresent())
{
DbgPrint("..STATUS_STACK_BUFFER_OVERRUN encountered..");
__asm(int 3);
}
->kv 得到 002af8e4 74c97022 74c97038 ... KERNELBASE!UnhandledExceptionFilter+0x5f 第三列的74c97038就是ExInfo(反汇编可知道)
->dt _EXCEPTION_POINTERS 74c97038 -r得到ExceptionCode:On-1073740791 其中r是显示成员中是子结构成员的信息
->?? (unsigned int)(-1073740791) 得到 unsigned int 0xc0000409 其中??是专门用来评估C++表达式的
得到结论:函数中有专门的针对溢出异常(0xc0000409)的代码,这部分代码会判断当前系统是否被内核调试,如果是,执行断点指令(int 3)触发断点异常(0x80000003)。再继续执行,最终触发WER服务.
7.再接上章电源崩溃,利用JIT进行调试,
->拷贝WinDbg到问题电脑(复制到U盘->xcopy /s到目标电脑),注意拷贝时盘符问题(有可能C盘变D盘,D盘变E盘,我使用VM中的Win7没有出现盘符变化)。
->设置JIT,需要注意,在WinRE中的注册表不是原始的win7的,需要通过"文件->加载配置单元->选择win7盘\windows\system32\config\SOFTWARE文件",然后在[win7\Microsoft\Windows NT\CurrentVersion\AeDebug]下增加
"Debugger"="c:\WinDbg\WinDbg.exe -p %ld -e %ld -g -loga c:\WindDbg.log"
"Auto"="1"
-loga c:\WindDbg.log是让WinDbg将工作情况写入指定文件中
->重启计算机后,发现黑屏,远程内核调试跟踪后结论如下:
1.电源服务进程启动后,从umpo!UmpoMain处开始执行,先调用RegisterServiceCtrlHandleExW向服务管理器(services.exe)注册服务控制函数。然后调用umpo!UmpoAlpcinit函数创建一个新的线程,入口为umpo!UmpoAlpcListenForPoMessages,用于监听由全局变量umpo!UmpoAlpcPoPort所标识的ALPC端口,接收ALPC消息,向外提供服务.
2.UmpoMain线程在调用UmpoAlpcInit函数后,继续调用UmpoGroupPolicyInit函数,此函数调用UmpoNotifyKernelAllPowerPolicyChanged函数通知电源策略变化,但在处理的过程中发生了栈溢出崩溃。因为注册了JIT调试器,系统启动JIT调试器附加到这个进程,电源服务进程处于挂起状态。
3.服务器管理进程得到电源服务启动的消息,进程中的scext模块(Service Control Manager Extension DLL)企图通过ALPC消息注册电源事件回调。但电源进程挂起,注册受阻导致服务管理器的服务线程被阻塞。
4.LSM进程在启动RpcSs服务时与服务器管理器进行通信,但是因为服务器管理器进程的服务器线程阻塞,导致这个进程迟迟不能设置WinLogon主线程所等待的TermSrvReadyEvent事件,于是系统启动的关键路径被阻塞,长时间黑屏。
->黑屏解决方案:把电源服务启动类型设为"禁止",需要注意的WinDbg命令有,!object 938024f8 观察事件对象相信信息,
!thread 9383a030 查看线程详细信息,包含参数信息的栈回溯。
->重启后没有WinDbg窗口,但任务管理中已有,原因在于运行在会话0(Session 0)中。让Session 0可见。1.Power服务属性-登录-允许服务于桌面交互。
2.开启交互服务检测(Interactive Server Dectection)服务,当Session 0有界面显示时,此服务会创建通知进程(UI0Dectect.exe)到用户会话(Session 1),点击对话框的View the Message进入Session 0
3.在Power服务启动时就启动调试器,以免后边被阻塞,通过配置可执行文件的执行选项,HKLM\SOFT\WARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options下建立svshost.exe子健,创建名为Debugger的键值,将其设置为c:\WinDbg\WinDbg.exe
4.再启动电源服务,UI0Detect程序出现,顺利进入会话0。
-> 遥控会话0中的JIT调试器,系统其它服务启动后才触发的JIT,网络和命令或许可用。将AeDebug下的JIT设置为
c:\WinDbg\WinDbg.exe -p %ld -e %ld -QY -c ".server tcp:port=2000" -loga c:\WinDbg.log注意关掉-g是因为溢出崩溃时。调试器首先收到断点异常,如果有-g调试器会当成初始断点,-g是忽略初始断点,错过了好时机,.server tcp:port=2000让WinDbg创建调试服务,并监听TCP网络端口。 去掉Image File Execution Options设置,等任务管理器中出现WinDbg后, 在启动WinDbg-File-Connect to Remote Session-输入tcp:port=2000,server=127.0.0.1,从而连接成功。
8.往PowerPoint拖放脚本文件时崩溃,用ADPlus拍照,产生转储文件,日志文件,报告文件,永久保存现场资料。
Adplus -pn powerpnt.exe -pn wincmd32.exe -hang -o c:\test其中“-pn”开关用于指定您希望 ADPlus 分析的进程名。要指定多个进程,请使用多个“-pn process name”开关。"-hang"此开关将 ADPlus 配置为在挂起模式下运行。您必须将此开关与“-iis”、“-pn”或“-p”开关一起使用。不能将“-hang”与“-crash”开关一起使用。"-o"此开关指示 ADPlus 在哪里放置调试输出文件。如果使用长文件名,则必须用双引号将它们括起来。此外,还可以使用 UNC 路径 (\\server\share)。如果使用 UNC 路径,则 ADPlus 在紧跟您指定的 UNC 路径的下方创建一个新文件夹。根据正在运行 ADPlus 的服务器命名该文件夹(例如,\\server\share\Web1 或 \\server\share\Web2)。如果 ADPlus 在 Web 场中的多台计算机上(这些计算机全都将其输出放置于同一网络共享上)运行,则此开关十分有用。
->附加进程 ~0s 切换到0号线程,因为几乎所有windows GUI程序,编号为0的初始线程就是UI线程。kn 100查看栈回溯,发现UI线程进入内核界面就是失去响应了,从公开API HRESULT OleCreateFromFileEx(REFCLSID rclsid, LPCOLESTR lpszFilenName,...)下手,通过栈回溯得到执行此函数对应栈帧的基地址是0013a98,du 0013a98+c (0013a98+0对应的是父EBP,0013a98+4对应返回地址,0013a98+8对应第一个参数,0013a98+c对应第二个参数)显示lpszFilenName字符串,发现是脚本文件全路径,确认方向没错。
->打开第二个WinDbg开始本地内核调试 File-kernel Debug-Local,找到PowerPoint程序 !process 0 0 powerpnt.exe 列出该进程的各个线程结构 !process 88bfbb80 2,显示UI线程的详细信息 !thread 891c4020,发现很多包含SendMessage的函数,发送消息都是 3e0 窗口指针都是 bc74ed08,现在关键要查出是向哪个窗口发送消息的,dd bc74ed08l1得到001506c4, 验证它是不是窗口句柄可以用 Spy++,也可以用Skywing编写的sdbgext扩展模块中的hwnd扩展命令来观察 !sdbgext.hwnd 001506c4 得到该窗口句柄的进程ID 线程ID
->打开第三个WinDbg,附加这个进程,~*列出所有线程,切换到上边查找的线程,这里是~0s 然后kn 100发现根本没有等待和处理窗口消息,在服务管理中终止该服务,分离附加的PwoerPoint(Debug-Detach Debuggee 或 .detach),恢复正常。
->原因总结,先前的栈回溯中有
...
05 0013a638 775f9904 ole32!BdeBindToObject+0xde
06 0013a8c0 7757c79c ole32!CPackagerMoniker::BindToObject+0xc7
...
通过跟踪发现是CpackagerMoniker:BindToObject发生问题,win7中没有调用DdeBingToObject,在win7中跟踪BindToObject执行过程发现调用了CoCreateInstance分析参数,得到要创建对象的组件ID,查注册表可以找到对应的组件,于是BindToObject调用这个组件对象,不调用DdeBingToObject.而XP中代码逻辑相似,只是没有找到ID对应的组件,从而调用DdeBingToObject出现问题。
9.邮件中的PDF附件,下载直接打开PDF阅读器无响应。附加到阅读器,.dump /mfh c:\dumps\pdf\acrobat.dmp 产生转储文件,/m代表Mimi Dump,但文件其实一点都不小,因为windows下用户态转储文件都是用的MiniDumpWriteDump这个API来写的, /f 是full的意思,代表完整的用户态转储,包含用户态空间的所有内存数据。
->打开转储文件,File-Open Crash Dump..., 执行~* kv1 其中1代表只显示每个线程的第一个栈帧。发现很多线程都是在执行不同形式的Wait函数。查看参数中的Timeout都是0xFFFFFFFF,也就是INFINITE无限等待,除此之外0号线程没有执行等待,~0s所切换到0号线程,u 反汇编代码,发现代码一直在循环。所有线程都在等待或忙碌,没有可以响应界面的线程。
->等待和锁相关,查看处于锁定状态的锁 !locks,关键区通过RTL_CRITICAL_SECTION结构来实现的。
dt _RTL_CRITICAL_SECTION其中LockCount标识关键区的锁状态,RecursionCount记录递归次数,支持同一线程多次进入关键区,OwningThread进入关键区的线程ID,LockSemaphore是这个关键区对应的事件对象,为空表示没有线程在等待。!locks不能显示LockSemaphore字段,!cs -l 则可以显示LockSemaphore字段,再结合前边栈回溯可以发现,线程2460拥有关键区对象事件2e0等待2dc,线程25f4拥有关键区对象事件2dc等待2e0,从而2个线程现死锁。
10.意外蓝屏得到dump文件,打开dump发现系统运行时间33s,推断系统正在启动中,!process 0 0只有3个进程其中有CSRSS,说明已经完成了内核初始化,执行体初始化,大部分驱动加载工作,驱动出问题蓝屏有可能。!analyze -v自动分析结果,第一个参数08代表此次异常类型,双误异常。结果中错误代码指向push ebp,定位源程序发现是定义变量。打开dump时windbg提示:WARNING: Process directory table base C7F45840 doesn't match CR3 00185000,Implicit process is now 85ad72c0,!process85ad72c0 0得到当前进程DirBase:C7F45840而r cr3是00185000, !process 4 0发现DirBase:00185000,Image:System, 可以断定双误异常,原因是当双误发生后,CPU启动硬件的线程切换机制把CR3寄存器切换到00185000,也就是系统进程所使用的页目录基地址。然后在新的线程中开始执行处理双误的代码,让系统崩溃和保存转储信息。因为是硬件做的切换线程,所以操作系统没有来得及更新当前进程。
->执行kvn发现#0号栈帧后边有FPO: TSS 28:0这是任务门信息,表示前一个线程的TSS段选择子是0x28,.tss 28切换到崩溃的线程。再次定位到push ebx发生异常,由于这条指令发生异常几率很低,所以观察上一条指令sub esp,408h这条指令更低,所以基本确认push指令引发双无异常。
->模拟push执行过程,1.递减指针挪出空间,ESP=ESP-4=f655cfb8-4=f655cfb4 2.将要压入的内容写入到挪出的空间中,执行ed f655cfb4 f655d7dc,错误:Memory access error in 'ed f655cfb4 f655d7dc'。dd f655cfb4得到发现??号直到f655d000,有可能这段内存没有包含在转储文件中,有可能内存没有分配,用!pte观察内存页情况, !pte f655cfb4 得到not valid, !pte f655d000得到pfn 100e1,结果显然是push执行时访问了无效内存。
->!thread 得到Stack Init f6560000 Current f655de8c Base f6560000 Limit f655d000,边界是f655d000,ESP(f655cfb8)显然已经越界。在每次页错误异常时,CPU会把访问的地址写到CR2中,r cr2得到cr2=f655cfb4 这正是ESP-4的位置。CPU执行压栈第一步后,第二步向f655cfb4写数据,因为无法访问出现异常了。CR2就是一个证明,最后定位连续的代码 char szXXX[MAX_PATH] 造成内核态栈的溢出,x86 CPU新内核态栈初始化大小是12KB,X64是24KB 本例栈大小为. Base f6560000-Limit f655d000 = 3000 =12KB 执行k可以看出的确是用光了发生了栈溢出。
11.linux平台下用户态后台程序MyDaemon与内核态驱动程序MyDriver通过驱动程序创建的虚拟文件/sys/mydevice/vfile来通信,发现通信有问题,打印通讯信息发现多出了printf调试信息,虚拟文件和调试信息混合为一体了,导致问题的原因是MyDaemon中关闭了3个标准流,而后当它继续执行打开虚拟文件时,系统复用了空闲出来的标准文件描述符,将他们返回给应用程序,于是虚拟文件的描述复用了标准输出的描述符,也就是1。写虚拟文件时,用1描述符把信息写给驱动程序,当执行printf时,任按printf的逻辑找stdout指针,得到文件描述符也是1。结果便是printf输出的调试信息意外写到了虚拟文件中去,不一定非利用虚拟文件与驱动文件通讯才有问题,只要关闭了标准流,再打开普通文件,都可能乱套。在windows中也可以重现,因为标准流和C函数都是按工业标准实现的。
12.windows系统补丁安装失败,打开事件查看器-windows logs-Application-根据时间查找-查看错误信息的一条显示了安装错误的记录,按照记录提示启动安装日志,1.单击开始,然后单击运行。2.在打开框中,键入 gpedit.msc 以启动“组策略编辑器”。3.依次展开计算机配置、管理模板、Windows 组件,然后单击 Windows 安装程序。4.双击日志记录,然后单击启用。5.在日志记录框中,指定您想记录的选项。日志文件 Msi.log 出现在系统卷的 Temp 文件夹中。重新执行补丁程序,没有发现日志文件,上调试器。
->打开winDbg监视文件操作,设置条件断点 bp KERNELBASE!CreateFileW+05 "dU /c 50 poi(@ebp+8);gc"其中+05是因为CreateFileW开始的汇编代码是
757400d9 8bff mov edi,edi
757400db 55 push ebp
757400dc 8bec mov ebp,esp
757400de 83e4f8 and esp,0FFFFFFF8h
加5正好是执行了mov ebp,esp便于后边的取参数,dU显示Unicode字符串,/c显示字符的单位数量50个,poi是MASM表达式支持的特殊运算符,在MASM中 @ebp+8代表一个地址,这里是第一个参数lpFileName,要取它的值就需要使用poi操作符,poi的含义是从指定地址取指针长度的数据,分号后边是继续执行的指令,gc是从一个条件断点返回,这样一来就可以监视函数参数了。g 运行后从结果中找到临时目录的html文件打开查看,提示说OpenFileMapping错误,条件断点观察,
bp KERNELBASE!OpenFileMappingW+5 "kv 10;dU /c 30 poi(@ebp+10)"
跟踪无果,再看html文件中有“Applicability for Installing: evaluating each item...(适用性:评估每一项) 应用的项目数为0”,说明补丁程序没有修补对象。
->补丁程序判断适用性是依据释放出的ParameterInfo.xml,其中信息有补丁的名称,大小,GUID,修补对象的GUID,及其版本。
通过修补对象GUID再注册表中找到了对应的软件产品是.NET Framework 4 Extended,版本好也匹配,说明是适应的,所以调试Setup.EXE文件,看看执行过程中出现了什么问题。
->打开补丁程序Setup.exe设置条件断点 bp KERNELBASE!CreateFileW+05 "dU /c 50 poi(@ebp+8);gu;r eax;gc" 增加了gu;r eax两条命令是为了执行完CreateFileW函数后显示出函数返回值,从而知道成功还是失败。记录中发现打开186ceccc.msi文件失败,.restart /f 让目标复位,修改断点让其失败就停止,
bp KERNELBASE!CreateFileW+05 "dU /c 50 poi(@ebp+8);gu;r eax;.if(@eax<0){.echo hit}.else{gc}"其中.echo hit是显示hit字符串,算是一个断下来的提示。执行g命令,中断后执行k 100从栈回溯中发现公开函数MsiOpenDatabase,函数的第一个参数是文件名,它的栈帧基地址是0423ed44,dU /c 50 poi(0423ed44+8)得到"C:\Windows\Installer\186ceccc.msi"正是打开失败的文件,再看下MsiDeterminePatchSequence函数,它是专门用来评估补丁的应用顺心的,第一个参数是目标产品的代码dU /c 50 poi(0423f324+8)得到的GUID正是前边修补对象的GUID,综上所述可以得到,补丁程序是MsiDeterminePatchSequence函数失败,因为没有打开186ceccc.msi文件.
->C:\Windows\Installer下有很多MSI文件,是用来卸载和修复已安装软件的,在C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SetupCache\Extended\中找到复本,执行后发现C:\Windows\Installer增加了MSI,再次运行补丁安装程序正常安装了。
13.运行Setup.exe安装windows7 SDK过程中卡壳,WinDbg附加SDKSetup进程,因为Setup是预备程序,只是为了以管理员身份启动SDKSetup,安装的SDK是64位版本所以SDKSetup也是64位,WinDbg也要用64位的。查看每个线程的Last Error,
~*e ? @$tid;!gle 其中~*是对每个线程,e是执行一系列命令,如果没有e就只能执行最后一个命令,合起来就是针对每条线程显示线程ID和Last Error值(相当于调用GetLastError() API)。结果中发现0线程的LastErrorValue(DosC错误码)为0表示没有错误,且它的LastStatusValue(NT操作系统的状态码)为0xc0000034表示有错误,这2个值都是保存在线程TEB中的,dt _TEB -y Last 可以看到这两个字段,其中-y是附加搜索选项,Last开头的字段。造成一对一错的原因在于:某些API只修改了LastErrorValue不修改LastStatusValue,windows API一般是将调用系统服务失败返回的NT状态码传给RtlNtStatusToDosError函数,这个函数内部会将状态码更新到TEB的LastStatusValue字段,然后调用错误码转换函数得到对应的DOS错误码并返回。总的来说它最近一次调用API是成功的,暂时pass它寻找2个都错误的线程。发现9号线程都错,切换到它~9s查看栈回溯k 100,发现多个栈帧无法解析到任何地址结合栈中多次出现mscorwks模块,推测是及时编译(JIT)出的托管代码,说明使用了.NET开发。
->多个栈帧中的函数只有二进制地址,没有函数名不易理解,加载用于解析托管语义的SOS扩展命令模块。执行
.loadby sos mscorwks,然后执行!clrstack观察托管方法调用经过,通过观察完美的回溯发现get_Length方法遇到了意外调用,WinIoError函数内部抛出了异常,使用SOS的!threads命令观察线程信息,发现9号线程发生了System.IO.FileNotFoundException异常。查看异常对象详情,!do 0000000002901750 再查看详情中message字段的值
!do 0000000002901570看到了这个异常的文字描述,其中的文件路径错误,以上步骤也可以直接用SOS的!printexception命令打印出当前线程的异常信息。
->反汇编SDKSetup,.NET的反汇编使用ILDASM(在SDK目录中一般为:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin),打开文件,便显示出了SDKSetup的中间代码,通过前边的回溯可以判断发生问题的函数Dispose,其中使用到!ip2md 0x7ff`001c0dbd 让SOS查找这个地址对应的托管方法。通过分析得出问题是因为对异常情况的估计不足,处理错误逻辑不充分,造成程序流程跑飞,进入死循环从而卡壳。
14. 无效句柄的追踪。WinDbg加载dump文件,k命令栈回溯发现NtGetContextThread,其它比较乱没有函数名,当前在写转储文件的线程,.ecxr读取异常数据流, 回到了异常现场,定位到RtlRaiseStatus函数,u命令反汇编该函数,发现先调用了RtlCaptureContext在调用了ZwRaiseException,说明该函数是抛出异常错误状态的函数,寻找是谁调用了它。
->kn 观察栈回溯
***Stack trace for last set context - .thread/.cxr resets it
00 0`5df7f530 0`773c78e9 ntdll!RtlRaiseStatus+0x18
01 0`5df7fad0 1`40007d7a ntdll!??::FNODOBFM::'string'
02 0`5df7fb00 1`4000c09e XXModule+0x7d7a
此环境是64位的 ***行表明现在的观察点不是最新的时间点,因为前边执行了.ecxr而时光倒流到以前的时间点上了。因为RtlRaiseStatus不是公开函数,用户代码不能太直接调用它,所以应该是1号调用的,还原1号的名称。
->反向反汇编1号栈帧调用返回点 ub 00000001'40007d7a得到此地址前八条汇编指令。最近的一个条是call qword ptr [XXModule + 0x520b8(00000001'400520b8)],但是没有函数名,ln命令通过地址(函数指针)得到最近的符号(函数名称),ln poi(00000001'400520b8)得到函数名称为RtlLeaveCriticalsection,可见XXModule中调用了该函数造成了异常。栈帧1号显示比较混乱是因为二进制优化造成的,优化工具将高执行概率的代码挪动到一起,使它们相邻,运行时这些代码有更高的概率被prefetch技术预先读入内存,或是因为其中的模块代码被执行,让其它代码借光调入内存,执行概率低的代码比如用作错误处理的代码就会被流放到比边远地方去,优化工具只是调整代码不会调整符号,调试器就近寻找参照物,所以栈帧1号显示比较混乱。所有环境都还原以后,下一步就是找发生问题的原因了。
->ub 773c78e9 反向反汇编RtlRaiseStatus返回点,跟踪发现是ZwSetEvent执行失败造成的,它的第一个参数是句柄,推测应该是该句柄无效造成的,找出这个句柄值,这里是通过RCX传值的,但调用动作已经过去很久,寄存器已改变,追踪方法是:追踪感兴趣的寄存器值是否来自内存或向内存中写过。
->RtlLeaveCriticalsection的第一个参数是_RTL_CRITICAL_SECTION结构其中成员LockSemaphore是存放关键区事件句柄的,ub 40007d7a发现
lea rcx,[rdi+18h]推测rdi是结构体,追踪发现rdi来之rcx其中有mov qword ptr [rsp+38h],rcx 追踪rsp+38h 通过前边栈回溯知道rsp为5df7fb00, dq 5df7fb00+38 l1查看内存值 得到00000001`4006fbb0 观察对象偏移18的关键区结构dt _RTL_CRITICAL_SECTION 000000014006fbb0+18得到LockSemaphore为460,找到句柄。另一种寻找关键区的方法是 !cs命令列出进程中所有关键区,1'4006fbc8在其中,然后观察进程里的其它线程,有一个线程正在等待进入关键区,观察它的寄存器上下文,rbx=000000014006fbc8,正是同一个关键区.找到无效句柄,通过分析源程序及其日志修改解决问题。
15. 系统挂死,强制系统崩溃,热键触发崩溃右Ctrl+Scroll Lock 2次,开启热键触发崩溃 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\i8042prt\Parameters\ 键值名:CrashOnCtrlScroll 类型:REG_DWORD 值:1启动 2禁用。强制蓝屏崩溃后,产生转储文件需要一会时间,当进度到100%完成就可以重启电脑了,这时就会有dump文件了一般路径为:c:\Windows\Minidump,打开转储文件,!anayze -v自动分析显示结果是固定的,因为是手工触发的所以停止码是固定的,kn看到PS/2键盘驱动触发蓝屏的过程。
->!pcr命令观察处理器控制区,其中显示CurrentThread: 80551d20 NextThread: 89b512e8 IdleThread: 80551d20 DpcQueue有很多任务。PCR是管理CPU状态的一段内存区,格式为KPCR结构,两个内存页大小8KB,X86架构上,NT会使用FS指向这个段,dg @fs显示该段详情,dg是显示段的选择子,看到基地址是ffdff000 边界是1fff 大小正好是8KB 这种正好和!pcr看到的是一样。!pcr的信息显示 CPU执行的是Idle线程,表示比较空闲,处于空闲循环中。
->切换到Idle线程,.thread 80551d20 执行kn通过栈回溯发现usbehci!EHCI_RH_PortResetComplete的问题,uf usbehci!EHCI_RH_PortResetComplete反汇编该函数,观察逻辑发现CPU反复读写一个硬件寄存器造成死循环,函数第一个参数对应了此硬件寄存器,该函数的栈帧地址是805490c4,得到硬件寄存器的线性地址dd 805490c4+8 l1得到bafde064,得到对应物理地址!vtop 0 bafde064得到物理地址b0000064,核实下是否正确!arbiter 2可以列出系统中物理地址分配情况,发现b0000064正好属于usbehci验证正确,至于反复读写没成功的原因在于电脑太老了,硬件有点问题了。
16. 笔记本合上休眠后唤醒失败。热键强制系统蓝屏产生转储文件,WinDbg打开转储文件.分析方法有两个,方法1!pcr 0和!pcr 1观察双核CPU控制区了解CPU状态,其中一个处于IdleThread空闲中,另一个在scan32.exe中,系统挂死一般在内核态,所以方法1pass。方法2 !locks寻找内核对象死锁,显示3个对象,且持有者都是87474da8线程,!thread 87474da8观察可疑线程发现属于WinLogon进程,处于等待状态,切换到该线程.thread /p 87474da8 执行kvn 100 观察栈回溯,发现了导演睡眠过程的NtSetSystemPowerState函数,通知设备驱动的PopSetDevicesSystemState,PopWakeDeviceList,PopNotifyDevice函数,通过这些知道这个线程是系统中执行睡眠任务的关键线程。将线程置入等待状态的ser2pl驱动程序,得到驱动信息!drvobj ser2pl,得到驱动对象地址86851358,设备对象地址85163500,查看详情dt _DEVICE_OBJECT 85163500,观察设备栈!devstack 85163500,显示所有设备的设备树!devnode 0 1以上信息可以得ser2pl是USB转串口设备的驱动程序。随后通过!irp来观察各个设备驱动间的信息传递,比如!irp 8761f600.最终问题在于USB转串行口的转接头,处理电源事件存在问题。
17. MSN的死循环。CPU占用100%有2种情况,第一种:病毒扫描程序或索引服务需要处理大量事务,第二种:程序BUG内部死循环。MSN属于第二种,WinDbg附加进程。寻找死循环线程。方法有4种,方法1:如果是GUI程序,界面失去响应,一般是UI线程问题,通常0号线程就是UI线程。方法2:使用~*e.ttime 列出每个线程运行时间,根据运行时间来寻找可疑线程。方法3:如果线程不多可以用~*kv列出每个线程栈回溯,根据函数信息判断可疑线程。方法4:使用Process Explorer观察工具,查看进程各个线程CPU占用率,优先检查占用率高的线程。这里由于MSN界面失去响应所以使用方法1 ~0s。
->熟悉现场的信息,kn 100查看栈回溯,缺少调试符号显示不全,只有00 0006fb045 5fc021e3 msvcr80!_vsnwprintf_s+0x19显示正确,bp msvcr80!_vsnwprintf_s+0x19,g该恢复运行发现不断中断到此函数,说明此函数处于循环内部。显示栈帧基地址附近数据dd 0006fb045,得到第一个参数地址01939ff5(EBP+8),dU 01939ff5出现调试信息,观察更多信息bp msvcr80!_vsnwprintf_s+0x19 "dU poi(@ebp+8);kv 3;gc"命令表示条件中断到调试器后执行打印参数1,显示3个栈帧的栈回溯,然后恢复执行。执行后出现大量信息,ctrl+break中断到调试器。
->定位死循环的函数,方法有2种,方法1:对栈回溯中每个栈帧返回点由上到下依次设置断点,如果反复命中说明此函数可以返回上一级,不是问题所在解除此断点,对下一个栈帧返回地址设置断点,如此重复直到设置断点恢复执行后程序出现死循环,说明死循环放生在当前函数中。方法2:选取2次栈回溯,从下至上比较最后一列,最后一个相同的地址代表可能是死循环所在函数,对其设断用方法1判断,如果不是再次取样比较。选用方法2,发现最后一个相同点是CreateObjectStoreService+0x14969
,设置断点对其返回再设一个断点,bp LiveTransport!CreateObjectStoreService+0x14969,bp LiveTransport!CreateObjectStoreService+0x14c24
发现始终命中0x14969返回点0x14c24不命中,说明死循环发生在0x14969所在的函数中,反向反汇编0x14c24, ub LiveTransport!CreateObjectStoreService+0x14c24得到 call LiveTransport!CreateObjectStoreService+0x145ad(5f342ff1),由于缺少调试符号显示的符号信息是就近的,是不准确的,所以死循环函数是5f342ff1,不知道名称。接下来就是分析代码。
->入口指令sub esp,2Ch推断局部变量区长度0x2c即44字节,根据"EBP-n"索引局部变量原则,发现指令中有ebp-4,ebp-14,ebp-2c共3种写法,推断第一变量长度是4字节可能是整数或指针,后两个分别是16字节,24字节可能是结构体。根据"EBP+n"索引参数的规律,找到ebp+8所以此函数只有一个参数,观察ECX的使用,推测是C++类的方法,ECX中传递this指针。手动跟踪可以发现死循环点
5f3430b5 call LiveTransport!CreateObjectStoreService+0x32e11(5f361555)
5f3430ba cmp dword ptr [ebp-4],0
5f3430be jne LiveTransport!CreateObjectStoreService+0x148e0(5f343024)
5f3430c4 push 0Ah
调用函数,比较局部变量,条件跳转,并没有用到函数的返回结果,怀疑是否应该把返回值赋值给局部变量再比较呢?漏泄了代码?追踪局部变量。
->从栈回溯中得到这个栈帧基地址是0006fc78 所以ebp-4是0006fc74,ba w4 0006fc74其中ba是内存断点,当对0006fc74到(0006fc74+4)地址写操作时断下,bd禁止其它断点然后恢复MSN执行,断下后分析函数发现,死循环没对EBP-4局部变量赋值,它的子函数赋值的,解除了先前的疑问。模拟执行观察下情况,执行recx=poi(ecx);r ecx;z(ecx!=0)循环命令,其含义是把ecx所在地址的值赋给ecx,显示ecx,反复操作直到ecx为0。执行后发现不停循环,ctrl+break停止。修复死循环,方法有2种,方法1:修正数据,让循环自然结束,方法2:修改EIP跳出循环,这里使用方法2,be命令恢复前面的断点,恢复运行,断下后单步来到jne修改EIP,R eip=5f3430c4,来到下一条指令跳出循环执行g,MSN继续运行正常了。
18.OneNote进程满负荷占用一个CPU,推断死循环。附加进程,定位死循环线程,执行~*e ? @$tid;.ttime显示每个线程的ID和运行时间,观察可知线程0X18e4就是可疑线程,切换到这个线程,~~[18e4]s, 其中~~[n]s是切换到线程ID为n的线程,k观察栈回溯,发现这个线程主要运行在msxml3这个模块中,查看详情lm vm msxml3,然后深入代码逻辑,一般先确定循环范围,可以用上章的栈回溯比较法(对于函数调用层次很多的情况),也可以用简单的单步跟踪法(对于函数调用层次比较少的),此例用单步跟踪,经过跟踪发现是RemonveAdd方法的问题,此函数原理是:多任务下载一个任务失败将从头移到尾,尝试其它任务下载,其它结束再尝试下载它,是一个很好的设计,但如果只有一个任务且下载失败就会造成死循环了。
19.windows内核调试连接方式,最早是串口,XP增加了1394,VISTA增加了USB2.0,前边三种多少有各自难处,win8增加了网络调试和USB3.0。
win8网络调试KDNET的配置步骤:
第一步,将目标机和主机都连接到同一个以太网络,为了让目标机端能连接到主机端(可以用ping测试),然后记录下主机端IP地址。
第二步,在目标机端以管理员身份启动一个控制台窗口,执行如下命令:
bcdedit /dbgsettings net hostip:192.168.1.100 port:50000 key:a.b.c.d
bcdedit -debug on
hostip对应的是主机IP,port是端口,key是密码.
有多个网卡的情况下,需要指定网卡:
bcdedit /set {dbgsettings} busparams 11.0.0
其中 11.0.0分别是总线号,设备号,功能号,在设备管理器的网卡设备属性里可以查到。如果是VM虚拟机不需要指定此项.
设置完以后可以用 bcdedit /enum {dbgsettings}来查看设置是否正确
如果要删除busparams的设置可以执行 bcdedit /deletevalue {dbgsetttings} busparams
第三步,主机端使用新版WinDbg->Kernel Debug->NET填入信息确定后进入等待状态。
主机端就绪后,重启目标机的win8,片刻之后就连接成功。测试得到如果VM虚拟机是win8 64位时不能连接成功,32位连接正常。
win8 USB3调试KDUSB3的配置方法:
bcdedit /dbgsettings usb targetname:<目标名称>
bcdedit /set dbgtransport kdusb3.dll
kbcedit /debug on
如果要删除dbgtransport设置,可以执行bcdedit /deletevalue dbgtransport
其中目标名称对应的Windbg中Kernel Debug->USB的targetname
除了手工配置也可以通过VS来自动配置,打开VS2012->Drivers->Test->Configure Computers来执行配置向导。
作者:WhatDay