再谈360内核inline Hook
前几天写了一篇 360内核 inline Hook 分析 有朋友感觉我没有把360的Hook的函数说清楚以至于产生误会。上一篇博客确实没有把这个问题说清楚。
在上篇文章中说到360是Hook nt!KiFastCallEntry+0xe1 这个位置是没有错的,只是严格的讲是在_KiSystemServiceRepeat 当中进行的Hook,很有意思的是_KiSystemServiceRepeat 是被包含在_KiFastCallEntry这个函数当中,而又从外部可以独立调用。
为什么要在这儿Hook呢?这要从ring3进入ring0的过程说起。
我们知道从ring3 进入ring0,有两种方法一种是int 2E;另一种,就是我们常用的sysenter;前一种现在已经不用了,速度太慢主要是在windows 2000当中用。现在主要是用sysenter。当在ring3中调用sysenter就会发生模式的切换从ring3->ring0。sysenter这条指令Intel是优化过的,主要是借助三个MSR寄存器来完成加速过程的,不过这两种方法最终都要确定内核的调用例程的位置,这点是不变的。那么这个例程是什么呢?其实最终就是调用了内核当中的_KiSystemService。
代码如下:
PUBLIC _KiSystemService
_KiSystemService proc
ENTER_SYSCALL kss_a, kss_t ; set up trap frame and save state
jmp _KiSystemServiceRepeat
_KiSystemService endp
上面的函数很简单就是跳转到了_KiSystemServiceRepeat。
那么有人可能会问 SSDT,Shadow SSDT这两个表是什么时候被用到了,答案就在_KiSystemServiceRepeat。
注意:_KiSystemServiceRepeat这个函数是被包含在_KiFastCallEntry当中的,我就把_KiFastCallEntry全部贴上来。
360 Hook的地方我已经高亮标记出来了,真正调用SSDT例程的位置我也标记了。
PUBLIC _KiFastCallEntry
_KiFastCallEntry proc
;
; Sanitize the segment registers
;
mov ecx, KGDT_R3_DATA OR RPL_MASK
push KGDT_R0_PCR
pop fs
mov ds, ecx
mov es, ecx
;
; When we trap into the kernel via fast system call we start on the DPC stack. We need
; shift to the threads stack before enabling interrupts.
;
mov ecx, PCR[PcTss] ;
mov esp, [ecx]+TssEsp0
push KGDT_R3_DATA OR RPL_MASK ; Push user SS
push edx ; Push ESP
pushfd
Kfsc10:
push 2 ; Sanitize eflags, clear direction, NT etc
add edx, 8 ; (edx) -> arguments
popfd ;
.errnz(EFLAGS_INTERRUPT_MASK AND 0FFFF00FFh)
or byte ptr [esp+1], EFLAGS_INTERRUPT_MASK/0100h ; Enable interrupts in eflags
push KGDT_R3_CODE OR RPL_MASK ; Push user CS
push dword ptr ds:[USER_SHARED_DATA+UsSystemCallReturn] ; push return address
push 0 ; put pad dword for error on stack
push ebp ; save the non-volatile registers
push ebx ;
push esi ;
push edi ;
mov ebx, PCR[PcSelfPcr] ; Get PRCB address
push KGDT_R3_TEB OR RPL_MASK ; Push user mode FS
mov esi, [ebx].PcPrcbData+PbCurrentThread ; get current thread address
;
; Save the old exception list in trap frame and initialize a new empty
; exception list.
;
push [ebx].PcExceptionList ; save old exception list
mov [ebx].PcExceptionList, EXCEPTION_CHAIN_END ; set new empty list
mov ebp, [esi].ThInitialStack
;
; Save the old previous mode in trap frame, allocate remainder of trap frame,
; and set the new previous mode.
;
push MODE_MASK ; Save previous mode as user
sub esp,TsPreviousPreviousMode ; allocate remainder of trap frame
sub ebp, NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
mov byte ptr [esi].ThPreviousMode,MODE_MASK ; set new previous mode of user
;
; Now the full trap frame is build.
; Calculate initial stack pointer from thread initial stack to contain NPX and trap.
; If this isn't the same as esp then we are a VX86 thread and we are rejected
;
cmp ebp, esp
jne short Kfsc91
;
; Set the new trap frame address.
;
and dword ptr [ebp].TsDr7, 0
test byte ptr [esi].ThDebugActive, 0ffh ; See if we need to save debug registers
mov [esi].ThTrapFrame, ebp ; set new trap frame address
jnz Dr_FastCallDrSave ; if nz, debugging is active on thread
Dr_FastCallDrReturn: ;
SET_DEBUG_DATA ; Note this destroys edi
sti ; enable interrupts
?FpoValue = 0
_KiSystemServiceRepeat:
mov edi, eax ; copy system service number
shr edi, SERVICE_TABLE_SHIFT ; isolate service table number
and edi, SERVICE_TABLE_MASK ;
mov ecx, edi ; save service table number
add edi, [esi]+ThServiceTable ; compute service descriptor address
mov ebx, eax ; save system service number
and eax, SERVICE_NUMBER_MASK ; isolate service table offset
;
; If the specified system service number is not within range, then attempt
; to convert the thread to a GUI thread and retry the service dispatch.
;
cmp eax, [edi]+SdLimit ; check if valid service
jae Kss_ErrorHandler ; if ae, try to convert to GUI thread
;
; If the service is a GUI service and the GDI user batch queue is not empty,
; then call the appropriate service to flush the user batch.
;
cmp ecx, SERVICE_TABLE_TEST ; test if GUI service
jne short Kss40 ; if ne, not GUI service
mov ecx, PCR[PcTeb] ; get current thread TEB address
xor ebx, ebx ; get number of batched GDI calls
KiSystemServiceAccessTeb:
or ebx, [ecx]+TbGdiBatchCount ; may cause an inpage exception
jz short Kss40 ; if z, no batched calls
push edx ; save address of user arguments
push eax ; save service number
call [_KeGdiFlushUserBatch] ; flush GDI user batch
pop eax ; restore service number
pop edx ; restore address of user arguments
;
; The arguments are passed on the stack. Therefore they always need to get
; copied since additional space has been allocated on the stack for the
; machine state frame. Note that we don't check for the zero argument case -
; copy is always done regardless of the number of arguments because the
; zero argument case is very rare.
;
Kss40: inc dword ptr PCR[PcPrcbData+PbSystemCalls] ; system calls
if DBG
mov ecx, [edi]+SdCount ; get count table address
jecxz short @f ; if zero, table not specified
inc dword ptr [ecx+eax*4] ; increment service count
@@: ;
endif
FPOFRAME ?FpoValue, 0
mov esi, edx ; (esi)->User arguments
mov ebx, [edi]+SdNumber ; get argument table address
xor ecx, ecx
mov cl, byte ptr [ebx+eax] ; (ecx) = argument size
mov edi, [edi]+SdBase ; get service table address
mov ebx, [edi+eax*4] ; (ebx)-> service routine
sub esp, ecx ; allocate space for arguments 360是在这儿Hook的
shr ecx, 2 ; (ecx) = number of argument DWORDs
mov edi, esp ; (edi)->location to receive 1st arg
cmp esi, _MmUserProbeAddress ; check if user address
jae kss80 ; if ae, then not user address
KiSystemServiceCopyArguments:
rep movsd ; copy the arguments to top of stack.
; Since we usually copy more than 3
; arguments. rep movsd is faster than
; mov instructions.
;
; Check if low resource usage should be simulated.
;
if DBG
test _MmInjectUserInpageErrors, 2
jz short @f
stdCall _MmTrimProcessMemory, <0>
jmp short kssdoit
@@:
mov eax,PCR[PcPrcbData+PbCurrentThread]
mov eax,[eax]+ThApcState+AsProcess
test dword ptr [eax]+PrFlags,0100000h ; is this a inpage-err process?
je short @f
stdCall _MmTrimProcessMemory, <0>
@@:
endif
;
; Make actual call to system service
;
kssdoit:
call ebx ; call system service 看到这儿就感觉很爽
kss60:
;
; Check for return to user mode at elevated IRQL.
;
if DBG
test byte ptr [ebp]+TsSegCs,MODE_MASK ; test if previous mode user
jz short kss50b ; if z, previous mode not user
mov esi,eax ; save return status
CurrentIrql ; get current IRQL
or al,al ; check if IRQL is passive level
jnz kss100 ; if nz, IRQL not passive level
mov eax,esi ; restore return status
;
; Check if kernel APCs are disabled or a process is attached.
;
mov ecx,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
mov dl,[ecx]+ThApcStateIndex ; get APC state index
or dl,dl ; check if process attached
jne kss120 ; if ne, process is attached
mov edx,[ecx]+ThCombinedApcDisable ; get kernel APC disable
or edx,edx ; check if kernel APCs disabled
jne kss120 ; if ne, kernel APCs disabled.
kss50b: ;
endif
kss61:
;
; Upon return, (eax)= status code. This code may also be entered from a failed
; KiCallbackReturn call.
;
mov esp, ebp ; deallocate stack space for arguments
;
; Restore old trap frame address from the current trap frame.
;
kss70: mov ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address
mov edx, [ebp].TsEdx ; restore previous trap frame address
mov [ecx].ThTrapFrame, edx ;
;
; System service's private version of KiExceptionExit
; (Also used by KiDebugService)
;
; Check for pending APC interrupts, if found, dispatch to them
; (saving eax in frame first).
;
public _KiServiceExit
_KiServiceExit:
cli ; disable interrupts
DISPATCH_USER_APC ebp, ReturnCurrentEax
;
; Exit from SystemService
;
EXIT_ALL NoRestoreSegs, NoRestoreVolatile
;
; The address of the argument list is not a user address. If the previous mode
; is user, then return an access violation as the status of the system service.
; Otherwise, copy the argument list and execute the system service.
;
kss80: test byte ptr [ebp].TsSegCs, MODE_MASK ; test previous mode
jz KiSystemServiceCopyArguments ; if z, previous mode kernel
mov eax, STATUS_ACCESS_VIOLATION ; set service status
jmp kss60 ;
;++
;
; _KiServiceExit2 - same as _KiServiceExit BUT the full trap_frame
; context is restored
;
;--
public _KiServiceExit2
_KiServiceExit2:
cli ; disable interrupts
DISPATCH_USER_APC ebp
;
; Exit from SystemService
;
EXIT_ALL ; RestoreAll
if DBG
kss100: push PCR[PcIrql] ; put bogus value on stack for dbg
?FpoValue = ?FpoValue + 1
FPOFRAME ?FpoValue, 0
mov byte ptr PCR[PcIrql],0 ; avoid recursive trap
cli ;
;
; IRQL_GT_ZERO_AT_SYSTEM_SERVICE - attempted return to usermode at elevated
; IRQL.
;
; KeBugCheck2(IRQL_GT_ZERO_AT_SYSTEM_SERVICE,
; System Call Handler (address of system routine),
; Irql,
; 0,
; 0,
; TrapFrame);
;
stdCall _KeBugCheck2,<IRQL_GT_ZERO_AT_SYSTEM_SERVICE,ebx,eax,0,0,ebp>
;
; APC_INDEX_MISMATCH - attempted return to user mode with kernel APCs disabled
; or a process attached.
;
; KeBugCheck2(APC_INDEX_MISMATCH,
; System Call Handler (address of system routine),
; Thread->ApcStateIndex,
; Thread->CombinedApcDisable,
; 0,
; TrapFrame);
;
kss120: movzx eax,byte ptr [ecx]+ThApcStateIndex ; get APC state index
mov edx,[ecx]+ThCombinedApcDisable ; get kernel APC disable
stdCall _KeBugCheck2,<APC_INDEX_MISMATCH,ebx,eax,edx,0,ebp>
endif
ret
_KiFastCallEntry endp
从上面的代码可以看出360 实际上是拦截了所有的SSDT的调用,包括Shadow SSDT。这种方式比瑞星的Hook更加彻底,技术上也更高一筹。现在我认为360的inline Hook我已经说清楚了!