User Mode
C++ Demo(Debug)
int
wmain(
int argc,
WCHAR * argv[]
)
{
00DF13C0 push ebp
00DF13C1 mov ebp,esp
00DF13C3 push 0FFFFFFFEh
00DF13C5 push 0DF6EB8h
00DF13CA push 0DF107Dh
00DF13CF mov eax,dword ptr fs:[00000000h]
00DF13D5 push eax
00DF13D6 add esp,0FFFFFF38h :分配局部变量。Q:没有局部变量,只需esp-8,分配EXCEPTION_REGISTRATION_RECORD剩余的8字节,但为什么这里还有这么大空间?求解答
00DF13DC push ebx
00DF13DD push esi
00DF13DE push edi
00DF13DF lea edi,[ebp-0D8h]
00DF13E5 mov ecx,30h
00DF13EA mov eax,0CCCCCCCCh
00DF13EF rep stos dword ptr es:[edi] :对栈上的(局部变量)进行0xcccccccc赋值。每个局部变量的两端都会被增加0xcccccccc,也就是说一个DWORD实际会占用3个DWORD。使用0xcccccccc的原因:1.数值较大,且为负,容易被察觉 2.int 3的机器码为0xcc,可以探测到栈被执行
00DF13F1 mov eax,dword ptr ds:[00DF8000h]
00DF13F6 xor dword ptr [ebp-8],eax
00DF13F9 xor eax,ebp
00DF13FB push eax :计算cookie
00DF13FC lea eax,[ebp-10h]
00DF13FF mov dword ptr fs:[00000000h],eax
00DF1405 mov dword ptr [ebp-18h],esp :存入esp
__try{ :至此,异常帧构建完毕,并且插入了fs:[0],开始生效
00DF1408 mov dword ptr [ebp-4],0
getchar();
00DF140F mov esi,esp
00DF1411 call dword ptr ds:[0DF92B8h]
00DF1417 cmp esi,esp
00DF1419 call __RTC_CheckEsp (0DF1145h)
}__except(EXCEPTION_EXECUTE_HANDLER){
00DF141E mov dword ptr [ebp-4],0FFFFFFFEh
00DF1425 jmp $LN6+0Ah (0DF1437h)
$LN10:
00DF1427 mov eax,1
$LN9:
00DF142C ret
$LN6:
00DF142D mov esp,dword ptr [ebp-18h]
GetExceptionCode();
}
00DF1430 mov dword ptr [ebp-4],0FFFFFFFEh
return 0;
00DF1437 xor eax,eax
}
00DF1439 mov ecx,dword ptr [ebp-10h]
00DF143C mov dword ptr fs:[0],ecx :撤销之前插入的异常链节点
00DF1443 pop ecx :拿到cookie
00DF1444 pop edi
00DF1445 pop esi
00DF1446 pop ebx
}
00DF1447 add esp,0D8h
00DF144D cmp ebp,esp
00DF144F call __RTC_CheckEsp (0DF1145h)
00DF1454 mov esp,ebp
00DF1456 pop ebp
00DF1457 ret
堆栈图:
ntdll!__RtlUserThreadStart
.text:778337CE ; __stdcall __RtlUserThreadStart(x, x)
.text:778337CE ___RtlUserThreadStart@8 proc near ; CODE XREF: _RtlUserThreadStart(x,x)+16p
.text:778337CE
.text:778337CE ExitStatus = dword ptr -24h
.text:778337CE var_20 = dword ptr -20h
.text:778337CE var_1C = dword ptr -1Ch
.text:778337CE ms_exc = CPPEH_RECORD ptr -18h
.text:778337CE arg_0 = dword ptr 8
.text:778337CE arg_4 = dword ptr 0Ch
.text:778337CE
.text:778337CE ; FUNCTION CHUNK AT .text:777D5E77 SIZE 00000009 BYTES
.text:778337CE ; FUNCTION CHUNK AT .text:77847EEB SIZE 00000030 BYTES
.text:778337CE ; FUNCTION CHUNK AT .text:77848260 SIZE 0000001E BYTES
.text:778337CE
.text:778337CE 000 push 14h
.text:778337D0 004 push offset off_77821278
.text:778337D5 008 call __SEH_prolog4
.text:778337DA 038 and [ebp+ms_exc.registration.TryLevel], 0
... ...
---------------------------------------------------------------------------------------------------
.text:77822C0C __SEH_prolog4 proc near ; CODE XREF: LdrFlushAlternateResourceModules()+7p
.text:77822C0C ; RtlpCopyMappedMemoryEx(x,x,x,x,x,x)+7p ...
.text:77822C0C
.text:77822C0C arg_4 = dword ptr 8
.text:77822C0C
.text:77822C0C 000 push offset __except_handler4
.text:77822C11 004 push large dword ptr fs:0
.text:77822C18 008 mov eax, [esp+8+arg_4]
.text:77822C1C 008 mov [esp+8+arg_4], ebp
.text:77822C20 008 lea ebp, [esp+8+arg_4]
.text:77822C24 008 sub esp, eax
.text:77822C26 008 push ebx
.text:77822C27 00C push esi
.text:77822C28 010 push edi
.text:77822C29 014 mov eax, ___security_cookie
.text:77822C2E 014 xor [ebp-4], eax
.text:77822C31 014 xor eax, ebp
.text:77822C33 014 push eax
.text:77822C34 018 mov [ebp-18h], esp
.text:77822C37 018 push dword ptr [ebp-8] :这块和上面的例子不一样,因为要把__SEH_prolog4函数下一条指令的地址重新压栈,为了后面的retn能正常返回
.text:77822C3A 01C mov eax, [ebp-4]
.text:77822C3D 01C mov dword ptr [ebp-4], 0FFFFFFFEh
.text:77822C44 01C mov [ebp-8], eax
.text:77822C47 01C lea eax, [ebp-10h]
.text:77822C4A 01C mov large fs:0, eax
.text:77822C50 01C retn
.text:77822C50 __SEH_prolog4 endp
- 只要发生异常第一就是进入内核,然后构造EXCEPTION_RECORD和CONTEXT,并且进行处理逻辑,然后返回UserMode,在某个时机调用异常链上的Handler。
mouseOS《SEH stack 结构探索(1)— 从 SEH 链的最底层(线程第1个SEH结构)说起》:
http://www.mouseos.com/windows/SEH3.html
Kernel Mode
Tarp00(divide error)
- 发生异常之后,陷入内核,执行Trap00函数
ASSUME DS:NOTHING, SS:NOTHING, ES:NOTHING
ENTER_DR_ASSIST kit0_a, kit0_t,NoAbiosAssist
align dword
public _KiTrap00
_KiTrap00 proc
push 0 ; push dummy error code
ENTER_TRAP kit0_a, kit0_t :构建TrapFrame
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [ebp]+TsEFlags+2,EFLAGS_V86_MASK/010000h
jnz Kt0040 ; trap occured in V86 mode :如果是虚拟86模式
test byte ptr [ebp]+TsSegCs, MODE_MASK ; Is previous mode = USER
jz short Kt0000 :如果是用户模式
cmp word ptr [ebp]+TsSegCs,KGDT_R3_CODE OR RPL_MASK
jne Kt0020 :如果是vdm
;
; Set up exception record for raising Integer_Divided_by_zero exception
; and call _KiDispatchException
;
Kt0000:
if DBG
test [ebp]+TsEFlags, EFLAGS_INTERRUPT_MASK ; faulted with
jnz short @f ; interrupts disabled?
xor eax, eax
mov esi, [ebp]+TsEip ; [esi] = faulting instruction
stdCall _KeBugCheck2,<IRQL_NOT_LESS_OR_EQUAL,eax,-1,eax,esi,ebp>
@@:
endif
sti
;
; Flat mode
;
; The intel processor raises a divide by zero exception on DIV instructions
; which overflow. To be compatible with other processors we want to
; return overflows as such and not as divide by zero's. The operand
; on the div instruction is tested to see if it's zero or not.
;
stdCall _Ki386CheckDivideByZeroTrap,<ebp>
mov ebx, [ebp]+TsEip ; (ebx)-> faulting instruction
jmp CommonDispatchException0Args ; Won't return :跳到异常处理过程
Kt0010:
;
; 16:16 mode
;
sti
mov ebx, [ebp]+TsEip ; (ebx)-> faulting instruction
mov eax, STATUS_INTEGER_DIVIDE_BY_ZERO
jmp CommonDispatchException0Args ; never return
Kt0020:
; Check to see if this process is a vdm
mov ebx,PCR[PcPrcbData+PbCurrentThread]
mov ebx,[ebx]+ThApcState+AsProcess
cmp dword ptr [ebx]+PrVdmObjects,0 ; is this a vdm process?
je Kt0010
Kt0040:
stdCall _Ki386VdmReflectException_A, <0>
or al,al
jz short Kt0010 ; couldn't reflect, gen exception
jmp _KiExceptionExit
_KiTrap00 endp
- 准备数据结构,然后真正的去处理异常
CommonDispatchException0Args:
xor ecx, ecx ; zero arguments
call CommonDispatchException
CommonDispatchException1Arg0d:
xor edx, edx ; zero edx
CommonDispatchException1Arg:
mov ecx, 1 ; one argument
call CommonDispatchException ; there is no return
CommonDispatchException2Args0d:
xor edx, edx ; zero edx
CommonDispatchException2Args:
mov ecx, 2 ; two arguments
call CommonDispatchException ; there is no return
-------------------------------------------------------------------------------------------------
public CommonDispatchException
align dword
CommonDispatchException proc
cPublicFpo 0, ExceptionRecordLength/4
;
; Set up exception record for raising exception
;
sub esp, ExceptionRecordLength
; allocate exception record
mov dword ptr [esp]+ErExceptionCode, eax
; set up exception code
xor eax, eax
mov dword ptr [esp]+ErExceptionFlags, eax
; set exception flags
mov dword ptr [esp]+ErExceptionRecord, eax
; set associated exception record
mov dword ptr [esp]+ErExceptionAddress, ebx
mov dword ptr [esp]+ErNumberParameters, ecx
; set number of parameters
cmp ecx, 0
je short de00
lea ebx, [esp + ErExceptionInformation]
mov [ebx], edx
mov [ebx+4], esi
mov [ebx+8], edi :整个这一段都是在栈上准备EXCEPTION_RECORD
de00:
;
; set up arguments and call _KiDispatchException
;
mov ecx, esp ; (ecx)->exception record
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [ebp]+TsEFlags+2,EFLAGS_V86_MASK/010000h
jz short de10
mov eax,0FFFFh
jmp short de20
de10: mov eax,[ebp]+TsSegCs
de20: and eax,MODE_MASK
; 1 - first chance TRUE
; eax - PreviousMode
; ebp - trap frame addr
; 0 - Null exception frame
; ecx - exception record addr
stdCall _KiDispatchException,<ecx, 0, ebp, eax, 1> :正儿八经去处理异常了
mov esp, ebp ; (esp) -> trap frame
jmp _KiExceptionExit
CommonDispatchException endp
- 调用KiDispatchException
VOID
KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
);
- 注意最后一个参数,FirstChance,我们陷入内核是第一次。如果是UserMode Exception,主要是构建CONTEXT结构,并且返到ntdll!KiUserExceptionDispatcher,给用户态机会,来调用异常链,如果异常链没有处理OK,则会在ntdll!KiUserExceptionDispatcher中调用ZwRaiseException,进行第二次机会的处理