欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]windows7内核分析之x86&x64第二章系统调用

[原创]windows7内核分析之x86&x64第二章系统调用

2.1内核与系统调用

上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e( 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32cpux86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可以执行以前的16位程序 也包含了保护模式:可以执行32位的程序 64cpulong模式:分为两种 64位模式:只执行64位的程序和compatibility模式:可以执行x86模式的程序 老式的cpu不支持 不提供sysenter指令,只能由int 2e模拟中断方式进入内核,调用系统服务)这两者什么区别呢?

1,Int 2e速度慢 首先从TSS中加载内核堆栈的ss esp->保存5个寄存器的现场(ss esp eip eflags cs)->然后还要去IDT中查找isr,这个过程消耗的时间太多

2,sysenter 提供了三个MSR寄存器 分别是SYSENTER_CS_MSR SYSENTER_EIP_MSR SYSENTER_ESP_MSR 分别指示了内核对应处理例程的cs选择子(函数所在段的选择子 通过选择子找到GDT 从逻辑地址转换为线性地址 最后访问线性地址 会根据四级表转换为物理地址)和内核函数地址和内核堆栈地址 这样就省去了查找idtTSS段 获得内核函数地址和内核堆栈地址 节省了大量时间

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X64syscall 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

syscall 的内核入口点是 KiSystemCall64() ,系统在 KiInitializeBootStructures() 里对 syscall/sysret 执行环境进行了设置:

nt!KiInitializeBootStructures+0x233:

fffff800`03f12f63 498b442408      mov     rax,qword ptr [r12+8]

fffff800`03f12f68 b968000000      mov     ecx,68h

fffff800`03f12f6d 66894866        mov     word ptr [rax+66h],cx

fffff800`03f12f71 48b80000000010002300 mov rax,23001000000000h

fffff800`03f12f7b b9810000c0      mov     ecx,0C0000081h                         ; MSR_STAR

fffff800`03f12f80 488bd0          mov     rdx,rax

fffff800`03f12f83 48c1ea20        shr     rdx,20h

fffff800`03f12f87 0f30            wrmsr

fffff800`03f12f89 488d05701cdbff  lea     rax,[nt!KiSystemCall32 (fffff800`03cc4c00)]

fffff800`03f12f90 b9830000c0      mov     ecx,0C0000083h                         ; MSR_CSTAR

fffff800`03f12f95 488bd0          mov     rdx,rax

fffff800`03f12f98 48c1ea20        shr     rdx,20h

fffff800`03f12f9c 0f30            wrmsr

fffff800`03f12f9e 488d051b1fdbff  lea     rax,[nt!KiSystemCall64 (fffff800`03cc4ec0)]

fffff800`03f12fa5 b9820000c0      mov     ecx,0C0000082h                         ; MSR_LSTAR

fffff800`03f12faa 488bd0          mov     rdx,rax

fffff800`03f12fad 48c1ea20        shr     rdx,20h

fffff800`03f12fb1 0f30            wrmsr

fffff800`03f12fb3 b800470000      mov     eax,4700h

fffff800`03f12fb8 b9840000c0      mov     ecx,0C0000084h                         ; MSR_SFMASK

fffff800`03f12fbd 488bd0          mov     rdx,rax

fffff800`03f12fc0 48c1ea20        shr     rdx,20h

fffff800`03f12fc4 0f30            wrmsr

fffff800`03f12fc6 85ed            test    ebp,ebp

fffff800`03f12fc8 750a            jne     nt!KiInitializeBootStructures+0x2a4 (fffff800`03f12fd4)

MSR_STAR 寄存器里的值被设为 23001000000000h,它意味着:

SYSCALL_EIP 0

SYSCALL_CS 0x10

SYSRET_CS 0x23

SYSRET_CS 中,SYSRET_CS.RPL = 3 返回的权限级别是 3 级(用户代码)。

 

