x64 番外篇——保护模式相关
阅读原文时间:2022年04月02日阅读:5

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


华丽的分割线


  在64位下,有两种CPU模式,一种是IA-32e模式,是IA-32模式的扩展,另一个是Legacy模式。IA-32e模式是指内核64位,用户64或32位均可,它强制平坦段,不支持任务切换;而Legacy模式指内核32位,用户32位支持非平坦段、任务切换、虚拟8086、实模式等。

  在IA-32e模式下,代码段和数据段仍使用64位描述符,强制平坦(FSGS除外);TSS段描述符扩展到128位,TSS段不用来任务切换,主要保存一堆rsp备用指针;中断门描述符扩展到128位。

  有些寄存器我们需要知道的,如下所示:

MSR 寄存器名

索引数值

IA32_EFER_MSR

C0000080H

IA32_FS_BASE

C0000100H

IA32_GS_BASE

C0000101H

IA32_KERNEL_GS_BASE

C0000102H

IA32_EFER_MSR

  该MSR结构如下所示:

  索引0位表示SYSCALL/SYSRET这类指令是否启用,索引8位表示IA-32e模式是否启用,索引10位表示IA-32e模式是否处于处于活动状态,索引11位知识是否启用PAE分页的XD位是否有效。如果到这里有不会的地方,请回去复习。

  下面我们在虚拟机中读取该寄存器的值:

kd> rdmsr C0000080
msr[c0000080] = 00000000`00000d01
kd> .formats 00000000`00000d01
Evaluate expression:
  Hex:     00000000`00000d01
  Decimal: 3329
  Octal:   0000000000000000006401
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00001101 00000001
  Chars:   ........
  Time:    Thu Jan  1 08:55:29 1970
  Float:   low 4.66492e-042 high 0
  Double:  1.64474e-320

  从上面的值我们可以看出寄存器里面的所有的功能处于启用状态。

IA32_FS_BASE

  这个MSR寄存器保存的是fs的基址,由于是64位内核,直接读取的话该值是0,我们可以读取一下:

kd> rdmsr C0000100
msr[c0000100] = 00000000`00000000

IA32_GS_BASE

  这个MSR寄存器保存的是gs的基址,内核指向KPCR结构体(和32位的fs一样的作用),我们可以尝试以下:

kd> rdmsr C0000101
msr[c0000101] = fffff805`5e089000

kd> dt _KPCR fffff805`5e089000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : 0xfffff805`65690fb0 _KGDTENTRY64
   +0x008 TssBase          : 0xfffff805`6568f000 _KTSS64
   +0x010 UserRsp          : 0
   +0x018 Self             : 0xfffff805`5e089000 _KPCR
   +0x020 CurrentPrcb      : 0xfffff805`5e089180 _KPRCB
   +0x028 LockArray        : 0xfffff805`5e089870 _KSPIN_LOCK_QUEUE
   +0x030 Used_Self        : 0x00000079`22c2a000 Void
   +0x038 IdtBase          : 0xfffff805`6568e000 _KIDTENTRY64
   +0x040 Unused           : [2] 0
   +0x050 Irql             : 0 ''
   +0x051 SecondLevelCacheAssociativity : 0xc ''
   +0x052 ObsoleteNumber   : 0 ''
   +0x053 Fill0            : 0 ''
   +0x054 Unused0          : [3] 0
   +0x060 MajorVersion     : 1
   +0x062 MinorVersion     : 1
   +0x064 StallScaleFactor : 0xb58
   +0x068 Unused1          : [3] (null)
   +0x080 KernelReserved   : [15] 0
   +0x0bc SecondLevelCacheSize : 0x900000
   +0x0c0 HalReserved      : [16] 0xad178600
   +0x100 Unused2          : 0
   +0x108 KdVersionBlock   : (null)
   +0x110 Unused3          : (null)
   +0x118 PcrAlign1        : [24] 0
   +0x180 Prcb             : _KPRCB

IA32_KERNEL_GS_BASE

  这个MSR的作用是作为GS基址的交换目标。说人话的话就是一个缓存,交换比较方便。我们从3环到0环。从0环到3环,GS内核指向KPCR,3环指向TEB,是如何做到呢?这个就是一个非常重要的寄存器.进0环的时候,把3环的交给它;出0环的时候,把0环的交给它。

  我们来读一下:

kd> rdmsr C0000102
msr[c0000102] = 00000079`22c2a000

  按照上述说法,这个就是TEB了,但由于这个是3环的地址,我们无法读取里面的结构体内容。

  由于现在64位CPU一般安装的系统都是IA-32e模式下的,即内核64位的,故后续所有介绍都会以IA-32e模式为主。

  在64位系统下,段的保护被进一步弱化。绝大多数的段被强制平坦。何为强制平坦,就是段的首地址和界限的值不再起作用,被保留置0。首地址默认是0,界限默认十六进制全是F。但也有例外,比如GSFS,因为它们指向一些结构体,这些结构体的首地址肯定不是0。

  64位的段和32位的段大体上就上述变化,但有一个段比较特别,那就是代码段,我们来看一下它的结构:

  代码段比32位的多了个位L,它的作用是来指示是32位的还是64位的,如果是0表示兼容模式(x86模式),为1则表示x64模式。

  里面还有一个D位,它的作用是指示默认大小的。如果L == 0,如果该位是0,默认的数据和地址大小为16位;反之为32位。如果L == 1D位必须为0,否则会触发通用保护异常。

  最后要介绍的段就是TSS段,被扩展为128位,用于满足寻址要求,如下结构所示:

  TSS和32位的操作系统一样,只是用来保存一堆寄存器的,并不用于任务切换。

  在64位下分页不再是PAE分页,而是被Intel称之为4级分页(4-LEVEL PAGING)的东西。当CR0.PG == 1 && CR4.PAE == 1 && IA32_EFER.LME == 1时会启用该分页模式。4级分页将48位线性地址转换为52位物理地址。虽然52位对应于4 PBytes,但线性地址仅限于48位,最多可以访问256 TBytes的线性地址空间。也就是说剩余的16位是被保留的。

  4级分页使用分页结构的层次结构来生成线性地址的转换。CR3用于定位第一个分页结构,即PML4表。将CR3与4级分页一起使用取决于是否已通过设置CR4.PCIDE启用进程上下文标识符。

  这个CR3也是结构的,并不直接是物理地址的,如果CR4.PCIDE为0,则它的结构如下:

  如果CR4.PCIDE为1,则它的结构如下:

  其中MMAXPHYADDR的缩写,最大是52,这个值通过CPUID.80000008H:EAX[7:0]进行查询,在我的虚拟机操作系统这个值为十进制的39

