KeUserModeCallback 过程详细分析

 

介绍

KeUserModeCallback经常在内核中被用来执行用户态代码,一直没时间分析其过程,今天花点时间过一下。过之前,先介绍下KeUserModeCallback是什么以及它是执行用户态代码的。

本次分析在Win10 1703 x64版本上进行:

函数声明:

非文档化,但是导出,因此使用前需要MmGetSystemRoutineAddress
KeUserModeCallback (
    IN ULONG ApiNumber,
    IN PVOID InputBuffer,
    IN ULONG InputLength,
    OUT PVOID *OutputBuffer,
    IN PULONG OutputLength
    )

执行限制

但是KeUserModeCallback过程中会有一些限制,如下过程梳理中会有遇到:

  1. 检查线程的KTHREAD->MicsFlag中是否有 CalloutActive Flag
  2. 检查当前Irql,必须是PASSIVE_LEVEL。
  3. 检查KTHREAD->ApcStateIndex,必须是0,即不能使注入到其它进程的线程,这个是其它话题,此文不提
  4. 检查KTHREAD->KernelApcDisable,必须是False.
  5. 检查KTHREAD->CallbackNestingLevel不能大于0x1f,深度限制。

ApiNumber

如上参数中,ApiNumberPEB->KernelCallbackTable函数表的一个index,Windbg中使用如下命令查看