MSR_CSTAR 寄存器被设为 nt!KiSystemCall32 (fffff800`03cc4c00) 地址值,这是为了 compaitibility 模式代码调用而设置的。

MSR_LSTAR 寄存器被设为 nt!KiSystemCall64 (fffff800`03cc4ec0) 地址值,是为 64-bit 模式而准备的。CSTAR 寄存器为 compatibility 模式下的代码提供 rip 值,当 processor comatibility 模式下运行时,执行了 syscall 指令,此时 rip 值将从 MSR_STAR 寄存器中加载。请记住:只能在 AMD processor 使用 compaitibility 模式下的调用。照顾通用性,为了在 Intel AMD processor 上都能够使用 fast call 功能,操作系统的设计者应该要避免在 comaptibility 模式下使用 syscall 指令。前面提到过,建议在 compatibility 模式下先切换到 64-bit 模式后,再执行 syscall 指令

; NtReadFile

.text:7DE8F905                 mov     ecx, 1Ah

.text:7DE8F90A                 lea     edx, [esp+FileHandle]

.text:7DE8F90E                 call    large dword ptr fs:0C0h //注意在win7x64 long模式下的 运行32位程序 就会进入兼容模式下 兼容模式下调用NtReadFile 这里就是上面说的必须切换到64位模式 这个函数里面 就是切换模式的 完后调用syscall

.text:7DE8F915                 add     esp, 4

.text:7DE8F918                 retn    24h

MSR_SFMASK 寄存器设为 4700h,意味着:

NT DF IF TF

这些 rflags 寄存器中的标志位在进入 KiSystemCall64() 后会被清 0

long mode(win7 x64 ) 下,当执行 syscall 指令时,当前的 rflags 寄存器值被保存在 r11 寄存器,processor 在执行 syscall 时,准备的目标执行环境中,rflags 将会根据 SFMASK 寄存器的值进行设置:如果 SFMASK 寄存器的某一位置为 1,那么 rflags 寄存器中相应的位将会被清 0,置为 0 时,rflags 寄存器中相应位不变

它的逻辑 C 描述为:

rflags = rflags & (~sfmask);

你应该在系统服务例程先保存 r11 值(原来的 rflags 寄存器值),以便 sysret 执行返回时可以恢复原来的 rflags

 

那么,对于要进入系统调用的代码来说:

ntdll!NtReadFile:

00000000`773ad410 4c8bd1           mov     r10,rcx //保存原来的rcx

00000000`773ad413 b803000000       mov     eax,3 //系统调用号 这里NtReadFile的内核号码是3

00000000`773ad418 0f05              Syscall

00000000`773ad41a c3                ret

执行 syscall 后,rcx 会保存返回值,因此应该要保存 rcx 原来的值

 

KiSystemCall64  proc near

     swapgs//将gs的基址与MSR[c0000102]内容互换,设置过后gs:0指向内核处理器控制域_KPCR

      mov     gs:10h, rsp//将用户态堆栈指针保存到_KPCR+0x010的成员UserRsp里

      mov     rsp, gs:1A8h//使用_KPCR+0x1A8的成员RspBase设置当前rsp,这个成员存储了当前线程核心态的堆栈指针

          push    2Bh

          push    qword ptr gs:10h

          push    r11//r11里保存的是rflags

          push    33h

          push    rcx// rcx里保存的是用户态syscall的下一条指令地址

          mov     rcx, r10// 把系统调用的第一个参数重新赋给rcx

          sub     rsp,8

          push    rbp

          sub     rsp,158h// rsp与设置伊始相比共减少了0x190字节。期间恢复了rcx,在堆栈上保存了一些值。使用下面的命令,我们可以看出,0x190正是_KTRAP_FRAME的大小:

          lea     rbp, [rsp+190h+var_110]//现在rsp指向_KTRAP_FRAME的起始地址,rbp指向_KTRAP_FRAME+0x80的位置

(4)      mov     [rbp+0C0h],rbx

          mov     [rbp+0C8h], rdi

          mov     [rbp+0D0h], rsi

          mov     byte ptr [rbp-55h], 2

(5)      mov     rbx, gs:188h

          prefetchw byte ptr [rbx+1D8h]

          stmxcsr dword ptr [rbp-54h]

          ldmxcsr dword ptr gs:180h

(6)      cmp     byte ptr [rbx+3],0//是否在被调试 调试就保存寄存器

(7)           mov     word ptr [rbp+80h],0

          jz      loc_140071B50

          mov     [rbp-50h], rax

