Windows内核神功葵花宝典之系统调用篇

本文详细解析了用户态进程如何通过中断、异常、自陷和快速系统调用等方式进入系统态,阐述了Windows系统中自陷指令和快速系统调用机制的内部实现,包括KiSystemService和KiFastCallEntry函数的具体流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  用户态进入系统态有以下几种方式:
  中断(Interrupt)。在允许中断的情况下,有外部设备的中断请求到达,CPU自动转入系统空间,并从预定地址执行指令。中断只发生在两条指令之间,不会打断正在执行的指令。对CPU而言,进入中断是被动的,所以中断的发生可以看作是“异步”的。
  异常(Exception)。不管是用户空间或系统空间,执行指令失败都会引起一次“异常”,CPU因此转入系统空间,并从预定地址执行指令。异常在形式上与中断十分相似,只是异常发生在执行指令的过程中,发生异常后当前指令半途而废。异常同样也是被动无法预知的,除了那些故意引发异常的情况。
  自陷(Trap)。为了让CPU主动进入系统空间,绝大多数CPU都设有专门的“自陷”指令。自陷与中断相似,就像一次由CPU主动发出的中断请求。自陷是主动的,是成功执行了一条自陷指令的结果。
  “调用门”机制,但基本无人采用;“快速系统调用”机制,使用专门的sysenter、sysexit指令以及新增的三个寄存器。

  早期Windows(Win2K及以前)使用自陷指令“int 0x2e”来实现系统调用。执行“int 0x2e”时,CPU首先从用户态切换成系统态,然后从“任务状态段”TSS(TR寄存器指向当前任务的TSS,ltr、str特权指令可设置TR寄存器)中装入本线程系统空间的SS寄存器和ESP寄存器,接着依次将用户空间的SS寄存器、ESP寄存器、EFLAGS标志寄存器、CS、EIP压入系统空间堆栈,关闭中断,最后从“中断向量表”IDT中以0x2e为“中断向量”,找到入口地址开始执行。系统调用返回则通过“中断返回”iret指令,实现上述的逆过程。
  XP及以后,使用“快速系统调用”机制来实现系统调用(若CPU支持)。“快速系统调用”指令不涉及堆栈操作,执行sysenter指令时,CPU进入系统态,并把SYSENTER_CS_MSR(0x174编号)寄存器加载到CS中、SYSENTER_EIP_MSR(0x176编号)寄存器加载到EIP中、SYSENTER_CS_MSR寄存器内容+8加载到SS中、SYSENTER_ESP_MSR(0x175编号)寄存器加载到ESP中。MSR(Mode Specific Register)寄存器只能通过特权指令rdmsr和wrmsr进行读写。SYSENTER_CS_MSR被预先设为0x8(KGDT_R0_CODE),则SS段寄存器的取值就为0x10(KGDT_R0_DATA)。ENTER_ESP_MSR被设为KPRCB.DpcStack,这是一个不属于任何线程的独立堆栈,ENTER_EIP_MSR被设为KiFastCallEntry函数的地址。执行sysexit指令时,CPU进入用户态,并把SYSENTER_CS_MSR内容+16加载到CS中(KGDT_R3_CODE | RPL_MASK)、edx内容加载到EIP中、SYSENTER_CS_MSR内容+24加载到SS中(KGDT_R3_DATA | RPL_MASK)、ecx内容加载到ESP中。

  以ReadFile函数为例,ReactOS系统调用的实现过程:
  应用程序调用ReadFile(kernel32.dll中)函数读取文件内容,查看ReadFile函数源码(dll\win32\kernel32\file\rw.c),在检查验证相关参数后函数内部调用NtReadFile,NtReadFile在内核模块ntoskrnl.exe和ntdll.dll中都存在,内核模块中是真正的系统服务实现,ntdll.dll中的则是用户模式下的存根代码(从ring3进ring0调用系统服务),显然此处ReadFile函数中调用的是ntdll.dll的NtReadFile。
  ntdll.dll所有系统服务的存根代码都是在编译时通过nci.exe工具自动生成的,具体详见tools\nci\ncitool.c,如NtReadFile部分为:
public _NtReadFile@36
_NtReadFile@36:
    mov eax, 0beh
    mov ecx, KUSER_SHARED_SYSCALL
    call [ecx]
    ret 24h
  KUSER_SHARED_SYSCALL被定义为0x7FFE0300(include\ndk\asm.h),显然这里保存着一个函数指针,系统在初始化时(ntoskrnl\ps\psmgr.c)根据CPU是否支持快速系统调用,将此函数指针指向KiIntSystemCall或KiFastSystemCall。
  先看KiIntSystemCall的实现(dll\ntdll\main\i386\dispatch.S):
_KiIntSystemCall@0:
    /* Set stack in EDX and do the interrupt */
    lea edx, [esp+8]
    int 0x2E
    /* Return to caller */
    ret
  KiIntSystemCall使用int 2e自陷指令进入内核调用系统服务的,其中eax存放着系统服务号,edx存放着参数地址。
  再看KiFastSystemCall的实现(dll\ntdll\main\i386\dispatch.S):
_KiFastSystemCall@0:
    /* Put ESP in EDX and do the SYSENTER */
    mov edx, esp
    sysenter
_KiFastSystemCallRet@0:
    /* Just return to caller */
    ret
  KiFastSystemCall是使用sysenter快速系统调用指令进入内核调用系统服务的,其中eax存放着系统服务号,edx放着当前用户堆栈地址(根据此值可以得到如参数、返回地址等信息),相应的内核服务使用sysexit指令返回到KiFastSystemCallRet处。

  中断描述符表(IDT)每项占8个字节,其地址被设置保存在IDTR寄存器中(线性地址),在include\ndk\i386\ketypes.h中定义为:
typedef struct _KIDTENTRY
{
    USHORT Offset;
    USHORT Selector;
    USHORT Access;
    USHORT ExtendedOffset;
} KIDTENTRY, *PKIDTENTRY;
  Offset(低16位)和ExtendedOffset(高16位)组成一个32位的中断处理例程地址,Selector为段选择子,Access描述一些权限属性。在ntoskrnl\ke\i386\trap.s中定义了一张中断描述符表KiIdt(ntoskrnl\ke\i386\trap.s),其格式为方便人类阅读,和上述CPU要求的格式并不相同,因此系统初始化过程中会调用KeInitExceptions函数(ntoskrnl\ke\i386\exp.c)将其转换为intel定义的格式。
  KiIdt中断表第2e项的中断例程函数为KiSystemService,就是系统服务的内核实现,由之前描述CPU执行自陷指令时自动压栈的一些动作可知,此时系统空间的堆栈存的内容为:
用户空间EIP值        --------- 低地址
用户空间CS值
EFLAGS寄存器值
用户空间ESP值
用户空间SS值        ---------- 高地址
  看一下函数的实现(ntoskrnl\ke\i386\trap.s):
_KiSystemService:
    /* Enter the shared system call prolog */
    SYSCALL_PROLOG kss_a, kss_t
    /* Jump to the actual handler */
    jmp SharedCode
  SYSCALL_PROLOG是个宏,其定义(ntoskrnl\include\internal\i386\asmmacro.S):
.macro SYSCALL_PROLOG Label EndLabel
    /* Create a trap frame */
    push 0
    push ebp
    push ebx
    push esi
    push edi
    push fs

    /* Load PCR Selector into fs */
    mov ebx, KGDT_R0_PCR
    .byte 0x66
    mov fs, bx

    /* Get a pointer to the current thread */
    mov esi, PCR[KPCR_CURRENT_THREAD]

    /* Save the previous exception list */
    push PCR[KPCR_EXCEPTION_LIST]

    /* Set the exception handler chain terminator */
    mov dword ptr PCR[KPCR_EXCEPTION_LIST], -1

    /* Save the old previous mode */
    push [esi+KTHREAD_PREVIOUS_MODE]

    /* Skip the other registers */
    sub esp, 0x48

    /* Set the new previous mode based on the saved CS selector */
    mov ebx, [esp+0x6C]
    and ebx, 1
    mov byte ptr [esi+KTHREAD_PREVIOUS_MODE], bl

    /* Go on the Kernel stack frame */
    mov ebp, esp

    /* Save the old trap frame pointer where EDX would be saved */
    mov ebx, [esi+KTHREAD_TRAP_FRAME]
    mov [ebp+KTRAP_FRAME_EDX], ebx

    /* Flush DR7 */
    and dword ptr [ebp+KTRAP_FRAME_DR7], 0

    /* Check if the thread was being debugged */
    test byte ptr [esi+KTHREAD_DEBUG_ACTIVE], 0xFF

    /* Set the thread's trap frame and clear direction flag */
    mov [esi+KTHREAD_TRAP_FRAME], ebp
    cld

    /* Save DR registers if needed */
    jnz Dr_&Label

    /* Set the trap frame debug header */