0: kd> dt nt!_PEB @$peb -y Kernel
   +0x058 KernelCallbackTable : 0x00007ff8`279a1000 Void
0: kd> dqs 0x00007ff8`279a1000 L100
00007ff8`279a1000  00007ff8`27933a30 USER32!_fnCOPYDATA
00007ff8`279a1008  00007ff8`2799a940 USER32!_fnCOPYGLOBALDATA
00007ff8`279a1010  00007ff8`279411c0 USER32!_fnDWORD
00007ff8`279a1018  00007ff8`27944520 USER32!_fnNCDESTROY
00007ff8`279a1020  00007ff8`27943c70 USER32!_fnDWORDOPTINLPMSG
00007ff8`279a1028  00007ff8`2799af70 USER32!_fnINOUTDRAG
00007ff8`279a1030  00007ff8`27944ea0 USER32!_fnGETTEXTLENGTHS
00007ff8`279a1038  00007ff8`2799ac80 USER32!_fnINCNTOUTSTRING
00007ff8`279a1040  00007ff8`2799ad30 USER32!_fnINCNTOUTSTRINGNULL
00007ff8`279a1048  00007ff8`2799add0 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ff8`279a1050  00007ff8`27942b10 USER32!__fnINLPCREATESTRUCT
00007ff8`279a1058  00007ff8`2799ae20 USER32!_fnINLPDELETEITEMSTRUCT
00007ff8`279a1060  00007ff8`27946fd0 USER32!__fnINLPDRAWITEMSTRUCT
00007ff8`279a1068  00007ff8`2799ae70 USER32!_fnINLPHELPINFOSTRUCT
00007ff8`279a1070  00007ff8`2799ae70 USER32!_fnINLPHELPINFOSTRUCT
00007ff8`279a1078  00007ff8`2794d2e0 USER32!__fnINLPMDICREATESTRUCT
00007ff8`279a1080  00007ff8`2794bef0 USER32!_fnINOUTLPMEASUREITEMSTRUCT
00007ff8`279a1088  00007ff8`27943e50 USER32!_fnINLPWINDOWPOS
00007ff8`279a1090  00007ff8`27942ff0 USER32!_fnINOUTLPPOINT5
00007ff8`279a1098  00007ff8`27946890 USER32!_fnINOUTLPSCROLLINFO
00007ff8`279a10a0  00007ff8`279464f0 USER32!_fnINOUTLPRECT
00007ff8`279a10a8  00007ff8`27943c00 USER32!_fnINOUTNCCALCSIZE
00007ff8`279a10b0  00007ff8`27942ff0 USER32!_fnINOUTLPPOINT5
00007ff8`279a10b8  00007ff8`2799b020 USER32!_fnINPAINTCLIPBRD
00007ff8`279a10c0  00007ff8`2799b0d0 USER32!_fnINSIZECLIPBRD
00007ff8`279a10c8  00007ff8`2794d9d0 USER32!_fnINDESTROYCLIPBRD
00007ff8`279a10d0  00007ff8`27946f70 USER32!__fnINSTRING
00007ff8`279a10d8  00007ff8`27933e00 USER32!_fnINSTRINGNULL
00007ff8`279a10e0  00007ff8`27944980 USER32!__fnINDEVICECHANGE
00007ff8`279a10e8  00007ff8`27933930 USER32!_fnPOWERBROADCAST
00007ff8`279a10f0  00007ff8`27944380 USER32!_fnINLPUAHDRAWMENU
00007ff8`279a10f8  00007ff8`279462e0 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00007ff8`279a1100  00007ff8`279462e0 USER32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00007ff8`279a1108  00007ff8`2799b1c0 USER32!_fnOUTDWORDINDWORD
00007ff8`279a1110  00007ff8`27945970 USER32!_fnOUTLPRECT
00007ff8`279a1118  00007ff8`27933e70 USER32!_fnOUTSTRING
00007ff8`279a1120  00007ff8`2799ae70 USER32!_fnINLPHELPINFOSTRUCT
00007ff8`279a1128  00007ff8`2799ad30 USER32!_fnINCNTOUTSTRINGNULL
00007ff8`279a1130  00007ff8`2799b270 USER32!_fnSENTDDEMSG
00007ff8`279a1138  00007ff8`279419a0 USER32!_fnINOUTLPSIZE
00007ff8`279a1140  00007ff8`279401e0 USER32!_fnHkINDWORD
00007ff8`279a1148  00007ff8`27948ec0 USER32!_fnHkINLPCBTACTIVATESTRUCT
00007ff8`279a1150  00007ff8`27933aa0 USER32!__fnHkINLPCBTCREATESTRUCT
00007ff8`279a1158  00007ff8`2799aa40 USER32!_fnHkINLPDEBUGHOOKSTRUCT
00007ff8`279a1160  00007ff8`279401a0 USER32!_fnHkINLPMOUSEHOOKSTRUCTEX
00007ff8`279a1168  00007ff8`27946370 USER32!_fnHkINLPKBDLLHOOKSTRUCT
00007ff8`279a1170  00007ff8`27946e30 USER32!_fnHkINLPMSLLHOOKSTRUCT
00007ff8`279a1178  00007ff8`27940220 USER32!__fnHkINLPMSG
00007ff8`279a1180  00007ff8`2799aaa0 USER32!_fnHkINLPRECT
00007ff8`279a1188  00007ff8`2799aaf0 USER32!_fnHkOPTINLPEVENTMSG
00007ff8`279a1190  00007ff8`2794be00 USER32!__xxxClientCallDelegateThread
00007ff8`279a1198  00007ff8`279531c0 USER32!_xxxClientCallManipulationThread
00007ff8`279a11a0  00007ff8`2799b160 USER32!_fnKEYBOARDCORRECTIONCALLOUT
00007ff8`279a11a8  00007ff8`27944770 USER32!_fnOUTLPCOMBOBOXINFO
00007ff8`279a11b0  00007ff8`27943e50 USER32!_fnINLPWINDOWPOS
00007ff8`279a11b8  00007ff8`27949860 USER32!_xxxClientCallDevCallbackCapture
00007ff8`279a11c0  00007ff8`27933bd0 USER32!_xxxClientCallDitThread
00007ff8`279a11c8  00007ff8`27952770 USER32!_xxxClientEnableMMCSS
00007ff8`279a11d0  00007ff8`2799b730 USER32!_xxxClientUpdateDpi
00007ff8`279a11d8  00007ff8`279337d0 USER32!_xxxClientExpandStringW
00007ff8`279a11e0  00007ff8`2799a410 USER32!_ClientCopyDDEIn1
00007ff8`279a11e8  00007ff8`2799a480 USER32!_ClientCopyDDEIn2
00007ff8`279a11f0  00007ff8`2799a500 USER32!_ClientCopyDDEOut1
00007ff8`279a11f8  00007ff8`2799a540 USER32!_ClientCopyDDEOut2
00007ff8`279a1200  00007ff8`2793ba90 USER32!_ClientCopyImage
00007ff8`279a1208  00007ff8`27952850 USER32!__ClientEventCallback
00007ff8`279a1210  00007ff8`2799a590 USER32!_ClientFindMnemChar
00007ff8`279a1218  00007ff8`2799a5f0 USER32!_ClientFreeDDEHandle
00007ff8`279a1220  00007ff8`27947620 USER32!__ClientFreeLibrary
00007ff8`279a1228  00007ff8`27949940 USER32!_ClientGetCharsetInfo
00007ff8`279a1230  00007ff8`2799a630 USER32!_ClientGetDDEFlags
00007ff8`279a1238  00007ff8`2799a6a0 USER32!_ClientGetDDEHookData
00007ff8`279a1240  00007ff8`27947d10 USER32!_ClientGetListboxString
00007ff8`279a1248  00007ff8`27944650 USER32!_ClientGetMessageMPH
00007ff8`279a1250  00007ff8`27935400 USER32!__ClientLoadImage
00007ff8`279a1258  00007ff8`27933c30 USER32!_ClientLoadLibrary   *********InjectFunc*********
......

