Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html
Windows系统调用中的现场保存
我们之前介绍过三环进零环的步骤,通过中断或者快速调用来实现。
但是我们是否考虑过CPU从三环进入零环时,其三环的寄存器该如何保存。
这一篇文件就来介绍其系统调用中的(三环)现场保存的问题。
一、几个重要的结构体介绍
1. _Ktrap_frame
该结构体简单来说用于三环的寄存器保存,存储于零环,由操作系统维护,每个线程都有自己的 _Ktrap_frame 结构体(ethread+0x108处)。
详细信息可以查看这篇文章:解析windows内核每日一讲 陷阱调度
我们之前讲过进入0环时获取新的四个寄存器非常重要, SS\CS\EIP\ESP
如下图,当进入零环时,操作系统会获取ESP值,该值指向_Ktrap_frame结构体,将旧的SS\CS\EIP\ESP\EFLAG依次压入(0x78-0x68处),之后ESP +0x070 ErrCode处。
之后便进入获取的EIP来执行内核函数。
2. _ETHREAD 结构体
该结构体保存了和线程相关的信息,位于0环(其并非三环的 TEB,线程环境块)
在_ETHREAD结构体第一个成员是 _KTHRAD,可以看出其大小0x200,里面保存了线程中的一些信息
kd > dt _ethread
ntdll!_ETHREAD
+ 0x000 Tcb : _KTHREAD
+ 0x200 CreateTime : _LARGE_INTEGER
····
kd > dt _kthread
ntdll!_KTHREAD
+ 0x000 Header : _DISPATCHER_HEADER
···
+0x128 TrapFrame : Ptr32 _KTRAP_FRAME
3. KPCR结构体(kernel processor control region 内核线程控制区)
有一篇文章,里面大体介绍了该结构体 --> [Windows内核分析]KPCR结构体介绍 (CPU控制区 Processor Control Region)
简单来说,KPCR结构体中保存着 关于CPU 的信息,一个核有一个自己私有的KPCR结构体,八核则每个核有自己的单独的KPCR结构体
我们需要用到最后一个成员中的中的CurrentThread来获取当前线程的_KTHREAD结构体。
kd > dt _kpcr
ntdll!_KPCR
+ 0x000 NtTib : _NT_TIB
+ 0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
····
+0x120 PrcbData : _KPRCB
kd > dt _KPRCB
ntdll!_KPRCB
+ 0x000 MinorVersion : Uint2B
+ 0x002 MajorVersion : Uint2B
+ 0x004 CurrentThread : Ptr32 _KTHREAD
+ 0x008 NextThread : Ptr32 _KTHREAD
···
二、分析 nt!KiSystemService 内核函数
该函数位于ntkrnlpa.exe真正的内核中(并非ntdll.dll),一开始希望使用IDA反汇编查看其代码,但是无法显示。
我们只能采取"曲线救国"的方法,通过int 2eh中断号查找GDT表,从表中获取中断描述符,将中断描述符拼接拆分,获取 函数的EIP 地址。
之后,我们使用 Windbg 的U指令,来查看该其该函数在内存中的反汇编代码
反汇编代码可能有点长,我们在正式分析之前需要明确几个注意事项。
0)这些反汇编代码的目的就是将 _Ktrap_frame 结构体中的寄存器填满,然后将[KTHREAD+0x128]处旧的TRAPFRAME切换为这个新的KTRAPFRAME然后调用内核函数。
1)函数开始时ESP位于_Ktrap_framec+0x070 ErrCode处。ErroCode这里没有,因此可以看待汇编代码中第一行使用 push 0。
2)FS寄存器在0环时是指向自己的KPCR,而在3环时保存的是该线程的TEB结构体。(R3可以查看:利用C++实现模块隐藏)
因此在反汇编代码 83e8cff6 、83e8cffb 是手动将FS寄存器存储KPCR。
原理是以30h作为段选择子查找GDT这张表,找出段描述符,然后加载到fs段寄存器中。
3) 0x83e8d007处 KPCR:[124] 为当前线程的ETHREAD结构体(看一中的表,KTHREAD也即ETHREAD的头部),将其存储到寄存器esi中,之后要经常使用。
4)0x83e8d026处修改esp,将其指向 _Ktrap_frame 结构体第一个成员,0x83e8d036 处也将ebp修改为 _Ktrap_frame 结构体第一个成员。
5)因为 esi 指向ETHREAD, [esi+128h] 则表示当前线程的_Ktrap_frame,在进入内核时,其保存了一个旧的frame。
0x83e8d038将获取旧的_Ktrap_frame
0x83e8d03e将旧的Frame放入新的Frame的edx寄存器中
0x83e8d049将新的Frame挂靠在ETHREAD+0x128处,这样完成了新旧替换。
下图三个箭头依次对应上面三个阶段,这样通过FS寄存器就可以查找到该FRAME了,回去只需要找到这里完成替换即可。
6)Frame前部分成员和调试有关,因此0x83e8d045这里需要判断是否在调试状态,如果处于调试状态,则jmp进一个地址,将寄存器入栈再回来。
硬件调试,其实就是通过这个部分。对于反调试,这里可以做些文章...
7)还有部分涉及权限切换,0x83e8d030 比如有些API可以让3环也可以让0环权限调用,但有的不让(看保护模式调用门等知识),此时就会进行位运算判断"先前模式"的权限级别。
8)当这些工作全部完成之后,会执行 83e8d06d e9dd000000 jmp nt!KiFastCallEntry+0x8f (83e8d14f)
对,你没看错,就是快速调用时的函数,相当于中断多走了一步,处理了上面的一些信息,而快速调用提前就可以处理好直接调用 nt!KiFastCallEntry+0x8f函数。
三、nt!KiSystemService函数的反汇编解读
1 nt!KiSystemService: 2 // 压栈 按照 _Ktrap_frame 结构中寄存的值 3 83e8cfee 6a00 push 0 // errorcode 填入零来进行对齐 4 83e8cff0 55 push ebp 5 83e8cff1 53 push ebx 6 83e8cff2 56 push esi 7 83e8cff3 57 push edi 8 83e8cff4 0fa0 push fs 9 10 // 根据30h这个值作为段选择子,查找gdt这张表,找出段描描述符,将其加载到fs段寄存器中 11 // 30h -> 0011 0000 -> index = 00110 | TI = 0 |00 -> TI=0查LDT表,查索引为6查出来的是 834093f7`ac003748 这个值。 12 // 834093f7`ac003748 段描述符,根据段描述符属性拼接起来的 83f7ac00 13 // 83f7ac00 指向的是 KPCR,FS在零环的时候,指向KPCR,不再是三环时线程TEB这个结构体 14 83e8cff6 bb30000000 mov ebx,30h 15 83e8cffb 668ee3 mov fs,bx 16 83e8cffe bb23000000 mov ebx,23h 17 83e8d003 8edb mov ds,bx 18 83e8d005 8ec3 mov es,bx 19 20 21 // 有些API 0环和三环都可以调用,通过下面操作可以查看原来调用的是0环还是3环,再进行调用API的权限验证 22 // 验证完该线程,如果是0环,则bl为0;如果是3环,则bl=1 23 83e8d007 648b3524010000 mov esi,dword ptr fs:[124h] // 将当前CPU正在跑的线程放到esi中 24 83e8d00e 64ff3500000000 push dword ptr fs:[0] // 把老的EXCEPTION_LIST 存入 _Ktrap_frame 结构体中(fs上边那部分) 25 83e8d015 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh // 将现在EXCPTION_LIST放入异常链表 26 83e8d020 ffb63a010000 push dword ptr [esi+13Ah] // 将老的"先前模式"也保存到栈中 27 83e8d026 83ec48 sub esp,48h // 将当前ESP提升到 _Ktrap_frame 第一个成员 28 83e8d029 8b5c246c mov ebx,dword ptr [esp+6Ch] // 取出3环压入的参数CS _KTRAP_FRAME + 0x6c,指向三环原来的CS值 29 83e8d02d 83e301 and ebx,1 // 将原来CS的值进行与运算, 0环最低为为0,3环最低为为1 30 83e8d030 889e3a010000 mov byte ptr [esi+13Ah],bl // 新的"先前模式",原来是三环模式为1;原来是零环模式为0 31 83e8d036 8bec mov ebp,esp // ebp 同样指向_Ktrap_frame第一个成员 32 83e8d038 8b9e28010000 mov ebx,dword ptr [esi+128h] // esi通过0x83e8d007处[KPCR+124],指向Ethread // Ethread+128h 指向的是 _KTRAP_FRAME这个结构体指针 33 83e8d03e 895d3c mov dword ptr [ebp+3Ch],ebx // 将该 _KTRAP_FRAME地址暂时保存在 edx 中 34 83e8d041 83652c00 and dword ptr [ebp+2Ch],0 35 83e8d045 f64603df test byte ptr [esi+3],0DFh // 判断是否属于调试状态 一个位 +0x003 DebugActive : UChar 36 83e8d049 89ae28010000 mov dword ptr [esi+128h],ebp // 因为新的 trap_frame地址已经发生变化,所以要将新的trapFrame放到线程放到这个位置 37 38 83e8d04f fc cld 39 83e8d050 0f859afeffff jne nt!Dr_kss_a (83e8cef0) // 在0x83e8d045处根据判断结果,如果是调试状态,则进行跳转。 40 // 若为调试,则跳过去将调试相关的寄存器也存入_KTRAP_FRAME这个结构体 41 // 否则不是调试器,则将中是-1。(硬件断点使用这个,反调试很管用) 42 83e8d056 8b5d60 mov ebx,dword ptr [ebp+60h] // 3环的EBP 43 83e8d059 8b7d68 mov edi,dword ptr [ebp+68h] // 3环的EIP 44 83e8d05c 89550c mov dword ptr [ebp+0Ch],edx // edx存放着三环参数的指针 45 // {mov edx,esp;sysenter} 46 83e8d05f c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h // 操作系统需要用的标志 47 83e8d066 895d00 mov dword ptr [ebp],ebx // 3环的ebp存储到DbgEbp的位置 48 83e8d069 897d04 mov dword ptr [ebp+4],edi // 3环的eip存放到 DbgEip 49 83e8d06c fb sti // 关闭中断 50 83e8d06d e9dd000000 jmp nt!KiFastCallEntry+0x8f (83e8d14f) // 取出系统调用号,3环传进来的。 51 // 注意,这个就是快速调用首先进入的函数,相当于中断多走了这一步,系统调用直接调用。
四、nt!KiFastCallEntry的反汇编代码
该反汇编代码阅读量同样很大,找时间一定给读出来,如果感兴趣可以自行百度相关解读。
1 nt!KiFastCallEntry: 2 83e8d0c0 b923000000 mov ecx,23h 3 83e8d0c5 6a30 push 30h 4 83e8d0c7 0fa1 pop fs 5 83e8d0c9 8ed9 mov ds,cx 6 83e8d0cb 8ec1 mov es,cx 7 83e8d0cd 648b0d40000000 mov ecx,dword ptr fs:[40h] 8 83e8d0d4 8b6104 mov esp,dword ptr [ecx+4] 9 83e8d0d7 6a23 push 23h 10 83e8d0d9 52 push edx 11 83e8d0da 9c pushfd 12 83e8d0db 6a02 push 2 13 83e8d0dd 83c208 add edx,8 14 83e8d0e0 9d popfd 15 83e8d0e1 804c240102 or byte ptr [esp+1],2 16 83e8d0e6 6a1b push 1Bh 17 83e8d0e8 ff350403dfff push dword ptr ds:[0FFDF0304h] 18 83e8d0ee 6a00 push 0 19 83e8d0f0 55 push ebp 20 83e8d0f1 53 push ebx 21 83e8d0f2 56 push esi 22 83e8d0f3 57 push edi 23 83e8d0f4 648b1d1c000000 mov ebx,dword ptr fs:[1Ch] 24 83e8d0fb 6a3b push 3Bh 25 83e8d0fd 8bb324010000 mov esi,dword ptr [ebx+124h] 26 83e8d103 ff33 push dword ptr [ebx] 27 83e8d105 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh 28 83e8d10b 8b6e28 mov ebp,dword ptr [esi+28h] 29 83e8d10e 6a01 push 1 30 83e8d110 83ec48 sub esp,48h 31 83e8d113 81ed9c020000 sub ebp,29Ch 32 83e8d119 c6863a01000001 mov byte ptr [esi+13Ah],1 33 83e8d120 3bec cmp ebp,esp 34 83e8d122 7597 jne nt!KiFastCallEntry2+0x49 (83e8d0bb) 35 83e8d124 83652c00 and dword ptr [ebp+2Ch],0 36 83e8d128 f64603df test byte ptr [esi+3],0DFh 37 83e8d12c 89ae28010000 mov dword ptr [esi+128h],ebp 38 83e8d132 0f8538feffff jne nt!Dr_FastCallDrSave (83e8cf70) 39 83e8d138 8b5d60 mov ebx,dword ptr [ebp+60h] 40 83e8d13b 8b7d68 mov edi,dword ptr [ebp+68h] 41 83e8d13e 89550c mov dword ptr [ebp+0Ch],edx 42 83e8d141 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h 43 83e8d148 895d00 mov dword ptr [ebp],ebx 44 83e8d14b 897d04 mov dword ptr [ebp+4],edi 45 83e8d14e fb sti 46 83e8d14f 8bf8 mov edi,eax 47 83e8d151 c1ef08 shr edi,8 48 83e8d154 83e710 and edi,10h 49 83e8d157 8bcf mov ecx,edi 50 83e8d159 03bebc000000 add edi,dword ptr [esi+0BCh] 51 83e8d15f 8bd8 mov ebx,eax 52 83e8d161 25ff0f0000 and eax,0FFFh 53 83e8d166 3b4708 cmp eax,dword ptr [edi+8] 54 83e8d169 0f8333fdffff jae nt!KiBBTUnexpectedRange (83e8cea2) 55 83e8d16f 83f910 cmp ecx,10h 56 83e8d172 751a jne nt!KiFastCallEntry+0xce (83e8d18e) 57 83e8d174 8b8e88000000 mov ecx,dword ptr [esi+88h] 58 83e8d17a 33f6 xor esi,esi 59 83e8d17c 0bb1700f0000 or esi,dword ptr [ecx+0F70h] 60 83e8d182 740a je nt!KiFastCallEntry+0xce (83e8d18e) 61 83e8d184 52 push edx 62 83e8d185 50 push eax 63 83e8d186 ff154c99fb83 call dword ptr [nt!KeGdiFlushUserBatch (83fb994c)] 64 83e8d18c 58 pop eax 65 83e8d18d 5a pop edx 66 83e8d18e 64ff05b0060000 inc dword ptr fs:[6B0h] 67 83e8d195 8bf2 mov esi,edx 68 83e8d197 33c9 xor ecx,ecx 69 83e8d199 8b570c mov edx,dword ptr [edi+0Ch] 70 83e8d19c 8b3f mov edi,dword ptr [edi] 71 83e8d19e 8a0c10 mov cl,byte ptr [eax+edx] 72 83e8d1a1 8b1487 mov edx,dword ptr [edi+eax*4] 73 83e8d1a4 2be1 sub esp,ecx 74 83e8d1a6 c1e902 shr ecx,2 75 83e8d1a9 8bfc mov edi,esp 76 83e8d1ab 3b351c97fb83 cmp esi,dword ptr [nt!MmUserProbeAddress (83fb971c)] 77 83e8d1b1 0f832e020000 jae nt!KiSystemCallExit2+0xa5 (83e8d3e5) 78 83e8d1b7 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 79 83e8d1b9 f6456c01 test byte ptr [ebp+6Ch],1 80 83e8d1bd 7416 je nt!KiFastCallEntry+0x115 (83e8d1d5) 81 83e8d1bf 648b0d24010000 mov ecx,dword ptr fs:[124h] 82 83e8d1c6 8b3c24 mov edi,dword ptr [esp] 83 83e8d1c9 89993c010000 mov dword ptr [ecx+13Ch],ebx 84 83e8d1cf 89b92c010000 mov dword ptr [ecx+12Ch],edi 85 83e8d1d5 8bda mov ebx,edx 86 83e8d1d7 f6050869f88340 test byte ptr [nt!PerfGlobalGroupMask+0x8 (83f86908)],40h 87 83e8d1de 0f954512 setne byte ptr [ebp+12h] 88 83e8d1e2 0f858c030000 jne nt!KiServiceExit2+0x17b (83e8d574) 89 83e8d1e8 ffd3 call ebx 90 83e8d1ea f6456c01 test byte ptr [ebp+6Ch],1 91 83e8d1ee 7434 je nt!KiFastCallEntry+0x164 (83e8d224) 92 83e8d1f0 8bf0 mov esi,eax 93 83e8d1f2 ff156801e583 call dword ptr [nt!_imp__KeGetCurrentIrql (83e50168)] 94 83e8d1f8 0ac0 or al,al 95 83e8d1fa 0f853b030000 jne nt!KiServiceExit2+0x142 (83e8d53b) 96 83e8d200 8bc6 mov eax,esi 97 83e8d202 648b0d24010000 mov ecx,dword ptr fs:[124h] 98 83e8d209 f68134010000ff test byte ptr [ecx+134h],0FFh 99 83e8d210 0f8543030000 jne nt!KiServiceExit2+0x160 (83e8d559) 100 83e8d216 8b9184000000 mov edx,dword ptr [ecx+84h] 101 83e8d21c 0bd2 or edx,edx 102 83e8d21e 0f8535030000 jne nt!KiServiceExit2+0x160 (83e8d559) 103 83e8d224 8be5 mov esp,ebp 104 83e8d226 807d1200 cmp byte ptr [ebp+12h],0 105 83e8d22a 0f8550030000 jne nt!KiServiceExit2+0x187 (83e8d580) 106 83e8d230 648b0d24010000 mov ecx,dword ptr fs:[124h] 107 83e8d237 8b553c mov edx,dword ptr [ebp+3Ch] 108 83e8d23a 899128010000 mov dword ptr [ecx+128h],edx