Dr_&EndLabel:
    SET_TF_DEBUG_HEADER

    /* Enable interrupts */
    sti
.endm
  SYSCALL_PROLOG首先构建一个栈帧,这是一个系统调用约定的框架结构,所有自陷、中断、异常处理过程都使用这样一个相同的栈帧用来恢复返回到用户空间。接着把KGDT_R0_PCR(0x30)加载到fs段寄存器中,因为Windows规定运行在内核时fs选择子必须指向KPCR结构,0x30表示为RPL0环、GDT表中的第6项。KPCR定义为(include\ddk\winddk.h):
typedef struct _KPCR {
  KPCR_TIB  Tib;                /* 00 */
  struct _KPCR  *Self;          /* 1C */
  struct _KPRCB  *Prcb;         /* 20 */
  KIRQL  Irql;                  /* 24 */
  ULONG  IRR;                   /* 28 */
  ULONG  IrrActive;             /* 2C */
  ULONG  IDR;                   /* 30 */
  PVOID  KdVersionBlock;        /* 34 */
  PUSHORT  IDT;                 /* 38 */
  PUSHORT  GDT;                 /* 3C */
  struct _KTSS  *TSS;           /* 40 */
  USHORT  MajorVersion;         /* 44 */
  USHORT  MinorVersion;         /* 46 */
  KAFFINITY  SetMember;         /* 48 */
  ULONG  StallScaleFactor;      /* 4C */
  UCHAR  SpareUnused;           /* 50 */
  UCHAR  Number;                /* 51 */
} KPCR, *PKPCR;                 /* 54 */
  其中KPCR_TIB定义为(include\ddk\winddk.h):
typedef struct _KPCR_TIB {
  PVOID  ExceptionList;         /* 00 */
  PVOID  StackBase;             /* 04 */
  PVOID  StackLimit;            /* 08 */
  PVOID  SubSystemTib;          /* 0C */
  _ANONYMOUS_UNION union {
    PVOID  FiberData;           /* 10 */
    DWORD  Version;             /* 10 */
  } DUMMYUNIONNAME;
  PVOID  ArbitraryUserPointer;  /* 14 */
  struct _KPCR_TIB *Self;       /* 18 */
} KPCR_TIB, *PKPCR_TIB;         /* 1C */
  继续往下看,把edi赋值为PCR[KPCR_CURRENT_THREAD],PCR是个宏被定义为“fs:”,而KPCR_CURRENT_THREAD定义为0x124,实际上从KPCR开始位置偏移0x120处保存着另一个名为KPRCB的数据结构(include\ndk\i386\ketypes.h),KPRCB结构定义很大,在其0x04处是一个KTHREAD*的CurrentThread指针成员。
  KPCR_EXCEPTION_LIST定义为0,KTHREAD_PREVIOUS_MODE定义为0xD7,代码中把KPCR.Tib. ExceptionList和KPRCB.CurrentThread->PreviousMode压入栈保存,并将KPCR.Tib. ExceptionList置-1,即链表终止。esp减0x48是给调试寄存器等留空间,然后栈帧中cs段最低位赋值给KPRCB.CurrentThread->PreviousMode,先前模式0为内核模式,1为用户模式。
  用户模式下调用系统服务,cs段CPL一定为3,赋值后先前模式就为1了。但如果在内核中调用系统服务,cs段CPL为0,则先前模式就变为0,系统服务在返回时根据先前模式的不同会有不同的动作。举例来说,NtReadFile和ZwReadFile,在用户存根(ntdll.dll)代码里,这两个函数没区别,而内核中的NtReadFile和ZwReadFile,内核NtReadFile是具体的实现是被系统服务所调用的,内核ZwReadFile则是模拟自陷指令构建一个栈帧,从而进入系统服务入口(KiSystemService),自然这里的先前模式就为0了。
  到这里可知自陷、中断、异常所共同使用的栈帧结构的完整框架定义为(ndk\i386\ketypes.h):