通常注入时使用的函数是USER32!_ClientLoadLibrary

InputBuffer

参数InputBuffer根据不同的调用,情况不同,但是通常由fix-length + variable-length来一起组成,对于_ClientLoadLibrary如下:

typedef struct _USERHOOK
{
DWORD      dwBufferSize;        // 整个结构的长度
DWORD      dwAdditionalData;    // variable-length部分的长度
DWORD      dwFixupsCount;        // 需要修正的个数
LPVOID     pbFree                // 指向variable-length末尾    
DWORD      offCbkPtrs;            // 修正表的偏移
DWORD      bFixed;                // 是否已修正
UNICODE_STRING lpDLLPath;         // 要加载的Dll名字
union
{
DWORD      lpfnNotify              // 要加载的函数相对于模块的RVA
UNICODE_STRING lpInitFunctionName; // 要加载的函数名,必须导出
}
DWORD      offCbk[2];              // 修正表
} _USERHOOK_s;

其中,dwBufferSizebFixed部分是fix部分,从lpDllPath到结尾是variable-length部分,具体含义参考注释。

具体如何申请该部分,本文不介绍,请参考win32k!ClientLoadLibrary,win32k!AllocCallbackMessag,win32k!CaptureCallbackData

具体修正过程,参考如下代码

void FixupCallbackPointers(_USERHOOK_s *pData)
{
    LPWORD offsetPointers;
    DWORD fixup;
    offsetPointers = (LPBYTE)pData + pData->offCbkPtrs;
    for(fixup=0;fixup < pData->dwFixupsCount;fixup++){
        pData[*offsetPointers] += (LPVOID)pData;
    offsetPointers++;
    }
}

OutputBuffer

在执行完成之后,会将结果拷贝到OutputBuffer中,针对USER32!_ClientLoadLibrary来说,其结构尚未完整,但是

typedef struct _LOAD_OUTPUT
{
    LPVOID lpBaseAddress;            // 加载的模块基址
    ...
} _LOAD_OUTPUT_s;

 

KeUserModeCallback过程分析

以下分析略去失败情况的代码,从上往下依次执行