kd> r cr4
cr4=0000000000070678

kd> .formats 0000000000070678
Evaluate expression:
  Hex:     00000000`00070678
  Decimal: 460408
  Octal:   0000000000000001603170
  Binary:  00000000 00000000 00000000 00000000 00000000 00000111 00000110 01111000
  Chars:   .......x
  Time:    Tue Jan  6 15:53:28 1970
  Float:   low 6.45169e-040 high 0
  Double:  2.27472e-318

  首先我们得确认这个CR4.PCIDE是在哪个位上,发现是在索引17位:

  这个位被置1了,说明是第二种形式。有关这部分细节,对于我们不写操作系统的人来说似乎没啥用处,就不追究了。

  下面我们来看看4级分页线性地址的结构,如下是线性地址如何翻译成物理地址的:

  按照之前的说法套路,给一个非正式的名字9-9-9-9-12分页。又了之前的底子,我就不赘述内部的详细结构,直接用一张图进行总结(在Intel白皮书的第2832页):

  细心的你可能会发现protection key这个东西,这是啥呢?这个就是4级分页的一个新的机制,提供更完全的保护,当CR4.PKE == 1时有效,不幸的是,在我的虚拟机的操作系统并不使用该位,所以无效,如果对这块技术感兴趣请仔细阅读Intel白皮书的第2835页。

  当然上述分页还有大页的情形,如下所示:

  下面我们就用x86下的实验方式来学习分页,首先你得有新版的CheatEngine,下面开始:

  如上图所示,我打开了一个CheatEngine和一个Notepad,并且在记事本里面写上hello,world!。然后我们开始老套路搜索内存:

  把,改为%,定位到指定的地址,如下图所示:

  我们先用vtop执行偷懒看看结果:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
    SessionId: 1  Cid: 091c    Peb: 4e42d08000  ParentCid: 0b70
    DirBase: 9bd65002  ObjectTable: ffffdd8291064c40  HandleCount: 356.
    Image: notepad.exe

kd> !vtop 9bd65000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009bd65000
Amd64VtoP: PML4E 000000009bd65018
Amd64VtoP: PDPE 000000009d581bd8
Amd64VtoP: PDE 00000000a3882ec0
Amd64VtoP: PTE 00000000827ae020
Amd64VtoP: Mapped phys 0000000027fca4b0
Virtual address 1defb0044b0 translates to physical address 27fca4b0.
kd> !db 27fca4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  为什么我在遍历进程的后面加上notepad.exe呢?是因为在Win10上,就算你原装最低配置,也有一大串的进程,带来很大的烦恼。我明确是记事本的进程叫这个名字,我就在后面限定它。

  你或许还有一个疑问,为什么命名目录表基址的值为9bd65002,但为什么vtop的时候后面的2没了呢?那么我们再回过头来看看上图的Cr3结构你就会明白为什么。

  在做这个实验的时候,发现了一个十分有意思的现象,当你恢复虚拟机执行其他操作的时候,再次中断回到Windbg,注意我没有退出记事本,回来发现它的DirBase发生了变化。

  好,我们继续我们的实验:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
    SessionId: 1  Cid: 091c    Peb: 4e42d08000  ParentCid: 0b70
    DirBase: 9c065002  ObjectTable: ffffdd8291064c40  HandleCount: 359.
    Image: notepad.exe

kd> !vtop 9c065000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009c065000
Amd64VtoP: PML4E 000000009c065018
Amd64VtoP: PDPE 0000000036881bd8
Amd64VtoP: PDE 00000000a3982ec0
Amd64VtoP: PTE 00000000828ae020
Amd64VtoP: Mapped phys 0000000027fca4b0

  好我们手动拆分虚拟地址并用访问物理地址的方式进行该实验。首先拆分以下我们的虚拟地址:

000000011 = 0x3
101111011 = 0x17B
111011000 = 0x1D8
000000100 = 0x4
010010110000 = 0x4B0

  拆解完毕后,我们来开始进行测试:

kd> !dq 9c065000
#9c065000 8a000000`13a79867 00000000`00000000
#9c065010 00000000`00000000 8a000000`36881867
#9c065020 00000000`00000000 00000000`00000000
#9c065030 00000000`00000000 00000000`00000000
#9c065040 00000000`00000000 00000000`00000000
#9c065050 00000000`00000000 00000000`00000000
#9c065060 00000000`00000000 00000000`00000000
#9c065070 00000000`00000000 00000000`00000000
kd> !dq 36881000+17b*8
#36881bd8 0a000000`a3982867 00000000`00000000
#36881be8 00000000`00000000 00000000`00000000
#36881bf8 00000000`00000000 00000000`00000000
#36881c08 00000000`00000000 00000000`00000000
#36881c18 00000000`00000000 00000000`00000000
#36881c28 00000000`00000000 00000000`00000000
#36881c38 00000000`00000000 00000000`00000000
#36881c48 00000000`00000000 00000000`00000000
kd> !dq A3982000+1D8*8
#a3982ec0 0a000000`828ae867 0a000000`27aa6867
#a3982ed0 0a000000`1222f867 00000000`00000000
#a3982ee0 00000000`00000000 00000000`00000000
#a3982ef0 00000000`00000000 00000000`00000000
#a3982f00 00000000`00000000 00000000`00000000
#a3982f10 00000000`00000000 00000000`00000000
#a3982f20 0a000000`99493867 0a000000`91a7b867
#a3982f30 00000000`00000000 00000000`00000000
kd> !dq 828ae000+4*8
#828ae020 81000000`27fca867 81000000`10be0867
#828ae030 81000000`892f5867 81000000`980f7847
#828ae040 81000000`2aef6867 81000000`93dfe867
#828ae050 81000000`12f06867 81000000`5210f867
#828ae060 81000000`98211867 81000000`a4310847
#828ae070 81000000`b011f867 81000000`2b920867
#828ae080 81000000`95d23847 81000000`6da22847
#828ae090 81000000`0e728847 81000000`88827867
kd> !db 27fca000+4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  当然上述我们有不严谨的地方,我们并没有判断是否是大页的情况,但对于一般变量来说,大页的情况还是比较少的。

  学过我之前写的教程都知道,操作系统可是不能这么直接访问物理内存的,它依旧通过线性地址进行访问内容,必然在某个地址放着这几张表。按照之前的学习路线,我们必然得需要逆向MmIsAddressValid,下面我们开始继续:

; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
                public MmIsAddressValid
MmIsAddressValid proc near              ; CODE XREF: KiMarkBugCheckRegions+B3↑p
                                        ; KiMarkBugCheckRegions+1E8↑p ...
                sub     rsp, 28h
                call    MmIsAddressValidEx
                add     rsp, 28h
                retn
MmIsAddressValid endp

  可以看出,这个啥也没做,只是调用了MmIsAddressValidEx这个函数,我们点进去看看:

; BOOLEAN __fastcall MmIsAddressValidEx(PVOID VirtualAddress)
MmIsAddressValidEx proc near            ; CODE XREF: MiIncreaseUsedPtesCount+64↑p
                                        ; MiCommitExistingVad+65D↓p ...

PXE             = qword ptr -28h
PPE             = qword ptr -20h
PDE             = qword ptr -18h
PTE             = qword ptr -10h
VirtualAddress  = qword ptr  8

; FUNCTION CHUNK AT .text:00000001402234DC SIZE 00000056 BYTES

                mov     [rsp+VirtualAddress], rbx
                push    rdi
                sub     rsp, 20h
                mov     rax, rcx        ; VirtualAddress
                mov     r10, rcx
                sar     rax, 47         ; 实现的虚拟地址是48位的,如果是内核地址,则最后是-1,用户地址是0
                inc     rax             ; 自增1
                cmp     rax, 1          ; 如果大于1,有问题
                ja      InvalidAddr
                mov     rdx, rcx        ; VirtualAddress
                mov     rdi, 0FFFFF68000000000h ; PML4 表基址
                shr     rdx, 9
                mov     rcx, 7FFFFFFFF8h
                and     rdx, rcx
                mov     rax, rdi
                add     rdx, rax
                mov     [rsp+28h+PXE], rdx ; 获取 PXE
                shr     rdx, 9
                and     rdx, rcx
                mov     rax, rdi
                add     rdx, rax
                mov     [rsp+28h+PPE], rdx ; 获取 PPE
                shr     rdx, 9
                and     rdx, rcx
                mov     rax, rdi
                add     rdx, rax
                mov     [rsp+28h+PDE], rdx ; 获取 PDE
                shr     rdx, 9
                and     rdx, rcx
                mov     rax, rdi
                add     rdx, rax
                mov     r11, 0FFFFF6FB7DBED000h
                mov     [rsp+28h+PTE], rdx ; 获取 PTE
                mov     edx, 4
                mov     rbx, 0FFFFF6FB7DBED7F8h

loc_1400AB248:                          ; CODE XREF: MmIsAddressValidEx+BF↓j
                mov     r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
                dec     rdx
                mov     rcx, [r9]
                mov     rax, r11
                cmp     r9, rax
                jb      short loc_1400AB263
                mov     rax, rbx
                cmp     r9, rax
                jbe     short loc_1400AB27E

loc_1400AB263:                          ; CODE XREF: MmIsAddressValidEx+A9↑j
                                        ; MmIsAddressValidEx+D9↓j ...
                test    cl, 1
                jz      short InvalidAddr
                test    cl, cl
                js      short loc_1400AB2BD
                test    rdx, rdx
                jnz     short loc_1400AB248

loc_1400AB271:                          ; CODE XREF: MmIsAddressValidEx+113↓j
                                        ; MmIsAddressValidEx+122↓j
                mov     al, 1

loc_1400AB273:                          ; CODE XREF: MmIsAddressValidEx+126↓j
                mov     rbx, [rsp+28h+VirtualAddress]
                add     rsp, 20h
                pop     rdi
                retn
; ---------------------------------------------------------------------------

loc_1400AB27E:                          ; CODE XREF: MmIsAddressValidEx+B1↑j
                mov     eax, cs:MiFlags
                test    eax, 0C00000h
                jz      short loc_1400AB263
                mov     rax, gs:_KPCR.Prcb.CurrentThread
                mov     r8, [rax+_KTHREAD.___u25.ApcState.Process]
                cmp     [r8+_EPROCESS.Pcb.AddressPolicy], 1
                jz      short loc_1400AB263
                test    cl, 1
                jz      short InvalidAddr
                test    cl, 20h
                jz      loc_1402234DC
                test    cl, 42h
                jnz     short loc_1400AB263
                jmp     loc_1402234DC
; ---------------------------------------------------------------------------

loc_1400AB2BD:                          ; CODE XREF: MmIsAddressValidEx+BA↑j
                mov     rax, rdi
                cmp     r10, rax
                jb      short loc_1400AB271
                mov     rax, 0FFFFF6FFFFFFFFFFh
                cmp     r10, rax
                ja      short loc_1400AB271

InvalidAddr:                            ; CODE XREF: MmIsAddressValidEx+1B↑j
                                        ; MmIsAddressValidEx+B6↑j ...
                xor     al, al
                jmp     short loc_1400AB273
MmIsAddressValidEx endp

  但不幸的是,我根本无法弄懂这块代码的所有细节,完全弄懂的部分均用注释标注,主要是如下代码看起来十分奇怪:

loc_1400AB248:                          ; CODE XREF: MmIsAddressValidEx+BF↓j
                mov     r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
                dec     rdx
                mov     rcx, [r9]
                mov     rax, r11
                cmp     r9, rax
                jb      short loc_1400AB263
                mov     rax, rbx
                cmp     r9, rax
                jbe     short loc_1400AB27E

  根据堆栈图,它是在该函数直接提栈的局部变量区,而我未发现任何初始化该区域数据的汇编代码。该代码里面还有ShadowMapping这个东西,从名字上来看是影子映射相关的,但我同样弄不出来相关细节。网络上有关x64的资料少之又少,我无法解决,所以有关页的相关内容,只能在此告一段落。

  门在x86上是十分重要的东西,尤其是中断门在Win系统扮演了十分重要的角色。鉴于之前在基础篇保护模式介绍的内容基础,这里就只讲调用门和中断门。

  注意:我不太建议做和学WinXP一样的内核实验,因为PG有可能会被触发导致蓝屏,搞懂原理即可。

调用门

  在x64下,调用门的结构如下:

  可以看出,调用门被扩展为128位,基本内容并没有发生任何变化。我们看看GDT表里面的内容:

kd> dq gdtr
fffff803`86c90fb0  00000000`00000000 00000000`00000000
fffff803`86c90fc0  00209b00`00000000 00409300`00000000
fffff803`86c90fd0  00cffb00`0000ffff 00cff300`0000ffff
fffff803`86c90fe0  0020fb00`00000000 00000000`00000000
fffff803`86c90ff0  86008bc8`f0000067 00000000`fffff803
fffff803`86c91000  0040f300`00003c00 00000000`00000000
fffff803`86c91010  00000000`00000000 00000000`00000000
fffff803`86c91020  00000000`00000000 00000000`00000000

  里面的内容少了不少,相比于XP系统来说。有关调用门细节就介绍这些。

中断门

  中断门也被同样的扩展为128位,其余的功能并没有发生任何变化。

  然后我们再看一下IDT表:

kd> dq idtr
fffff803`86c8e000  81158e00`00101100 00000000`fffff803
fffff803`86c8e010  81158e04`00101180 00000000`fffff803
fffff803`86c8e020  81158e03`00101200 00000000`fffff803
fffff803`86c8e030  8115ee00`00101280 00000000`fffff803
fffff803`86c8e040  8115ee00`00101300 00000000`fffff803
fffff803`86c8e050  81158e00`00101380 00000000`fffff803
fffff803`86c8e060  81158e00`00101400 00000000`fffff803
fffff803`86c8e070  81158e00`00101480 00000000`fffff803

  我们可以用!idt获取每一个索引对应的函数,如下为部分展示,后面有stack的说明该函数有自己独占的栈。

kd> !idt

Dumping IDT: fffff80386c8e000

00: fffff80381151100 nt!KiDivideErrorFaultShadow
01: ffff80381151180 nt!KiDebugTrapOrFaultShadow Stack = 0xFFFFF80386C929D0
02: fffff80381151200 nt!KiNmiInterruptShadow Stack = 0xFFFFF80386C927D0
03: fffff80381151280 nt!KiBreakpointTrapShadow
04: fffff80381151300 nt!KiOverflowTrapShadow
05: fffff80381151380 nt!KiBoundFaultShadow
06: fffff80381151400 nt!KiInvalidOpcodeFaultShadow
07: fffff80381151480 nt!KiNpxNotAvailableFaultShadow
08: fffff80381151500 nt!KiDoubleFaultAbortShadow Stack = 0xFFFFF80386C923D0
……

  有关中断门相关内容,暂时就这么多。

  x64CPU又增加了一些标识来增加安全性:SMEPSMAP。这两个标识在CR4中,我们再拿来看看:

  SMEP英文全称是supervisor-mode execution prevention,看意思就是限制执行代码的。SMAP英文全称为supervisor-mode access prevention,看意思就是限制访问内存的。这些限制是限制谁的,限制内核的。在WinXP下,只要是内核就具有至高无上的权限,我想读哪块内存就读,想执行啥代码就执行。

  说这两个标志位的功能倒是挺容易的,为什么要说这个东西呢?之前的实验应用层程序通过调用门提权并执行代码就会出现问题,我们需要对这两个标志位进行清0操作,否则就会出错。

  有一个特殊的汇编指令CLAC,它的作用是清除EFLAGS寄存器中的AC标志位。这将禁用用户模式数据访问的任何对齐检查。如果在CR4寄存器中设置了SMAP位,这个位就不会起作用;STAC可以恢复使用SMAP。有关这两个位介绍就这么多。

  x64 番外篇—— WOW64