typedef struct _KTRAP_FRAME
{
    ULONG DbgEbp;
    ULONG DbgEip;
    ULONG DbgArgMark;
    ULONG DbgArgPointer;
    ULONG TempSegCs;
    ULONG TempEsp;
    ULONG Dr0;
    ULONG Dr1;
    ULONG Dr2;
    ULONG Dr3;
    ULONG Dr6;
    ULONG Dr7;
    ULONG SegGs;
    ULONG SegEs;
    ULONG SegDs;
    ULONG Edx;
    ULONG Ecx;
    ULONG Eax;
    ULONG PreviousPreviousMode;    // 0x48
    struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList;
    ULONG SegFs;
    ULONG Edi;
    ULONG Esi;
    ULONG Ebx;
    ULONG Ebp;
    ULONG ErrCode;
    ULONG Eip;                        // 0x68
    ULONG SegCs;
    ULONG EFlags;
    ULONG HardwareEsp;
    ULONG HardwareSegSs;
    ULONG V86Es;
    ULONG V86Ds;
    ULONG V86Fs;
    ULONG V86Gs;
} KTRAP_FRAME, *PKTRAP_FRAME;
  接着看下面的代码,KTHREAD_TRAP_FRAME定义为0x0110,KTRAP_FRAME_EDX定义为0x3C,将KPRCB.CurrentThread->TrapFrame保存到栈帧的edx寄存器(进入前edx指向参数地址,返回后此寄存器无关紧要,所以可以拿来使用),并将KPRCB.CurrentThread->TrapFrame指向当前已构建的栈帧地址,KTRAP_FRAME_DR7定义为0x2C,即把栈帧的dr7寄存器清0。DF标志位复位,esi、edi串操作时向高地址自增。
  检测KPRCB.CurrentThread->DispatcherHeader.DebugActive值,如果调试状态下跳转至Dr_kss_a标签处,非调试继续往下走,是个SET_TF_DEBUG_HEADER宏,此宏定义为:
.macro SET_TF_DEBUG_HEADER
    /* Get the Debug Trap Frame EBP/EIP */
    mov ebx, [ebp+KTRAP_FRAME_EBP]
    mov edi, [ebp+KTRAP_FRAME_EIP]

    /* Write the debug data */
    mov [ebp+KTRAP_FRAME_DEBUGPOINTER], edx
    mov dword ptr [ebp+KTRAP_FRAME_DEBUGARGMARK], 0xBADB0D00
    mov [ebp+KTRAP_FRAME_DEBUGEBP], ebx
    mov [ebp+KTRAP_FRAME_DEBUGEIP], edi
.endm
  主要针对栈帧中调试寄存器做一些赋值,最后sti开中断。

  消化一下,然后看KiSystemService的后一段:jmp SharedCode,SharedCode标签的定义(ntoskrnl\ke\i386\trap.s):
SharedCode:
    /*
     * Find out which table offset to use. Converts 0x1124 into 0x10.
     * The offset is related to the Table Index as such: Offset = TableIndex x 10
     */
    mov edi, eax
    shr edi, SERVICE_TABLE_SHIFT
    and edi, SERVICE_TABLE_MASK
    mov ecx, edi

    /* Now add the thread's base system table to the offset */
    add edi, [esi+KTHREAD_SERVICE_TABLE]

    /* Get the true syscall ID and check it */
    mov ebx, eax
    and eax, SERVICE_NUMBER_MASK
    cmp eax, [edi+SERVICE_DESCRIPTOR_LIMIT]

    /* Invalid ID, try to load Win32K Table */
    jnb KiBBTUnexpectedRange

    /* Check if this was Win32K */
    cmp ecx, SERVICE_TABLE_TEST
    jnz NotWin32K

    /* Get the TEB */
    mov ecx, PCR[KPCR_TEB]

    /* Check if we should flush the User Batch */
    xor ebx, ebx