mov     qword ptr [rsp+arg_18], r9
mov     [rsp+arg_10], r8d
mov     [rsp+Src], rdx
mov     [rsp+arg_0], ecx
push    rbx
push    rsi
push    rdi
push    r12
push    r13
push    r14
push    r15
sub     rsp, 60h
mov     edi, r8d                    ; 保存长度
mov     rbx, gs:188h                ; KPCRB->CurrentThread
mov     [rsp+98h+var_60], rbx
test    dword ptr [rbx+74h], 1000h      ; if KTHREAD->MicsFlag has CalloutActive flag
jnz     loc_1406070C4                ; must didn't has
mov     rax, cr8                    ; check irql
test    al, al
jnz     loc_1406070DF                ;
movzx   eax, byte ptr [rbx+24Ah]        ; KTHREAD->ApcStateIndex
test    al, al
jnz     loc_140607124                ; must be zero
cmp     dword ptr [rbx+1E4h], 0        ; KTHREAD->KernelApcDisable
jnz     loc_140607124                ; must be zero
inc     byte ptr [rbx+2DBh]            ; KTHREAD->CallbackNestingLevel
movzx   eax, byte ptr [rbx+2DBh]
cmp     al, 1Fh
ja      loc_140607102                ; 大于的的情况不考虑
mov     eax, [rbx+24Ch]                ; KTHREAD->IdealProcessor  number
lea     rcx, KiProcessorBlock
mov     rax, [rcx+rax*8]            ; 获取对应的KPRCB
mov     rcx, [rax+0C0h]                ; KPRCB->ParentNode
mov     r8, rbx
movzx   edx, word ptr [rcx+92h]        ; KNODE->NodeNumber
xor     ecx, ecx
call    MmCreateKernelStack            ; 创建一个新的Kernel Stack,其内实现不是本文重点
mov     r14, rax                   ; r14保存新的KernelStack base
mov     [rsp+98h+var_58], rax
test    rax, rax
jz      loc_140607114
mov     [rax-30h], rax                ; 将栈基址保存到-30处
mov     eax, cs:KeKernelStackSize
mov     rcx, r14
sub     rcx, rax                    ; 调整到栈底
mov     [r14-28h], rcx                 ; 保存
mov     rax, [rbx+38h]
mov     [r14-20h], rax                 ; 保存原来的内核栈基址
mov     rax, [rbx+30h]
mov     [r14-18h], rax                 ; 保存原来的stack limit
mov     rax, [rbx+28h]                 
mov     [r14-8], rax                 ; 保存原来的 InitialStack
mov     r15, [rbx+90h]                 ; KTHREAD->TrapFrame
mov     [rsp+98h+var_50], r15
mov     r12, [r15+180h]                 ; KTRAP_FRAME->Rsp
mov     [rsp+98h+var_48], r12
mov     r8d, edi        ; Size          ; 设置size
lea     rax, [rdi+0Fh]
and     rax, 0FFFFFFFFFFFFFFF0h
add     rax, 58h
mov     rsi, r12                     ; r12 是用户态Rsp
sub     rsi, rax
and     rsi, 0FFFFFFFFFFFFFFF0h           ; 调整rsi
test    rax, rax
jz      short loc_1404B695A
mov     rdx, rsi
test    sil, 0Fh
jnz     short loc_1404B6955
lea     rcx, [rax-1]
add     rcx, rsi
cmp     rsi, rcx
ja      short loc_1404B6935
mov     rax, 7FFFFFFF0000h
cmp     rcx, rax
jnb     short loc_1404B6935
and     rcx, 0FFFFFFFFFFFFF000h
add     rcx, 1000h
loc_1404B691B:
movzx   eax, byte ptr [rdx]
mov     [rdx], al
and     rdx, 0FFFFFFFFFFFFF000h
add     rdx, 1000h
cmp     rdx, rcx
jnz     short loc_1404B691B
jmp     short loc_1404B695A                ; 以上都是一些判断
loc_1404B695A:
lea     rdi, [rsi+58h]
mov     rdx, [rsp+98h+Src] ; Src
mov     rcx, rdi        ; Dst
call    memmove                          ; 将InputBuffer中的内容,拷贝到用户态地址空间去
mov     [rsi+20h], rdi                   ; 将新地址保存到rsi+20位置
mov     eax, [rsp+98h+arg_10]
mov     [rsi+28h], eax                   ; rsi+28h位置保存 长度
mov     eax, [rsp+98h+arg_0]
mov     [rsi+2Ch], eax                   ; 2c位置保存api number
mov     [rsi+48h], r12                   ; rsi+48保存用户态rsp值
mov     rax, [r15+168h]
mov     [rsi+30h], rax                   ; 将返回的rip保存至rsi+30处
jmp     short loc_1404B69A5
;   } // starts at 1404B68E4
loc_1404B69A5:
mov     [r15+180h], rsi                    ; 调整了KTRAP_FRAME->中的Rsp值
mov     r9, r14         ; int             ; r14新的KernelStakcBase
lea     r8, [r14-30h]   ; int             ; r14-30h处保存原来的一些栈值
mov     rdx, qword ptr [rsp+98h+arg_20] ; int ; 参数output length
mov     rcx, qword ptr [rsp+98h+arg_18] ; int ; 参数output buffer
call    KiCallUserMode                    ; 核心调用
mov     edi, eax
mov     [rsp+98h+var_64], eax

 