(7)      mov     [rbp-48h], rcx//以下若干条指令将rcx、rdx、r8、r9存入了_KTRAP_FRAME当中,其中rbp-48h相当于rsp+80g-48h即rsp+38h

          mov     [rbp-40h], rdx

          test    byte ptr [rbx+3],3

          mov     [rbp-38h], r8

          mov     [rbp-30h], r9

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X86sysenter 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

ntdll!ZwReadFile:

776262dc b811010000      mov     eax,111h//系统调用号

776262e1 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)//这个7ffe03000地址固定的 系统初始化 查看是否支持快速系统调用 支持 这个地址里面存的就是KiFastSystemCall 否则就是KiInitSystemCall(int 0x2e模拟中断)的地址 这些函数都存在于用户空间ntdll.dll(和内核ntdll不一样)中 这个dll 对于每个进程 地址不变 一直在内存中

7ffe03000 和0x7fe0000 一个是r3的地址 一个是内核的地址 两个64kb地址映射到同一个物理内存

776262e6 ff12            call    dword ptr [edx]

776262e8 c22400          ret     24h

776262eb 90              nop

 

ntdll!KiFastSystemCall:

776270d0 8bd4            mov     edx,esp//进入KiFastSystemCall之前 已经被调用方压入了各种参数 最后压入的是返回地址 当前esp指向它 在书中 被压入堆栈的参数区域叫参数块  处理完后 调用sysexit 出栈ret 返回

776270d2 0f34            sysenter

Sysenter进入内核后 的总入口是KiFastCallEntry

和int2e的区别:快速系统调用的sysenter 堆栈地址保存在edx 返回地址保存在SharedUserData->SystemCallReturn指向KiFastSystemCallRet地址

 

nt!KiFastCallEntry:

83e888e0 b923000000      mov     ecx,23h

83e888e5 6a30            push    30h

83e888e7 0fa1            pop     fs//fs指向kpcr

83e888e9 8ed9            mov     ds,cx//指向用户空间数据段

83e888eb 8ec1            mov     es,cx

83e888ed 648b0d40000000  mov     ecx,dword ptr fs:[40h]//从kpcr获取TSS段的起点

83e888f4 8b6104          mov     esp,dword ptr [ecx+4]//从TSS获取系统空间段的指针

83e888f7 6a23            push    23h//压栈r3的数据段选择子(模仿中断自陷 异常进入内核的指令 会自动堆栈上创建一个框架 这个框架结构一样 所以这里虽然是快速系统调用 但是也是进入内核 模仿自陷 中断 异常进入内核的函数入口样子 照猫画虎 但是注意int2e的内核入口函数没有照猫画虎 因为int 2e本身就是中断 所以cpu进入内核后 堆栈上已经画出老虎了

)

83e888f9 52              push    edx//压栈r3的esp

83e888fa 9c              pushfd//压栈r3的eflags

83e888fb 6a02            push    2

83e888fd 83c208          add     edx,8//跳过用户堆栈的参数块

83e88900 9d              popfd//r0的eflags 所有标志都为0 中断关闭

83e88901 804c240102      or      byte ptr [esp+1],2

83e88906 6a1b            push    1Bh//模仿自陷中断异常的push cs eip83e88908 ff350403dfff    push    dword ptr ds:[0FFDF0304h]//KiFastSystemCallRet地址

83e8890e 6a00            push    0//这里以下见①注释

83e88910 55              push    ebp

83e88911 53              push    ebx

83e88912 56              push    esi

83e88913 57              push    edi

83e88914 648b1d1c000000  mov     ebx,dword ptr fs:[1Ch]

83e8891b 6a3b            push    3Bh

83e8891d 8bb324010000    mov     esi,dword ptr [ebx+124h]

83e88923 ff33            push    dword ptr [ebx]

83e88925 c703ffffffff    mov     dword ptr [ebx],0FFFFFFFFh

83e8892b 8b6e28          mov     ebp,dword ptr [esi+28h]

83e8892e 6a01            push    1

83e88930 83ec48          sub     esp,48h

83e88933 81ed9c020000    sub     ebp,29Ch

83e88939 c6863a01000001  mov     byte ptr [esi+13Ah],1

83e88940 3bec            cmp     ebp,esp

