我们知道,在用户层运行的应用程序常常需要调用很多windows的API函数,通过这些API函数来应用做很多重要的操作。而事实上,很多用户层调用的这些windows的API函数,并不是实际上执行这些操作的函数,这些都是中间商,而真正的原厂商是内核层,需要到内核里面由内核驱动程序去执行的。
本次尽可能只说快速调用,快速调用不需要访问内存也更方便。因此,跟中断门相关的一些操作就不介绍了,而快速调用也区分在x86和x86_64的系统有区别(也就是32位和64位的),这里暂时不咬文嚼字(因为,所谓32位和64位又涉及到CPU,总线,操作系统等等,涉及地址宽度,寄存器宽度,咱们都粗略的当成一个内容,就是一次内存读取能够处理的数据宽度好了)
在具体展示如何调用的之前,需要做一个准备工作,就是装一个windows7的32位的操作系统,然后在里面安装下x64dbg,不知道哪里下载可以去吾爱找(你懂得)https://down.52pojie.cn/,当准备好之后也下载好之后,我们可以选一个目标函数作为分析,比如CloseHandle,这个函数又简单,又好分析。
1
2
3
|
BOOL CloseHandle(
[in] HANDLE hObject
);
|
首先,随便找一个程序在x32dbg打开,比如calc.exe,也就是计算器,位置在windows/system32文件夹里,当打开之后会进入初始页面。

接着快捷键ctrl+g,直接输入CloseHandle,会有提示的。会带你进入kernel32.dll的地址空间,也就是这个api的起始地址。

在函数开始处添加一个断电(快捷键F2),并让程序继续向后走,程序会断在这个地方,然后继续步过运行,走到如图位置,jmp那里。

然后步进,跟着一起跳转,会发现它已经进入到了kernelbase.dll,这个是更加靠近内核的dll,如果怕丢失,可以在函数开始再添加一个断点。

可以看到有一个NtClose的函数,跟着进入这个函数,可以看到进入到了一个结构简单,不断重复的区域,而且是ntdll.dll的地址范围。具体这段重复的函数其实进行了一个非常简单的调用,就是传了一个编号给eax寄存器,然后调用一个叫做KiFastsystetmCall的函数,而且这个函数的地址都是在7FFE0300。

然后跟进这个函数,会发现,只有三行,而且有一个特别的指令,就是sysenter,这个指令就是快速跳转到内核层,然后去调用内核对应的服务函数,而内核对应的服务函数也是类似的同名函数。

借此,我们可以大概推断,有很一大批window用户层的api函数,都是在用户层,通过调用一些windows的dll库,然后通过ntdll.dll这个动态库文件,通过里面存有的调用号,并通过KiFastsystetmCall这个函数,以及sysenter这个CPU指令,切入到内核,调用对应内核的服务函数,并通过内核的服务函数来真正处理相应的请求。
刚刚的过程是很快速的,毕竟在用户层其实使用通过一个跳板函数进入到内核,而真正完成服务内容的是内核,在这里首先先码住几个地址:
KiFastsystetmCall函数的地址:起始地址是0x77496c00,结束地址是,0x77496c04,在上个图片中可以看到。
保存这个函数地址的地址:0x7FFE0300,在上文进入KiFastsystetmCall这个函数之前,先通过一个确定的地址来找到这个函数的地址,这个地址就是它。
在用户层和内核层中间有一个共享的内存区域,这个区域就是一个结构体,_KUSER_SHARED_DATA,这个结构体对用户层可读,对内核层可读可写。里面就保存了KiFastsystetmCall的地址,在x86下,该地址为0x7FFE0000,而这个结构体的0x300的位置和0x304就是KiFastsystetmCall起始和返回位置。