KiCallUserMode分析

sub     rsp, 138h
lea     rax, [rsp+138h+var_38]
movaps  [rsp+138h+var_108], xmm6
movaps  [rsp+138h+var_F8], xmm7
movaps  [rsp+138h+var_E8], xmm8
movaps  [rsp+138h+var_D8], xmm9
movaps  [rsp+138h+var_C8], xmm10
movaps  xmmword ptr [rax-80h], xmm11
movaps  xmmword ptr [rax-70h], xmm12
movaps  xmmword ptr [rax-60h], xmm13
movaps  xmmword ptr [rax-50h], xmm14
movaps  xmmword ptr [rax-40h], xmm15    ; 从-108 ---> -78 阶段保存所有xmm值
mov     [rax-8], rbp                ; 从 -40处开始保存
mov     rbp, rsp                    ; 赋值rbp
mov     [rax], rbx                    
mov     [rax+8], rdi
mov     [rax+10h], rsi
mov     [rax+18h], r12
mov     [rax+20h], r13
mov     [rax+28h], r14
mov     [rax+30h], r15
xor     eax, eax
xor     r10, r10
xor     r12, r12
xor     r13, r13
xor     r14, r14
xor     r15, r15
mov     [rbp+0D8h], rcx                ; -60保存 rcx
mov     [rbp+0E0h], rdx                ; -58保存 rdx
mov     rbx, gs:188h                ; 线程
mov     [r8+20h], rsp                ; 又往上个函数的栈信息保存区域保存了一个rsp
xor     edx, edx
mov     rsi, [rbx+90h]
mov     [rbp+0D0h], rsi                ; -68 保存了当前TrapFrame
cli                                  ; 禁用中断
mov     rdi, gs:8                    ; 获取当前KPCR->StackBase
mov     [rbx+28h], r8                ; 开始修改当前线程的KTHREAD->InitStack
mov     [rbx+38h], r9                ; 修改KTHREAD->StackBase
mov     [rdi+4], r8                    ; 在这也保存了一份
mov     ecx, cs:KeKernelStackSize
sub     r9, rcx                        ; 调整到栈底
mov     gs:1A8h, r8                    ; 更新KPRCB->Rspbase
mov     [rbx+30h], r9                ; 修改KTHREAD->StackLimit        
lea     rsp, [r8-190h]                ; 此处调整了rsp
mov     rdi, rsp
mov     ecx, 32h
rep movsq                            ; 拷贝了TrapFrame信息过来
xor     edi, edi
cmp     byte ptr [rbx+0C2h], 0          ; KTHREAD->ApcState->UserApcPending
jnz     loc_14016EDA0                ; 则表示有UserApcPending

通过KiSystemServiceExit返回

loc_14016EDA0:
lea     rbp, [rsp+arg_78]
mov     word ptr [rbp+0F0h], 33h        ; 设置新栈的TrapFrame中的SegCs
mov     rax, cs:KeUserCallbackDispatcher ; 保存的是 ntdll!KiUserCallbackDispatch
mov     [rbp+0E8h], rax                   ; 调整新的RIP
lea     rcx, KiSystemServiceExit
jmp     rcx

通过Sysret返回