83e88942 7597            jne     nt!KiFastCallEntry2+0x49 (83e888db)

83e88944 83652c00        and     dword ptr [ebp+2Ch],0

83e88948 f64603df        test    byte ptr [esi+3],0DFh

83e8894c 89ae28010000    mov     dword ptr [esi+128h],ebp

83e88952 0f8538feffff    jne     nt!Dr_FastCallDrSave (83e88790)

83e88958 8b5d60          mov     ebx,dword ptr [ebp+60h]

83e8895b 8b7d68          mov     edi,dword ptr [ebp+68h]

83e8895e 89550c          mov     dword ptr [ebp+0Ch],edx

83e88961 c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h

83e88968 895d00          mov     dword ptr [ebp],ebx

83e8896b 897d04          mov     dword ptr [ebp+4],edi

83e8896e fb              sti

83e8896f 8bf8            mov     edi,eax

83e88971 c1ef08          shr     edi,8

83e88974 83e710          and     edi,10h

83e88977 8bcf            mov     ecx,edi

83e88979 03bebc000000    add     edi,dword ptr [esi+0BCh]

83e8897f 8bd8            mov     ebx,eax

83e88981 25ff0f0000      and     eax,0FFFh

83e88986 3b4708          cmp     eax,dword ptr [edi+8]

83e88989 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (83e886c2)

83e8898f 83f910          cmp     ecx,10h

83e88992 751a            jne     nt!KiSystemServiceAccessTeb+0x12 (83e889ae)

83e88994 8b8e88000000    mov     ecx,dword ptr [esi+88h]

83e8899a 33f6            xor     esi,esi

 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 


 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是int 2e 讲解  参考总结+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ntdll!ZwReadFile:

    push ebp

mov ebp,esp

Mov eax,111h//系统调用号

Lea edx,[8+ebp]//指向参数块

Int 2e//不支持syscall/sysenter的cpu 只能用这个了

Pop ebp

    ret     9h

   

2.2系统调用的内核入口KiSystemService()

上面讲到老的cpu是通过int 2e 模拟进入内核 而不是新cpu 直接支持快速系统调用

那么KiSystemService()就是int 2e的处理函数 进入这个函数之前 cpu会自动读取TR寄存器 找到TSS段 读取里面的ss esp 就是内核堆栈地址了 完后往这个地址保存用户空间的堆栈 eflags cs eip 具体开头已经说了 这里就不废话了

nt!KiSystemService:

83e8880e 6a00            push    0   //①是一种类似TrapFrame 进入这个函数之前 cpu会自动压入各种ss esp eip等等 你可以理解为一种上下文 这里push 0 是因为函数最后要把他的值存入eax当做状态码返回 另外就是操作系统 把这个Frame 定义了一个结构 然而异常发生后 cpu会自动压入一个错误码 中断和自陷都没有 所以为了通用 这个结构里面不管是异常还是中断进入内核 结构第一个元素都是0 占个位置的意思

83e88810 55              push    ebp//保存栈帧

83e88811 53              push    ebx//函数下面要用到ebx 所以这里先保存一下

83e88812 56              push    esi//函数下面 要用到esi保存kthread 所以这先保存

83e88813 57              push    edi//函数下面 要用edi引用调用号 所以这也要保存

83e88814 0fa0            push    fs//内核fs指向kpcr 用户层指向TEB 所以这里提前保存 因为进了内核 fs要改变了

83e88816 bb30000000      mov     ebx,30h//r0fs指向kpcr fscs一样都是段选择子 所以这六30hGDTkpcr的索引

:

83e8881b 668ee3          mov     fs,bx

83e8881e bb23000000      mov     ebx,23h//数据段的选择子 见上图 内核中都规定好

83e88823 8edb            mov     ds,bx

83e88825 8ec3            mov     es,bx

83e88827 648b3524010000  mov     esi,dword ptr fs:[124h]//使esi指向当前的ethread结构

83e8882e 64ff3500000000  push    dword ptr fs:[0]//保存老的exceptionList

83e88835 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh新的exceptionlist为空白

83e88840 ffb63a010000    push    dword ptr [esi+13Ah]//保存老的先前模式