而这个地址在内核层的逻辑地址是0xffdf0000,我们可以用windbg来查看,命令行输入如下内容,会显示对应的结构体的结构,我们也可以看到0x300的位置对应的函数内容
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
|
kd> dt _KUSER_SHARED_DATA 0xffdf0000
win32k!_KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : 0
+0x004 TickCountMultiplier : 0xf99a027
...
+0x2f4 DataFlagsPad : [1] 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x77496c00 //KiFastsystetmCall起始位置
+0x304 SystemCallReturn : 0x77496c04 //KiFastsystetmCall结束位置
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0x14a3
+0x320 ReservedTickCountOverlay : [3] 0x14a3
+0x32c TickCountPad : [1] 0
+0x330 Cookie : 0xf56e4cd
+0x334 CookiePad : [1] 0
+0x338 ConsoleSessionForegroundProcessId : 0n1192
+0x340 Wow64SharedInformation : [16] 0
+0x380 UserModeGlobalLogger : [16] 0
+0x3a0 ImageFileExecutionOptions : 0
+0x3a4 LangGenerationCount : 1
+0x3a8 Reserved5 : 0
+0x3b0 InterruptTimeBias : 0
+0x3b8 TscQpcBias : 0
+0x3c0 ActiveProcessorCount : 1
+0x3c4 ActiveGroupCount : 1
+0x3c6 Reserved4 : 0
+0x3c8 AitSamplingValue : 0
+0x3cc AppCompatFlag : 1
+0x3d0 SystemDllNativeRelocation : 0
+0x3d8 SystemDllWowRelocation : 0
+0x3dc XStatePad : [1] 0
+0x3e0 XState : _XSTATE_CONFIGURATION
...
|
而经过这个函数的跳转之后,就正式切入内核来处理对应的请求了。
当进入内核之后,进入的是KiFastCallEntry,内核的函数分析,可以用IDA这个静态逆向代码分析工具来分析辅助分析,首先明确一点,x86的内核文件是ntosknrl.exe这个文件,目录在windows/system32里面。
将文件拖入IDA中,等待解析完成后,在左侧的解析到的Functions中输入ctrl + F,在输入框输入函数名,即可来到这个函数其实地址了。

不知道是符号文件加载的问题还是什么原因,出来好几个这个函数,其实就是这个原名函数,而且没有这么短,后面的也是他,应该是软件分析产生了问题,可以忽略。
首先第一部分:
1
2
3
4
5
6
7
8
9
10
|
.text:00435200 mov ecx, 23h ; '#'
.text:00435205 push 30h ; '0'
.text:00435207 pop fs
.text:00435209 mov ds, ecx
.text:0043520B mov es, ecx
.text:0043520D mov ecx, large fs:40h
.text:00435214 mov esp, [ecx+4]
.text:00435217 push 23h ; '#'
.text:00435219 push edx
.text:0043521A pushf
|
1
2
3
4
5
6
7
8
9
|
.text:00435205 push 30h ; '0'
.text:00435207 pop fs
// fs寄存器在用户层存储的是TEB,也就是线程环境块,也就是每一个用户线程的环境
// fs寄存器在内核层,存放的局势线程环境块的,存储的是KPCR,
// KPCR中存储了CPU本身要用的一些重要数据:GDT、IDT以及线程相关的一些信息
// 一般是一个核一个KPCR结构体
// 所以这里更该了fs寄存器的内容,也就更改了通过fs能够指引到的地址,也就换成了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
|
//0x3748 bytes (sizeof)
struct _KPCR
{
union
{
struct _NT_TIB NtTib; //0x0
struct
{
struct _EXCEPTION_REGISTRATION_RECORD* Used_ExceptionList; //0x0
VOID* Used_StackBase; //0x4
VOID* Spare2; //0x8
VOID* TssCopy; //0xc
ULONG ContextSwitches; //0x10
ULONG SetMemberCopy; //0x14
VOID* Used_Self; //0x18
};
};
struct _KPCR* SelfPcr; //0x1c
struct _KPRCB* Prcb; //0x20
UCHAR Irql; //0x24
ULONG IRR; //0x28
ULONG IrrActive; //0x2c
ULONG IDR; //0x30
VOID* KdVersionBlock; //0x34
struct _KIDTENTRY* IDT; //0x38
struct _KGDTENTRY* GDT; //0x3c
struct _KTSS* TSS; //0x40
USHORT MajorVersion; //0x44
USHORT MinorVersion; //0x46
ULONG SetMember; //0x48
ULONG StallScaleFactor; //0x4c
UCHAR SpareUnused; //0x50
UCHAR Number; //0x51
UCHAR Spare0; //0x52
UCHAR SecondLevelCacheAssociativity; //0x53
ULONG VdmAlert; //0x54
ULONG KernelReserved[14]; //0x58
ULONG SecondLevelCacheSize; //0x90
ULONG HalReserved[16]; //0x94
ULONG InterruptMode; //0xd4
UCHAR Spare1; //0xd8
ULONG KernelReserved2[17]; //0xdc
struct _KPRCB PrcbData; //0x120
};
|
为了方便,我们可以在结构体窗口框中直接添加这个结构体,并让汇编解析的时候,直接映射过去,这样看的会更方便,如下操作:
shift+F9,或者依次打开view-open subviews-structures,就可以了,然后再结构体窗口中输入insert键盘按钮,然后在弹出的框中输入_KPCR,也是有自动提示的(前提是加载符号文件都是正常的)

