Windows XP 系统调用(一)

ʕ •ᴥ•ʔ ɔ:

1 Windows API

  1. Application Programming Interface,简称 API 函数。
  2. 主要是存放在 C:\WINDOWS\system32下面所有的dll导出的函数。
  3. 几个重要的DLL
  • Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等。
  • User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等。
  • GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用了其中的函数来画这个窗口。
  • Ntdll.dll:大多数API都会通过这个DLL进入内核(0环)

R3下无论如何调用,均无法绕过SSDT HOOK,R0下调用Nt*可以绕过SSDT HOOK。Rtl* 函数是windows ddk提供的编写驱动的函数。

内核驱动全部集合

2 3环进0环

2.1 分析ReadProcessMemory3环调用过程

分析kernel32.dll导出函数ReadProcessMemory(x,x,x,x)调用流程。

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
.text:7C8021C6 ; ---------------------------------------------------------------------------
.text:7C8021CB align 10h
.text:7C8021D0 ; Exported entry 682. ReadProcessMemory
.text:7C8021D0
.text:7C8021D0 ; =============== S U B R O U T I N E =======================================
.text:7C8021D0
.text:7C8021D0 ; Attributes: bp-based frame
.text:7C8021D0
.text:7C8021D0 ; BOOL __stdcall ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead)
.text:7C8021D0 public _ReadProcessMemory@20
.text:7C8021D0 _ReadProcessMemory@20 proc near ; CODE XREF: GetProcessVersion(x)+2F12F↓p
.text:7C8021D0 ; GetProcessVersion(x)+2F14E↓p ...
.text:7C8021D0
.text:7C8021D0 hProcess = dword ptr 8
.text:7C8021D0 lpBaseAddress = dword ptr 0Ch
.text:7C8021D0 lpBuffer = dword ptr 10h
.text:7C8021D0 nSize = dword ptr 14h
.text:7C8021D0 lpNumberOfBytesRead= dword ptr 18h
.text:7C8021D0
.text:7C8021D0 mov edi, edi
.text:7C8021D2 push ebp
.text:7C8021D3 mov ebp, esp
.text:7C8021D5 lea eax, [ebp+nSize]
.text:7C8021D8 push eax ; NumberOfBytesRead
.text:7C8021D9 push [ebp+nSize] ; NumberOfBytesToRead
.text:7C8021DC push [ebp+lpBuffer] ; Buffer
.text:7C8021DF push [ebp+lpBaseAddress] ; BaseAddress
.text:7C8021E2 push [ebp+hProcess] ; ProcessHandle
.text:7C8021E5 call ds:__imp__NtReadVirtualMemory@20 ; NtReadVirtualMemory(x,x,x,x,x)
.text:7C8021EB mov ecx, [ebp+lpNumberOfBytesRead]
.text:7C8021EE test ecx, ecx
.text:7C8021F0 jnz short loc_7C8021FD
.text:7C8021F2
.text:7C8021F2 loc_7C8021F2: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+32↓j
.text:7C8021F2 test eax, eax
.text:7C8021F4 jl short loc_7C802204
.text:7C8021F6 xor eax, eax
.text:7C8021F8 inc eax
.text:7C8021F9
.text:7C8021F9 loc_7C8021F9: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+3C↓j
.text:7C8021F9 pop ebp
.text:7C8021FA retn 14h
.text:7C8021FD ; ---------------------------------------------------------------------------
.text:7C8021FD
.text:7C8021FD loc_7C8021FD: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+20↑j
.text:7C8021FD mov edx, [ebp+nSize]
.text:7C802200 mov [ecx], edx
.text:7C802202 jmp short loc_7C8021F2
.text:7C802204 ; ---------------------------------------------------------------------------
.text:7C802204
.text:7C802204 loc_7C802204: ; CODE XREF: ReadProcessMemory(x,x,x,x,x)+24↑j
.text:7C802204 push eax ; Status
.text:7C802205 call _BaseSetLastNTError@4 ; BaseSetLastNTError(x)
.text:7C80220A xor eax, eax
.text:7C80220C jmp short loc_7C8021F9
.text:7C80220C _ReadProcessMemory@20 endp
.text:7C80220C
.text:7C80220C ; ---------------------------------------------------------------------------

双击查看.text:7C8021E5 call ds:__imp__NtReadVirtualMemory@20 ; NtReadVirtualMemory(x,x,x,x,x),结果如下:

1
2
3
4
5
6
7
.idata:7C801418 ; NTSTATUS __stdcall NtReadVirtualMemory(HANDLE ProcessHandle, 
PVOID BaseAddress,
PVOID Buffer,
SIZE_T NumberOfBytesToRead,
PSIZE_T NumberOfBytesRead
)
.idata:7C801418 extrn __imp__NtReadVirtualMemory@20:dword

__imp__表示这里的NtReadVirtualMemory函数是从导入表导入的,在IDA导入表中查看可得NtReadVirtualMemory:位于ntdll.dll。

1.png

分析得如下结论:

  • ReadProcessMemory:位于kernel32.dll
  • NtReadVirtualMemory:位于ntdll.dll
1
2
3
4
5
6
7
8
9
10
11
BOOL __stdcall ReadProcessMemory(
[IN] HANDLE hProcess, //目标进程的句柄,该句柄必须对目标进程具有PROCESS_VM_READ的访问权限。
[IN] LPCVOID lpBaseAddress, //从目标进程中读取数据的起始地址。
[OUT] LPVOID lpBuffer, //用来接收数据的缓存区地址。
[IN] SIZE_T nSize, // 从目标进程读取数据的字节数。如果被指定为NULL,那么将忽略此参数。
[OUT] SIZE_T *lpNumberOfBytesRead)//实际被读取数据大小的存放地址。
{
...
NtReadVirtualMemory(hProcess,lpBaseAddress,Buffer,nSize,&nSize);
...
}

进入到ntdll.dll的导出表查看NtReadVirtualMemory函数,发现其调用了0x7FFE0300处的函数。

1
2
3
4
5
6
7
8
9
.text:7C92D9E0 ; __stdcall NtReadVirtualMemory(x, x, x, x, x)
.text:7C92D9E0 public _NtReadVirtualMemory@20
.text:7C92D9E0 _NtReadVirtualMemory@20 proc near ; CODE XREF: LdrFindCreateProcessManifest(x,x,x,x,x)+1CC↓p
.text:7C92D9E0 ; LdrCreateOutOfProcessImage(x,x,x,x)+7C↓p ...
.text:7C92D9E0 mov eax, 0BAh ; NtReadVirtualMemory
.text:7C92D9E5 mov edx, 7FFE0300h
.text:7C92D9EA call dword ptr [edx]
.text:7C92D9EC retn 14h
.text:7C92D9EC _NtReadVirtualMemory@20 endp

可以看到在这里调用了一个地址:0x7FFE0300(其中eax存的值为系统服务号,即一个0环函数的编号),具体该地址是什么函数需要学习下一节。

2.2 _KUSER_SHARED_DATA结构

_KUSER_SHARED_DATA结构为一块3环和0环共享的内存块(共享同一个物理页)
  1. 在 User 层和 Kernel 层分别定义了一个_KUSER_SHARED_DATA结构区域,用于 User 层和 Kernel 层共享某些数据。
  2. 它们使用固定的地址值映射,_KUSER_SHARED_DATA结构区域在 User 和 Kernel 层地址分别为:
    • User 层地址为:0x7FFE0000 只读
    • Kernnel 层地址为:0xFFDF0000 可读可写

特别说明:虽然指向的是同一个物理页,但在User 层是只读的,在Kernnel层是可读可写的。同一个物理页映射了两个线性地址,这两个线性地址的对应的访问权限不同。(PDE、PTE的R/W位),使用dd 0x7FFE0000dd 0xFFDF0000里面的数据是一样的。

该结构如下:

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
kd> dt _KUSER_SHARED_DATA 0x7FFE0000
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0x1572
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 Reserved2 : [8] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 5
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 0
+0x2dc DismountCount : 0
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0
+0x2e8 NumberOfPhysicalPages : 0x3ff77
+0x2ec SafeBootMode : 0 ''
+0x2f0 TraceLogging : 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x83dcba5

在偏移0x300的地方即为NtReadVirtualMemory函数调用的0x7FFE0300处的函数,为一个SystemCall(系统调用函数)。该函数但汇编代码为:

1
2
3
4
5
6
7
8
9
10
11
12
kd> u 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter
ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret
7c92e4f5 8da42400000000 lea esp,[esp]
7c92e4fc 8d642400 lea esp,[esp]
ntdll!KiIntSystemCall:
7c92e500 8d542408 lea edx,[esp+8]
7c92e504 cd2e int 2Eh
7c92e506 c3 ret

可以看到该函数使用了指令sysenter进入到0环,同时EDX指向栈顶。该指令具体的实现请看下一节。

2.3 SystemCall

SystemCall是两个能从3环进入到0环的函数的名称。系统调用有中断调用快速调用两种方式,中断调用是通过中断门进0环,此过程需要查IDT表和TSS表;快速调用则是使用sysenter指令进0环,这种方式不需要查内存,而是直接从CPU的MSR寄存器中获取所需数据,所以称为快速调用。

怎么判断系统是否支持快速调用?
当通过 eax = 1 来执行cpuid指令时,处理器的特征信息被放在 ecxedx 寄存器中,其中edx包含了一个SEP位(EDX值的第12位,下标为11),该位指明了当前处理器知否支持sysenter/sysexit指令(SEP == 1,支持)

  • SEP == 1,支持。SystemCall为:
    ntdll.dll!KiFastSystemCall()
  • SEP == 0,不支持。SystemCall为:
    ntdll.dll!KiIntSystemCall()
1
2
3
4
5
6
7
8
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter

ntdll!KiIntSystemCall:
7c92e500 8d542408 lea edx,[esp+8]
7c92e504 cd2e int 2Eh
7c92e506 c3 ret

实验:

将eax = 1,ecx = 0、edx = 0,执行cpuid指令前:

2.png

执行cpuid指令后如下图,edx = 0x1F8BFBFF,B = 1011,第12位为1,说明支持快速调用。

3.png

关于KiFastSystemCall()KiIntSystemCall()这两个函数:

  1. 这两个函数都叫做SystemCall。如果系统支持快速调用,就会把KiFastSystemCall()函数的地址写到0x7FFE0300处。不支持就会把KiIntSystemCall()的函数地址写到0x7FFE0300处。
  2. 这两个函数的相同点:都是找新的CS、SS、ESP、EIP。不同点:找的方式不同。

2.4 3环进0环需要更改的4个寄存器

3环进0环需要更改哪些寄存器?

  1. CS的权限由3变为0 ,意味着需要新的CS
  2. SS与CS的权限永远一致,需要新的SS
  3. 权限发生切换的时候,堆栈也一定会切换,需要新的ESP
  1. 进0环后代码的位置,需要新的EIP

简单复习一下,中断门进0环时,我们在IDT表里填的中断门描述符,然后根据中断门描述符中的段选择子找到一个代码段描述符去提权,包含了0环的CS和EIP,而SS和0环的ESP是在TSS里存储的,当时我们还有一个结论,Windows里不使用任务,所以TSS的唯一作用就是提权时提供ESP0、SS0和EIP。

现在,我们知道了进0环需要更改的4个寄存器,接下来分析 KiFastSystemCall 和 KiIntSystemCall 时,只要明白一点,这两个函数做的事情就是更改这4个寄存器。

2.4 重写ReadProcessMemory和WriteProcessMemory