test    dword ptr [rbx], 48010000h
jnz     short loc_14016EDA0
lea     rbp, [rsi-110h]                    ; rsi此刻依然是KTRAP_FRAME地址
ldmxcsr dword ptr [rbp-54h]                ; 从TrapFrame中恢复MXCSR
xor     esi, esi
test    byte ptr [rbx+3], 3
jnz     short loc_14016ED7
返回
mov     rcx, cs:KeUserCallbackDispatcher
loc_14016ED41:
mov     r8, [rbp+100h]                    ; 从TrapFrame中获取Rsp,注意此Rsp已经发生改变
mov     r9, [rbp+0D8h]                    ; 从TrapFrame中获取Rbp
pxor    xmm0, xmm0
pxor    xmm1, xmm1
pxor    xmm2, xmm2
pxor    xmm3, xmm3
pxor    xmm4, xmm4
pxor    xmm5, xmm5
mov     r11, [rbp+0F8h]                    ; 从TrapFrame中获取Eflags
xor     ebx, ebx
mov     rbp, r9
mov     rsp, r8
swapgs
sysret
恢复DR寄存器(可略过)
loc_14016ED7C:
call    KiRestoreDebugRegisterState
mov     r10, cs:KeUserCallbackDispatcher
mov     rcx, [rbx+0B8h]            KTHREAD->ApcState->Process
mov     rcx, [rcx+2C8h]            KPROCESS->InstrumentationCallback
or      rcx, rcx
jnz     short loc_14016ED41
xchg    rcx, r10
jmp     short loc_14016ED41

 

KiUserCallbackDispatch

.text:00000001800A9090 arg_18          = qword ptr  20h
.text:00000001800A9090 arg_20          = dword ptr  28h
.text:00000001800A9090 arg_24          = dword ptr  2Ch
.text:00000001800A9090                 mov     rcx, [rsp+arg_18]
.text:00000001800A9095                 mov     edx, [rsp+arg_20]
.text:00000001800A9099                 mov     r8d, [rsp+arg_24]
.text:00000001800A909E                 mov     rax, gs:60h        ; 获取peb    
.text:00000001800A90A7                 mov     r9, [rax+58h]    ; 获取KernelCallbackTable
.text:00000001800A90AB                 mov     rax, [r9+r8*8]    ; 根据index获取对应的函数
.text:00000001800A90AF                 call    KiUserCallForwarder
.text:00000001800A90B4
.text:00000001800A90B4 KiUserCallbackDispatcherContinue:       ; DATA XREF: KiUserCallbackDispatcherHandler+49↑o
.text:00000001800A90B4                 xor     ecx, ecx
.text:00000001800A90B6                 xor     edx, edx
.text:00000001800A90B8                 mov     r8d, eax
.text:00000001800A90BB                 call    ZwCallbackReturn
.text:00000001800A90C0                 mov     esi, eax
.text:00000001800A90C2
.text:00000001800A90C2 loc_1800A90C2:                          ; CODE XREF: KiUserCallbackDispatcher+39↓j
.text:00000001800A90C2                 mov     ecx, esi
.text:00000001800A90C4                 call    RtlRaiseStatus
.text:00000001800A90C9 ; ---------------------------------------------------------------------------
.text:00000001800A90C9                 jmp     short loc_1800A90C2
.text:00000001800A8F30 KiUserCallForwarder proc near           ; CODE XREF: KiUserApcDispatcher+29↓p
.text:00000001800A8F30 var_28          = qword ptr -28h
.text:00000001800A8F30 var_20          = qword ptr -20h
.text:00000001800A8F30 var_18          = qword ptr -18h
.text:00000001800A8F30 var_10          = qword ptr -10h
.text:00000001800A8F30
.text:00000001800A8F30                 sub     rsp, 48h
.text:00000001800A8F34                 mov     [rsp+48h+var_28], rcx
.text:00000001800A8F39                 mov     [rsp+48h+var_20], rdx
.text:00000001800A8F3E                 mov     [rsp+48h+var_18], r8
.text:00000001800A8F43                 mov     [rsp+48h+var_10], r9
.text:00000001800A8F48                 xchg    rax, rcx
.text:00000001800A8F4A                 call    cs:__guard_check_icall_fptr
.text:00000001800A8F50                 xchg    rax, rcx
.text:00000001800A8F52                 mov     rcx, [rsp+48h+var_28]
.text:00000001800A8F57                 mov     rdx, [rsp+48h+var_20]
.text:00000001800A8F5C                 mov     r8, [rsp+48h+var_18]
.text:00000001800A8F61                 mov     r9, [rsp+48h+var_10]
.text:00000001800A8F66                 add     rsp, 48h
.text:00000001800A8F6A                 jmp     rax                ; 跳转到对应的函数执行
.text:00000001800A8F6A KiUserCallForwarder endp

 