83e88846 83ec48          sub     esp,48h//为之后要保存的调试寄存器 留下空间

83e88849 8b5c246c        mov     ebx,dword ptr [esp+6Ch]//系统调用前夕的cs印象

83e8884d 83e301          and     ebx,1//0环最低位为0  3环最低位为1

83e88850 889e3a010000    mov     byte ptr [esi+13Ah],bl//新的先前模式

83e88856 8bec            mov     ebp,esp

83e88858 8b9e28010000    mov     ebx,dword ptr [esi+128h]//kthread 结构里的TrapFrame 因为KiSystemService里面可能调用其他api 也会再次进入KiSystemService 梯归调用 那么肯定要梯归返回 所以需要提前保存好上下文 但是驱动可以搜索特征码 直接定位函数地址 绕过KiSystemService

83e8885e 895d3c          mov     dword ptr [ebp+3Ch],ebx//暂时保存在这里

83e88861 83652c00        and     dword ptr [ebp+2Ch],0//dr7 设置为0

83e88865 f64603df        test    byte ptr [esi+3],0DFh//判断当前是否被调试

83e88869 89ae28010000    mov     dword ptr [esi+128h],ebp//新的TrapFrame是此时堆栈上的框架Frame

83e8886f fc              cld //屏蔽中断

83e88870 0f859afeffff    jne     nt!Dr_kss_a (83e88710)//如果被调试 那么要保存好调试寄存器

83e88876 8b5d60          mov     ebx,dword ptr [ebp+60h]//这里书上也没讲清楚 知道的告诉我一下

83e88879 8b7d68          mov     edi,dword ptr [ebp+68h]

83e8887c 89550c          mov     dword ptr [ebp+0Ch],edx

83e8887f c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h

83e88886 895d00          mov     dword ptr [ebp],ebx

83e88889 897d04          mov     dword ptr [ebp+4],edi

83e8888c fb              sti//开启中断

83e8888d e9dd000000      jmp     nt!KiFastCallEntry+0x8f (83e8896f)

 

nt!KiFastCallEntry+0x8f:

83e8896f 8bf8            mov     edi,eax//系统调用号

83e88971 c1ef08          shr     edi,8//除以256

83e88974 83e710          and     edi,10h//调用号是否大于1000

83e88977 8bcf            mov     ecx,edi//00 或者10 nt4.0之前小于1000 那么ecx就是00 否则10

 

83e88979 03bebc000000    add     edi,dword ptr [esi+0BCh]//kthread 结构里有一个ServiceTable指针 其实默认他不是指向KeServiceDescriptorTableShadow(win32k.sys) 就是指向KeServiceDescriptorTable(基本系统调用)也就是说每个线程 可以指向不同的表 脑洞大开 即使tp对KeServiceDescriptorTable做了手脚 你也可以提前备份一个表 完后让线程的ServiceTable指向它 KeServiceDescriptorTable[0]是1000以下的系统调用 [1]是1000以上的系统调用(KeServiceDescriptorTableShadow[1])  里面每一项 都是一个结构 KService_Table_Descriptor 第一个元素就是MainSSDT,MainSSDT={{NtOpenFile},{NtCloseFile},{}......}

83e8897f 8bd8            mov     ebx,eax

83e88981 25ff0f0000      and     eax,0FFFh//获取调用号的低12位

83e88986 3b4708        cmp     eax,dword ptr [edi+8]//和上面KService_Table_Descriptor结构的Limit比较

83e88989 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (83e886c2)//如果超了 肯定是大于1000那么就是shadow表里的 就跳到错误处理的地方 完后会加载win32k.sys 使当前线程指向KeServiceDescriptorTableShadow[1]

83e8898f 83f910          cmp     ecx,10h//如果是10 那么就是win32k调用

83e88992 751a            jne     nt!KiSystemServiceAccessTeb+0x12 (83e889ae)

83e88994 8b8e88000000    mov     ecx,dword ptr [esi+88h]//使用win32k系统调用表 获取表项里面的地址 完后跳过去执行

83e8899a 33f6            xor     esi,esi

 

83e889ae 64ff05b0060000  inc     dword ptr fs:[6B0h]

83e889b5 8bf2            mov     esi,edx//使esi指向 用户空间堆栈上的参数块

