DmpAnalysis-中断和异常

半世苍凉 提交于 2020-03-07 00:11:38

part2-2-中断和异常-用户态异常

1. x86中断

以除零异常为例。cpu从idtr获取idt地址,idt是一个4或8字节描述符(中断向量)数组。cpu计算处理函数地址并调用,当然还要保存上下文。

中断向量可以从dmp中提取出来

前32个中断向量由intel使用。

windows启动时cpu从实模式切换到保护模式,这之前,cpu就会创建idt,并用sidt指令使idtr指向idt。

下面的UML是内核模式异常处理流程。用户模块流程还需要从R3栈切换到R0栈。

对于内核dmp,使用!pcr PID指令可以查看Processor Control Region 信息:

0: kd> !pcr 0 
KPCR for Processor 0 at ffdff000: 
    Major 1 Minor 1 
 NtTib.ExceptionList: f2178b8c 
     NtTib.StackBase: 00000000 
    NtTib.StackLimit: 00000000 
  NtTib.SubSystemTib: 80042000 
       NtTib.Version: 0005c645 
   NtTib.UserPointer: 00000001 
       NtTib.SelfTib: 7ffdf000 
             SelfPcr: ffdff000 
                Prcb: ffdff120 
                Irql: 0000001f 
                 IRR: 00000000 
                 IDR: ffffffff 
       InterruptMode: 00000000 
                 IDT: 8003f400 
                 GDT: 8003f000                  TSS: 80042000 
       CurrentThread: 88c1d3c0 
          NextThread: 00000000 
          IdleThread: 808a68c0 
           DpcQueue: 

IDT对应的结构体叫做_KIDTENTRY

0: kd> dt _KIDTENTRY 8003f400 
   +0x000 Offset           : 0x47ca 
   +0x002 Selector         : 8 
   +0x004 Access           : 0x8e00 
   +0x006 ExtendedOffset   : 0x8083 

拼接两个offset就是KiTrap00(0x808347ca),ln指令可显示目标地址及附近的符号:

0: kd> ln 0x808347ca 
(808347ca)   nt!KiTrap00   |  (808348a5)   nt!Dr_kit1_a 
Exact matches: 
    nt!KiTrap00 

栈回溯方法:

0: kd>.ecxr;k 
...	esp=f2178c1c ...
STACK_TEXT: 
f2178b9c 8083484f nt!Ki386CheckDivideByZeroTrap+0×41 
f2178b9c bf972586 nt!KiTrap00+0×88 
f2178c90 bf94c23c driver!foo+0xf9

0: kd> dds f2178c1c-100 f2178c1c+100 
...
f2178c08  f2178c90 
f2178c0c  00000000 ; ErrorCode 
f2178c10  bf972586 driver!foo+0xf9 ; EIP 
f2178c14  00000008 ; CS 
f2178c18  00010293 ; EFLAGS 
f2178c1c  00000000 ; <- ESP before interrupt 
f2178c20  0013c220 
...

虽然ErrorCode这里等于中断号,但两者不一样。

!idt只会显示用户定义的idt。

2. x64中断

idtr为10字节,其中8字节为idt地址,2字节为限长。IDT的每个向量为16字节。

另外,处理程序的名字也不叫KiTrapXX

!pcr给出的IDT地址是错误的,需要使用dt:

kd> !pcr 0 
KPCR for Processor 0 at fffff80001176000: 
...
kd> dt _KPCR fffff80001176000 
nt!_KPCR 
   +0×000 NtTib            : _NT_TIB 
   +0×000 GdtBase          : 0xfffff800`00124000 _KGDTENTRY64 
   +0×008 TssBase          : 0xfffff800`00125070 _KTSS64 
   +0×010 PerfGlobalGroupMask : (null) 
   +0×018 Self             : 0xfffff800`01176000 _KPCR 
   +0×020 CurrentPrcb      : 0xfffff800`01176180 _KPRCB 
   +0×028 LockArray        : 0xfffff800`011767f0 _KSPIN_LOCK_QUEUE 
   +0×030 Used_Self        : 0×00000000`7ef95000 
   +0×038 IdtBase          : 0xfffff800`00124070 _KIDTENTRY64 
   ...
kd> dt _KIDTENTRY64 0xfffff800`00124070 
nt!_KIDTENTRY64 
   +0x000 OffsetLow        : 0xf240 
   +0x002 Selector         : 0x10 
   +0x004 IstIndex         : 0y000 
   +0x004 Reserved0        : 0y00000 (0) 
   +0x004 Type             : 0y01110 (0xe) 
   +0x004 Dpl              : 0y00 
   +0x004 Present          : 0y1 
   +0×006 OffsetMiddle     : 0×103 
   +0×008 OffsetHigh       : 0xfffff800 
   +0×00c Reserved1        : 0 
   +0×000 Alignment        : 0×1038e00`0010f240 

拼接3个offset,得到中断处理函数地址:

kd> u 0xfffff8000103f240
nt!KiDivideErrorFault: 
fffff800`0103f240 4883ec08        sub     rsp,8
...
kd> ln 0xfffff8000103f240 
(fffff800`0103f240)   nt!KiDivideErrorFault   | 
(fffff800`0103f300)   nt!KiDebugTrapOrFault 

其余步骤和x86一样,也能在栈中找到值为0的errorcode,参照intel手册 Volume 3A,0代表:

  • 内存缺页
  • 读访问异常
  • R0
  • 页目录标志位没有置为0
  • 不是由取指令异常导致的

3. 中断帧以及栈修复

x86于保护模式(R0)中断时的行为:

push EFLAGS 
push CS 
push EIP 
push ErrorCode 
EIP := IDT[VectorNumber].ExtendedOffset<<16 + 
   IDT[VectorNumber].Offset 

这是cpu用来保存状态的中断帧(不是陷阱帧_KTRAP_FRAME)。

如果是R3,则会先保存用户栈:

push SS 
push ESP 
push EFLAGS 
push CS 
push EIP 
push ErrorCode 
EIP := IDT[VectorNumber].ExtendedOffset<<16 + 
   IDT[VectorNumber].Offset 

如果是x84,无论R0还是R3,ss:rsp都会被保存。

在windows平坦内存模式(flat memory mode)下,CS == 0×1b && SS == 0×23,容易识别。

在完整的dmp中,可以看到dump时发生的异常。

案例

PROCESS 89a94800 SessionId: 1 Cid: 1050 Peb: 7ffd7000 ParentCid: 08a4 
...
DebugPort         899b6a40 

如果dmp中有DebugPort,说明有异常发生,执行!process 0 1可以查找没有处理的异常。

0: kd> .process 89a94800 	;指定
0: kd> .reload 
0: kd> !process 89a94800 	;显示信息
...
f44dc8c4 808346b4 nt!KiDispatchException+0×1ea 
f44dc92c 80834650 nt!CommonDispatchException+0×4a 
f44dc9b8 80a801ae nt!Kei386EoiHelper+0×16e 
0012f968 0046915d hal!HalpDispatchSoftwareInterrupt+0×5e 
0012f998 0047cb72 processA!CalculateClientSizeFromPoint+0×5f
...

!process可以看到线程栈中有KiDispatchException

貌似异常发生在processA!CalculateClientSizeFromPoint,但并没有nt!KiTrapXXX,而且hal!HalpDispatchSoftwareInterrupt有用户空间返回地址,这时就需要查看原始栈数据,找一下中断帧。查找的关注点顺序是:

  1. KiDispatchException
  2. KiTrap
  3. 0000001b

如果0000001b和00000023被两个DWORD(EFLAGS, ESP)隔开,那就找到了。