NtCallbackReturn

text:000000014016EED0 NtCallbackReturn proc near              ; DATA XREF: .pdata:000000014039EE80↓o
.text:000000014016EED0
.text:000000014016EED0 arg_28          = xmmword ptr  30h
.text:000000014016EED0 arg_38          = xmmword ptr  40h
.text:000000014016EED0 arg_48          = xmmword ptr  50h
.text:000000014016EED0 arg_58          = xmmword ptr  60h
.text:000000014016EED0 arg_68          = xmmword ptr  70h
.text:000000014016EED0 arg_F8          = byte ptr  100h
.text:000000014016EED0
.text:000000014016EED0                 mov     r11, gs:188h
.text:000000014016EED9                 mov     r10, [r11+28h]  ; KTHREAD->InitialStack
.text:000000014016EEDD                 mov     r9, [r10+20h]   ; 获取出当时保存的Context地址
.text:000000014016EEE1                 test    r9, r9
.text:000000014016EEE4                 jz      loc_14016F008
.text:000000014016EEEA                 mov     eax, r8d
.text:000000014016EEED                 mov     rbx, [r9+0D8h]  ; 获取OutputBuffer地址
.text:000000014016EEF4                 mov     [rbx], rcx      ; 保存
.text:000000014016EEF7                 mov     rbx, [r9+0E0h]  ; 获取OutputBufferLen地址
.text:000000014016EEFE                 mov     [rbx], edx
.text:000000014016EF00                 cli
.text:000000014016EF01                 mov     r8, [r9+0D0h]   ; 获取原始的TrapFrame
.text:000000014016EF08                 mov     rbx, [r11+90h]  ; 当前的KTRAP_FRAME
.text:000000014016EF0F                 mov     [r11+90h], r8   ; 恢复原来的KTrapFrame
.text:000000014016EF16                 test    byte ptr [r11+3], 3
.text:000000014016EF1B                 mov     word ptr [r8+100h], 0 ; DR7
.text:000000014016EF25                 jz      short loc_14016EF6D ; OrgStackBase
.text:000000014016EF27                 mov     rcx, [rbx+0D8h] ; DR0
.text:000000014016EF2E                 mov     rdx, [rbx+0E0h] ; DR1
.text:000000014016EF35                 mov     [r8+0D8h], rcx
.text:000000014016EF3C                 mov     [r8+0E0h], rdx
.text:000000014016EF43                 mov     rcx, [rbx+0E8h] ; DR2
.text:000000014016EF4A                 mov     rdx, [rbx+0F0h] ; DR3
.text:000000014016EF51                 mov     [r8+0E8h], rcx
.text:000000014016EF58                 mov     [r8+0F0h], rdx
.text:000000014016EF5F                 mov     rcx, [rbx+100h]
.text:000000014016EF66                 mov     [r8+100h], rcx  ; DR7
.text:000000014016EF6D
.text:000000014016EF6D loc_14016EF6D:                          ; CODE XREF: NtCallbackReturn+55↑j
.text:000000014016EF6D                 mov     rcx, [r10+10h]  ; OrgStackBase
.text:000000014016EF71                 mov     [r11+38h], rcx
.text:000000014016EF75                 mov     edx, cs:KeKernelStackSize
.text:000000014016EF7B                 sub     rcx, rdx
.text:000000014016EF7E                 mov     [r11+30h], rcx  ; StackLimit
.text:000000014016EF82                 mov     rcx, [r10+28h]  ; OrgInitialStack
.text:000000014016EF86                 mov     [r11+28h], rcx
.text:000000014016EF8A                 mov     r8, gs:8
.text:000000014016EF93                 mov     [r8+4], rcx     ; 设置TSS->Rsp0
.text:000000014016EF97                 mov     gs:1A8h, rcx    ; KPRCB->RspBase
.text:000000014016EFA0                 mov     rsp, r9
.text:000000014016EFA3                 lea     rcx, [rsp+arg_F8]
.text:000000014016EFAB                 movaps  xmm6, [rsp+arg_28]
.text:000000014016EFB0                 movaps  xmm7, [rsp+arg_38]
.text:000000014016EFB5                 movaps  xmm8, [rsp+arg_48]
.text:000000014016EFBB                 movaps  xmm9, [rsp+arg_58]
.text:000000014016EFC1                 movaps  xmm10, [rsp+arg_68]
.text:000000014016EFC7                 movaps  xmm11, xmmword ptr [rcx-80h]
.text:000000014016EFCC                 movaps  xmm12, xmmword ptr [rcx-70h]
.text:000000014016EFD1                 movaps  xmm13, xmmword ptr [rcx-60h]
.text:000000014016EFD6                 movaps  xmm14, xmmword ptr [rcx-50h]
.text:000000014016EFDB                 movaps  xmm15, xmmword ptr [rcx-40h]
.text:000000014016EFE0                 mov     rbx, [rcx]
.text:000000014016EFE3                 mov     rdi, [rcx+8]
.text:000000014016EFE7                 mov     rsi, [rcx+10h]
.text:000000014016EFEB                 mov     r12, [rcx+18h]
.text:000000014016EFEF                 mov     r13, [rcx+20h]
.text:000000014016EFF3                 mov     r14, [rcx+28h]
.text:000000014016EFF7                 mov     r15, [rcx+30h]
.text:000000014016EFFB                 mov     rbp, [rcx-8]
.text:000000014016EFFF                 add     rsp, 138h       ; 恢复当时保存的值,继续执行
.text:000000014016F006                 sti
.text:000000014016F007                 retn
.text:000000014016F008 ; ---------------------------------------------------------------------------
.text:000000014016F008
.text:000000014016F008 loc_14016F008:                          ; CODE XREF: NtCallbackReturn+14↑j
.text:000000014016F008                 mov     eax, 0C0000258h
.text:000000014016F00D                 retn

 

