文章目录
windbg,IDA - 通过dmp文件,准确定位函数中的汇编代码行
概述
前几天,win10突然崩溃了。
现象:
本本都在正常用,开机用了很长时间。中间好像升级几个软件,不太确定具体是升级啥了。
在用VS2019在看开源的工程,单步调试。想关掉VS2019时,怎么也关不掉。
那重新启动一下计算机吧。
正常关机过程中,始终在转圈(一个蓝色的关机界面,上面有个始终在转动的圆圈),等了10分钟还那样。
直接按本本右上角的关机按钮,长按关机。
PA : 再启动本本,正常到达登录的界面。
点击登录,输入用户名和口令,点击登录。
此时崩溃, win10显示正在收集崩溃信息,收集完成100%后,自动重启。
PB : 然后自动回到PA点,在登录马上崩溃。
如果正常开机,登录,从PA点到PB点就是一个循环,win10已经不能正常用了。
想进入安全模式看看谁搞崩溃的,但是没准备好windbg。此时在安全模式(带网络连接)下,U盘插入也不认。虽然可以搞一个U盘启动盘,将dmp文件拷贝出来。但是思想准备不足(手忙就乱)。想看看崩溃原因,也看不了(好久没用windbg了,咋看?)。
只能重新装win10了。
现在win10装好了,想做一些准备工作,等哪天再崩溃,看我收拾他(如果到那时,至少要定位崩溃原因,然后再针对已经的确定原因,再想处理方法就容易了,如果确定了哪个软件或者文件引起的问题,如果卸载不掉(安全模式下,打不开windows installer服务,可能导致有些软件卸载不掉),也可以将那个文件改名,能正常进入win10后,再卸载或者更新引起问题的软件)。
现在想个长治久安的方法,有蓝屏问题自己处理,这样就不用动不动的重装win10了。重装windows好闹心。
先做个前置任务的实验 - 目标
在本机准备好windbg环境和IDA环境,模拟一个应用层的崩溃,由win10产生dmp文件。
用windbg载入dmp文件,确定崩溃点。
去IDA中找崩溃点的汇编代码地址,从而确切知道在哪个函数崩溃,通过分析,就知道函数调用关系了。也能清楚的知道崩溃逻辑和可能原因。
然后试试在安全模式(带网络)的环境下,是否可以调试分析dmp文件,如果可以,那就可以在本机上解决win10蓝屏的问题。
笔记
建立调试符号本地缓存目录
D:\WinDbgSysSymbolsWin10
windbg安装 + 配置
安装程序 20348.1.210507-1500.fe_release_WindowsSDK.iso,默认是全部选择的,都装上。
打开windbg配置调试符号路径
SRV*D:\WinDbgSysSymbolsWin10*https://msdl.microsoft.com/download/symbols/
保存工作区,否则再打开windbg时,符号路径为空,就白设置了。
IDA配置
IDA用的52的IDA_Pro_v8.3_Portable.zip(最高只有这个版本可以在win10中单步调试,其他高版本只能在win11中单步调试),都配置好后保存为 IDA_Pro_v8.3_Portable_cfg_ok.7z
配置的内容有2个.cfg, 都在/cfg目录中
cfg/ida.cfg
# 如果要用IDA调试时,选择windbg调试器,就需要改这里
DBGTOOLS = "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\";
cfg/pdb.cfg
# 设置调试符号和调试符号的本地缓存目录
_NT_SYMBOL_PATH = "SRV*D:\\WinDbgSysSymbolsWin10*https://msdl.microsoft.com/download/symbols";
设置win10系统级转存dmp文件
%SystemRoot%\MEMORY.DMP
设置win10应用级转存dmp文件
建立崩溃dmp文件的存放目录 D:\CrashDump
EnableDump.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps]
"DumpType"=dword:00000002
"DumpFolder"="D:\\CrashDump"
"CustomDumpFlags"=dword:00000000
合并这个reg文件到注册表
到此,调试环境就弄完了,如果win10中出现崩溃,无论是系统级或者应用级的软件发生崩溃,都有dmp文件可供分析
模拟一个应用级的dmp文件
// @file test_sys_dmp.cpp
// @brief 模拟一个崩溃
#include <iostream>
void fun(char* me)
{
me[0] = 'a';
}
int main()
{
std::cout << "begin test sys dmp, please wait\n";
fun(NULL);
std::cout << "Hello World!\n";
}
用VS2019Debug版编译这个程序就行,编译后的EXE为 test_sys_dmp.exe
将原来的工程目录改名,防止IDA找到调试文件,那样就没意思了(因为在win10上崩溃的软件,一般都不是我们自己弄的)。
D:\my_dev\my_study_re\src\sys_dump\test_sys_dmp-
此时关掉VS2019, 双击test_sys_dmp.exe单独运行。看到cmd窗口过了2秒,自己消失了。
此时,在D:\CrashDump中可以看到test_sys_dmp.exe.13092.dmp,size为 8915KB
用windbg和IDA来查找具体引起崩溃的函数的汇编代码行
打开windbg, 载入test_sys_dmp.exe.13092.dmp
.cls
!analyze -v
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
00d1f868 00c0251b 00000000 00c01023 00c01023 test_sys_dmp+0x1243c //! 就是在这里崩溃了
00d1f940 00c02cc3 00000001 01395e90 01395db8 test_sys_dmp+0x1251b
00d1f960 00c02b17 116e71bf 00c01023 00c01023 test_sys_dmp+0x12cc3
00d1f9bc 00c029ad 00d1f9cc 00c02d48 00d1f9dc test_sys_dmp+0x12b17
00d1f9c4 00c02d48 00d1f9dc 76c0fcc9 00eb8000 test_sys_dmp+0x129ad
00d1f9cc 76c0fcc9 00eb8000 76c0fcb0 00d1fa38 test_sys_dmp+0x12d48
00d1f9dc 779882ae 00eb8000 c7f3754e 00000000 kernel32!BaseThreadInitThunk+0x19
00d1fa38 7798827e ffffffff 779a931b 00000000 ntdll!__RtlUserThreadStart+0x2f
00d1fa48 00000000 00c01023 00eb8000 00000000 ntdll!_RtlUserThreadStart+0x1b
反汇编报错点
0:000> uf test_sys_dmp+0x1243c
test_sys_dmp+0x1243c:
00c0243c c6040a61 mov byte ptr [edx+ecx],61h
00c02440 5f pop edi
00c02441 5e pop esi
00c02442 5b pop ebx
00c02443 81c4c0000000 add esp,0C0h
00c02449 3bec cmp ebp,esp
00c0244b e83aeeffff call test_sys_dmp+0x1128a (00c0128a)
00c02450 8be5 mov esp,ebp
00c02452 5d pop ebp
00c02453 c3 ret
但是我想知道这行在哪个函数中,如果uf test_sys_dmp+0xXXXXX, 不断减小XXXXX的值也行,不过很蛮烦。
现在去IDA中找这个地址。
用IDA64打开test_sys_dmp.exe,不管是exe是x64还是x86, 都可以用IDA64打开分析。
.text:00411348 ; [00000005 BYTES: COLLAPSED FUNCTION j____scrt_is_managed_app. PRESS CTRL-NUMPAD+ TO EXPAND]
.text:0041134D
.text:0041134D ; =============== S U B R O U T I N E =======================================
.text:0041134D
.text:0041134D ; Attributes: thunk
在IDA中看到的地址都是在载入地址0x400000上加上一个偏移地址,这样和windbg中看到的地址对应不起来。
将IDA中的载入(基)地址都改为0.
默认的基地址都是0x400000, 改为0,确定退出。
.text:00011343 ; [00000005 BYTES: COLLAPSED FUNCTION j___RTC_Shutdown. PRESS CTRL-NUMPAD+ TO EXPAND]
.text:00011348 ; [00000005 BYTES: COLLAPSED FUNCTION j____scrt_is_managed_app. PRESS CTRL-NUMPAD+ TO EXPAND]
.text:0001134D
.text:0001134D ; =============== S U B R O U T I N E =======================================
.text:0001134D
.text:0001134D ; Attributes: thunk
.text:0001134D
.text:0001134D sub_1134D proc near ; CODE XREF: __vsprintf_s_l+3↓p
.text:0001134D ; ___scrt_initialize_default_local_stdio_options+6↓p
.text:0001134D jmp
此时,可以看到,IDA中显示的地址就和windbg中一样了。
上面从windbg中看到的报错地址为test_sys_dmp+0x1243c
在IDA中转到这个地址
地址的格式 .text:0xXXX
在windbg中看到的地址为 test_sys_dmp+0x1243c
那么在IDA中的地址应该为 .text:0x1243c
转到该地址后,看到的代码如下:
.text:00012410 ; =============== S U B R O U T I N E =======================================
.text:00012410
.text:00012410 ; Attributes: bp-based frame
.text:00012410
.text:00012410 ; int __cdecl sub_12410(_BYTE *)
.text:00012410 sub_12410 proc near ; CODE XREF: sub_110FF↑j
.text:00012410
.text:00012410 arg_0 = dword ptr 8
.text:00012410
.text:00012410 push ebp
.text:00012411 mov ebp, esp
.text:00012413 sub esp, 0C0h
.text:00012419 push ebx
.text:0001241A push esi
.text:0001241B push edi
.text:0001241C mov edi, ebp
.text:0001241E xor ecx, ecx
.text:00012420 mov eax, 0CCCCCCCCh
.text:00012425 rep stosd
.text:00012427 mov ecx, offset unk_1E029
.text:0001242C call j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x)
.text:00012431 mov eax, 1
.text:00012436 imul ecx, eax, 0
.text:00012439 mov edx, [ebp+arg_0]
.text:0001243C mov byte ptr [edx+ecx], 61h ; 'a' ; //! 这里是崩溃点
.text:00012440 pop edi
.text:00012441 pop esi
.text:00012442 pop ebx
.text:00012443 add esp, 0C0h
.text:00012449 cmp ebp, esp
.text:0001244B call j___RTC_CheckEsp
.text:00012450 mov esp, ebp
.text:00012452 pop ebp
.text:00012453 retn
.text:00012453 sub_12410 endp
.text:00012453
.text:00012453 ; ---------------------------------------------------------------------------
.text:00012454 db 1Ch dup(0CCh)
.text:00012470
.text:00012470 ; =============== S U B R O U T I N E =======================================
当然,这个程序是我们自己写的,当然明白这里的逻辑。
如果是别人写的,就需要自己仔细分析。
如果要大概看一下,就用F5功能看伪码。
int __cdecl sub_12410(_BYTE *a1)
{
int result; // eax
__CheckForDebuggerJustMyCode(&unk_1E029);
result = 1;
*a1 = 97; // 给参数传入的指针赋值时报错,那么参数传入的指针有可能是空指针。
return result;
}
再结合X看交叉引用,就会逐渐靠近这个函数的调用点。
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
__CheckForDebuggerJustMyCode(&unk_1E029);
sub_111A4(std::cout, "begin test sys dmp, please wait\n");
sub_110FF(0); // ! 这里传入的空指针作为了函数的入参。
sub_111A4(std::cout, "Hello World!\n");
return 0;
}
假设这是系统级的dmp, 只要能拿到dmp文件和对应的PE文件,在另外一台计算机上,也是能找到具体崩溃代码行的。
想试试在本地崩溃的情况下,是否能在本机直接找到问题。
如果本机崩溃,进不了桌面。那么只能开机后,按F8, 选择进入安全模式(带网络)。
那么看看,在安全模式(带网络)的环境下,是否能用windbg和IDA分析dmp和EXE.
以Dell3561为例,进入安全模式(带网络)
因为我上次win10崩溃是看到登录界面的,只是登录后崩溃。
那么我就可以模拟从登录界面进入安全模式。
重启计算机,到登录界面位置,不登陆。
随便点击登录页面,看到了登录框。此时UI右下角能看到关机按钮。
此时按住SHIFT键 + 鼠标点击按钮下拉列表中的重启项,此时电脑重启,进入重启后,松开SHIFT键。
重启后,再进入的界面就是疑难解答界面,有多个小块块。
依次选择点击小块块 => 疑难解答 => 高级选项 => 启动设置 => 启动。
重启后,再进入的界面就是可以启动的模式列表, 每个选项都是按数字来选择的,选择了之后,就可以进入对应模式的windows
我这里选择的是5(安全模式带网路连接)。
我的电脑是用内置无线网卡的,进入了安全模式(带网络),无线网卡未连接。
还好,我的本本有一个有限网口,插入网线,有限网络就能用了
在安全模式(带网络)环境下,自带截图的快捷键不能用了,但是可以点击截图工具的新建按钮来截图,然后贴在画图中编辑。
可以写博客和贴图,这就可以在系统崩溃时,写一些在线笔记了。
用windbg载入test_sys_dmp.exe.13092.dmp正常,分析正常。
IDA分析和改分析基地址正常,分析正常,可以溯源到崩溃函数的调用点。
备注
以上实验说明,我已经为win10的再次崩溃做好了准备。
如果win10下次再崩溃,我在本机就能分析出崩溃的原因。
坐等win10再次崩溃:)
等win10再死给我看,我就能收拾他了。
备注
可能是旺旺卖家版引起的问题。
因为我重新去旺旺官网下载旺旺,安装的版本和我电脑崩溃时的版本不同,是蓝色界面的。
那天win10崩溃时,确实升级过旺旺买家版。升级后,是内嵌web页面的版本,界面是银白色的。用起来确实方便。
估计是那一版用chrome内核引起的问题。
因为我以前用chrome旧版,就出现过浏览网页后,win10蓝屏的问题。