0: kd> dds esp esp+1000 
...
f44dc4f8 80998bfd nt!Ki386CheckDivideByZeroTrap+0x273 
f44dc4fc 8083484f nt!KiTrap00+0x88  ; first KiTrap00 
f44dc500 00000001 
f44dc504 0000bb40 
f44dc508 f44dc8c4 
f44dc50c 8081a94f nt!KiDispatchException+0×1ea 
...
f44dc8c8 808346b4 nt!CommonDispatchException+0×4a ;!!!!!!!!!
...
f44dc92c 8083484f nt!KiTrap00+0×88  ; second KiTrap00 !!!!!!!!
f44dc930 80834650 nt!Kei386EoiHelper+0×16e 
f44dc934 0012f968 
f44dc938 00469583 processA!LPtoDP+0×19 
...
f44dc998 00000000 ; ErrorCode 
f44dc99c 00469583 processA!LPtoDP+0×19 ; EIP 
f44dc9a0 0000001b ; CS 
f44dc9a4 00010246 ; EFLAGS 
f44dc9a8 0012f934 ; ESP 
f44dc9ac 00000023 ; SS 
f44dc9b0 8982e7e0 

注意,之所以跳过第一个KiTrap00,是因为KiDispatchException应该在它之后调用。

0: kd> u 8083484f 
nt!KiTrap00+0×88: 
8083484f mov  ebx,dword ptr [ebp+68h] 
80834852 jmp  nt!Kei386EoiHelper+0×167 (80834649) 
...
0: kd> u 80834649 
nt!Kei386EoiHelper+0×167: 
80834649 xor  ecx,ecx 
8083464b call nt!CommonDispatchException (8083466a) 
80834650 xor  edx,edx ; nt!Kei386EoiHelper+0×16e 
80834652 mov  ecx,1 
80834657 call nt!CommonDispatchException (8083466a) 
8083465c xor  edx,edx 
8083465e mov  ecx,2 
80834663 call nt!CommonDispatchException (8083466a) 
0: kd> ub 808346b4 
nt!CommonDispatchException+0×38: 
808346a2 mov  eax,dword ptr [ebp+6Ch] 
808346a5 and  eax,1 
808346a8 push 1 
808346aa push eax 
808346ab push ebp 
808346ac push 0 
808346ae push ecx 
808346af call nt!KiDispatchException (80852a53) 

这样,调用顺序就明确了:KiTrap00-> CommonDispatchException ->KiDispatchException。根据中断帧,异常时,EIP == 00469583 && ESP == 0012f934

但为了修复栈,我们还需要ebp,所以在栈里查找一下:

0: kd> dds 0012f934-10 0012f934+100 
0012f924 00000000 
0012f928 0012f934 ; 
0012f92c 0012f968 ;!!!!!!!!!!!!!! … 
...
0012f968 0012f998 
0012f96c 0046915d processA!CalculateClientSizeFromPoint+0×5f 
...

0: kd> k L=0012f968 0012f934 00469583 100 ; ebp esp eip
ChildEBP RetAddr 
0012f930 00469a16 processA!LPtoDP+0x19 
0012f968 0046915d processA!GetColumnWidth+0x45 
0012f998 0047cb72 processA!CalculateClientSizeFromPoint+0x5f 
0012f9bc 0047cc1d processA!CalculateFromPoint+0x30 
0012fa64 0047de83 processA!DrawUsingMemDC+0x1b9 
...

这样,异常时的栈就被修复了。

4. x86 trap指令

KiTrap系列函数开头会保存寄存器。查看陷阱帧trap frame指令:dt _KTRAP_FRAME

要和中断帧区分,R0中断帧只保存EIP, CS, EFLAGS和ErrorCode,R3还会保存栈指针SS:ESP。

.trap指令会切换到异常线程环境,!analyze -v可以看到TrapFrame地址,然后用dt _KTRAP_FRAME查看。

但如果栈损坏,windbg无法自动找到TrapFrame,就得手动去找。

   +0x034 SegEs            : 0x23 
   +0x038 SegDs            : 0x23 

windows平坦模式下,es和ds的值是0x23。在栈中能找到多处连续0x23,需要找的是KiTrap函数返回地址。