83e889b7 33c9            xor     ecx,ecx

83e889b9 8b570c          mov     edx,dword ptr [edi+0Ch]

83e889bc 8b3f            mov     edi,dword ptr [edi]//使edi指向具体的系统调用表

83e889be 8a0c10          mov     cl,byte ptr [eax+edx]//函数指针

83e889c1 8b1487          mov     edx,dword ptr [edi+eax*4]

83e889c4 2be1            sub     esp,ecx//在系统空间上留出空间

83e889c6 c1e902          shr     ecx,2

83e889c9 8bfc            mov     edi,esp

83e889cb f6457202        test    byte ptr [ebp+72h],2

83e889cf 7506            jne     nt!KiSystemServiceAccessTeb+0x3b (83e889d7)

83e889d1 f6456c01        test    byte ptr [ebp+6Ch],1

83e889d5 740c            je      nt!KiSystemServiceCopyArguments (83e889e3)

83e889d7 3b355078fb83    cmp     esi,dword ptr [nt!MmUserProbeAddress (83fb7850)]//参数块的位置 不得高于MmUserProbeAddress 这个定义了用户空间最大地址

83e889dd 0f832e020000    jae     nt!KiSystemCallExit2+0xa5 (83e88c11)

nt!KiSystemServiceCopyArguments:

83e889e3 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]//复制用户空间参数到内核堆栈上

83e889e5 f6456c01        test    byte ptr [ebp+6Ch],1

83e889e9 7416            je      nt!KiSystemServiceCopyArguments+0x1e (83e88a01)

83e889eb 648b0d24010000  mov     ecx,dword ptr fs:[124h]

83e889f2 8b3c24          mov     edi,dword ptr [esp]

83e889f5 89993c010000    mov     dword ptr [ecx+13Ch],ebx

83e889fb 89b92c010000    mov     dword ptr [ecx+12Ch],edi

83e88a01 8bda            mov     ebx,edx

83e88a03 f6058837f88340  test    byte ptr [nt!PerfGlobalGroupMask+0x8 (83f83788)],40h

83e88a0a 0f954512        setne   byte ptr [ebp+12h]

83e88a0e 0f8580030000    jne     nt!KiServiceExit2+0x179 (83e88d94)

83e88a14 ffd3            call    ebx//调用内核目标函数

 

nt!KiSystemServicePostCall:

83e88a16 f6456c01        test    byte ptr [ebp+6Ch],1

83e88a1a 7434            je      nt!KiSystemServicePostCall+0x3a (83e88a50)

83e88a1c 8bf0            mov     esi,eax

83e88a1e ff1568c1e483    call    dword ptr [nt!_imp__KeGetCurrentIrql (83e4c168)]

83e88a24 0ac0            or      al,al

83e88a26 0f852f030000    jne     nt!KiServiceExit2+0x140 (83e88d5b)

83e88a2c 8bc6            mov     eax,esi

83e88a2e 648b0d24010000  mov     ecx,dword ptr fs:[124h]

83e88a35 f68134010000ff  test    byte ptr [ecx+134h],0FFh

83e88a3c 0f8537030000    jne     nt!KiServiceExit2+0x15e (83e88d79)

83e88a42 8b9184000000    mov     edx,dword ptr [ecx+84h]

83e88a48 0bd2            or      edx,edx

83e88a4a 0f8529030000    jne     nt!KiServiceExit2+0x15e (83e88d79)

83e88a50 8be5            mov     esp,ebp //回到自陷框架

83e88a52 807d1200        cmp     byte ptr [ebp+12h],0

83e88a56 0f8544030000    jne     nt!KiServiceExit2+0x185 (83e88da0)

83e88a5c 648b0d24010000  mov     ecx,dword ptr fs:[124h]//使ecx指向当前线程的kthread

83e88a63 8b553c          mov     edx,dword ptr [ebp+3Ch]//取出保存的TrapFrame框架

83e88a66 899128010000    mov     dword ptr [ecx+128h],edx//恢复kthread里的Frame

nt!KiServiceExit:

83e88a6c fa              cli//关闭中断

83e88a6d f6457202        test    byte ptr [ebp+72h],2