重写API最重要的六点:

  1. 进0环前函数服务号。
  2. 进0环的方式(中断门/快速调用)。
  3. 进0环前寄存器的值(EAX、EDX)。
  4. 进0环前堆栈的值。
    • 中断门进入0环时:中断门本身会将SS3、ESP3、EFLAG3、CS3、返回地址压栈,故不需要考虑堆栈恢复(被调用的API会自己POP返回地址)。
    • 快速调用进入0环时:需要自己压栈返回地址,且该返回地址要紧挨着原来的参数,EBP指向的位置应该是第二个返回地址的坑,需要先将里面的值保存起来,还要保存起来原来的ESP,实际上就是在模拟中断门保存ESP和返回地址
  5. 进0环前返回地址。
  6. 大部分API调用约定都是__stdcall,被调用的API会自己平衡压入参数的堆栈。
  1. 服务号:函数服务号 = 0xBA;
  2. 进0环的方式:
    • 中断门int 0x2e:EAX = 服务号 = 0xBA、EDX = 第一个参数的地址
    • 快速调用sysenter:EAX = 服务号 = 0xBA。EDX = 返回地址的地址
  3. 寄存器的值:第2点中所述。
  4. 进0环前堆栈的值:本函数仅需注意函数的返回地址。
  5. 返回地址:中断门无需提供返回地址(返回地址被压栈)、快速调用需要自己压栈返回地址,且该返回地址要紧挨着原来的参数,EBP指向的坑需要放入当前的返回地址,原来的值需要先保存起来,还要保存起来原来的ESP,实际上就是在模拟中断门保存ESP和返回地址。。
  6. 注入函数的堆栈平衡。

重写ReadProcessMemory,分析:

先观察原来API调用的堆栈变化:

7.png

可以看到,在函数ReadProcessMemory(x,x,x,x)中重新把自己的参数压栈了一次,目的是调用NtReadVirtualMemory(x,x,x,x,x),然后调用一次函数压栈一次返回地址,总共调用了两次函数。所以自己重写API时,要让两个返回地址紧挨着堆栈里的参数。

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__asm
{
//模拟中断门调用,保存esp和返回地址
mov eax,[ebp]; //ebp的坑应该是第二个函数返回地址,里面的值需要先保存起来
mov dwRet,eax;
mov dwESP,esp; //esp的值也需要保存起来

mov eax,0xBA; //设置系统服务号
lea ebx, NtReadVirtualMemoryRet;//call KiFastSystemCall的返回地址
mov [ebp],ebx; //把新的返回地址填到返回地址的坑
mov edx,ebp; //edx = 返回地址的地址
_emit 0x0F; //sysenter
_emit 0x34;
NtReadVirtualMemoryRet:
mov ntStatus,eax;//函数返回值
mov esp,dwESP;
mov eax,dwRet;
mov [ebp],eax;
}

进入上面的函数时,堆栈情况如下:故当前EBP指向的位置(坑)需要放入第二个返回地址,需要先将里面的值保存起来,从0环返回时恢复。由于快速调用返回时ESP会变化,故ESP的值也需要保存起来(有必要时EBP的值也需要保存起来)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Debug模式下该函数的反汇编代码:
004010C0 push ebp
004010C1 mov ebp,esp
004010C3 sub esp,4Ch
004010C6 push ebx
004010C7 push esi
004010C8 push edi
004010C9 lea edi,[ebp-4Ch]
004010CC mov ecx,13h
004010D1 mov eax,0CCCCCCCCh
004010D6 rep stos dword ptr [edi]
...
此时的堆栈情况:
0012FECC 80 FF 12 00 .... <--EBP
0012FED0 65 E2 40 00 e.@. <--EBP+4返回地址
0012FED4 FF FF FF FF .... <--参数1
0012FED8 64 FF 12 00 d... <--参数2
0012FEDC 38 FF 12 00 8... <--参数3
0012FEE0 18 00 00 00 .... <--参数4
0012FEE4 7C FF 12 00 |... <--参数5
0012FEE8 00 00 00 00 ....
0012FEEC 00 00 00 00 ....

总结有:快速调用需要保存原来的ESP、返回地址(模拟中断门调用)。

注意,VC、VS内联汇编不支持sysenter指令,可以用_emit代替。

WriteProcessMemory逆向分析可知,会先调用NtProtectVirtualMemory(x,x,x,x,x)对要写的内存进行权限检查等工作,如果可写就会调用__imp__NtWriteVirtualMemory@20 ; NtWriteVirtualMemory(x,x,x,x,x)进行内存写入。

1
2
3
4
5
6
7
8
9
.text:7C92DF90 ; __stdcall NtWriteVirtualMemory(x, x, x, x, x)
.text:7C92DF90 public _NtWriteVirtualMemory@20
.text:7C92DF90 _NtWriteVirtualMemory@20 proc near ; CODE XREF: RtlInitializeContext(x,x,x,x,x)+AA↓p
.text:7C92DF90 ; RtlCreateUserProcess(x,x,x,x,x,x,x,x,x,x)+1F2↓p ...
.text:7C92DF90 mov eax, 115h ; NtWriteVirtualMemory
.text:7C92DF95 mov edx, 7FFE0300h
.text:7C92DF9A call dword ptr [edx]
.text:7C92DF9C retn 14h
.text:7C92DF9C _NtWriteVirtualMemory@20 endp

重写实现代码如下:

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
168
169
170
171
172
173
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>

// 读进程内存(中断门调用)
BOOL WINAPI ReadProcessMemory_INT(HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead
)
{
DWORD NtStatus;
__asm
{
// 直接模拟 KiIntSystemCall
lea edx,hProcess; // 要求 edx 存储最后入栈的参数
mov eax, 0xBA;
int 0x2E;
mov NtStatus, eax;
}
if (lpNumberOfBytesRead != NULL)
{
*lpNumberOfBytesRead = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}

// 读进程内存(快速调用)
BOOL WINAPI ReadProcessMemory_FAST(HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead
)
{
DWORD ntStatus;
DWORD dwRet;
DWORD dwESP;
__asm
{
//模拟中断门调用,保存esp和返回地址
mov eax,[ebp]; //ebp的坑应该是第二个函数返回地址,里面的值需要先保存起来
mov dwRet,eax;
mov dwESP,esp; //esp的值也需要保存起来

mov eax,0xBA; //设置系统服务号
lea ebx, NtReadVirtualMemoryRet;//call KiFastSystemCall的返回地址
mov [ebp],ebx; //把新的返回地址填到返回地址的坑
mov edx,ebp; //edx = 返回地址的地址
_emit 0x0F; //sysenter
_emit 0x34;
NtReadVirtualMemoryRet:
mov ntStatus,eax;//函数返回值
mov esp,dwESP; //恢复esp
mov eax,dwRet;
mov [ebp],eax; //恢复原来的ebp坑里面的值
}
if (lpNumberOfBytesRead != NULL)
{
*lpNumberOfBytesRead = nSize;
}
// 错误检查
if (ntStatus < 0)
{
return FALSE;
}
return TRUE;
}

// 写进程内存(中断门调用)
BOOL WINAPI WriteProcessMemory_INT(HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD
lpNumberOfBytesRead
)
{
LONG NtStatus;
__asm
{
// 直接模拟 KiIntSystemCall
lea edx,hProcess; // 要求 edx 存储最后入栈的参数
mov eax, 0x115;
int 0x2E;
mov NtStatus, eax;
}
if (lpNumberOfBytesRead != NULL)
{
*lpNumberOfBytesRead = nSize;
}
// 错误检查
if (NtStatus < 0)
{
return FALSE;
}
return TRUE;
}

// 读进程内存(快速调用)
BOOL WINAPI WriteProcessMemory_FAST(HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead
)
{
DWORD ntStatus;
DWORD dwRet;
DWORD dwESP;
__asm
{
mov eax,[ebp]; //ecx保存上一个函数的返回地址
mov dwRet,eax;
mov dwESP,esp;

mov eax,0x115; //设置系统服务号
lea ebx, NtWriteVirtualMemoryRet;//call KiFastSystemCall的返回地址
mov [ebp],ebx; //把新的返回地址填到返回地址的坑
mov edx,ebp; //edx = 返回地址的地址
_emit 0x0F; //sysenter
_emit 0x34;
NtWriteVirtualMemoryRet:
mov ntStatus,eax;//函数返回值
mov esp,dwESP;
mov eax,dwRet;
mov [ebp],eax;
}
if (lpNumberOfBytesRead != NULL)
{
*lpNumberOfBytesRead = nSize;
}
// 错误检查
if (ntStatus < 0)
{
return FALSE;
}
return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwReadLen = 0;
char szRead[] = {"This is Test Read Data."};
char szWrite[] = {"Write Data."};
char szBufferRead[0x40] = {0};

printf("原数据为:%s\n",szRead);
ReadProcessMemory_FAST(GetCurrentProcess(),(LPCVOID)szRead,szBufferRead,sizeof(szRead),&dwReadLen);
printf("FASTCALL调用方式读取的数据为:%s\n",szBufferRead);
memset(szBufferRead,0,sizeof(szBufferRead));
ReadProcessMemory_INT(GetCurrentProcess(),(LPCVOID)szRead,szBufferRead,sizeof(szRead),&dwReadLen);
printf("INT调用方式读取的数据为:%s\n",szBufferRead);


printf("写入的数据为:%s\n",szWrite);
WriteProcessMemory_INT(GetCurrentProcess(),(LPCVOID)((DWORD)szRead+4),szWrite,sizeof(szWrite),&dwReadLen);
ReadProcessMemory_FAST(GetCurrentProcess(),(LPCVOID)szRead,szBufferRead,sizeof(szRead),&dwReadLen);
printf("FAST调用方式读取写入后读取INT写入的数据为:%s\n",szRead);

WriteProcessMemory_FAST(GetCurrentProcess(),(LPCVOID)((DWORD)szRead+4),"HHHH",sizeof(szWrite),&dwReadLen);
ReadProcessMemory_FAST(GetCurrentProcess(),(LPCVOID)szRead,szBufferRead,sizeof(szRead),&dwReadLen);
printf("FAST调用方式读取写入后读取FAST写入的数据为:%s\n",szRead);

getchar();
return 0;
}

6.png

3 分析 INT 0x2E 和 sysenter

3.1 中断门进0环

4.png

EAX保存系统调用号,内核函数编号。
EDX保存的指针,指向函数参数的起始地址。
然后通过中断门进入内核。且中断号都是0x2E。

分析:INT 0x2E进0环
步骤一:在IDT表中找到0x2E号门描述符。
步骤二:分析CS/SS/ESP/EIP的来源。
步骤三:分析EIP是什么。

查看IDT表中的中断门描述符看一下提权跳转过程:

  1. 0x2E = 46D,即第47项。8054ee00`00082451

    5.png

    可以看到:

    • 0xE = 1110:P = 1,DPL = 11,S = 0(系统段)。

    • Type = 0xE = 1110: 为32位中断门

    • 代码段选择子:0008 = 1 0 00

      • RPL = 00
      • TI = 0,查GDT
      • Index = 1,第二项,查得00cf9b00`0000ffff,代码段Base = 0x0。
    • 中断门偏移:0x80542451

    • 中断门跳转执行地址 = Code.Base + Interrupt.Offset = 0x0 + 0x80542451 = 0x80542451。该地址已经为高2G(0环)。

      15.png

      55.png

    • 得到EIP = 0x80542451。这个是内核模块的KiSystemService函数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      kd> u 0x80542451
      nt!KiSystemService:
      80542451 6a00 push 0
      80542453 55 push ebp
      80542454 53 push ebx
      80542455 56 push esi
      80542456 57 push edi
      80542457 0fa0 push fs
      80542459 bb30000000 mov ebx,30h
      8054245e 668ee3 mov fs,bx
  2. 综上,已经知道CS0来自调用门中的代码段选择子,EIP = Code.Base + Interrupt.Offset = 0x80542451。接下来还要分析来自TSS的SS0和ESP0。

    • 获取TR的值。(任务段执行流程:3环CALL任务段选择子 –> 根据段选择子到GDT找到对应任务段描述符 –> 将任务段描述符加载到TR寄存器,同时根据任务段中的Base找到TSS内存块的起始地址 –> 根据TSS中的EIP去执行代码 –> IRETD返回。)

      1
      2
      kd> r tr
      tr=00000028
    • 可以得到TR寄存器选择子的值为0x28 = 00101 0 00,Index = 5,查GDT表如下。获取TSS任务段描述符。

      1
      2
      3
      4
      5
      6
      kd> r gdtr
      gdtr=8003f000
      kd> dq 8003f000
      8003f000 00000000`00000000 00cf9b00`0000ffff
      8003f010 00cf9300`0000ffff 00cffb00`0000ffff
      8003f020 00cff300`0000ffff 80008b04`200020ab

      得到GDT描述符 = 80008b04`200020ab,为32位TSS任务段,TSS.Base = 0x80042000。

    79.png

    • 可以得到:ESP0 =0xaabe9de0,SS0 = 0x0010。

      1
      2
      3
      4
      kd> dd 0x80042000
      80042000 0c458b24 aabe9de0 8b080010 758b0855
      80042010 eac14008 ffe68110 030000ff 06e400a0
      80042020 e1750855 08458b5e 0310e8c1 c25d0845

      76.png

3.2 快速调用进0环

在执行sysenter指令之前,操作系统必须指定0环的CS段、SS段、EIP以及ESP,SS = CS+8。操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质是一样的!

MSR 地址 含义
IA32_SYSENTER_CS 174H 低16位值指定了特权级0的代码段和栈段的段选择符
IA32_SYSENTER_ESP 175H 内核栈指针的32 位偏移
IA32_SYSENTER_EIP 176H 目标例程的32位偏移

可以通过RDMSR/WRMST来进行读写(操作系统使用WRMST写该寄存器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kd> rdmsr 174
msr[174] = 00000000`00000008 //查看CS
kd> rdmsr 175
msr[175] = 00000000`f7a1a000 //查看ESP
kd> rdmsr 176
msr[176] = 00000000`80542520 //查看EIP
SS = CS + 8 = 0x8 + 8 = 0x10

kd> u 80542520
nt!KiFastCallEntry:
80542520 b923000000 mov ecx,23h
80542525 6a30 push 30h
80542527 0fa1 pop fs
80542529 8ed9 mov ds,cx
8054252b 8ec1 mov es,cx
8054252d 648b0d40000000 mov ecx,dword ptr fs:[40h]
80542534 8b6104 mov esp,dword ptr [ecx+4]
80542537 6a23 push 23h

操作系统启动的时候就已经把这些值写入到MSR寄存器中了。

3.3 总结

API通过中断门进0环:
1) 固定中断号为0x2E
2) CS/EIP由门描述符提供,ESP/SS由TSS提供
3) 进入0环后执行的内核函数:NT!KiSystemService