f24f87b0  00000023 
f24f87b4  00000023 
f24f87b8  00000000 
...
f24f8a58  00000111 
f24f8a5c  f24f8a74 
f24f8a60  e088bc08 nt!KiTrap0E+0xdc 
...
f24f8aa4  00000000 
f24f8aa8  00000023 
f24f8aac  00000023 

根据偏移得到帧地址。

3: kd> dt _KTRAP_FRAME f24f8aac-38 
3: kd> .trap f24f8aac-38

完整dmp中,栈帧地址会这样显示出来:

3: kd> kL 
ChildEBP RetAddr 
f24f8ae8 de667677 driver!foo+0x16 
...
f24f8d54 7c94ed54 nt!KiFastCallEntry+0xfc (TrapFrame @ f24f8d64) 

5. x64 trap指令

x64中断时,栈中也保存_KTRAP_FRAME

6: kd> uf nt!KiPageFault 
nt!KiPageFault: 
fffff800`0102d400 push    rbp 
fffff800`0102d401 sub     rsp,158h 
fffff800`0102d408 lea     rbp,[rsp+80h] 
fffff800`0102d410 mov     byte ptr [rbp-55h],1 
fffff800`0102d414 mov     qword ptr [rbp-50h],rax 
fffff800`0102d418 mov     qword ptr [rbp-48h],rcx 
fffff800`0102d41c mov     qword ptr [rbp-40h],rdx 
fffff800`0102d420 mov     qword ptr [rbp-38h],r8 
fffff800`0102d424 mov     qword ptr [rbp-30h],r9 
fffff800`0102d428 mov     qword ptr [rbp-28h],r10 
fffff800`0102d42c mov     qword ptr [rbp-20h],r11 
... 
6: kd> dt _KTRAP_FRAME 
   +0x000 P1Home           : Uint8B 
   +0x008 P2Home           : Uint8B 
   +0x010 P3Home           : Uint8B 
   +0x018 P4Home           : Uint8B 
   +0x020 P5               : Uint8B 
   +0x028 PreviousMode     : Char 
   +0x029 PreviousIrql     : UChar 
   +0x02a FaultIndicator   : UChar 
   +0x02b ExceptionActive  : UChar 
   +0x02c MxCsr            : Uint4B 
   +0x030 Rax              : Uint8B 
   +0x038 Rcx              : Uint8B 
   +0x040 Rdx              : Uint8B 
   +0x048 R8               : Uint8B 
   +0x050 R9               : Uint8B 
   +0x058 R10              : Uint8B 
   +0x060 R11              : Uint8B 
   +0x068 GsBase           : Uint8B 
   +0x068 GsSwap           : Uint8B 
   +0x070 Xmm0             : _M128A 
   +0x080 Xmm1             : _M128A 
   +0x090 Xmm2             : _M128A 
   +0x0a0 Xmm3             : _M128A 
   +0x0b0 Xmm4             : _M128A 
   +0x0c0 Xmm5             : _M128A 
   +0x0d0 FaultAddress     : Uint8B 
   +0x0d0 ContextRecord    : Uint8B 
   +0x0d0 TimeStamp        : Uint8B 
   +0x0d8 Dr0              : Uint8B 
   +0x0e0 Dr1              : Uint8B 
   +0x0e8 Dr2              : Uint8B 
   +0x0f0 Dr3              : Uint8B 
   +0x0f8 Dr6              : Uint8B 
   +0x100 Dr7              : Uint8B 
   +0x108 DebugControl     : Uint8B 
   +0x110 LastBranchToRip  : Uint8B 
   +0x118 LastBranchFromRip : Uint8B 
   +0x120 LastExceptionToRip : Uint8B 
   +0x128 LastExceptionFromRip : Uint8B 
   +0x108 LastBranchControl : Uint8B 
   +0x110 LastBranchMSR    : Uint4B 
   +0x130 SegDs            : Uint2B 
   +0x132 SegEs            : Uint2B 
   +0x134 SegFs            : Uint2B 
   +0x136 SegGs            : Uint2B 
   +0x138 TrapFrame        : Uint8B 
   +0x140 Rbx              : Uint8B 
   +0x148 Rdi              : Uint8B 
   +0x150 Rsi              : Uint8B 
   +0x158 Rbp              : Uint8B 
       
   +0×160 ErrorCode        : Uint8B 
   +0×160 ExceptionFrame   : Uint8B 
   +0×168 Rip              : Uint8B 
   +0×170 SegCs            : Uint2B 
   +0×172 Fill1            : [3] Uint2B 
   +0×178 EFlags           : Uint4B 
   +0×17c Fill2            : Uint4B 
   +0×180 Rsp              : Uint8B 
   +0×188 SegSs            : Uint2B 
   +0×18a Fill3            : [1] Uint2B 
   +0×18c CodePatchCycle   : Int4B 

根据ds和es找陷阱帧这时就不管用了,因为KiPageFault没有保存它们。

记住,x64中,即使寄存器只用16或32bit,也需要填充为64bit, Fill1, Fill2, Fill3, CodePatchCycle就是用来填充16bit的cs、ss,还有32bit的rflags。而cs、ss是有固定值的:

6: kd> r cs 
cs=0010 
6: kd> r ss 
ss=0018 

6: kd> dqs rsp
...
fffffadc`6e02caa0  00000000`00000000 ; ErrorCode 
fffffadc`6e02caa8  fffff97f`ff591ed3 driver+0x44ed3 ; RIP 
fffffadc`6e02cab0  00000000`00000010 ; CS 
fffffadc`6e02cab8  00000000`00010282 ; RFLAGS 
fffffadc`6e02cac0  fffffadc`6e02cad0 ; RSP 
fffffadc`6e02cac8  00000000`00000018 ; SS 
...

通过ss偏移锁定陷阱帧:

6: kd> .trap fffffadc`6e02cac8-188 

