[原创]windows7内核分析之x86&x64第二章系统调用
2.1内核与系统调用
上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit 在 compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32位cpu是x86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可以执行以前的16位程序 也包含了保护模式:可以执行32位的程序 64位cpu是long模式:分为两种 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 从逻辑地址转换为线性地址 最后访问线性地址 会根据四级表转换为物理地址)和内核函数地址和内核堆栈地址 这样就省去了查找idt表 TSS段 获得内核函数地址和内核堆栈地址 节省了大量时间
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X64的syscall 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X86的sysenter 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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//r0的fs指向kpcr fs和cs一样都是段选择子 所以这六30h是GDT的kpcr的索引
表②:

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

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