介绍
KeUserModeCallback
经常在内核中被用来执行用户态代码,一直没时间分析其过程,今天花点时间过一下。过之前,先介绍下KeUserModeCallback
是什么以及它是执行用户态代码的。
本次分析在Win10 1703 x64版本上进行:
函数声明:
非文档化,但是导出,因此使用前需要MmGetSystemRoutineAddress
KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
IN PULONG OutputLength
)
执行限制
但是KeUserModeCallback过程中会有一些限制,如下过程梳理中会有遇到:
- 检查线程的
KTHREAD->MicsFlag
中是否有CalloutActive
Flag - 检查当前Irql,必须是PASSIVE_LEVEL。
- 检查
KTHREAD->ApcStateIndex
,必须是0,即不能使注入到其它进程的线程,这个是其它话题,此文不提 - 检查
KTHREAD->KernelApcDisable
,必须是False. - 检查
KTHREAD->CallbackNestingLevel
不能大于0x1f,深度限制。
ApiNumber
如上参数中,ApiNumber
是PEB->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;
其中,dwBufferSize
到bFixed
部分是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
总结及关键结构
- 首先从当前的
TrapFrame
中获取用户态栈地址,然后将InputBuffer
中的内容拷贝到用户态,同时将参数信息等依次填入到用户态栈空间中,最后调整栈指针,更新TrapFrame
中的用户态栈地址,这样使返回到用户态后,可以直接获取参数信息等。 - 从内核返回到用户态,支持两种返回,一种是构建
KTrapFrame
,然后跳转到KiSystemServiceExit
返回,一种是通过sysret
直接返回。 - 返回到用户态后,从已经修改的用户态栈地址空间获取要调用的
ApiIndex
,从PEB的KernelCallbackTable
地址获取要执行的Callback,再进行了CFG检查后,进行最终的调用。 - 调用完成后,直接使用
ZwCallbackReturn
函数进行返回,函数本身并没有什么特殊动作,将KTHREAD
的状态,依次恢复,包括TrapFrame
,InitialStack
,Tss
中的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;
其他
就该方案来说,攻击点可以是:
- ApiIndex 大小是可控的,也就是说,可以利用该ApiIndex来溢出到你的Shellcode地址,但是请注意ApiIndex的长度限制,sizeof(ULONG)。
- 整个过程并不是很复杂,完全可以定义一套自身的KeUserModeCallback机制,这样,返回到用户态的地址完全可以替换为Shellcode地址,而参数信息,都保存在自申请的KernelStack中,同样可以利用NtCallbackReturn来返回。
当然,由于执行者本身就处于Ring0级别,即一切都可控制。