ReadBatch:
    or ebx, [ecx+TEB_GDI_BATCH_COUNT]
    jz NotWin32K

    /* Flush it */
    push edx
    push eax
    //call [_KeGdiFlushUserBatch]
    pop eax
    pop edx

NotWin32K:
    /* Increase total syscall count */
    inc dword ptr PCR[KPCR_SYSTEM_CALLS]

#ifdef DBG
    /* Increase per-syscall count */
    mov ecx, [edi+SERVICE_DESCRIPTOR_COUNT]
    jecxz NoCountTable
    inc dword ptr [ecx+eax*4]
#endif

    /* Users's current stack frame pointer is source */
NoCountTable:
    mov esi, edx

    /* Allocate room for argument list from kernel stack */
    mov ebx, [edi+SERVICE_DESCRIPTOR_NUMBER]
    xor ecx, ecx
    mov cl, [eax+ebx]

    /* Get pointer to function */
    mov edi, [edi+SERVICE_DESCRIPTOR_BASE]
    mov ebx, [edi+eax*4]

    /* Allocate space on our stack */
    sub esp, ecx

    /* Set the size of the arguments and the destination */
    shr ecx, 2
    mov edi, esp

    /* Make sure we're within the User Probe Address */
    cmp esi, _MmUserProbeAddress
    jnb AccessViolation

CopyParams:
    /* Copy the parameters */
    rep movsd

    /* Do the System Call */
    call ebx

AfterSysCall:
#ifdef DBG
    /* Make sure the user-mode call didn't return at elevated IRQL */
    test byte ptr [ebp+KTRAP_FRAME_CS], MODE_MASK
    jz SkipCheck
    mov esi, eax                /* We need to save the syscall's return val */
    call _KeGetCurrentIrql@0
    or al, al
    jnz InvalidIrql
    mov eax, esi                /* Restore it */

    /* Get our temporary current thread pointer for sanity check */
    mov ecx, PCR[KPCR_CURRENT_THREAD]

    /* Make sure that we are not attached and that APCs are not disabled */
    mov dl, [ecx+KTHREAD_APC_STATE_INDEX]
    or dl, dl
    jnz InvalidIndex
    mov edx, [ecx+KTHREAD_COMBINED_APC_DISABLE]
    or edx, edx
    jnz InvalidIndex
#endif

SkipCheck:

    /* Deallocate the kernel stack frame  */
    mov esp, ebp

KeReturnFromSystemCall:

    /* Get the Current Thread */
    mov ecx, PCR[KPCR_CURRENT_THREAD]

    /* Restore the old trap frame pointer */
    mov edx, [ebp+KTRAP_FRAME_EDX]
    mov [ecx+KTHREAD_TRAP_FRAME], edx
.endfunc

.func KiServiceExit
_KiServiceExit:
    /* Disable interrupts */
    cli

    /* Check for, and deliver, User-Mode APCs if needed */
    CHECK_FOR_APC_DELIVER 1

    /* Exit and cleanup */
    TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything
.endfunc
  不得不说好长啊,慢慢看不急,一想到神功即成,难掩兴奋之情:)。
  分段详解,前面知道由int 0x2e自陷进入KiSystemService系统服务时,eax存放着系统调用号,edx指向参数地址。这里先将调用号右移8位再与0x10相与,结果存入ecx,ecx不是等于0x00就是等于0x10。合起来看的意思是,根据调用号范围,0x1000以下或以上,ecx有不同的偏移量。接着KPRCB.CurrentThread->ServiceTable加上偏移量,ServiceTable指向系统服务描述表,结构定义为(include\ndk\ketypes.h):
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    PULONG_PTR Base;
    PULONG Count;
    ULONG Limit;
#if defined(_IA64_)
    LONG TableBaseGpOffset;