然后回到汇编代码页面里,在fs:40h的40h上点击,然后输入键盘上的t字母,会弹出这样一个框,让你选择对应的结构体元素,通过上面结构体的定义,可以知道,第一个就是我们要选择的。

继续看汇编代码,接着修改了ds和es两个段寄存器,修改为0x23,在内核特权级别里,数据段选择子是0x23(至于为什么,后面再详叙,这涉及到提权后特权级别以及内存的段页划分机制等),其次将KPCR的0x40位置的值取了出来,也就是上文解析的_KTSS的结构体指针。
这个_KTSS结构体的定义如下:
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
|
//0x20ac bytes (sizeof)
struct _KTSS
{
USHORT Backlink; //0x0
USHORT Reserved0; //0x2
ULONG Esp0; //0x4
USHORT Ss0; //0x8
USHORT Reserved1; //0xa
ULONG NotUsed1[4]; //0xc
ULONG CR3; //0x1c
ULONG Eip; //0x20
ULONG EFlags; //0x24
ULONG Eax; //0x28
ULONG Ecx; //0x2c
ULONG Edx; //0x30
ULONG Ebx; //0x34
ULONG Esp; //0x38
ULONG Ebp; //0x3c
ULONG Esi; //0x40
ULONG Edi; //0x44
USHORT Es; //0x48
USHORT Reserved2; //0x4a
USHORT Cs; //0x4c
USHORT Reserved3; //0x4e
USHORT Ss; //0x50
USHORT Reserved4; //0x52
USHORT Ds; //0x54
USHORT Reserved5; //0x56
USHORT Fs; //0x58
USHORT Reserved6; //0x5a
USHORT Gs; //0x5c
USHORT Reserved7; //0x5e
USHORT LDT; //0x60
USHORT Reserved8; //0x62
USHORT Flags; //0x64
USHORT IoMapBase; //0x66
struct _KiIoAccessMap IoMaps[1]; //0x68
UCHAR IntDirectionMap[32]; //0x208c
};
|
1
|
.text:00435214 mov esp, [ecx+4]
|
通过接下来的这一句汇编代码我们了解,他是把_KTSS结构体里存储的ESP0,修改为当前esp寄存器的值,但是有一个有意思的点在于,我们都知道用户层是通过sysenter进入内核的,其次,还要知道一个事情就是sysenter进入内核的时候做了下面这几个事情:
- 从IA32_SYSENTER_CS从取出段选择子加载到CS中。
- 从IA32_SYSENTER_EIP取出指令指针放到EIP中
- 将IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。
- 从IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中
- 切换到0层。
- 若EFLAGS中VM标志已被置,则清除VM标志。
- 开始执行选择的系统过程。
其中, IA32_SYSENTER_CS、IA32_SYSENTER_EIP和IA32_SYSENTER_ESP是MSR寄存器,在CUP里有好多个MSR寄存器,这就是为什么sysenter能够快速调用的一个原因,关键的内容存在的寄存器里,而不需要再访问内存,以上的过程,满足了存储切换之后的堆栈段,代码段,以及指令指针和堆栈顶的任务。
既然知道了上面的事情,为啥在进入内核KiFastCallEntry这个函数之后,还要重新制定ESP呢,事实是windows并没有用它这个寄存器里的ESP,而是从这个结构体了获取并填充了ESP。
然后进入了无情push阶段,就相当于不断向栈里填充内容,更像是在填充一个结构体,事实上这个结构体是_KTRAP_FRAME
定义如下:
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
|
//0x8c bytes (sizeof)
struct _KTRAP_FRAME
{
ULONG DbgEbp; //0x0
ULONG DbgEip; //0x4
ULONG DbgArgMark; //0x8
ULONG DbgArgPointer; //0xc
USHORT TempSegCs; //0x10
UCHAR Logging; //0x12
UCHAR Reserved; //0x13
ULONG TempEsp; //0x14
ULONG Dr0; //0x18
ULONG Dr1; //0x1c
ULONG Dr2; //0x20
ULONG Dr3; //0x24
ULONG Dr6; //0x28
ULONG Dr7; //0x2c
ULONG SegGs; //0x30
ULONG SegEs; //0x34
ULONG SegDs; //0x38
ULONG Edx; //0x3c
ULONG Ecx; //0x40
ULONG Eax; //0x44
ULONG PreviousPreviousMode; //0x48
struct _EXCEPTION_REGISTRATION_RECORD* ExceptionList; //0x4c
ULONG SegFs; //0x50
ULONG Edi; //0x54
ULONG Esi; //0x58
ULONG Ebx; //0x5c
ULONG Ebp; //0x60
ULONG ErrCode; //0x64
ULONG Eip; //0x68
ULONG SegCs; //0x6c
ULONG EFlags; //0x70
ULONG HardwareEsp; //0x74
ULONG HardwareSegSs; //0x78
ULONG V86Es; //0x7c
ULONG V86Ds; //0x80
ULONG V86Fs; //0x84
ULONG V86Gs; //0x88
};
|
push的过程是有高地址向低地址填充,所以是从最后一个地址开始填充的。但是,事实上这个是从倒数第五个开始,也就是HardwareSegSs开始的(暂且记住吧)
注意:这里的一个push有一个重要的意义,将edx存给了这个结构体的HardwareEsp这个值,而这个edx是什么呢,回到上文中可以看到,在用户层最后把esp给到了edx,也就是用户层的esp最后给到了这个结构体中的HardwareEsp,也就是用户层的堆栈栈顶的位置。
1
|
.text:00435219 push edx
|
然后就是把eflags保存。
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
|
.text:0043521B push 2
.text:0043521D add edx, 8
.text:00435220 popf
.text:00435221 or byte ptr [esp+1], 2
.text:00435226 push 1Bh
.text:00435228 push dword ptr ds:0FFDF0304h
.text:0043522E push 0
.text:00435230 push ebp
.text:00435231 push ebx
.text:00435232 push esi
.text:00435233 push edi
.text:00435234 mov ebx, large fs:1Ch
.text:0043523B push 3Bh ; ';'
.text:0043523D mov esi, [ebx+124h]
.text:00435243 push dword ptr [ebx]
.text:00435245 mov dword ptr [ebx], 0FFFFFFFFh
.text:0043524B mov ebp, [esi+28h]
.text:0043524E push 1
.text:00435250 sub esp, 48h
.text:00435253 sub ebp, 29Ch
.text:00435259 mov byte ptr [esi+13Ah], 1
.text:00435260 cmp ebp, esp
.text:00435262 jnz loc_4351D9
.text:00435268 test byte ptr [ebp+6Ch], 1
.text:0043526C jnz short loc_43528C
.text:0043526E lfence
.text:00435271 jmp loc_43541E
|
其中,下面这两句并不是在push,而是在尝试修改eflags寄存器(标志位)
1
2
3
4
5
6
7
|
.text:0043521B push 2
.text:00435220 popf
.text:00435221 or byte ptr [esp+1], 2
//首先push和popf会把当前的标志位,几乎都置0,其中IF也置位0,也就是不可中断状态
//其次是将保存到KTRAP_FRAME中的IF标志位值1,也就是用户层的IF是可中断的状态
|
然后继续看
1
2
3
4
5
6
7
8
9
10
11
12
|
// edx存的是用户层的esp,esp + 8,其实就是从用户层传入的参数的地址
.text:0043521D add edx, 8
// 保存用户层的CS地址,代码段段选择子
.text:00435226 push 1Bh
// 保存刚刚多次提过的KiFastsystetmCall的结束地址,也就是最后要回去的地址
// ULONG Eip;
.text:00435228 push dword ptr ds:0FFDF0304h
// 将3B给了FS,也就是用户层的FS,是TEB
.text:0043523B push 3Bh ; ';'
|
其中:这部分要看下
1
2
|
// 从fs也就是kpcr的1B拿出了一个值,这个值还是KPCR自己
.text:00435234 mov ebx, large fs:1Ch
|
如果想要具体论证这个是不是自己的地址,可以看下这篇文章https://blog.csdn.net/hu3167343/article/details/7612595
继续往下分析:
但是KPCR的0x124的位置还是一个结构体,也就是_KPRCB,这个结构体的定义实在太长了,我就粘一部分,具体可以看这个链接https://www.vergiliusproject.com/kernels/x86/windows-7/sp1/_KPRCB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//0x3628 bytes (sizeof)
struct _KPRCB
{
USHORT MinorVersion; //0x0
USHORT MajorVersion; //0x2
struct _KTHREAD* CurrentThread; //0x4
struct _KTHREAD* NextThread; //0x8
struct _KTHREAD* IdleThread; //0xc
UCHAR LegacyNumber; //0x10
UCHAR NestingLevel; //0x11
USHORT BuildType; //0x12
CHAR CpuType; //0x14
CHAR CpuID; //0x15
...
|
可以看出, esi被赋的值是struct _KTHREAD* CurrentThread;也就是当前线程结构体的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 接着就是把KPCR的首地址内容push进去
.text:00435243 push dword ptr [ebx]
// 但是KPCR的首地址也是一个结构体
//0x1c bytes (sizeof)
struct _NT_TIB
{
struct _EXCEPTION_REGISTRATION_RECORD* ExceptionList; //0x0
VOID* StackBase; //0x4
VOID* StackLimit; //0x8
VOID* SubSystemTib; //0xc
union
{
VOID* FiberData; //0x10
ULONG Version; //0x10
};
VOID* ArbitraryUserPointer; //0x14
struct _NT_TIB* Self; //0x18
};
// 也就是ExceptionList这个异常链的地址,刚好对应存入的就是KTRAP_FRAME的异常链成员
// 而且刚刚保存之后就给覆盖掉了
.text:00435245 mov dword ptr [ebx], 0FFFFFFFFh
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 然后获取线程结构体的第0x28处的值赋值到ebp中
.text:0043524B mov ebp, [esi+28h]
//_KTHREAD结构体部分定义
//0x200 bytes (sizeof)
struct _KTHREAD
{
struct _DISPATCHER_HEADER Header; //0x0
volatile ULONGLONG CycleTime; //0x10
volatile ULONG HighCycleTime; //0x18
ULONGLONG QuantumTarget; //0x20
VOID* InitialStack; //0x28
VOID* volatile StackLimit; //0x2c
VOID* KernelStack; //0x30
ULONG ThreadLock; //0x34
union _KWAIT_STATUS_REGISTER WaitRegister; //0x38
volatile UCHAR Running; //0x39
UCHAR Alerted[2]; //0x3a
...
// 从上文的结构体定义可以看出,赋的是InitialStack,也就是初始化栈,其实也就是当前栈空间的最栈底的位置。
|
1
2
3
4
|
.text:0043524E push 1
// 给PreviousPreviousMode赋值1,
// 先前模式,是从用户层还是内核层发起的调用,就是切换前是什么层,1为用户层
|
继续向后看:
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
|
// 现在栈的位置就是KTRAP_FRAME的0x48处,所以sub之后直接到结构体首位置
.text:00435250 sub esp, 48h
.text:00435253 sub ebp, 29Ch
// 线程结构体操作
.text:00435259 mov byte ptr [esi+13Ah], 1
// 以下是部分thread线程结构体内容:
UCHAR Preempted; //0x137
UCHAR AdjustReason; //0x138
CHAR AdjustIncrement; //0x139
CHAR PreviousMode; //0x13a
CHAR Saturation; //0x13b
ULONG SystemCallNumber; //0x13c
ULONG FreezeCount; //0x140
// 给线程的先前模式赋值1,表示线程是从用户层进入内核的调用,是一个用户层的线程
// 比较ebp和esp的值是否相等,如果不相等就调到异常了,所以这俩一定要相等。
.text:00435260 cmp ebp, esp
.text:00435262 jnz loc_4351D9
// 判断cs代码段是否为1,如果是1,那就继续,表示是用户层,
.text:00435268 test byte ptr [ebp+6Ch], 1
.text:0043526C jnz short loc_43528C
.text:0043526E lfence
// 直接进入下一段代码(这里可能会有区别但是大差不差)
.text:00435271 jmp loc_43541E
|
接着下一段代码:
1
2
3
4
5
6
7
8
9
|
// KTRAP_FRAME 的0x2C位置的值置0,也就是ULONG Dr7;
.text:0043541E and dword ptr [ebp+2Ch], 0
// 然后判断线程结构体的0x3的值,
.text:00435422 test byte ptr [esi+3], 0DFh
// 将KTRAP_FRAME 的地址,再保存到线程里面去
.text:00435426 mov [esi+128h], ebp
.text:0043542C jnz Dr_FastCallDrSave
|
线程块的第一个结构体的定义如下:
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
|
//0x10 bytes (sizeof)
struct _DISPATCHER_HEADER
{
union
{
struct
{
UCHAR Type; //0x0
union
{
UCHAR TimerControlFlags; //0x1
struct
{
UCHAR Absolute:1; //0x1
UCHAR Coalescable:1; //0x1
UCHAR KeepShifting:1; //0x1
UCHAR EncodedTolerableDelay:5; //0x1
};
UCHAR Abandoned; //0x1
UCHAR Signalling; //0x1
};
union
{
UCHAR ThreadControlFlags; //0x2
struct
{
UCHAR CpuThrottled:1; //0x2
UCHAR CycleProfiling:1; //0x2
UCHAR CounterProfiling:1; //0x2
UCHAR Reserved:5; //0x2
};
UCHAR Hand; //0x2
UCHAR Size; //0x2
};
union
{
UCHAR TimerMiscFlags; //0x3
struct
{
UCHAR Index:1; //0x3
UCHAR Processor:5; //0x3
UCHAR Inserted:1; //0x3
volatile UCHAR Expired:1; //0x3
};
UCHAR DebugActive; //0x3
struct
{
UCHAR ActiveDR7:1; //0x3
UCHAR Instrumented:1; //0x3
UCHAR Reserved2:4; //0x3
UCHAR UmsScheduled:1; //0x3
UCHAR UmsPrimary:1; //0x3
};
UCHAR DpcActive; //0x3
};
};
volatile LONG Lock; //0x0
};
LONG SignalState; //0x4
struct _LIST_ENTRY WaitListHead; //0x8
};
//可以看出0x3位置的值,大多都是跟调试相关的,暂时可以先不在这里详细描述
|
后面的这一段,也都是在保存当前的其他寄存器的内容,就不详细叙述了。
1
2
3
4
5
6
7
8
9
|
.text:00435432 mov ebx, [ebp+60h]
.text:00435435 mov edi, [ebp+68h]
.text:00435438 mov [ebp+0Ch], edx
.text:0043543B mov dword ptr [ebp+8], 0BADB0D00h
.text:00435442 mov [ebp+0], ebx
.text:00435445 mov [ebp+4], edi
// 最后将当前eflags的IF标志位置1,表示可以中断
.text:00435448 sti
|
至此,我们这个KTRAP_FRAME填充都结束了,主要就是用户层切入内核之后,保存其状态,等切回的时候还可以还原其上下文。
因为篇幅过长,我们姑且这一篇写到这里,后面再继续介绍保存好环境之后要做的事情。