API通过sysenter指令进0环:
1) CS/ESP/EIP由MSR寄存器提供(SS是算出来的)
2) 进入0环后执行的内核函数:NT!KiFastCallEntry

内核模块:ntoskrnl.exe/ntkrnlpa.exe(10-10-12/2-9-9-12)

4 进入0环后分析

之前的课程,我们学习了API系统调用在3环部分做的事情,有两种方式进0环,分别是中断门int 0x2e和快速调用sysenter。通过中断门进入0环后,会调用函数KiSystemService、通过快速调用进入0环后会调用函数KiFastCallEntry

分析之前,思考四个问题:

  1. 进0环后,原来的寄存器存在哪里?
  2. 如何根据系统调用号(eax中存储)找到要执行的内核函数?
  3. 调用时参数是存储到3环的堆栈,如何传递给内核函数?
  4. 2种调用方式是如何返回到3环的?

要分析 KiSystemService 和 KiFastCallEntry ,我们需要先了解几个结构体,_Trap_Frame,_ETHREAD,_KTHREAD,_KPCR,_NT_TIB 和 _KPRCB。

接下来,按照 KiSystemService 代码执行顺序,依次介绍涉及到的结构体。

4.1 分析KiSystemService

本节通过分析中断门int 0x2e进入0环后,调用函数KiSystemService的过程,解释线程进0环后,原来的寄存器存在哪里?

使用IDA Pro打开ntkrnlpa.exe,并连接远程ntkrnlpa.pdb文件(Windows XP 驱动开发(一)第二章节)。

  1. Alt+T搜索_IDT。

    8.png

  2. 双击后如下。

    9.png

  3. 找到0x2e对应的KiSystemService函数然后双击。待会儿分析push 0的含义。

    10.png

  4. 保存3环的通用寄存器。

    • 从3环进入0环的时候,CPU会往0环的堆栈压5个值:eip、cs、eflag、esp、ss。
    • 但是操作系统会将3环的寄存器都保存起来,方便从0环返回,于是操作系统就维护了一个_Trap_Frame结构来保存这些3环的寄存器。

4.2 _Trap_Frame结构

首先复习一下TSS表,一个CPU只有一张TSS表,但是系统里有成百上千的线程,线程进0环时,假设使用中断门,0环的ESP和SS从TSS表获取,怎么保证每个线程都有自己的堆栈,不互相冲突呢?答案是,线程切换时会修改TSS表,确保每个线程执行时,TSS里的ESP,SS都对应当前线程。

_Trap_Frame是线程在0环的堆栈结构,由操作系统维护。

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
kd> dt _KTrap_Frame -v
nt!_KTRAP_FRAME
struct _KTRAP_FRAME, 35 elements, 0x8c bytes
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 to struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B

一个线程一个_Trap_Frame结构,每个线程有一个自己的ESP0,该ESP0指示该线程0环下的堆栈_Trap_Frame结构)在哪。

11.png

其中:

  • 高4个成员仅在虚拟8086模式下使用。
  • 从TSS中获取的ESP0一开始指向0x7C。(从TSS中获取ESP0后才能知道0环的堆栈在哪)
  • 从0x68~0x78由中断门进入0环时CPU自己填充。
  • 一个线程一个_Trap_Frame结构。
  • 一个CPU只有一块TSS内存块,每次线程切换时,TSS中的值都被更新为当前线程对应的值。

结合_Trap_Frame结构分析函数KiSystemService分析:

12.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
push    0	//push ErrCode
push ebp
push ebx
push esi
push edi
push fs

mov ebx, 30h
mov fs, bx //fs = 0x30 --> 00110 0 00 --> GDT -->ffc093df`f0000001(数据段)
push dword ptr ds:0FFDFF000h
mov dword ptr ds:0FFDFF000h,OFFFFFFFFh
mov esi,ds:0FFDFF124h
push dword ptr [esi+140h]
sub esp,48h
mov ebx,[esp+68h+arg_0]
and ebx,1

