windows系统调用(一)

简介:这是一篇关于windows系统调用的介绍,用户层如果需要使用windows提供的一些api,这个函数最终是如何实现的,通过这篇文章介绍下。

前言

我们知道,在用户层运行的应用程序常常需要调用很多windows的API函数,通过这些API函数来应用做很多重要的操作。而事实上,很多用户层调用的这些windows的API函数,并不是实际上执行这些操作的函数,这些都是中间商,而真正的原厂商是内核层,需要到内核里面由内核驱动程序去执行的。

用户层如何进入系统层

本次尽可能只说快速调用,快速调用不需要访问内存也更方便。因此,跟中断门相关的一些操作就不介绍了,而快速调用也区分在x86和x86_64的系统有区别(也就是32位和64位的),这里暂时不咬文嚼字(因为,所谓32位和64位又涉及到CPU,总线,操作系统等等,涉及地址宽度,寄存器宽度,咱们都粗略的当成一个内容,就是一次内存读取能够处理的数据宽度好了)

x86的快速调用

在具体展示如何调用的之前,需要做一个准备工作,就是装一个windows7的32位的操作系统,然后在里面安装下x64dbg,不知道哪里下载可以去吾爱找(你懂得)https://down.52pojie.cn/,当准备好之后也下载好之后,我们可以选一个目标函数作为分析,比如CloseHandle,这个函数又简单,又好分析。

1
2
3
BOOL CloseHandle(
  [in] HANDLE hObject
);
用户层分析

首先,随便找一个程序在x32dbg打开,比如calc.exe,也就是计算器,位置在windows/system32文件夹里,当打开之后会进入初始页面。

2

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

3

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

4

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

5

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

6

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

7

借此,我们可以大概推断,有很一大批window用户层的api函数,都是在用户层,通过调用一些windows的dll库,然后通过ntdll.dll这个动态库文件,通过里面存有的调用号,并通过KiFastsystetmCall这个函数,以及sysenter这个CPU指令,切入到内核,调用对应内核的服务函数,并通过内核的服务函数来真正处理相应的请求。

内核层

刚刚的过程是很快速的,毕竟在用户层其实使用通过一个跳板函数进入到内核,而真正完成服务内容的是内核,在这里首先先码住几个地址:

KiFastsystetmCall函数的地址:起始地址是0x77496c00,结束地址是,0x77496c04,在上个图片中可以看到。

保存这个函数地址的地址:0x7FFE0300,在上文进入KiFastsystetmCall这个函数之前,先通过一个确定的地址来找到这个函数的地址,这个地址就是它。

_KUSER_SHARED_DATA

在用户层和内核层中间有一个共享的内存区域,这个区域就是一个结构体,_KUSER_SHARED_DATA,这个结构体对用户层可读,对内核层可读可写。里面就保存了KiFastsystetmCall的地址,在x86下,该地址为0x7FFE0000,而这个结构体的0x300的位置和0x304就是KiFastsystetmCall起始和返回位置。

8

而这个地址在内核层的逻辑地址是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

当进入内核之后,进入的是KiFastCallEntry,内核的函数分析,可以用IDA这个静态逆向代码分析工具来分析辅助分析,首先明确一点,x86的内核文件是ntosknrl.exe这个文件,目录在windows/system32里面。

将文件拖入IDA中,等待解析完成后,在左侧的解析到的Functions中输入ctrl + F,在输入框输入函数名,即可来到这个函数其实地址了。

9

不知道是符号文件加载的问题还是什么原因,出来好几个这个函数,其实就是这个原名函数,而且没有这么短,后面的也是他,应该是软件分析产生了问题,可以忽略。

_KPCR

首先第一部分:

 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,也是有自动提示的(前提是加载符号文件都是正常的)

10

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

11

_KTSS

继续看汇编代码,接着修改了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进入内核的时候做了下面这几个事情:

  1. 从IA32_SYSENTER_CS从取出段选择子加载到CS中。
  2. 从IA32_SYSENTER_EIP取出指令指针放到EIP中
  3. 将IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。
  4. 从IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中
  5. 切换到0层。
  6. 若EFLAGS中VM标志已被置,则清除VM标志。
  7. 开始执行选择的系统过程。

其中, IA32_SYSENTER_CS、IA32_SYSENTER_EIP和IA32_SYSENTER_ESP是MSR寄存器,在CUP里有好多个MSR寄存器,这就是为什么sysenter能够快速调用的一个原因,关键的内容存在的寄存器里,而不需要再访问内存,以上的过程,满足了存储切换之后的堆栈段,代码段,以及指令指针和堆栈顶的任务。

既然知道了上面的事情,为啥在进入内核KiFastCallEntry这个函数之后,还要重新制定ESP呢,事实是windows并没有用它这个寄存器里的ESP,而是从这个结构体了获取并填充了ESP。

_KTRAP_FRAME

然后进入了无情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

继续往下分析:

_KPRCB

但是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;也就是当前线程结构体的地址。

_NT_TIB
 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
_KTHREAD
 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
_DISPATCHER_HEADER

线程块的第一个结构体的定义如下:

 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填充都结束了,主要就是用户层切入内核之后,保存其状态,等切回的时候还可以还原其上下文。

因为篇幅过长,我们姑且这一篇写到这里,后面再继续介绍保存好环境之后要做的事情。

updatedupdated2025-01-062025-01-06