83e88a71 7506            jne     nt!KiServiceExit+0xd (83e88a79)

83e88a73 f6456c01        test    byte ptr [ebp+6Ch],1//执行APC的时机是在(系统调用、中断、或异常处理之后)从内核返回用户空间的途中 我们这里是系统调用返回的时候 如果先前模式是用户层 有用户APC请求正在等待执行(KTHREAD_PENDING_USER_APC是ApcState.KernelApcPending在KTHREAD数据结构中的位移)。 那么要提交apc请求(类似内核发送给用户层的”中断信号” 例如 用户层要异步读写文件 那么ReadFile调用完毕 继续执行别的去了 内核设备驱动程序收到io请求执行读写 读写完毕 就会通知用户程序 我已经读写完毕了 你需要暂时停止其他工作 先处理我读写后的数据 怎么通知用户层呢 就是APC回调了 每个线程2个队列 一个是内核apc队列(其中的回调函数是在内核) 一个是用户层apc队列(其中的回调函数在用户层)  在这里读写文件 是用户层APC 执行用户apc前(每次只执行队列的第一个) 必须先把内核apc队列里的所有函数都执行完毕后再执行用户apc队列的函数  如果是内核模式的apc 那么就只执行线程内核apc队列所有函数)

83e88a77 7467            je      nt!KiServiceExit+0x74 (83e88ae0)

83e88a79 648b1d24010000  mov     ebx,dword ptr fs:[124h]

83e88a80 f6430202        test    byte ptr [ebx+2],2

83e88a84 7408            je      nt!KiServiceExit+0x22 (83e88a8e)

83e88a86 50              push    eax

83e88a87 53              push    ebx

83e88a88 e8ce660a00      call    nt!KiCopyCounters (83f2f15b)

83e88a8d 58              pop     eax

83e88a8e c6433a00        mov     byte ptr [ebx+3Ah],0

83e88a92 807b5600        cmp     byte ptr [ebx+56h],0

83e88a96 7448            je      nt!KiServiceExit+0x74 (83e88ae0)

83e88a98 8bdd            mov     ebx,ebp

83e88a9a 894344          mov     dword ptr [ebx+44h],eax

83e88a9d c743503b000000  mov     dword ptr [ebx+50h],3Bh

83e88aa4 c7433823000000  mov     dword ptr [ebx+38h],23h

83e88aab c7433423000000  mov     dword ptr [ebx+34h],23h

83e88ab2 c7433000000000  mov     dword ptr [ebx+30h],0

83e88ab9 b901000000      mov     ecx,1//APC Level

83e88abe ff155cc1e483    call    dword ptr [nt!_imp_KfRaiseIrql (83e4c15c)]//提升irql 每个调用来自用户空间的内核函数执行完毕 都会提交APC(类似linux信号 发送给用户层 让用户层”中断” )

83e88ac4 50              push    eax

83e88ac5 fb              sti

83e88ac6 53              push    ebx

83e88ac7 6a00            push    0

83e88ac9 6a01            push    1

83e88acb e8e53d0700      call    nt!KiDeliverApc (83efc8b5)

83e88ad0 59              pop     ecx

83e88ad1 ff1558c1e483    call    dword ptr [nt!_imp_KfLowerIrql (83e4c158)]//不能主动降低irql 只能是升高irql后 再降低到原始irql 降低后 可能会发生线程切换 这个函数里面可能会执行DPC(调用KiDispatchInterrupt())最后会看看 KPCR得字段QuantumEnd是否为null 如果是 那么要切换线程了

83e88ad7 8b4344          mov     eax,dword ptr [ebx+44h]

83e88ada fa              cli

83e88adb eb9c            jmp     nt!KiServiceExit+0xd (83e88a79)

83e88add 8d4900          lea     ecx,[ecx]

83e88ae0 8b54244c        mov     edx,dword ptr [esp+4Ch]

83e88ae4 64891500000000  mov     dword ptr fs:[0],edx

83e88aeb 8b4c2448        mov     ecx,dword ptr [esp+48h]

83e88aef 648b3524010000  mov     esi,dword ptr fs:[124h]

83e88af6 888e3a010000    mov     byte ptr [esi+13Ah],cl