可以看到段寄存器FS的在GDT表中对应的段描述符为ffc093df`f0000001,是一个数据段,FS.Base = 0xFFDFF000。

此时的FS有了新的含义,在3环,FS[0]指向TEB结构。但是在0环FS段寄存器被重新赋值,也有了新的意义,0环的FS指向一个KPCR的结构体

4.3 _KPCR结构

KPCR是个结构体,用来描述CPU状态。叫CPU控制区(Processor Control Region)。

CPU也有自己的控制块,每一个CPU有一个,叫KPCR。

0环下FS段寄存器的基址,即FS[0]指向KPCR结构,这里为0xFFDFF000

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
kd> dt _KPCR
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 DebugActive : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB

查看CPU数量(这里是单核):

1
2
3
kd> dd KeNumberProcessors 
80556a60 00000001 00000006 00008e0a a0013fff
80556a70 806ceec0 00000000 00000000 00000061

_KPCR结构中比较重要的两个结构(第一个和最后一个):_NT_TIB_KPRCB

4.3.1 _NT_TIB结构

先分析第一个结构_NT_TIB

1
2
3
4
5
6
7
8
9
10
kd> dt _NT_TIB
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB //结构体指针指向自己

第一个成员_EXCEPTION_REGISTRATION_RECORD是一个异常链表,存储的是异常的处理函数。

接着分析KiSystemService函数和_Trap_Frame结构、_KPCR结构:

13.png

1
2
push    dword ptr ds:0FFDFF000h
mov dword ptr ds:0FFDFF000h,OFFFFFFFFh

PUSH的作用就是保存老的异常链表,然后将新的ExceptionList设为-1。

4.3.2 _KPRCB结构

下面列举该结构的成员:

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
kd> dt _KPRCB
ntdll!_KPRCB
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B
+0x004 CurrentThread : Ptr32 _KTHREAD //当前CPU所执行线程的_ETHREAD
+0x008 NextThread : Ptr32 _KTHREAD //下一个_ETHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
+0x010 Number : Char //CPU编号
+0x011 Reserved : Char
+0x012 BuildType : Uint2B
+0x014 SetMember : Uint4B
+0x018 CpuType : Char
+0x019 CpuID : Char
+0x01a CpuStep : Uint2B
+0x01c ProcessorState : _KPROCESSOR_STATE
+0x33c KernelReserved : [16] Uint4B
+0x37c HalReserved : [16] Uint4B
+0x3bc PrcbPad0 : [92] UChar
+0x418 LockQueue : [16] _KSPIN_LOCK_QUEUE
+0x498 PrcbPad1 : [8] UChar
+0x4a0 NpxThread : Ptr32 _KTHREAD
+0x4a4 InterruptCount : Uint4B
+0x4a8 KernelTime : Uint4B
+0x4ac UserTime : Uint4B
+0x4b0 DpcTime : Uint4B
+0x4b4 DebugDpcTime : Uint4B
+0x4b8 InterruptTime : Uint4B
+0x4bc AdjustDpcThreshold : Uint4B
+0x4c0 PageColor : Uint4B
+0x4c4 SkipTick : Uint4B
+0x4c8 MultiThreadSetBusy : UChar
+0x4c9 Spare2 : [3] UChar
+0x4cc ParentNode : Ptr32 _KNODE
+0x4d0 MultiThreadProcessorSet : Uint4B
+0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB
+0x4d8 ThreadStartCount : [2] Uint4B
+0x4e0 CcFastReadNoWait : Uint4B
+0x4e4 CcFastReadWait : Uint4B
+0x4e8 CcFastReadNotPossible : Uint4B
+0x4ec CcCopyReadNoWait : Uint4B
+0x4f0 CcCopyReadWait : Uint4B
+0x4f4 CcCopyReadNoWaitMiss : Uint4B
+0x4f8 KeAlignmentFixupCount : Uint4B
+0x4fc KeContextSwitches : Uint4B
+0x500 KeDcacheFlushCount : Uint4B
+0x504 KeExceptionDispatchCount : Uint4B
+0x508 KeFirstLevelTbFills : Uint4B
+0x50c KeFloatingEmulationCount : Uint4B
+0x510 KeIcacheFlushCount : Uint4B
+0x514 KeSecondLevelTbFills : Uint4B
+0x518 KeSystemCalls : Uint4B
+0x51c SpareCounter0 : [1] Uint4B
+0x520 PPLookasideList : [16] _PP_LOOKASIDE_LIST
+0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x7a0 PacketBarrier : Uint4B
+0x7a4 ReverseStall : Uint4B
+0x7a8 IpiFrame : Ptr32 Void
+0x7ac PrcbPad2 : [52] UChar
+0x7e0 CurrentPacket : [3] Ptr32 Void
+0x7ec TargetSet : Uint4B
+0x7f0 WorkerRoutine : Ptr32 void
+0x7f4 IpiFrozen : Uint4B
+0x7f8 PrcbPad3 : [40] UChar
+0x820 RequestSummary : Uint4B
+0x824 SignalDone : Ptr32 _KPRCB
+0x828 PrcbPad4 : [56] UChar
+0x860 DpcListHead : _LIST_ENTRY
+0x868 DpcStack : Ptr32 Void
+0x86c DpcCount : Uint4B
+0x870 DpcQueueDepth : Uint4B
+0x874 DpcRoutineActive : Uint4B
+0x878 DpcInterruptRequested : Uint4B
+0x87c DpcLastCount : Uint4B
+0x880 DpcRequestRate : Uint4B
+0x884 MaximumDpcQueueDepth : Uint4B
+0x888 MinimumDpcRate : Uint4B
+0x88c QuantumEnd : Uint4B
+0x890 PrcbPad5 : [16] UChar
+0x8a0 DpcLock : Uint4B
+0x8a4 PrcbPad6 : [28] UChar
+0x8c0 CallDpc : _KDPC
+0x8e0 ChainedInterruptList : Ptr32 Void
+0x8e4 LookasideIrpFloat : Int4B
+0x8e8 SpareFields0 : [6] Uint4B
+0x900 VendorString : [13] UChar
+0x90d InitialApicId : UChar
+0x90e LogicalProcessorsPerPhysicalProcessor : UChar
+0x910 MHz : Uint4B
+0x914 FeatureBits : Uint4B
+0x918 UpdateSignature : _LARGE_INTEGER
+0x920 NpxSaveArea : _FX_SAVE_AREA
+0xb30 PowerState : _PROCESSOR_POWER_STATE
  • 查看KPRCB地址(KiProcessorBlock),这里是单核,所以只有一个值。

    1
    2
    kd> dd KiProcessorBlock  L2
    8055d5a0 ffdff120 00000000

    ffdff120这个地址减去0x120就是KPCR的地址。

  • 查看CPU数量(KeNumberProcessors )。

    1
    2
    3
    4
    kd> dd KeNumberProcessors
    80556a60 00000001 00000006 00008e0a a0013fff
    80556a70 806ceec0 00000000 00000000 00000061
    80556a80 8003f0e0 00000000 00000000 00000000

接着分析KiSystemService函数代码:

15.png

14.png

这里的FS:124为_KPRCB的第3个成员CurrentThread,是当前CPU所执行线程的_ETHREAD_ETHREAD部分成员如下:

1
2
3
4
5
6
7
8
9
kd> dt _ETHREAD
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY

第一个成员为_KTHREAD _KTHREAD 部分成员如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _KTHREAD
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x02c DebugActive : UChar
+0x02d State : UChar
...
+0x0e0 ServiceTable : Ptr32 Void //SystemServiceTable系统服务表SST
+0x0e4 Queue : Ptr32 _KQUEUE
...
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x140 PreviousMode : Char
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar

16.png

17.png

0FFDFF124h+140h就是PreviousMode,意为先前模式。

保存老的先前模式,因为一段代码如果是0环执行和3环执行是不一样的,记录调用这段代码之前是0环的还是3环的。

接着分析代码:

1
sub     esp, 48h

将esp指向_Trap_Frame的起始位置。

1
2
mov    ebx, [esp+68h+arg_0]
and ebx, 1

这里arg_0是4

那么就是将esp+6c位置上的值赋给ebx,并与上1。esp+6c位置就是cs的值,主要是为了判断权限的问题,如果是3环来的,cs的值是11,如果是0环来的,cs的值是8。所以与后如果是0,那么就是0环来的,如果是1,那么就是3环来的。

1
mov     [esi+140h], bl

esi是KTHREAD,esi+140偏移的地方正好是先前模式,将值赋给先前模式,“新的先前模式”。

1
mov     ebp, esp

esp和ebp此时都指向Trap_Frame的首地址。

1
mov     ebx, [esi+134h]

esi+134h是KTHREAD+134h偏移又是TrapFrame,这里也可以验证TrapFrame结构体实际上是一个线程一份,因为在KTHREAD这个线程结构体中有一份,而KTHREAD是每个线程一份。

1
2
mov     [ebp+3Ch], ebx
mov [esi+134h], ebp

将老的TrapFrame的值存在一个位置上,然后将新的TrapFrame放到esi+134h这个位置。

1
2
mov     ebx, [ebp+60h]
mov edi, [ebp+68h]

将原来三环的ebp放到ebx里,将三环eip放到了edi中

1
mov     [ebp+0Ch], edx

进0环的时候,edx保存的是参数的地址,这里赋值给TrapFrame+0Ch偏移正好是DbgArgPointer

1
2
mov     [ebp+0], ebx
mov [ebp+4], edi

TrapFrame+0h 和TrapFrame+4h分别是DbgEbp和DbgEip。

1
test    byte ptr [esi+2Ch], 0FFh

判断是否处于调试状态(DebugActive),如果是处于调试状态,才会把cr0-cr7存到TrapFrame结构体中,如果没处于调试状态就不存cr0-cr7的值。那么就是将esp+6c位置上的值赋给ebx,并与上1。esp+6c位置就是cs的值,主要是为了判断权限的问题,如果是3环来的,cs的值是11,如果是0环来的,cs的值是8。所以与后如果是0,那么就是0环来的,如果是1,那么就是3环来的。

1
mov     [esi+140h], bl

esi是KTHREAD,esi+140偏移的地方正好是先前模式,将值赋给先前模式,“新的先前模式”。

1
mov     ebp, esp

esp和ebp此时都指向Trap_Frame的首地址。

1
mov     ebx, [esi+134h]

esi+134h是KTHREAD+134h偏移又是TrapFrame,这里也可以验证TrapFrame结构体实际上是一个线程一份,因为在KTHREAD这个线程结构体中有一份,而KTHREAD是每个线程一份。

1
2
mov     [ebp+3Ch], ebx
mov [esi+134h], ebp

将老的TrapFrame的值存在一个位置上,然后将新的TrapFrame放到esi+134h这个位置。

1
2
mov     ebx, [ebp+60h]
mov edi, [ebp+68h]

将原来三环的ebp放到ebx里,将三环eip放到了edi中

1
mov     [ebp+0Ch], edx

进0环的时候,edx保存的是参数的地址,这里赋值给TrapFrame+0Ch偏移正好是DbgArgPointer

1
2
mov     [ebp+0], ebx
mov [ebp+4], edi

TrapFrame+0h 和TrapFrame+4h分别是DbgEbp和DbgEip。

1
test    byte ptr [esi+2Ch], 0FFh

判断是否处于调试状态(DebugActive),如果是处于调试状态(DebugActive != 0xFF),才会把Dr0-Dr7存到TrapFrame结构体中,如果没处于调试状态就不存Dr0-Dcr7的值。

通过对函数KiSystemService的分析,已经知道如下的问题一和问题三。原来的寄存器存储到了 _Trap_Frame 结构体里,3环API参数指针通过EDX传给0环。

  1. 进0环后,原来的寄存器存在哪里?
  2. 如何根据系统调用号(eax中存储)找到要执行的内核函数?
  3. 调用时参数是存储到3环的堆栈,如何传递给内核函数?
  4. 2种调用方式是如何返回到3环的?

4.4 完整分析KiSystemService

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
.text:0046A451 ; =============== S U B R O U T I N E =======================================
.text:0046A451
.text:0046A451
.text:0046A451 _KiSystemService proc near ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+C↑p
.text:0046A451 ; ZwAccessCheck(x,x,x,x,x,x,x,x)+C↑p ...
.text:0046A451
.text:0046A451 arg_0 = dword ptr 4
.text:0046A451
.text:0046A451 push 0 //PUSH Errode,错误码初始化为0
.text:0046A453 push ebp //保存3环寄存器的值
.text:0046A454 push ebx
.text:0046A455 push esi
.text:0046A456 push edi
.text:0046A457 push fs
.text:0046A459 mov ebx, 30h //使用段选择子0x30对应的段描述符来更新FS段寄存器FS的值
.text:0046A45E mov fs, bx //查GDT表得ffc093df`f0000001(0环数据段,RPL == 0)
.text:0046A461 assume fs:nothing//fs.base = ffdff000,指向当前CPU的KPCR结构
.text:0046A461 push large dword ptr fs:0 //保存旧的 ExceptionList,然后把新的清成-1
.text:0046A468 mov large dword ptr fs:0, 0FFFFFFFFh
.text:0046A473 mov esi, large fs:124h //esi = _KTHREAD结构
.text:0046A47A push dword ptr [esi+140h] //_KTHREAD+0x40 == PreviousMode压栈
.text:0046A480 sub esp, 48h //此时esp指向偏移0x48处,然后将其提升使其指向_TrapFrame首地址
.text:0046A483 mov ebx, [esp+68h+arg_0] //arg_0==4,ebx = CS3
.text:0046A487 and ebx, 1 //CS3 & 0x1
.text:0046A48A mov [esi+140h], bl //将CS3填充至先前模式PreviousMode
.text:0046A490 mov ebp, esp //ebp = esp = _TrapFrame首地
.text:0046A492 mov ebx, [esi+134h] //ebp = _KTHREAD+0x134 = _TrapFrame
.text:0046A498 mov [ebp+3Ch], ebx //_KTRAP_FRAME.Edx = ebp+3Ch,指向旧的 CurrentThread.TrapFrame
.text:0046A49B mov [esi+134h], ebp //CurrentThread.TrapFrame 指向当前 _KTRAP_FRAME
.text:0046A4A1 cld //df = 0
.text:0046A4A2 mov ebx, [ebp+60h] //ebx = ebp3
.text:0046A4A5 mov edi, [ebp+68h] //edi = eip3
.text:0046A4A8 mov [ebp+0Ch], edx //_KTRAP_FRAME.DbgArgPointer = edx,保存3环API参数指针
.text:0046A4AB mov dword ptr [ebp+8], 0BADB0D00h //目前不清楚具体意思
.text:0046A4B2 mov [ebp+0], ebx //_KTRAP_FRAME.DbgEbp = ebp3
.text:0046A4B5 mov [ebp+4], edi //_KTRAP_FRAME.DbgEip = eip3
.text:0046A4B8 test byte ptr [esi+2Ch], 0FFh //_KTHREAD+0x2C = DebugActive,判断是否处于调试状态
.text:0046A4BC jnz Dr_kss_a //如果是处于调试状态,才会把cr0-cr7存到TrapFrame结构体中
//如果没处于调试状态就不存cr0-cr7的值
.text:0046A4C2
.text:0046A4C2 loc_46A4C2: ; CODE XREF: Dr_kss_a+10↑j
.text:0046A4C2 ; Dr_kss_a+7C↑j
.text:0046A4C2 sti //允许中断
.text:0046A4C3 jmp loc_46A5AF
.text:0046A4C3 _KiSystemService endp

5 完整分析KiFastCallEntry

从3环KiFastSystemCall通过sysenter进入0环后,会调用函数KiFastCallEntry,其中CS0、EIP、SS0 = CS0 + 8从MSR寄存器中来,ESP0从TSS中获取

与中断门进入0环后调用KiSystemService函数中前部分是不太一样,快速调用进来0环的SS3、ESP3、EFLAG3、CS3、EIP3由操作系统来压栈(中断门进入由CPU自己压栈)。

KiFastCallEntry也需要将_KTRAP_FRAME结构填满。

5.1 进入0环前的堆栈与寄存器

通过正常的系统调用从3环进入0环都会从Ntdll.dll里面的函数调用KiFastSystemCall进去R0。

那么这里就来分析一下,一个 API 函数在调用Ntdll.dll前都会做一些什么工作?这里以Kernel32.dll中的函数ReadProcessMemory来进行举例说明。

函数ReadProcessMemory进入0环前,在3环堆栈以及寄存器的值如下:

50.png

这里查看一下IA32_SYSENTER_EIP是到0环的哪个地方执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> rdmsr 176
msr[176] = 00000000`80542520 //查看EIP

kd> u 80542520
nt!KiFastCallEntry:
80542520 b923000000 mov ecx,23h
80542525 6a30 push 30h
80542527 0fa1 pop fs
80542529 8ed9 mov ds,cx
8054252b 8ec1 mov es,cx
8054252d 648b0d40000000 mov ecx,dword ptr fs:[40h]
80542534 8b6104 mov esp,dword ptr [ecx+4]
80542537 6a23 push 23h

可以看到是0环的KiFastCallEntry函数。

5.2 KiFastCallEntry函数分析

注意事项

  1. 快速调用进入0环后,CPU并不会像中断门调用进入0环那样自动将寄存器SS、ESP3、Eflags3、CS、EIP3压入0环堆栈(TrapFrame)。

  2. 进入0环时,edx 寄存器并非直接指向3环 API 的参数

    1
    add edx, 8 //此时edx指向3环API参数。这也说明在3环时edx+8才指向API的参数
  3. 使用int 0x2e进入0环后,当TrapFrame的成员都初始化完成后,会在函数_KiSystemService中会设置KTRAP_FRAME._Edx = Currentthread->TrapFrame

    1
    2
    3
    4
    .text:0046A473                 mov     esi, large fs:_KPCR.PrcbData.CurrentThread
    ...
    .text:0046A492 mov ebx, [esi+_KTHREAD.TrapFrame]
    .text:0046A498 mov [ebp+_KTRAP_FRAME._Edx], ebx

    但是在KiFastCallEntry中并没有发现会将当前线程的TrapFrame保存起来。

    但是:在函数KiFastCallEntryNtRaiseException等这些函数中都会有如下代码:

    1
    2
    .text:0046A625                 mov     edx, [ebp+_KTRAP_FRAME._Edx] 
    .text:0046A628 mov [ecx+_KTHREAD.TrapFrame], edx

    用来恢复TrapFrame。尽管在KiFastCallEntry没有看到使用KTRAP_FRAME._Edx保存TrapFrame,但是从其他函数逆向分析来看,**“假装”KiFastCallEntry函数会像函数_KiSystemService中一样设置KTRAP_FRAME._Edx = Currentthread->TrapFrame**。

  1. 先处理段寄存器
    • FS = 0x30
    • DS = 0x23
    • ES = 0x23
  2. esp0 = TSS.ESP0,找到当前线程的栈顶

进入系统服务函数之前,KiFastCallEntry堆栈与寄存器图:

51.png

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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
.text:0046A520 // =============== S U B R O U T I N E =======================================
.text:0046A520
.text:0046A520 // ----
.text:0046A520 // 从3环函数 KiFastSystemCall 的 sysenter 指令调用
.text:0046A520 //
.text:0046A520 // edx = esp3, eax = 0xIndex
.text:0046A520 //
.text:0046A520 // ----
.text:0046A520
.text:0046A520 _KiFastCallEntry proc near // DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
.text:0046A520 // _KiTrap01+74↓o
.text:0046A520
.text:0046A520 var_B = byte ptr -0Bh
.text:0046A520
.text:0046A520 // FUNCTION CHUNK AT .text:0046A4ED SIZE 00000026 BYTES
.text:0046A520 // FUNCTION CHUNK AT .text:0046A7C0 SIZE 00000014 BYTES
.text:0046A520
.text:0046A520 mov ecx, 23h
.text:0046A525 push 30h // 开始修改段寄存器为0环的值
.text:0046A527 pop fs // fs = 0x30,查GDT表得ffc093df`f0000001(0环数据段,RPL == 0)
.text:0046A529 mov ds, ecx // ds = 0x23,00cffb00`0000ffff(3环数据段,RPL == 3 )
.text:0046A52B mov es, ecx // es = 0x23
.text:0046A52D mov ecx, large fs:_KPCR.TSS
.text:0046A534 mov esp, [ecx+4] // ----
.text:0046A534 // esp = TSS.esp0,切换到0环堆栈
.text:0046A534 //
.text:0046A534 // TSS.esp0 指向当前线程 TrapFrame 0x7C V86ES
.text:0046A534 //
.text:0046A534 // 从这里可以知道 KiFastEntry 的函数堆栈即为当前线程的 TrapFrame
.text:0046A534 // 下面开始从 HardwareSegss 开始填充 TrapFrame (当前函数堆栈)
.text:0046A534 // ----
.text:0046A537 push 23h // HardwareSegSs = 0x23
.text:0046A539 push edx // HardwareSegEsp = esp3 == 3环的第二个返回地址
.text:0046A53A pushf
.text:0046A53B
.text:0046A53B loc_46A53B: // CODE XREF: _KiFastCallEntry2+23↑j
.text:0046A53B push 2
.text:0046A53D add edx, 8 // 此时 edx 指向 3 环的第一个参数
.text:0046A540 popf // EFlags = 0x02,即清空0环所有标志位
.text:0046A541 or [esp+0Ch+var_B], 2 // ---
.text:0046A541 // 此时esp 指向 KTRAP_FRAME.EFlags
.text:0046A541 // eflag(8~15bit)
.text:0046A541 // 将3环 EFlags 的 IF = 1,以响应接受可屏蔽中断
.text:0046A541 // ---
.text:0046A546 push 1Bh // KTRAP_FRAME.SegCs = 0x1B = CS3
.text:0046A548 push dword ptr ds:0FFDF0304h // --
.text:0046A548 // KTRAP_FRAME.Eip = _KUSER_SHARED_DATA.SystemCallReturn返回地址
.text:0046A548 //
.text:0046A548 // 也就是说从 0 环返回 3 环时将会返回到这个函数
.text:0046A548 // --
.text:0046A54E push 0
.text:0046A550 push ebp
.text:0046A551 push ebx
.text:0046A552 push esi
.text:0046A553 push edi
.text:0046A554 mov ebx, large fs:_KPCR.SelfPcr
.text:0046A55B push 3Bh // _KTRAP_FRAME.SegFs = 0x3B,让3环FS找到对应的段描述符
.text:0046A55D mov esi, [ebx+_KPCR.PrcbData.CurrentThread]
.text:0046A563 push dword ptr [ebx] // 保存旧的 ExceptionList
.text:0046A565 mov dword ptr [ebx], 0FFFFFFFFh // 当前线程ExceptionList = -1
.text:0046A56B mov ebp, [esi+_KTHREAD.InitialStack] // ----
.text:0046A56B // ebp = _KPCR._KPRCB._KTHREAD.InitialStack(栈底),KernelStack(栈顶)
.text:0046A56B // 从线程切换分析可以知道:KTHREAD.InitialStack(栈底)指向当前线程的浮点寄存器 共0x210字节
.text:0046A56B //
.text:0046A56B // 也就是 KTHREAD 堆栈的是这样的:
.text:0046A56B // - TrapFrame(共0x8c字节) + 浮点寄存器(共0x210字节) == 0x29C
.text:0046A56B // - KTHREAD.InitialStack = 指向 TrapFrame + 浮点寄存器,【栈底】
.text:0046A56B //
.text:0046A56B // ----
.text:0046A56E push 1 // _KTRAP_FRAME.PreviousPreviousMode = 1,表示从3环来
.text:0046A570 sub esp, 48h // esp0 指向_TrapFrame首地址
.text:0046A573 sub ebp, 29Ch // esp0 = ebp0 = _KTRAP_FRAME首地址
.text:0046A579 mov [esi+_KTHREAD.PreviousMode], 1
.text:0046A580 cmp ebp, esp
.text:0046A582 jnz short loc_46A511 // esp0 != ebp0,跳到异常处理即 INT 6,6号异常中断--无效操作码
.text:0046A584 and [ebp+_KTRAP_FRAME.Dr7], 0
.text:0046A588 test [esi+_KTHREAD.DebugActive], 0FFh
.text:0046A58C mov [esi+_KTHREAD.TrapFrame], ebp // _KTHREAD.TrapFrame = ebp 存起来
.text:0046A592 jnz Dr_FastCallDrSave // DebugActive != -1,就表示被调试,会把Dr0-Dr7存到TrapFrame结构体中
.text:0046A592 // 如果当前的线程处于调试状态,那么这里面的值不为零
.text:0046A598
.text:0046A598 loc_46A598: // CODE XREF: Dr_FastCallDrSave+10↑j
.text:0046A598 // Dr_FastCallDrSave+7C↑j
.text:0046A598 mov ebx, [ebp+_KTRAP_FRAME._Ebp] // ebx == ebp3,但是在本函数内,并没有对ebp3进行赋值处理
.text:0046A59B mov edi, [ebp+_KTRAP_FRAME._Eip] // edi = eip3 == _KUSER_SHARED_DATA.SystemCallReturn
.text:0046A59E mov [ebp+_KTRAP_FRAME.DbgArgPointer], edx // edx 指向 3 环的第一个参数
.text:0046A5A1 mov [ebp+_KTRAP_FRAME.DbgArgMark], 0BADB0D00h
.text:0046A5A8 mov [ebp+_KTRAP_FRAME.DbgEbp], ebx
.text:0046A5AB mov [ebp+_KTRAP_FRAME.DbgEip], edi
.text:0046A5AE sti
.text:0046A5AF
.text:0046A5AF loc_46A5AF: // CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:0046A5AF // _KiSystemService+72↑j
.text:0046A5AF mov edi, eax // ---
.text:0046A5AF // 下面开始是 KiSystemService 和 KiFastCallEntry 的汇聚点
.text:0046A5AF // - esp = ebp = _KTRAP_FRAME首地址
.text:0046A5AF // - edx == 指向3环参数
.text:0046A5AF // - eax == 系统服务号
.text:0046A5AF // ---
.text:0046A5B1 shr edi, 8
.text:0046A5B4 and edi, 30h // --
.text:0046A5B4 // 取系统服务号的 bit12,bit13== 0。
.text:0046A5B4 // 即00bit13bit12 xxxx & 0011 0000 == 0001 0000/0000 0000
.text:0046A5B4 // 结果为:0x10/0x00
.text:0046A5B4 // --
.text:0046A5B7 mov ecx, edi
.text:0046A5B9 add edi, [esi+_KTHREAD.ServiceTable] // 取 SSDT/SSDT Shadow 表
.text:0046A5BF mov ebx, eax
.text:0046A5C1 and eax, 0FFFh // 取系统服务号的低 12 位,即在SST函数表的索引号
.text:0046A5C6 cmp eax, [edi+8] // 系统调用号 - SST.ServiceLimit,判断函数是否在表的范围内
.text:0046A5C9 jnb _KiBBTUnexpectedRange // 系统调用号 >= SST.ServiceLimit,不在表里,跳转异常处理
.text:0046A5CF cmp ecx, 10h
.text:0046A5D2 jnz short loc_46A5EF // --
.text:0046A5D2 // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5D2 // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5D2 // --
.text:0046A5D4 mov ecx, large fs:_KPCR.NtTib.Self
.text:0046A5DB xor ebx, ebx
.text:0046A5DD
.text:0046A5DD loc_46A5DD: // DATA XREF: _KiTrap0E+117↓o
.text:0046A5DD or ebx, [ecx+0F70h]
.text:0046A5E3 jz short loc_46A5EF // --
.text:0046A5E3 // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5E3 // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5E3 // --
.text:0046A5E5 push edx
.text:0046A5E6 push eax
.text:0046A5E7 call ds:_KeGdiFlushUserBatch
.text:0046A5ED pop eax
.text:0046A5EE pop edx
.text:0046A5EF
.text:0046A5EF loc_46A5EF: // CODE XREF: _KiFastCallEntry+B2↑j
.text:0046A5EF // _KiFastCallEntry+C3↑j
.text:0046A5EF inc large dword ptr fs:_KPCR.PrcbData.KeSystemCalls // --
.text:0046A5EF // 判断系统服务号的函数是ntoskrnl.exe
.text:0046A5EF // 系统调用计数加1,即函数KiFastCallEntry被调用次数++
.text:0046A5EF // --
.text:0046A5F6 mov esi, edx // edx 指向3环 API 的第一个参数
.text:0046A5F8 mov ebx, [edi+0Ch] // ebx 指向 SST 的参数表,ebx = SST.ArgmentTable
.text:0046A5FB xor ecx, ecx
.text:0046A5FD mov cl, [eax+ebx] // ecx = SST.ArgmentTable + 系统调用号低12位*1,即ecx = 函数参数大小
.text:0046A600 mov edi, [edi] // edi = SST.ServiceTable = KeServiceDescriptorTable
.text:0046A602 mov ebx, [edi+eax*4] // ebx = SST.ServiceTable + 系统调用号低12位*4 = 函数地址
.text:0046A605 sub esp, ecx // 0环抬高栈顶,马上将3环参数复制到0环
.text:0046A607 shr ecx, 2 // ecx = 参数个数 = 参数大小/4,作为拷贝的次数(一次拷贝4字节)
.text:0046A60A mov edi, esp
.text:0046A60C cmp esi, ds:_MmUserProbeAddress
.text:0046A612 jnb loc_46A7C0
.text:0046A618
.text:0046A618 loc_46A618: // CODE XREF: _KiFastCallEntry+2A4↓j
.text:0046A618 // DATA XREF: _KiTrap0E+10D↓o
.text:0046A618 rep movsd // 可以得知:当前函数的堆栈(也是当前线程的堆栈)从栈顶到栈底的分布情况:
.text:0046A618 // 1. 从3环拷贝过来的3环API参数
.text:0046A618 // 2. Trapframe(共0x8C字节)
.text:0046A618 // 3. 浮点寄存器(共0x210字节)
.text:0046A61A call ebx // ==========
.text:0046A61A // 此时:
.text:0046A61A // 1、esp 指向拷贝过来的3环API的第一个参数
.text:0046A61A // 2、ebp = 当前函数堆栈中 _KTRAP_FRAME 的首地址
.text:0046A61A // 3、ebx = 系统服务函数地址
.text:0046A61A // 4、edx:指向3环地址,即 API 的第一个参数
.text:0046A61A // 5、eax = 系统服务号的低 12 位
.text:0046A61A // 6、edi = 指向拷贝过来的3环API的最后一个参数(0环堆栈)
.text:0046A61A // 7、esi = 指向3环API的最后一个参数(3环堆栈),esi = edx + ecx*4
.text:0046A61A // 8、ecx = 3环 NT* API 函数的参数个数
.text:0046A61A // ==========
.text:0046A61C
.text:0046A61C loc_46A61C: // CODE XREF: _KiFastCallEntry+2AF↓j
.text:0046A61C // DATA XREF: _KiTrap0E+12D↓o ...
.text:0046A61C mov esp, ebp
.text:0046A61E
.text:0046A61E loc_46A61E: // CODE XREF: _KiBBTUnexpectedRange+38↑j
.text:0046A61E // _KiBBTUnexpectedRange+43↑j
.text:0046A61E mov ecx, large fs:124h
.text:0046A625 mov edx, [ebp+_KTRAP_FRAME._Edx] // ---
.text:0046A625 // 在ROS P24 有这样的代码:
.text:0046A625 //
.text:0046A625 // Save the old trap frame pointer where EDX would be saved
.text:0046A625 //
.text:0046A625 // mov ebx, [esi+KTHREAD_TRAP_FRAME] //KTHREAD 结构中的指针 TrapFrame
.text:0046A625 // mov [ebp+KTRAP_FRAME_EDX], ebx //暂时保存在这里
.text:0046A625 //
.text:0046A625 // 这是因为:加入一个线程多次从R3-->R0-->R3-->R0,每一次进来R0都会构建一个新
.text:0046A625 // 的TrapFrame结构在当前线程堆栈里面,那么可以使用当前KTRAP_FRAME_EDX来保存上一个
.text:0046A625 // TrapFrame,因为这一操作是在下面的指令前进行的操作:
.text:0046A625 // mov [esi+_KTHREAD.TrapFrame], ebp
.text:0046A625 // ---
.text:0046A628 mov [ecx+_KTHREAD.TrapFrame], edx
.text:0046A628 _KiFastCallEntry endp
.text:0046A628
.text:0046A62E
.text:0046A62E // =============== S U B R O U T I N E =======================================
.text:0046A62E
.text:0046A62E // 可以看到这个函数是 KiFastCallEntry 函数的一部分,紧接着这个函数的
.text:0046A62E
.text:0046A62E _KiServiceExit proc near // CODE XREF: KiCallUserMode(x,x)+EC↑j
.text:0046A62E // _KiSetLowWaitHighThread+80↓j ...
.text:0046A62E
.text:0046A62E arg_C = dword ptr 10h
.text:0046A62E arg_10 = dword ptr 14h
.text:0046A62E arg_40 = dword ptr 44h
.text:0046A62E arg_44 = dword ptr 48h
.text:0046A62E arg_48 = dword ptr 4Ch
.text:0046A62E arg_60 = dword ptr 64h
.text:0046A62E arg_64 = dword ptr 68h
.text:0046A62E arg_68 = dword ptr 6Ch
.text:0046A62E arg_6C = dword ptr 70h
.text:0046A62E
.text:0046A62E // FUNCTION CHUNK AT .text:0046A738 SIZE 00000088 BYTES
.text:0046A62E
.text:0046A62E cli
.text:0046A62F test [ebp+_KTRAP_FRAME.EFlags], 20000h // 判断是否是虚拟8086模式
.text:0046A636 jnz short loc_46A63E // 是虚拟8086模式
.text:0046A638 test byte ptr [ebp+_KTRAP_FRAME.SegCs], 1
.text:0046A63C jz short loc_46A694 // ---
.text:0046A63C // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A63C // 就是最近一次从3环进入到0环时使用的
.text:0046A63C // ---
.text:0046A63E
.text:0046A63E loc_46A63E: // CODE XREF: _KiServiceExit+8↑j
.text:0046A63E // _KiServiceExit+64↓j
.text:0046A63E mov ebx, large fs:124h // ---
.text:0046A63E // 从3环退出前,只要还有3环APC还没执行,就会不断的循环去执行
.text:0046A63E // 虽然在 KiDeliverApc 中一次只会执行一个用户APC,但是
.text:0046A63E // 本函数这里是一个循环
.text:0046A63E // ---
.text:0046A645 mov [ebx+_KTHREAD.Alerted], 0
.text:0046A649 cmp [ebx+_KTHREAD.ApcState.UserApcPending], 0
.text:0046A64D jz short loc_46A694 // ---
.text:0046A64D // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A64D // 就是最近一次从3环进入到0环时使用的
.text:0046A64D // ---
.text:0046A64F mov ebx, ebp
.text:0046A651 mov [ebx+_KTRAP_FRAME._Eax], eax
.text:0046A654 mov [ebx+_KTRAP_FRAME.SegFs], 3Bh
.text:0046A65B mov [ebx+_KTRAP_FRAME.SegDs], 23h
.text:0046A662 mov [ebx+_KTRAP_FRAME.SegEs], 23h
.text:0046A669 mov [ebx+_KTRAP_FRAME.SegGs], 0
.text:0046A670 mov ecx, 1 // NewIrql
.text:0046A675 call ds:__imp_@KfRaiseIrql@4 // KfRaiseIrql(x)
.text:0046A67B push eax
.text:0046A67C sti
.text:0046A67D push ebx
.text:0046A67E push 0
.text:0046A680 push 1
.text:0046A682 call _KiDeliverApc@12 // VOID KiDeliverApc (
.text:0046A682 // IN KPROCESSOR_MODE PreviousMode,
.text:0046A682 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0046A682 // IN PKTRAP_FRAME TrapFrame
.text:0046A682 // )
.text:0046A687 pop ecx // NewIrql
.text:0046A688 call ds:__imp_@KfLowerIrql@4 // KfLowerIrql(x)
.text:0046A68E mov eax, [ebx+_KTRAP_FRAME._Eax]
.text:0046A691 cli
.text:0046A692 jmp short loc_46A63E // ---
.text:0046A692 // 从3环退出前,只要还有3环APC还没执行,就会不断的循环去执行
.text:0046A692 // 虽然在 KiDeliverApc 中一次只会执行一个用户APC,但是
.text:0046A692 // 本函数这里是一个循环
.text:0046A692 // ---
.text:0046A694 // ---------------------------------------------------------------------------
.text:0046A694
.text:0046A694 loc_46A694: // CODE XREF: _KiServiceExit+E↑j
.text:0046A694 // _KiServiceExit+1F↑j
.text:0046A694 mov edx, [esp+_KTRAP_FRAME.ExceptionList] // ---
.text:0046A694 // 来自内核,此时esp指向的trapframe是线程堆栈最栈顶上面的那个,
.text:0046A694 // 就是最近一次从3环进入到0环时使用的
.text:0046A694 // ---
.text:0046A698 mov ebx, large fs:_KPCR.DebugActive // 如果当前的线程处于调试状态,那么这里面的值不为-1
.text:0046A69F mov large fs:_KPCR, edx
.text:0046A6A6 mov ecx, [esp+_KTRAP_FRAME.PreviousPreviousMode]
.text:0046A6AA mov esi, large fs:_KPCR.PrcbData.CurrentThread
.text:0046A6B1 mov [esi+_KTHREAD.PreviousMode], cl
.text:0046A6B7 test ebx, 0FFh
.text:0046A6BD jnz short loc_46A738 // 异常链表非空,有异常需要处理
.text:0046A6BF
.text:0046A6BF loc_46A6BF: // CODE XREF: _KiServiceExit+11A↓j
.text:0046A6BF // _KiServiceExit+149↓j
.text:0046A6BF test [esp+_KTRAP_FRAME.EFlags], 20000h // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A6C7 jnz loc_46AFD8 // 虚拟8086模式
.text:0046A6CD test word ptr [esp+_KTRAP_FRAME.SegCs], 0FFF8h
.text:0046A6D4 jz loc_46A78E // CS最低位为0,说明先前模式是0
.text:0046A6DA cmp word ptr [esp+_KTRAP_FRAME.SegCs], 1Bh
.text:0046A6E0 bt word ptr [esp+_KTRAP_FRAME.SegCs], 0 // CF位置1
.text:0046A6E7 cmc // ---
.text:0046A6E7 // CF取反,则 CF == 0
.text:0046A6E7 // https://blog.csdn.net/xiong_xin/article/details/103665440
.text:0046A6E7 // ---
.text:0046A6E8 ja loc_46A77C // CF ∨ ZF = 0 时跳转
.text:0046A6EE cmp word ptr [ebp+_KTRAP_FRAME.SegCs], 8
.text:0046A6F3 jz short loc_46A6FA
.text:0046A6F5
.text:0046A6F5 loc_46A6F5: // CODE XREF: _KiServiceExit+15B↓j
.text:0046A6F5 lea esp, [ebp+_KTRAP_FRAME.SegFs]
.text:0046A6F8 pop fs // 将3环的 FS 赋值给 FS 寄存器
.text:0046A6FA assume fs:nothing
.text:0046A6FA
.text:0046A6FA loc_46A6FA: // CODE XREF: _KiServiceExit+C5↑j
.text:0046A6FA lea esp, [ebp+_KTRAP_FRAME._Edi]
.text:0046A6FD pop edi
.text:0046A6FE pop esi
.text:0046A6FF pop ebx
.text:0046A700 pop ebp // 此时的esp指向ErrCode(+0x64)
.text:0046A701 cmp word ptr [esp-60h+arg_64], 80h // CS
.text:0046A708 ja loc_46AFF4
.text:0046A70E add esp, 4 // esp指向EIP3(+0x68)
.text:0046A711 test [esp-64h+arg_64], 1 // cs
.text:0046A711 _KiServiceExit endp // sp-analysis failed
.text:0046A711
.text:0046A719
.text:0046A719 // =============== S U B R O U T I N E =======================================
.text:0046A719
.text:0046A719 // ========
.text:0046A719 // 实际上,如果当前系统支持快速系统调用,那么,这条指令将
.text:0046A719 // 被修正为“jnz short _KiSystemCallExi2“。而KiSystemCallExit2 所指的代码使用 sysexit
.text:0046A719 // 指令返回用户模式。这条指令的修正工作是在 KiEnableFastSyscallReturn 函数中完成的,
.text:0046A719 // 该函数被 KiRestoreFastSyscallReturnState 调用
.text:0046A719 // 《Windows内核原理与实现》 P553
.text:0046A719 // ========
.text:0046A719 //
.text:0046A719
.text:0046A719 _KiSystemCallExitBranch proc near // DATA XREF: KiDisableFastSyscallReturn()+9↑w
.text:0046A719 // KiEnableFastSyscallReturn():loc_427854↑r ...
.text:0046A719 jnz short _KiSystemCallExit
.text:0046A71B pop edx // edx = eip3
.text:0046A71C pop ecx // 从栈中移除cs
.text:0046A71D popf
.text:0046A71E jmp edx
.text:0046A720 // ---------------------------------------------------------------------------
.text:0046A720
.text:0046A720 _KiSystemCallExit: // CODE XREF: _KiSystemCallExitBranch↑j
.text:0046A720 // _KiSystemCallExit2+5↓j
.text:0046A720 // DATA XREF: ...
.text:0046A720 iret
.text:0046A720 _KiSystemCallExitBranch endp // sp-analysis failed
.text:0046A720
.text:0046A721
.text:0046A721 // =============== S U B R O U T I N E =======================================
.text:0046A721
.text:0046A721
.text:0046A721 _KiSystemCallExit2 proc near // DATA XREF: KiRestoreFastSyscallReturnState()+16↑o
.text:0046A721
.text:0046A721 arg_5 = byte ptr 9
.text:0046A721
.text:0046A721 test [esp+arg_5], 1
.text:0046A726 jnz short _KiSystemCallExit // ===========================================================
.text:0046A726 // EFLAG3.TF(Trap flag)!= 0,也就是说是从陷井进来的0环,不是快速调用进来的。
.text:0046A726 //
.text:0046A726 // 注意,这里首先测试 eflags 标志寄存器中的TF(陷阱) 标志是必要的,因为
.text:0046A726 // 在 Pentium II 以后的 Windows 系统上,若应用程序绕过KiFastSystemCall,而直接
.text:0046A726 // 利用 “int 2e” 指令进入内核,那么,在此处将会被检测到,从而通过irerd 返回,
.text:0046A726 // 这样就不会发生进人内核模式和返回用户模式不对称的情形。
.text:0046A726 //
.text:0046A726 // 《Windows内核原理与实现》 P554
.text:0046A726 // ====================================================================
.text:0046A728 pop edx // esp指向EIP3(+0x68)
.text:0046A729 add esp, 4 // 从栈中移除cs,此时 esp 指向eflag3
.text:0046A72C and [esp-8+arg_5], 0FDh // 禁止eflags中的中断标志
.text:0046A731 popf
.text:0046A732 pop ecx // 弹出esp
.text:0046A733 sti // 恢复中断,因为sysexit指令不会恢复中断标志
.text:0046A734 sysexit // 返回用户模式
.text:0046A736 iret
.text:0046A736 _KiSystemCallExit2 endp // sp-analysis failed
.text:0046A736
.text:0046A736 // ---------------------------------------------------------------------------
.text:0046A737 align 4
.text:0046A738 // START OF FUNCTION CHUNK FOR _KiServiceExit
.text:0046A738
.text:0046A738 loc_46A738: // CODE XREF: _KiServiceExit+8F↑j
.text:0046A738 test dword ptr [ebp+70h], 20000h // 异常链表非空,有异常需要处理
.text:0046A73F jnz short loc_46A74E
.text:0046A741 test dword ptr [ebp+6Ch], 1
.text:0046A748 jz loc_46A6BF // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A74E
.text:0046A74E loc_46A74E: // CODE XREF: _KiServiceExit+111↑j
.text:0046A74E xor ebx, ebx
.text:0046A750 mov esi, [ebp+18h]
.text:0046A753 mov edi, [ebp+1Ch]
.text:0046A756 mov dr7, ebx
.text:0046A759 mov dr0, esi
.text:0046A75C mov ebx, [ebp+20h]
.text:0046A75F mov dr1, edi
.text:0046A762 mov dr2, ebx
.text:0046A765 mov esi, [ebp+24h]
.text:0046A768 mov edi, [ebp+28h]
.text:0046A76B mov ebx, [ebp+2Ch]
.text:0046A76E mov dr3, esi
.text:0046A771 mov dr6, edi
.text:0046A774 mov dr7, ebx
.text:0046A777 jmp loc_46A6BF // 判断是否是V86(虚拟8086即16位操作系统)
.text:0046A77C // ---------------------------------------------------------------------------
.text:0046A77C
.text:0046A77C loc_46A77C: // CODE XREF: _KiServiceExit+BA↑j
.text:0046A77C mov eax, [esp+arg_40]
.text:0046A780 add esp, 30h
.text:0046A783 pop gs
.text:0046A785 pop es
.text:0046A786 assume es:nothing
.text:0046A786 pop ds
.text:0046A787 assume ds:_data
.text:0046A787 pop edx
.text:0046A788 pop ecx
.text:0046A789 jmp loc_46A6F5
.text:0046A78E // ---------------------------------------------------------------------------
.text:0046A78E
.text:0046A78E loc_46A78E: // CODE XREF: _KiServiceExit+A6↑j
.text:0046A78E mov ebx, [esp+arg_C] // CS最低位为0,说明先前模式是0
.text:0046A792 mov [esp+arg_68], ebx
.text:0046A796 mov ebx, [esp+arg_10]
.text:0046A79A sub ebx, 0Ch
.text:0046A79D mov [esp+arg_60], ebx
.text:0046A7A1 mov esi, [esp+arg_6C]
.text:0046A7A5 mov [ebx+8], esi
.text:0046A7A8 mov esi, [esp+arg_68]
.text:0046A7AC mov [ebx+4], esi
.text:0046A7AF mov esi, [esp+arg_64]
.text:0046A7B3 mov [ebx], esi
.text:0046A7B5 add esp, 54h
.text:0046A7B8 pop edi
.text:0046A7B9 pop esi
.text:0046A7BA pop ebx
.text:0046A7BB pop ebp
.text:0046A7BC mov esp, [esp-64h+arg_60]
.text:0046A7BF iret
.text:0046A7BF // END OF FUNCTION CHUNK FOR _KiServiceExit
.text:0046A7C0 // ---------------------------------------------------------------------------
.text:0046A7C0 // START OF FUNCTION CHUNK FOR _KiFastCallEntry
.text:0046A7C0
.text:0046A7C0 loc_46A7C0: // CODE XREF: _KiFastCallEntry+F2↑j
.text:0046A7C0 test byte ptr [ebp+6Ch], 1
.text:0046A7C4 jz loc_46A618 // 可以得知:当前函数的堆栈(也是当前线程的堆栈)从栈顶到栈底的分布情况:
.text:0046A7C4 // 1. 从3环拷贝过来的3环API参数
.text:0046A7C4 // 2. Trapframe(共0x8C字节)
.text:0046A7C4 // 3. 浮点寄存器(共0x210字节)
.text:0046A7CA mov eax, 0C0000005h
.text:0046A7CF jmp loc_46A61C
.text:0046A7CF // END OF FUNCTION CHUNK FOR _KiFastCallEntry
.text:0046A7D4
.text:0046A7D4 // =============== S U B R O U T I N E =======================================
.text:0046A7D4
.text:0046A7D4
.text:0046A7D4 _KiServiceExit2 proc near // CODE XREF: NtContinue(x,x)+3B↓j
.text:0046A7D4 // NtRaiseException(x,x,x)+3E↓j ...
.text:0046A7D4
.text:0046A7D4 arg_C = dword ptr 10h
.text:0046A7D4 arg_10 = dword ptr 14h
.text:0046A7D4 arg_38 = dword ptr 3Ch
.text:0046A7D4 arg_3C = dword ptr 40h
.text:0046A7D4 arg_40 = dword ptr 44h
.text:0046A7D4 arg_44 = dword ptr 48h
.text:0046A7D4 arg_48 = dword ptr 4Ch
.text:0046A7D4 arg_60 = dword ptr 64h
.text:0046A7D4 arg_64 = dword ptr 68h
.text:0046A7D4 arg_68 = dword ptr 6Ch
.text:0046A7D4 arg_6C = dword ptr 70h
.text:0046A7D4
.text:0046A7D4 cli
.text:0046A7D5 test [ebp+_KTRAP_FRAME.EFlags], 20000h
.text:0046A7DC jnz short loc_46A7E4 // 虚拟8086/3环
.text:0046A7DE test byte ptr [ebp+_KTRAP_FRAME.SegCs], 1
.text:0046A7E2 jz short loc_46A818
.text:0046A7E4
.text:0046A7E4 loc_46A7E4: // CODE XREF: _KiServiceExit2+8↑j
.text:0046A7E4 // _KiServiceExit2+42↓j
.text:0046A7E4 mov ebx, large fs:124h // 虚拟8086/3环
.text:0046A7EB mov [ebx+_KTHREAD.Alerted], 0
.text:0046A7EF cmp [ebx+_KTHREAD.ApcState.UserApcPending], 0
.text:0046A7F3 jz short loc_46A818
.text:0046A7F5 mov ebx, ebp
.text:0046A7F7 mov ecx, 1 // NewIrql
.text:0046A7FC call ds:__imp_@KfRaiseIrql@4 // KfRaiseIrql(x)
.text:0046A802 push eax
.text:0046A803 sti
.text:0046A804 push ebx
.text:0046A805 push 0
.text:0046A807 push 1
.text:0046A809 call _KiDeliverApc@12 // VOID KiDeliverApc (
.text:0046A809 // IN KPROCESSOR_MODE PreviousMode,
.text:0046A809 // IN PKEXCEPTION_FRAME ExceptionFrame,
.text:0046A809 // IN PKTRAP_FRAME TrapFrame
.text:0046A809 // )
.text:0046A80E pop ecx // NewIrql
.text:0046A80F call ds:__imp_@KfLowerIrql@4 // KfLowerIrql(x)
.text:0046A815 cli
.text:0046A816 jmp short loc_46A7E4 // 虚拟8086/3环
.text:0046A818 // ---------------------------------------------------------------------------
.text:0046A818
.text:0046A818 loc_46A818: // CODE XREF: _KiServiceExit2+E↑j
.text:0046A818 // _KiServiceExit2+1F↑j
.text:0046A818 mov edx, [esp+_KTRAP_FRAME.ExceptionList]
.text:0046A81C mov ebx, large fs:_KPCR.DebugActive
.text:0046A823 mov large fs:_KPCR, edx
.text:0046A82A mov ecx, [esp+_KTRAP_FRAME.PreviousPreviousMode]
.text:0046A82E mov esi, large fs:124h
.text:0046A835 mov [esi+_KTHREAD.PreviousMode], cl
.text:0046A83B test ebx, 0FFh
.text:0046A841 jnz short loc_46A894
.text:0046A843
.text:0046A843 loc_46A843: // CODE XREF: _KiServiceExit2+D0↓j
.text:0046A843 // _KiServiceExit2+FB↓j
.text:0046A843 test [esp+arg_6C], 20000h
.text:0046A84B jnz loc_46AFD8 // 虚拟8086模式
.text:0046A851 test word ptr [esp+arg_68], 0FFF8h
.text:0046A858 jz short loc_46A8D4
.text:0046A85A mov edx, [esp+arg_38]
.text:0046A85E mov ecx, [esp+arg_3C]
.text:0046A862 mov eax, [esp+arg_40]
.text:0046A866 cmp word ptr [ebp+6Ch], 8
.text:0046A86B jz short loc_46A879
.text:0046A86D lea esp, [ebp+30h]
.text:0046A870 pop gs
.text:0046A872 pop es
.text:0046A873 pop ds
.text:0046A874 lea esp, [ebp+50h]
.text:0046A877 pop fs
.text:0046A879
.text:0046A879 loc_46A879: // CODE XREF: _KiServiceExit2+97↑j
.text:0046A879 lea esp, [ebp+54h]
.text:0046A87C pop edi
.text:0046A87D pop esi
.text:0046A87E pop ebx
.text:0046A87F pop ebp
.text:0046A880 cmp word ptr [esp-60h+arg_64], 80h
.text:0046A887 ja loc_46AFF4
.text:0046A88D add esp, 4
.text:0046A890 iret
.text:0046A890 // ---------------------------------------------------------------------------
.text:0046A891 align 4
.text:0046A894
.text:0046A894 loc_46A894: // CODE XREF: _KiServiceExit2+6D↑j
.text:0046A894 test dword ptr [ebp+70h], 20000h
.text:0046A89B jnz short loc_46A8A6
.text:0046A89D test dword ptr [ebp+6Ch], 1
.text:0046A8A4 jz short loc_46A843
.text:0046A8A6
.text:0046A8A6 loc_46A8A6: // CODE XREF: _KiServiceExit2+C7↑j
.text:0046A8A6 xor ebx, ebx
.text:0046A8A8 mov esi, [ebp+18h]
.text:0046A8AB mov edi, [ebp+1Ch]
.text:0046A8AE mov dr7, ebx
.text:0046A8B1 mov dr0, esi
.text:0046A8B4 mov ebx, [ebp+20h]
.text:0046A8B7 mov dr1, edi
.text:0046A8BA mov dr2, ebx
.text:0046A8BD mov esi, [ebp+24h]
.text:0046A8C0 mov edi, [ebp+28h]
.text:0046A8C3 mov ebx, [ebp+2Ch]
.text:0046A8C6 mov dr3, esi
.text:0046A8C9 mov dr6, edi
.text:0046A8CC mov dr7, ebx
.text:0046A8CF jmp loc_46A843
.text:0046A8D4 // ---------------------------------------------------------------------------
.text:0046A8D4
.text:0046A8D4 loc_46A8D4: // CODE XREF: _KiServiceExit2+84↑j
.text:0046A8D4 mov ebx, [esp+arg_C]
.text:0046A8D8 mov [esp+arg_68], ebx
.text:0046A8DC mov ebx, [esp+arg_10]
.text:0046A8E0 sub ebx, 0Ch
.text:0046A8E3 mov [esp+arg_60], ebx
.text:0046A8E7 mov esi, [esp+arg_6C]
.text:0046A8EB mov [ebx+8], esi
.text:0046A8EE mov esi, [esp+arg_68]
.text:0046A8F2 mov [ebx+4], esi
.text:0046A8F5 mov esi, [esp+arg_64]
.text:0046A8F9 mov [ebx], esi
.text:0046A8FB mov eax, [esp+arg_40]
.text:0046A8FF mov edx, [esp+arg_38]
.text:0046A903 mov ecx, [esp+arg_3C]
.text:0046A907 add esp, 54h
.text:0046A90A pop edi
.text:0046A90B pop esi
.text:0046A90C pop ebx
.text:0046A90D pop ebp
.text:0046A90E mov esp, [esp-64h+arg_60]
.text:0046A911 iret
.text:0046A911 _KiServiceExit2 endp // sp-analysis failed
.text:0046A911
.text:0046A912 // ---------------------------------------------------------------------------
.text:0046A912 retn
.text:0046A912 // ---------------------------------------------------------------------------