完整dmp中可以自动显示:

6: kd> kv 
Child-SP          RetAddr           Call Site 
...
fffffadc`6e02c940 fffff97f`ff591ed3 nt!KiPageFault+0x1e1 (TrapFrame @ 
fffffadc`6e02c940) 

6. R3异常

如果是R3异常,就不能像R0一样,在栈中找到SS:RSP, RFLAGS, CS:RIP

下面是完整dmp的情况:

kd> !process fffffadfe7055c20 2 
PROCESS fffffadfe7055c20 
    SessionId: 0  Cid: 0c64    Peb: 7fffffd7000  ParentCid: 07b0 
    DirBase: 27e3d000  ObjectTable: fffffa800073a550  HandleCount:  55. 
    Image: TDD64.exe 
        THREAD fffffadfe78f2bf0  Cid 0c64.0c68  Teb: 000007fffffde000 
Win32Thread: fffff97ff4d71010 WAIT: (Unknown) KernelMode Non-Alertable 
SuspendCount 1 
            fffffadfdf7b6fc0  SynchronizationEvent 
        THREAD ...
        ...
kd> .thread /r /p fffffadfe78f2bf0 
Implicit thread is now fffffadf`e78f2bf0 
Implicit process is now fffffadf`e7055c20 
Loading User Symbols 
kd> kL 100 
Child-SP          RetAddr           Call Site 
fffffadf`df7b6d30 fffff800`0103b063 nt!KiSwapContext+0x85 
fffffadf`df7b6eb0 fffff800`0103c403 nt!KiSwapThread+0xc3 
fffffadf`df7b6ef0 fffff800`013a9dc1 nt!KeWaitForSingleObject+0x528 
fffffadf`df7b6f80 fffff800`01336dcf nt!DbgkpQueueMessage+0x281 
fffffadf`df7b7130 fffff800`01011c69 nt!DbgkForwardException+0x1c5 
fffffadf`df7b74f0 fffff800`0104146f nt!KiDispatchException+0x264 
fffffadf`df7b7af0 fffff800`010402e1 nt!KiExceptionExit 
fffffadf`df7b7c70 00000001`40001690 nt!KiPageFault+0×1e1 
kd> dqs fffffadf`df7b7c70
...
fffffadf`df7b7dc8  00000000`00000111 ; RBP saved by KiPageFault 
fffffadf`df7b7dd0  00000000`00000006 ; Page-Fault Error Code 
fffffadf`df7b7dd8  00000001`40001690 
TDD64!CTestDefaultDebuggerDlg::OnBnClickedButton1 ; RIP 
fffffadf`df7b7de0  00000000`00000033 ; CS 
fffffadf`df7b7de8  00000000`00010246 ; RFLAGS 
fffffadf`df7b7df0  00000000`0012f198 ; RSP 
fffffadf`df7b7df8  00000000`0000002b ; SS 
...
kd> .asm no_code_bytes 
Assembly options: no_code_bytes 
kd> u KiPageFault 

Error code 6 is 110 in binary and volume 3A of Intel manual tells us that “the fault
was caused by a non-present page” (bit 0 is cleared), “the access causing the fault was a
write” (bit 1 is set) and “the access causing the fault originated when the processor was
executing in user mode” (bit 2 is set).

7. 如何区分 1ST AND 2ND CHANCES

This dump file has an exception of interest stored in it. 
The stored exception information can be accessed via .ecxr. 
(1254.1124): Access violation - code c0000005 (first/second chance not 
available) 
...
0:000> !teb 
TEB at 000007fffffde000 
    ExceptionList:        0000000000000000 
    StackBase:            0000000000130000 
    StackLimit:           000000000012b000 
    ...
0:000> s -d 000000000012b000 0000000000130000 c0000005 ;如果是第一次,就没有输出
00000000`0012f000  c0000005 00000000 00000000 00000000  .

从栈中甚至可以看出第二次异常的时间。

windbg二次异常:

00000000`0012e280  00000000`00000000 
00000000`0012e288  00000000`7790032c kernel32!IsDebugPortPresent+0×2c 
00000000`0012e290  00000000`00000000 
00000000`0012e298  00000000`00000000 
00000000`0012e2a0  00000000`00000000 
00000000`0012e2a8  00000000`7790032c kernel32!IsDebugPortPresent+0×2c 
00000000`0012e2b0  00000001`00010000 

8. 谁启动了后期调试器

dwwin.exe是微软Doctor Watson错误报告程序,错误发生时由faultrep.dll启动。

0:000> uf faultrep!StartDWException 
...
694575a6 ffb5a4f7ffff    push    dword ptr [ebp-85Ch] ; second parameter 
694575ac 50              push    eax  ; first parameter   
694575ad ff1558114569    call    dword ptr [faultrep!_imp__CreateProcessW 
0:000> dpu 0012dd68-85Ch l1 
0012d50c  0012d3ec "C:\WINDOWS\system32\dwwin.exe -x -s 208" 
0:000> uf kernel32!UnhandledExceptionFilter 
7c8636bf 53              push    ebx  ; second parameter 
7c8636c0 50              push    eax  ; first parameter  
7c8636c1 e86cecf9ff      call    kernel32!CreateProcessW (7c802332) 

9. vista错误报告

10. page fault

相关命令:

  • !pool
  • !pte,判断地址是否属于无效页
1: kd> !pte e16623fc 
               VA e16623fc 
PDE at 00000000C0603858    PTE at 00000000C070B310 
contains 00000000F5434863  contains 00000000E817A8C2 
pfn f5434 ---DA--KWEV                           not valid 
                       Transition: e817a 
                       Protect: 6 - ReadWriteExecute 
1: kd> .formats 00000000E817A8C2 
Evaluate expression: 
  Hex: e817a8c2 
  Decimal: -401102654 
  Octal: 35005724302 
  Binary: 11101000 00010111 1010 1 000 1100001 0

最低位为0,说明pte标记该页无效;位11为1,说明该页在修改列表中。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!