#endif
    PUCHAR Number;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
  X86系统有两组服务描述表,分别为KeServiceDescriptorTable[2]和KeServiceDescriptorTableShadow[2]( ntoskrnl\ke\procobj.c),两组表第0项的基址都指向基本系统调用函数表MainSSDT(obj-i386\ntoskrnl\include\internal\napi.h),调用号0x1000以下的函数地址均在此表中。不同的是KeServiceDescriptorTable[1]内容为空,而KeServiceDescriptorTableShadow[1].Base指向扩充的Win32k系统调用函数表Win32kSSDT(obj-i386\subsystems\win32\win32k\include\napi.h),即调用号在0x1000及以上的函数地址表。
KPRCB.CurrentThread->ServiceTable不是指向KeServiceDescriptorTable就是指向KeServiceDescriptorTableShadow。若调用号小于Limit并且ecx偏移不等0x10,则跳转NotWin32K标签处。
  KPRCB.KeSystemCalls引用计数加1,得到系统调用的参数字节,得到函数表中的函数地址,在检测edx参数地址不越系统空间界后便开始在当前栈分配空间,拷贝用户栈参数内容。之后调用服务实现函数。
  返回后,esp恢复到栈帧地址,从栈帧中恢复原先的KPRCB.CurrentThread->TrapFrame,关中断,后面是CHECK_FOR_APC_DELIVER和TRAP_EPILOG两个宏,CHECK_FOR_APC_DELIVER宏暂时先略过,它的作用是检查是否有执行用户空间APC的请求,为其做好准备。
  TRAP_EPILOG又是一个长篇大宏,完整定义在(ntoskrnl\include\internal\i386\asmmacro.S)中,主要操作有:恢复先前的KPCR.Tib. ExceptionList;恢复先前的KPRCB.CurrentThread->PreviousMode;恢复先前的fs段寄存器;恢复先前的edi、esi、ebx、ebp寄存器;栈帧cs段CPL为用户态时跳转至FastExit:
.ret:
    iret
FastExit:
    /* Is SYSEXIT Supported/Wanted? */
    cmp dword ptr ss:[_KiFastSystemCallDisable], 0
    jnz .ret
    test dword ptr [esp+8], EFLAGS_TF
    jnz .ret

    /* Restore FS to TIB */
    mov ecx, KGDT_R3_TEB + RPL_MASK
    mov fs, ecx

    /* We will be cleaning up the stack ourselves */
    pop edx                        /* New Ring 3 EIP */
    add esp, 4                      /* Skip Ring 3 DS */
    and dword ptr [esp], 0xfffffdff      /* Remove EFLAGS_INTERRUPT_MASK from EFLAGS */
    popf                           /* Restore old EFLAGS */
    pop ecx                        /* Old Ring 3 SS:ESP */

    /*
     * At this point:
     *  ECX points to the old User Stack.
     *  EDX points to the instruction to execute in usermode after the sysenter
    */
    sti
    sysexit
  如果CPU不支持快速系统调用或单步调试TF标志有效,则通过iret指令返回。否则把fs赋值为0x3B(KGDT_R3_TEB+RPL_MASK),即RPL为3,GDT表的第7项。加载栈帧的EIP到edx寄存器中、加到栈帧的EFLAGS并清除IF标志(中断允许位),加到栈帧的ESP到ecx寄存器中,开中断,执行sysexit返回。

  看完了int 0x2e具体的调用过程,再来看sysenter的流程,前面已述,执行sysenter后便跳转到了KiFastCallEntry(ntoskrnl\ke\i386\trap.s),其定义为:
_KiFastCallEntry:
    /* Enter the fast system call prolog */
    FASTCALL_PROLOG FastCallDrSave, FastCallDrReturn
SharedCode:
    …………
  SharedCode已经很熟悉了,是和前面KiSystemService系统服务入口跳转共用的部分,重点看一下FASTCALL_PROLOG宏的定义(ntoskrnl\include\internal\i386\ asmmacro.S):
