首页
社区
课程
招聘
[原创]KiFastCallEntry详解
发表于: 2013-4-19 19:07 5834

[原创]KiFastCallEntry详解

2013-4-19 19:07
5834

这里主要对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

先这么多吧、有事要走,回头再补。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
最新回复 (4)
雪    币: 5188
活跃值: (3427)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
内核模式全的准备工作  错字?全?
2013-4-19 19:34
0
雪    币: 115
活跃值: (46)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
很久前就想问 ring3通过KiFastCallEntry到ring0的过程,就是不懂r3怎么调用中断
我的理解 提交中断->系统当前线程切换成程序的->所以堆栈被切换,改变标志位->调用内核态KiFastCallEntry
这样正确吗

无论如何都学到了东西
2013-4-19 22:09
0
雪    币: 43
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
这个我不用我给你讲,好好看看杨季文的《80x86汇编语言程序设计教程》的第十章,这部分就能解决你的疑问。至于你说的最后调用KiFastCallEntry,没有你想的那么直接。KiFastCallEntry的调用函数是KiFastSystemCall,在这个函数中以前的系统使用int 2eh,现在系统使用sysenter(我看过win7是的)进入ring0。

都是学习我就跟你多说点。

a) 你的描述中有问题,我在帖子中说的很清楚,堆栈切换在KiFastCallEntry。
b) 杨季文的这本书中的例子,不能再普通的MASM中调试。需要使用NASM、bochs虚拟机,刚开始学时在这地方浪费了很多时间,希望这里能给你提个醒,少走点弯路。
2013-4-20 00:13
0
雪    币: 115
活跃值: (46)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
没办法啊又不想麻烦别人,我仅有的书是C++相关的,其实就是想问int 2e和sysenter怎么进的中断,恳请解答一下

另外问下,堆栈切换 是创建一条Native线程再切换为程序当前的线程(多线程系统是这个意思吗)
而且当前线程是在内核层使用cr3寄存器?
2013-4-21 13:20
0
游客
登录 | 注册 方可回帖
返回
//