总结及关键结构

  1. 首先从当前的TrapFrame中获取用户态栈地址,然后将InputBuffer中的内容拷贝到用户态,同时将参数信息等依次填入到用户态栈空间中,最后调整栈指针,更新TrapFrame中的用户态栈地址,这样使返回到用户态后,可以直接获取参数信息等。
  2. 从内核返回到用户态,支持两种返回,一种是构建KTrapFrame,然后跳转到KiSystemServiceExit返回,一种是通过sysret直接返回。
  3. 返回到用户态后,从已经修改的用户态栈地址空间获取要调用的ApiIndex,从PEB的KernelCallbackTable地址获取要执行的Callback,再进行了CFG检查后,进行最终的调用。
  4. 调用完成后,直接使用ZwCallbackReturn函数进行返回,函数本身并没有什么特殊动作,将KTHREAD的状态,依次恢复,包括TrapFrame, InitialStackTss中的Rsp0等。

如下结构是新申请的内核栈中,非常关键,用以保存原始信息的一个结构,详见具体分析:

typedef struct _INIT_STRUC{
    ULONG_PTR   NewStackBase;                // 0x0
    ULONG_PTR    NewStackBottom;                // 0x8
    ULONG_PTR     OrgStackBase;                // 0x10
    ULONG_PTR     OrgStackLimit;                // 0x18
    ULONG_PTR    SavedContextFrame;            // 0x20
    ULONG_PTR    OrgInitialStack;            // 0x28
}INIT_STRUC, *PINIT_STRUC;

 

其他

就该方案来说,攻击点可以是:

  1. ApiIndex 大小是可控的,也就是说,可以利用该ApiIndex来溢出到你的Shellcode地址,但是请注意ApiIndex的长度限制,sizeof(ULONG)。
  2. 整个过程并不是很复杂,完全可以定义一套自身的KeUserModeCallback机制,这样,返回到用户态的地址完全可以替换为Shellcode地址,而参数信息,都保存在自申请的KernelStack中,同样可以利用NtCallbackReturn来返回。

当然,由于执行者本身就处于Ring0级别,即一切都可控制。

 

参考

How to run userland code from the kernel on Windows

(完)