总结:可以看到无论是从函数KiSystemService,还是从函数KiFastCallEntry往下分析,都会调用地址0046A5AF ,只是在调用该地址前初始化工作有些不同。

进入地址0046A5AF 前,eax保存系统服务号,edx都指向3环API的参数起始地址。

往下的代码段 将会放到下一节开始分析余下的两个问题:

  1. 如何根据系统调用号(eax中存储)找到要执行的内核函数?
  2. 调用时参数是存储到3环的堆栈,如何传递给内核函数?

可参考:

6 从0环回3环

6.1 中断返回

6.2 快速调用返回

sysenter指令执行步骤如下:

  1. IA32_SYSENTER_CS保存到 CS中。
  2. IA32_SYSENTER_EIP保存到 EIP 中。
  3. IA32_SYSENTER_CS + 8保存到 SS 中。
  4. IA32_SYSENTER_ESP保存到 ESP 寄存器中。
  5. 切换到 R0 级别。
  6. 如果 EFLAGS 中的 VM 标志被设定,那么清 0 该标志。
  7. 开始执行 R0 代码。

sysexit指令执行前需要如下准备工作:设置EDX为ring3下要执行的指令的首地址。设置ECX为ring3下的栈指针。
sysexit指令的执行步骤如下:

  1. IA32_SYSENTER_CS + 16保存到 CS 中。(R3下代码段)
  2. 将 EDX 赋值给 EIP。
  3. IA32_SYSENTER_CS + 24保存到 SS 中。
  4. 将 ECX 赋值给 ESP。
  5. 切换到 R3 下继续执行 R3 代码。