83e88afc f744242cff23ffff test    dword ptr [esp+2Ch],0FFFF23FFh

83e88b04 0f857e000000    jne     nt!KiSystemCallExit2+0x1c (83e88b88)

83e88b0a f744247000000200 test    dword ptr [esp+70h],20000h

83e88b12 0f85340a0000    jne     nt!KiExceptionExit+0x134 (83e8954c)

83e88b18 66f744246cf9ff  test    word ptr [esp+6Ch],0FFF9h

83e88b1f 0f84b9000000    je      nt!KiSystemCallExit2+0x72 (83e88bde)

83e88b25 66837c246c1b    cmp     word ptr [esp+6Ch],1Bh

83e88b2b 660fba64246c00  bt      word ptr [esp+6Ch],0

83e88b32 f5              cmc

83e88b33 0f8793000000    ja      nt!KiSystemCallExit2+0x60 (83e88bcc)

83e88b39 66837d6c08      cmp     word ptr [ebp+6Ch],8

83e88b3e 7405            je      nt!KiServiceExit+0xd9 (83e88b45)

83e88b40 8d6550          lea     esp,[ebp+50h]

83e88b43 0fa1            pop     fs

83e88b45 8d6554          lea     esp,[ebp+54h]

83e88b48 5f              pop     edi

83e88b49 5e              pop     esi

83e88b4a 5b              pop     ebx

83e88b4b 5d              pop     ebp

83e88b4c 66817c24088000  cmp     word ptr [esp+8],80h

83e88b53 0f870f0a0000    ja      nt!KiExceptionExit+0x150 (83e89568)

83e88b59 83c404          add     esp,4

83e88b5c f744240401000000 test    dword ptr [esp+4],1

nt!KiSystemCallExitBranch:

83e88b64 7506            jne     nt!KiSystemCallExit2 (83e88b6c)

83e88b66 5a              pop     edx

83e88b67 59              pop     ecx/

83e88b68 9d              popfd//出栈r3的eflags

83e88b69 ffe2            jmp     edx//跳到edx返回地址

nt!KiSystemCallExit:

83e88b6b cf              iretd

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

为什么内核中不能直接调用NtReadFile()?

因为一方面NtReadFile不是导出函数 另外就是 调用这些函数 需要系统堆栈上有框架 但是内核中直接调用的话 肯定没有为本次调用的框架存在 可能是其他框架 比如自陷框架 中断异常框架 所以导致直接调用NtReadFile出错 可以通过调用ZwReadFile()调用

以下是x86

nt!ZwReadFile:

83e874cc b811010000      mov     eax,111h//因为是内核直接调用 所以无须保存各个寄存器

83e874d1 8d542404        lea     edx,[esp+4]//使edx指向堆栈上的参数快

83e874d5 9c              pushfd   // eflags

83e874d6 6a08            push    8//cs

83e874d8 e831130000      call    nt!KiSystemService (83e8880e)//通过调用它来形成框架

83e874dd c22400          ret     24h

 

以下是x64

nt!ZwReadFile:

fffff800`03c6f6e0 488bc4          mov     rax,rsp

fffff800`03c6f6e3 fa              cli

fffff800`03c6f6e4 4883ec10        sub     rsp,10h

fffff800`03c6f6e8 50              push    rax

fffff800`03c6f6e9 9c              pushfq

fffff800`03c6f6ea 6a10            push    10h

fffff800`03c6f6ec 488d05dd310000  lea     rax,[nt!KiServiceLinkage (fffff800`03c728d0)]

fffff800`03c6f6f3 50              push    rax

fffff800`03c6f6f4 b803000000      mov     eax,3

fffff800`03c6f6f9 e902690000 jmp  nt!KiServiceInternal (fffff800`03c76000)//KiServiceLinkage 和KiServiceInternal 都是初始化系统服务的里面会调用KiSystemServiceStart->KiSystemServiceRepeat(选择ssdt 还是shadow ssdt执行调用)->KiSystemServiceExit(退出调用)

fffff800`03c6f6fe 6690            xchg    ax,ax

 

 

     

 

 最近身体出问题了 所以进度很慢  希望大家多多支持

 


上传的附件 windows7内核分析之x86&x64第二章系统调用.docx

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功