系统调用
- 在用户层与内核层分别定义了一个_KUSER_SHARED_DATA结构体,用于用户层与内核层共享某些数据
- 用户层地址为:0x7ffe0000
- 内核层地址为:0xffdf0000
- 虽然指向同一个物理页,用户层是只读的,内核层是可写的
- 所有应用程序共享的,一改全部都会改
- 如果调用函数,会调用0x7ffe0300是kifastsystemcall函数
- 0xffdf0000是KPCR
kifastsystemcall函数
EAX放入调用号,edx是栈顶。MSR寄存器174-CS、175-ESP、176-EIP,SS是CS值+8,如果是以int 2E方式进入0环,里面调用KISTSTEMSERVICE如果是stsenter方式进入0环,会调用KIFASTCALLENTRY函数
KiFastCallEntry
进入0环
eax: 调用号
edx: 用户栈
[edx +0x00 ] : 返回地址1
[edx +0x04 ] : 返回地址2
[edx +0x08 ] : 参数1
[edx +0x0c ] : 参数2
[edx +0x10 ] : 参数3
nt!KiFastCallEntry:
83e810c0 b923000000 mov ecx,23h
83e810c5 6a30 push 30h
83e810c7 0fa1 pop fs ; 内核FS保存一个_KPRC结构体
;加载内核段选择子
83e810c9 8ed9 mov ds,cx
83e810cb 8ec1 mov es,cx
83e810cd 648b0d40000000 mov ecx,dword ptr fs:[40h] ;KPCR->TSS
83e810d4 8b6104 mov esp,dword ptr [ecx+4] ;TSS->ESP0 ;加载零环ESP
83e810d7 6a23 push 23h ;压入三环 SS
83e810d9 52 push edx ;压入三环 esp
83e810da 9c pushfd ;压入三环 EFLAGS
83e810db 6a02 push 2
83e810dd 83c208 add edx,8 ;将edx指向用户层参数
83e810e0 9d popfd ;EFLAGS = 2 ,清空EFLAGS
83e810e1 804c240102 or byte ptr [esp+1],2 ; 栈上EFLAGS->IF位置1, 开启中断
83e810e6 6a1b push 1Bh ;三环用户层的CS, TRAP_FRAM->CS
83e810e8 ff350403dfff push dword ptr ds:[0FFDF0304h] ; 返回用户层EIP
83e810ee 6a00 push 0 ; 错误码
83e810f0 55 push ebp ; 用户层的ebp
83e810f1 53 push ebx
83e810f2 56 push esi
83e810f3 57 push edi ;用户层的edi
83e810f4 648b1d1c000000 mov ebx,dword ptr fs:[1Ch] ;FS[1Ch]->自己 EBX保存KPCR
83e810fb 6a3b push 3Bh ;用户层FS寄存器
83e810fd 8bb324010000 mov esi,dword ptr [ebx+124h] ;esi->ETHREAD 结构体
83e81103 ff33 push dword ptr [ebx] ;TRAP_FRAME->Exceptiolist
83e81105 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh ; 异常链第一个节点-1
83e8110b 8b6e28 mov ebp,dword ptr [esi+28h] ; 内核栈底
83e8110e 6a01 push 1 ;先前模式
83e81110 83ec48 sub esp,48h ;构建好TRAP_FRAME
83e81113 81ed9c020000 sub ebp,29Ch
83e81119 c6863a01000001 mov byte ptr [esi+13Ah],1 ;保存ETHREAD->PreviousMode = 1
83e81120 3bec cmp ebp,esp ;是否是V86模式
83e81122 7597 jne nt!KiFastCallEntry2+0x49 (83e810bb)
;ebp和Esp都指向陷阱帧
83e81124 83652c00 and dword ptr [ebp+2Ch],0 ;DR7 = 0
83e81128 f64603df test byte ptr [esi+3],0DFh ;用于检查是否处于调试模式
83e8112c 89ae28010000 mov dword ptr [esi+128h],ebp ;将陷阱帧保存到 ETHREAD->Trap_frame
83e81132 0f8538feffff jne nt!Dr_FastCallDrSave (83e80f70) ; 如果是调试模式,就保存DR寄存器
83e81138 8b5d60 mov ebx,dword ptr [ebp+60h] ;用户ebp
83e8113b 8b7d68 mov edi,dword ptr [ebp+68h] ;用户eip
83e8113e 89550c mov dword ptr [ebp+0Ch],edx ;Trap_frame->参数指针 = 用户层参数
83e81141 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h ;参数掩码
83e81148 895d00 mov dword ptr [ebp],ebx ;Trap_frame->dbgebp = 用户层ebp
83e8114b 897d04 mov dword ptr [ebp+4],edi ;Trap_frame->dbgeip = 用户层eip
83e8114e fb sti ;开中断
;-------------------------------上面都是保存陷阱帧
;-------------------调用内核中API SSDT 表中的函数
83e8114f 8bf8 mov edi,eax ;edi保存调用号
83e81151 c1ef08 shr edi,8
83e81154 83e710 and edi,10h ;判断使用哪个SSDT (ShadowSSDT)
83e81157 8bcf mov ecx,edi ;ecx表示使用哪个表有效
83e81159 03bebc000000 add edi,dword ptr [esi+0BCh] ; 找到ETHREAD->ServiceTable 找到真正SSDT表
83e8115f 8bd8 mov ebx,eax
83e81161 25ff0f0000 and eax,0FFFh ;eax保存真实调用号
83e81166 3b4708 cmp eax,dword ptr [edi+8] ;必须小于 SSDT服务表最大支持的函数个数
83e81169 0f8333fdffff jae nt!KiBBTUnexpectedRange (83e80ea2) ;大于表示调用号无效
83e8116f 83f910 cmp ecx,10h ; 表示ecx表示使用哪个表有效
83e81172 751a jne nt!KiFastCallEntry+0xce (83e8118e) ;SSDT 有效
83e81174 8b8e88000000 mov ecx,dword ptr [esi+88h] ;ShadowSSDT有效
83e8117a 33f6 xor esi,esi
83e8117c 0bb1700f0000 or esi,dword ptr [ecx+0F70h]
83e81182 740a je nt!KiFastCallEntry+0xce (83e8118e)
83e81184 52 push edx
83e81185 50 push eax
83e81186 ff154cd9fa83 call dword ptr [nt!KeGdiFlushUserBatch (83fad94c)]
83e8118c 58 pop eax
83e8118d 5a pop edx
83e8118e 64ff05b0060000 inc dword ptr fs:[6B0h] ; KPCR->KeSystemCalls 调用次数+1
83e81195 8bf2 mov esi,edx ;edx ->用户参数
83e81197 33c9 xor ecx,ecx
;edx指向参数表
83e81199 8b570c mov edx,dword ptr [edi+0Ch] ;SSDT->ParamTableBase
;edi指向函数基址表
83e8119c 8b3f mov edi,dword ptr [edi] ;SSDT->ServiceTableBase
83e8119e 8a0c10 mov cl,byte ptr [eax+edx] ; ecx保存这个函数所需要的参数个数
83e811a1 8b1487 mov edx,dword ptr [edi+eax*4] ;edx保存真实函数地址(NtCreateProcess)
83e811a4 2be1 sub esp,ecx ;内核栈开辟一段空间用于存放函数参数
83e811a6 c1e902 shr ecx,2 ;参数所需字节/4,表示需要几个参数
83e811a9 8bfc mov edi,esp ;esi->用户参数,edi->内核参数
83e811ab 3b351cd7fa83 cmp esi,dword ptr [nt!MmUserProbeAddress (83fad71c)]
83e811b1 0f832e020000 jae nt!KiSystemCallExit2+0xa5 (83e813e5)
83e811b7 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ;用户栈上的参数拷贝内核栈上
83e811b9 f6456c01 test byte ptr [ebp+6Ch],1 ;调用来自用户层CS
83e811bd 7416 je nt!KiFastCallEntry+0x115 (83e811d5)
83e811bf 648b0d24010000 mov ecx,dword ptr fs:[124h]
83e811c6 8b3c24 mov edi,dword ptr [esp]
83e811c9 89993c010000 mov dword ptr [ecx+13Ch],ebx
83e811cf 89b92c010000 mov dword ptr [ecx+12Ch],edi
83e811d5 8bda mov ebx,edx ;ebx保存真实函数地址
83e811d7 f60508a9f78340 test byte ptr [nt!PerfGlobalGroupMask+0x8 (83f7a908)],40h
83e811de 0f954512 setne byte ptr [ebp+12h]
83e811e2 0f858c030000 jne nt!KiServiceExit2+0x17b (83e81574)
; esp 保存用户参数
; edx 保存真实函数地址
; eax 保存调用号
; ecx 保存参数个数
83e811e8 ffd3 call ebx ;调用真实函数 (NtCreateProcess)
SystemServiceTable系统服务表
SystemServiceTable分为两张,一种函数在Ntoskrl.exe中,一种函数Win32k.sys中,Win32k.sys是页面图形相关的,两张表中都有4个字段
ServiceTable: 函数地址表
count
servicelimit: 函数地址表多大(函数个数)
ArgmentTable:函数参数表-记录了参数多少个字节,以4字节对齐
Ntoskrl.exe中函数个数小于0x1000
- Ntoskrl.exe的SSDT可以通过KeServiceDescriptorTable全局变量找到
- Win32k.sys的SSDT可以通过未导出全局变量KeServiceDescriptorTableShadow的第二项找到,第一项为Ntoskrl.exe的SSDT,这一项只有GDI(界面)程序才有
// KeServiceDescriptorTable变量,声明就可以直接使用
typedef struct _ServiceDesriptorEntry
{
ULONG* ServiceTableBase; //服务表基址
ULONG* ServiceCounterTableBase; //函数表中每个函数被调用的次数
ULONG NumberOfService; //服务函数的个数,
ULONG ParamTableBase; //参数表基址
}SSDTEntry, * PSSDTEntry;
// 导入SSDT全局变量
NTSYSAPI SSDTEntry KeServiceDescriptorTable;