.macro FASTCALL_PROLOG Label EndLabel

    /* Set user selector */
    mov ecx, KGDT_R3_DATA | RPL_MASK

    /* Set FS to PCR */
    push KGDT_R0_PCR
    pop fs

    /* Set DS/ES to User Selector */
    mov ds, cx
    mov es, cx

    /* Set the current stack to Kernel Stack */
    mov ecx, PCR[KPCR_TSS]
    mov esp, [ecx+KTSS_ESP0]

    /* Set up a fake INT Stack. */
    push KGDT_R3_DATA + RPL_MASK
    push edx                            /* Ring 3 SS:ESP */
    pushf                               /* Ring 3 EFLAGS */
    push 2                              /* Ring 0 EFLAGS */
    add edx, 8                          /* Skip user parameter list */
    popf                                /* Set our EFLAGS */
    or dword ptr [esp], EFLAGS_INTERRUPT_MASK   /* Re-enable IRQs in EFLAGS, to fake INT */
    push KGDT_R3_CODE + RPL_MASK
    push dword ptr ds:KUSER_SHARED_SYSCALL_RET

    /* Setup the Trap Frame stack */
    push 0
    push ebp
    push ebx
    push esi
    push edi
    push KGDT_R3_TEB + RPL_MASK

    /* Save pointer to our PCR */
    mov ebx, PCR[KPCR_SELF]

    /* Get a pointer to the current thread */
    mov esi, [ebx+KPCR_CURRENT_THREAD]

    /* Set the exception handler chain terminator */
    push [ebx+KPCR_EXCEPTION_LIST]
    mov dword ptr [ebx+KPCR_EXCEPTION_LIST], -1

    /* Use the thread's stack */
    mov ebp, [esi+KTHREAD_INITIAL_STACK]

    /* Push previous mode */
    push UserMode

    /* Skip the other registers */
    sub esp, 0x48

    /* Make space for us on the stack */
    sub ebp, 0x29C

    /* Write the previous mode */
    mov byte ptr [esi+KTHREAD_PREVIOUS_MODE], UserMode

    /* Sanity check */
    cmp ebp, esp
    jnz BadStack

    /* Flush DR7 */
    and dword ptr [ebp+KTRAP_FRAME_DR7], 0

    /* Check if the thread was being debugged */
    test byte ptr [esi+KTHREAD_DEBUG_ACTIVE], 0xFF

    /* Set the thread's trap frame */
    mov [esi+KTHREAD_TRAP_FRAME], ebp

    /* Save DR registers if needed */
    jnz Dr_&Label

    /* Set the trap frame debug header */
Dr_&EndLabel:
    SET_TF_DEBUG_HEADER

    /* Enable interrupts */
    sti
.endm
  开头依旧是把指向KPCR的选择子赋给fs段寄存器,并把用户态的数据段选择子赋值给ds、es。将esp赋值为KPCR.TSS->Esp0,即当前线程的系统空间堆栈。然后手动构建一个自陷、中断、异常共同约定的栈帧框架,并把当前EFLAGS标志清0(位1保留位必须为1),edx+8修正为指向用户堆栈的参数,而栈帧中的返回地址EIP设为ds:KUSER_SHARED_SYSCALL_RET(0x7FFE0000+0x304),即KiFastSystemCallRet。最后把栈帧Dr7清0,不在调试状态下,开启中断,继续执行之后的SharedCode。

  从内核中发起系统调用:
  在内核中进行系统调用前,也就是调用KiSystemService函数前,需在当前堆栈构建栈帧框架,但不必是完全的,如栈帧的SS、ESP等就可不要,因为内核调用后还是返回内核状态,就不再需要进行堆栈切换了。eax存入系统服务号,edx指向参数地址,EFLAGS压栈,KGDT_R0_CODE压入,call _KiSystemService就OK了。
  最终SharedCode返回时,有如下代码 :
    test dword ptr [esp+4], 1

    /* It is, so use Fast Exit */
    jnz FastExit

    /* Jump back to stub */
    pop edx
    pop ecx
    popf
    jmp edx
  [esp+4]为栈帧的CS内容,显然,如果先前模式为0,即在内核中发起系统服务调用的,不会跳转至FastExit,而是直接清理残余栈帧后,返回到调用处。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值