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
有用户空间返回地址,这时就需要查看原始栈数据,找一下中断帧。查找的关注点顺序是:
- KiDispatchException
- KiTrap
- 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,说明该页在修改列表中。
来源:CSDN
作者:st4rr
链接:https://blog.csdn.net/Ga4ra/article/details/104698568