-
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 执行环境进行了设置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 指令
1
2
3
4
5
6
|
; 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 值
那么,对于要进入系统调用的代码来说:
1
2
3
4
5
|
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 原来的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
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 讲解 参考总结+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1
2
3
4
5
6
7
8
|
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的索引
表②:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
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的
1
2
3
4
5
6
7
|
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的
1
2
3
4
5
6
7
8
9
10
11
12
|
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 |
最近身体出问题了 所以进度很慢 希望大家多多支持