指令 寄存器的值 寄存器填装 对应GDT段描述符
sysenter CS = 0x1B、SS = 0x23、FS = 0x3B
DS = 0x23、ES = 0x23、GS = 0
CS0 = IA32_SYSENTER_CS
SS0 = IA32_SYSENTER_CS + 8
ESP0 = IA32_SYSENTER_ESP(临时)
EIP0 = IA32_SYSENTER_EIP
R0:
CS0 = 0x8,00001 0 00,Index = 1,GDT_CS = 00cf9b00`0000ffff。
SS0 = GDT_CS + 8,–> Index = 2,GDT_SS0 = 00cf9300`0000ffff。
sysexit CS = 0x8、SS = 0x10、FS = 0x30
DS = 0x23、ES = 0x23、GS = 0
CS3 = IA32_SYSENTER_CS + 16
SS3 = IA32_SYSENTER_CS + 24
ESP3 = ECX
EIP3 = EDX
R3:
CS3 = GDT_CS + 16,–> Index = 3,GDT_CS3 = 00cffb00`0000ffff。
SS3 = GDT_CS + 24,–> Index = 4,GDT_SS3 = 00cff300`0000ffff。
1
2
3
4
5
6
7
8
kd> r gdtr
gdtr=8003f000
kd> dq 8003f000
8003f000 00000000`00000000 00cf9b00`0000ffff
8003f010 00cf9300`0000ffff 00cffb00`0000ffff
8003f020 00cff300`0000ffff 80008b04`200020ab
8003f030 ffc093df`f0000001 0040f300`00000fff
8003f040 0000f200`0400ffff 00000000`00000000

