这里主要对KiFastCallEntry函数进行详细解释。但详细解释前需要对该函数的用处进行介绍。该函数主要用在ring3到ring0的特权级变换,为进入和返回内核模式做准备。我们最常见到的ring3到ring0的特权级变换就是应用程序调用内核服务。为了更好的讲解KiFastCallEntry,我先为大家简单介绍特权变换的过程。
实现外层(ring3)到内层(ring0)的途径是:使用段间调用指令call,通过调用门进行转移。当call指令后面的地址的选择子指向调用门描述符时,系统会主动放弃地址中的偏移,而把调用门内的48位全指针作为目标地址。当然取出调用门中的48位全指针全需要进行特权级检测。检测通过后开始使用KiFastCallEntry函数做进入内核模式全的准备工作,主要就是堆栈的切换。系统要求不同的级别有不同级别的栈,所以,进入内核模式时需要使用内核模式的栈。每一个线程都拥有自己的任务状态断,任务状态段中保存了创建非ring3级栈所需要的信息。所以准备工作中需要获取任务状态段TSS,使用其中的信息初始化堆栈段寄存器SS及ESP寄存器,从而创建出空栈。建立内层堆栈后,首先把外层堆栈指针SS和ESP等一些寄存器的值压入内层堆栈;然后从外层堆栈复制参数到内层栈,调用门中的DCOUNT字段决定复制的个数;之后调用的返回地址压入栈中,以便调用结束时返回。这些信息也叫做保存上下文。系统定义_KTRAP_FRAME结构体,在KiFastCallEntry函数中保存当前上下文。
下面来详细分析。
nt!KiFastCallEntry: //汇编代码为函数过程
主要功能有:
初始化系统堆栈
将上下文压入系统堆栈,压栈顺序见KTrap_Frame内核堆栈框架
将参数从用户模式堆栈复制到内核堆栈,以上步骤在关中断下进行的,应为要用到
KUser_Shared_Data
确定服务例程的入口地址,包括使用那个服务表,是Shadow SSDT还是SSDT
转入服务例程入口
等等
/*
当前线程在内核模式下执行时的数据段选择子为23h;fs寄存器在用户模式指向线程环境块(TEB),选择子为18h,在内核模式下指向处理器控制区(KPCR),选择子为30h。所以函数最初便初始化ds、fs,用于查询KPCR的地址。
*/
804de6f0 mov ecx,23h
804de6f5 push 30h
804de6f7 pop fs
804de6f9 mov ds,cx
804de6fb mov es,cx
/*
KPCR结构体:
typedef struct _KPCR {
union {
NT_TIB NtTib;
struct {
struct _EXCEPTION_REGISTRATION_RECORD *Used_ExceptionList;
PVOID Used_StackBase;
PVOID PerfGlobalGroupMask;
PVOID TssCopy;
ULONG ContextSwitches;
KAFFINITY SetMemberCopy;
PVOID Used_Self;
};
};
struct _KPCR *SelfPcr; // flat address of this PCR
struct _KPRCB *Prcb; // pointer to Prcb
KIRQL Irql;
ULONG IRR;
ULONG IrrActive;
ULONG IDR;
PVOID KdVersionBlock;
struct _KIDTENTRY *IDT;
struct _KGDTENTRY *GDT;
struct _KTSS *TSS;
USHORT MajorVersion;
USHORT MinorVersion;
KAFFINITY SetMember;
ULONG StallScaleFactor;
UCHAR SpareUnused;
UCHAR Number;
UCHAR Spare0;
UCHAR SecondLevelCacheAssociativity;
ULONG VdmAlert;
ULONG KernelReserved[14]; // For use by the kernel
ULONG SecondLevelCacheSize;
ULONG HalReserved[16]; // For use by Hal
ULONG InterruptMode;
UCHAR Spare1;
ULONG KernelReserved2[17];
struct _KPRCB PrcbData;
} KPCR, *PKPCR;
初始化后fs的值指向kpcr,KPCR的结构值偏移40h为任务状态段的内存指针。windows系统中不同级别的运行环境有不同级别的堆栈,所以ring3转到ring0后,系统需要重新为线程创建堆栈。非ring3级别的堆并不是所编创建的,创建这些堆的信息保存在当前线程所属的任务状态段中。
任务状态段结构:
BIT31—BIT16 BIT15—BIT1 BIT0 Offset
0000000000000000 链接字段 0
ESP0 4
0000000000000000 SS0 8
ESP1 0CH
0000000000000000 SS1 10H
ESP2 14H
0000000000000000 SS2 18H
CR3 1CH
EIP 20H
EFLAGS 24H
EAX 28H
ECX 2CH
EDX 30H
EBX 34H
ESP 38H
EBP 3CH
ESI 40H
EDI 44H
0000000000000000 ES 48H
0000000000000000 CS 4CH
0000000000000000 SS 50H
0000000000000000 DS 54H
0000000000000000 FS 58H
0000000000000000 GS 5CH
0000000000000000 LDTR 60H
I/O许可位图偏移 000000000000000 T 64H
在ring3到ring0 的任务人特权级变换中能用到大部分只有第二个四字节
dword ptr fs:[40h] == dword ptr ds:[0FFDFF040h]{TSS}
*/
804de6fd mov ecx,dword ptr ds:[0FFDFF040h]
804de703 mov esp,dword ptr [ecx+4]
/*
任务状态段TSS中的栈顶指针esp0其实指向_KTRAP_FRAME结构中的一个成员。
_KTRAP_FRAME结构:
dbgebp -
.......
HardwareSegSs
V86Es <----------------------esp0
...... +
所以现在esp指向_KTRAP_FRAME的V86Es。然后我们通过push指令依次向V86Es后面的成员赋值来保存当前上下文。
*/
804de706 push 23h
804de708 push edx
804de709 pushfd
804de70a push 2
804de70c add edx,8
804de70f popfd
804de710 or byte ptr [esp+1],2
804de715 push 1Bh
804de717 push dword ptr ds:[0FFDF0304h]
804de71d push 0
804de71f push ebp
804de720 push ebx
804de721 push esi
804de722 push edi
804de723 mov ebx,dword ptr ds:[0FFDFF01Ch]
804de729 push 3Bh
804de72b mov esi,dword ptr [ebx+124h]
804de731 push dword ptr [ebx]
804de733 mov dword ptr [ebx],0FFFFFFFFh
804de739 mov ebp,dword ptr [esi+18h]
804de73c push 1
804de73e sub esp,48h
804de741 sub ebp,29Ch
804de747 mov byte ptr [esi+140h],1
804de74e cmp ebp,esp
804de750 jne nt!KiFastCallEntry2+0x24 (804de6c8)
804de756 and dword ptr [ebp+2Ch],0
/*
这里判断当前是不是调试状态,如果是则为KTHREAD.DebugActive赋值
*/
804de75a test byte ptr [esi+2Ch],0FFh
804de75e mov dword ptr [esi+134h],ebp
804de764 jne nt!Dr_FastCallDrSave (804de5b0)
804de76a mov ebx,dword ptr [ebp+60h]
804de76d mov edi,dword ptr [ebp+68h]
804de770 mov dword ptr [ebp+0Ch],edx
804de773 mov dword ptr [ebp+8],0BADB0D00h
804de77a mov dword ptr [ebp],ebx
804de77d mov dword ptr [ebp+4],edi
//通过服务编号转向需要的服务
804de780 sti
804de781 mov edi,eax
804de783 shr edi,8
804de786 and edi,30h
804de789 mov ecx,edi
804de78b add edi,dword ptr [esi+0E0h]
804de791 mov ebx,eax
804de793 and eax,0FFFh
804de798 cmp eax,dword ptr [edi+8]
804de79b jae nt!KiBBTUnexpectedRange (804de4e2)
//判断函数指针是在SSDT中还是在shadow ssdt
804de7a1 cmp ecx,10h
804de7a4 jne nt!KiFastCallEntry+0xcc (804de7c0)
804de7a6 mov ecx,dword ptr ds:[0FFDFF018h]
804de7ac xor ebx,ebx
804de7ae or ebx,dword ptr [ecx+0F70h]
804de7b4 je nt!KiFastCallEntry+0xcc (804de7c0)
804de7b6 push edx
804de7b7 push eax
804de7b8 call dword ptr [nt!KeGdiFlushUserBatch (8055a164)]
804de7be pop eax
804de7bf pop edx
804de7c0 inc dword ptr ds:[0FFDFF638h]
804de7c6 mov esi,edx
804de7c8 mov ebx,dword ptr [edi+0Ch]
804de7cb xor ecx,ecx
804de7cd mov cl,byte ptr [eax+ebx]
804de7d0 mov edi,dword ptr [edi]
804de7d2 mov ebx,dword ptr [edi+eax*4]
804de7d5 sub esp,ecx
804de7d7 shr ecx,2
804de7da mov edi,esp
804de7dc cmp esi,dword ptr ds:[0EED02CF4h]
804de7e2 jae nt!KiSystemCallExit2+0x9f (804de990)
804de7e8 rep movs dword ptr es:[edi],dword ptr [esi]
804de7ea call ebx
804de7ec mov esp,ebp
804de7ee mov ecx,dword ptr ds:[0FFDFF124h]
804de7f4 mov edx,dword ptr [ebp+3Ch]
804de7f7 mov dword ptr [ecx+134h],edx
。。。。。。。。。
804de904 sysexit
先这么多吧、有事要走,回头再补。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!