1、需要解释下快速调用缺少的如下在KiSystemService的两句:

1
2
.text:0046A492                 mov     ebx, [esi+_KTHREAD.TrapFrame]
.text:0046A498 mov [ebp+_KTRAP_FRAME._Edx], ebx

2、快速调用返回函数KiFastSystemCallRet是紧跟在KiFastSystemCall之后的:

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
kd> u 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter
ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret
7c92e4f5 8da42400000000 lea esp,[esp]
7c92e4fc 8d642400 lea esp,[esp]
ntdll!KiIntSystemCall:
7c92e500 8d542408 lea edx,[esp+8]
7c92e504 cd2e int 2Eh
7c92e506 c3 ret

kd> u 0x7c92e4f4
ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret
7c92e4f5 8da42400000000 lea esp,[esp]
7c92e4fc 8d642400 lea esp,[esp]
ntdll!KiIntSystemCall:
7c92e500 8d542408 lea edx,[esp+8]
7c92e504 cd2e int 2Eh
7c92e506 c3 ret
7c92e507 90 nop
ntdll!RtlRaiseException:
7c92e508 55 push ebp

3、好好解释一下快速返回KiFastSystemCallRetret实际就是弹出NtReadVirtualMemory的返回地址去执行。

53.png

6.3 0环的返回3环寄存器

从0环返回三环,最重要的是找到 EIP3ESP3EBP3的位置。

两种返回方式:iretdsysexit

  1. 常规方式 :系统调用返回
    • 进入0环时

      • ESP3:TRAP_FRAME.HardwareSegEsp = edx,第二个返回地址(KiFastCallEntry)

      • EIP3:KTRAP_FRAME.Eip = _KUSER_SHARED_DATA.SystemCallReturn(KiFastCallEntry)

      • EBP3:TRAP_FRAME.Ebp = ebp (KiFastCallEntry)

        1
        2
        3
        4
        5
        .text:0046A539		push edx		//HardwareSegEsp = esp3 == 3环的第二个返回地址
        ...//eip3
        .text:0046A548 push dword ptr ds:0FFDF0304h
        ...//ebp3
        .text:0046A550 push ebp
    • 退出0环时

      • ESP3:ecx

      • EIP3:edx

      • EBP3:ebp

        1
        2
        3
        4
        5
        6
        //KiSystemCallExit2
        .text:0046A732 pop ecx //ecx = TrapFrame.HardwareSegEsp, sysexit --> esp3 = ecx
        ...//KiSystemCallExit2
        .text:0046A728 pop edx //edx = TrapFrame.Eip, sysexit --> eip3 = edx
        ...//KiServiceExit
        .text:0046A700 pop ebp //ebp = TrapFrame.Ebp, sysexit 不改变Ebp
  2. APC返回
    • 进入0环时:使用常规调用
    • 退出0环前:在执行到常规退出之前,修改TrapFrame
      • ESP3:在KiFastSystemCallRet函数里构造sizeof(CONTEXT) + 0x10 == 0x2DC,然后将这时3环的地址赋给TrapFrame.HardwareSegEsp(KiInitializeUserApc)。
      • EIP3:TrapFrame.Eip = KeUserApcDispatcher(KiInitializeUserApc)。
      • EBP3:在KiInitializeUserApc中没有处理,使用默认值。
    • 退出0环时:使用常规调用
  3. 用户异常返回(使用sysenter方式,非中断进入0环)
    • 进入0环时:使用常规调用
    • 退出0环前:在执行到常规退出之前,修改TrapFrame
      • ESP3:在KiDispatchException--KiEspToTrapFrame函数里赋值给TrapFrame.HardwareSegEsp,此时的Eip3指向KiUserExceptionDispatcher函数的栈顶(参数1、2,EXCEPTION_RECORD,CONTEXT)。
      • EIP3:TrapFrame.Eip = KeUserExceptionDispatcher(KiDispatchException)。
      • EBP3:在KiDispatchException中没有处理,使用默认值。
    • 退出0环时:使用常规调用

7 API前缀

12.png

21 22 23 24 25 26 年

25 26 27 28 29 30

b- b- b- b b- b-

​ 15 20 30

45 60

20*1.3 = 26