本篇文章为个人学习笔记,可能不适合正常人看

持续更新中···

基于滴水逆向中级班补充了部分64位的内容

很多数据结构源于win10 1511

保护模式

段寄存器结构

1
mov dword ptr ds:[0x1234],eax

真正读写的地址是:ds.base + 0x1234

段寄存器共有8个:ES CS SS DS FS GS LDTR TR

段寄存器共有96位,可见部分有前16位

用结构体描述:

1
2
3
4
5
6
struct SegMent{
WORD Seletor;
WORD Atrributes;//属性,rwe
DWORD Base;//段基址
DWORD Limit;//段长度
}

段描述符与段选择子

段描述符

  • P:P = 1 段描述符有效,反之无效,当我们通过指令将一个段描述符加载到段寄存器的时候,CPU第一件事情就是检查它的P位,若P无效则不会去做后续的其他检查
  • Atrributes:从第8字节Type到第23字节
  • Base:16-31 + 32-39 + 56-63
  • Limit:0-15 + 48-51 如果G位为1则单位为4K
  • S:S = 1 代码段或者数据段描述符,S = 0 系统段描述符
  • Type:当S = 1时:11位标志代码还是数据,A是否已经被访问,W是否可写,E是否向上拓展,R是否可读,C是否是一致代码段
  • Type:当S = 0时:
  • DB:影响CS段寻址,SS段ESP or SP,向下拓展的数据段的段上限4GB or 64KB

查看gdt表与gdt表长度(字节为单位):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kd> r gdtr
gdtr=fffff80082b0b000
kd> r gdtl
gdtl=006f
kd> dq fffff80082b0b000
fffff800`82b0b000 00000000`00000000 00000000`00000000
fffff800`82b0b010 00209b00`00000000 00409300`00000000
fffff800`82b0b020 00cffb00`0000ffff 00cff300`0000ffff
fffff800`82b0b030 0020fb00`00000000 00000000`00000000
fffff800`82b0b040 82008bb0`c0700067 00000000`fffff800
fffff800`82b0b050 5c40f321`30003c00 00000000`00000000
fffff800`82b0b060 00000000`00000000 00000000`00000000
fffff800`82b0b070 807c8e00`00102500 00000000`fffff800
kd> r idtr
idtr=fffff80082b0b070

可以看到fffff800`82b0b070开始就是idt表的内容,在x64中gdt表的作用并没有那么多了,最多就是在进入内核态时将cs寄存器的权限改为0

段选择子

  • RPL:请求特权级别
  • TI:TI=0 查GDT表;TI=1 查LDT表(windows下不使用LDT)
  • Index:GDT表索引

奇怪的指令

除了mov指令,我们还可以使用LES、LSS、LDS、LFS、LGS修改寄存器

CS不能通过上述的指令进行修改,CS位代码段,CS的改变会导致EIP的改变

1
2
3
4
char buffer[6];
__asm{
les eccx,fword ptr ds:[buffer] //高2字节给es,低4字节给ecx,好像这样比较隐蔽。。
}

注意:RPL <= DPL(在数值上)

段权限检查

CPL

CS、SS段选择子的后两位比较特殊称为:CPL(Current Privilege Level),标志当前CPU的特权等级

DPL

DPL(Descriptor Privilege Level 描述符特权等级):规定了访问该段所需要的特权级别是什么,如果你要访问我,你应具备什么样的特权。

比如:mov DS, AX ,如果AX指向的段DPL为0,那么当程序的CPL为3时这行指令是不会成功的。

RPL

RPL(Request Privilege Level 请求特权级别):RPL针对段选择子而言的,每个段的选择子都有自己的RPL

比如:mov ax,8 与 mov ax,b ,指向同一个段描述符,但RPL是不一样的

比如:当前程序处于0环,即CPL = 0

1
2
mov ax,000b //1011 RPL = 3
mov ds,ax // ax 指向的段描述符的DPL = 0

数据段(代码段和系统段并不相同)的权限检查:CPL <= DPL 并且 RPL <= DPL

为什么要有RPL?

  • 我们本可以用rw的权限去打开一个文件,但为了避免出错,有些时候我们使用只读的权限去打开。

思考SMEP、SMAP的实现

代码跨段跳转

举例JMP 0x20:0x004183d7

流程如下:

  1. 段选择子拆分,RPL = 00,TI = 0,Index = 4
  2. 查询GDT,因为TI = 0 所以查GDT表,Index = 4 找到对应的段描述符,四种情况可以跳转:代码段、调用门、TSS任务段、任务门(后三类都是系统段描述符)
  3. 权限检查,如果是非一致代码段,要求CPL == DPL 并且 RPL <= DPL;如果是一致代码段,要求CPL >= DPL
  4. 加载段描述符,通过以上检查后,CPU会将段描述符加载到CS段寄存器中
  5. 代码执行,CPU将CS.Base + Offset 的值写入EIP,然后执行CS:EIP处的代码,段间跳转结束
  • 一致代码段:内核态和用户态共享的代码段,允许低特权访问高特权的代码
  • 非一致代码段:只允许同级访问

长调用

JMP FAR 只能跳转到同级非一直代码段,但CALL FAR 可以通过调用门提权,提升CPL的权限。

跨段不提权

指令格式:CALL CS:EIP(EIP是废弃的,此处CS必须对应一个调用门段描述符)

1
2
push CS
push eip

使用RETF返回

跨段并提权

指令格式:CALL CS:EIP(EIP是废弃的)

1
2
3
4
5
//切换0环栈
push SS
push esp
push cs
push eip

调用门

现代操作系统大都是分页的,所以这玩意估计也被时代抛弃了,但还是要简单记录一下(windows没有使用调用门!! )

执行流程

  1. 根据CS的值查GDT表,找到对应的段描述符,这个描述符是一个调用门
  2. 在调用门描述符中储存另一个代码段的段选择子
  3. 选择子指向的段的Base + 调用门中的段中偏移值 就是真正要执行的地址

翻墙理论

去政府部门办事,进去的时候如果翻墙进去就会被KO,如果从大门进去把身份证件压在门卫那里就没问题;出来的时候不从大门出去而是翻墙出去一般是没人管你的XD

中断门

  • 系统调用(现代CPU一般都支持快速系统调用而不是使用中断)
  • 调试

查看IDT

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> r idtr
idtr=fffff8007830b070
kd> r idtl
idtl=0fff
kd> dq fffff8007830b070
fffff800`7830b070 75fd8e00`00104500 00000000`fffff800
fffff800`7830b080 75fd8e00`00104600 00000000`fffff800
fffff800`7830b090 75fd8e03`001047c0 00000000`fffff800
fffff800`7830b0a0 75fdee00`00104b40 00000000`fffff800
fffff800`7830b0b0 75fdee00`00104c40 00000000`fffff800
fffff800`7830b0c0 75fd8e00`00104d40 00000000`fffff800
fffff800`7830b0d0 75fd8e00`00104fc0 00000000`fffff800
fffff800`7830b0e0 75fd8e00`00105200 00000000`fffff800

IDT表的构成

  • 任务门描述符
  • 中断门描述符
  • 陷阱门描述符

64位是16字节,最高4位保留,次4位为偏移高4位

陷阱门

与中断门的区别

中断门执行时,会将IF位清零(存于EFLAGS中),但陷阱门不会。

如果IF为0意味着不再接受可屏蔽中断

  • 可屏蔽中断:触发中断时,CPU不再接受键盘等消息

  • 系统断电时触发不可屏蔽中断,电源管理器不管IF位为多少都会触发中断,利用主板中的电容告知CPU完成清理工作

任务段

任务状态段(Task-state segment, TSS)

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
typedef struct TSS {
DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。
// 这 6 个值是固定不变的,用于提权,CPU 切换栈的时候用
DWORD esp0; // 保存 0 环栈指针
DWORD ss0; // 保存 0 环栈段选择子
DWORD esp1; // 保存 1 环栈指针
DWORD ss1; // 保存 1 环栈段选择子
DWORD esp2; // 保存 2 环栈指针
DWORD ss2; // 保存 2 环栈段选择子
// 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。
DWORD cr3;
DWORD eip;
DWORD eflags;
DWORD eax;
DWORD ecx;
DWORD edx;
DWORD ebx;
DWORD esp;
DWORD ebp;
DWORD esi;
DWORD edi;
DWORD es;
DWORD cs;
DWORD ss;
DWORD ds;
DWORD fs;
DWORD gs;
DWORD ldt;
DWORD io_map;// 这个暂时忽略
} TSS;

TR寄存器

系统启动时会将GDT中TSS段描述符的数据加载到TR寄存器中

  • TR寄存器本质上是一个段寄存器

  • TR.Base指定TSS的基址

  • TR.Limit指定TSS的大小

任务门

虽然intel设计TSS旨用于多任务切换,但由于效率太低所以大多情况下只用于中断权限切换时更换esp和ss寄存器

任务门描述符:

任务门执行过程

  1. INT N
  2. 查IDT表,找到中断门描述符
  3. 通过中断门描述符,找到TSS段选择子
  4. 查GDT表,找到任务段描述符并加载到TR寄存器中
  5. 使用TR寄存器指向的TSS段中的值修改相应寄存器
  6. IRETD返回

分页

Bit Display when set Display when clear Meaning
0x200 C - Copy on write.
0x100 G - Global.
0x80 L - Large page. This only occurs in PDEs, never in PTEs.
0x40 D - Dirty.
0x20 A - Accessed.
0x10 N - Cache disabled.
0x8 T - Write-through.
0x4 U K Owner (user mode or kernel mode).
0x2 W R Writeable or read-only. Only on multiprocessor computers and any computer running Windows Vista or later.
0x1 V Valid.
E - Executable page. For platforms that do not support a hardware execute/noexecute bit, including many x86 systems, the E is always displayed.

虚拟地址转物理地址

以 9-9-9-9-12 分页举例:

先将要转化的地址比如fffff803a8f0a000后48位转化为二进制:

1
2
3
4
5
6
11111000 00000011 10101000 11110000 10100000 00000000 以9-9-9-9-12进行分割
111110000 1f0
000001110 0e
101000111 147
100001010 10a
000000000000 0

在cr3中取出顶级页表物理基址:

1
2
kd> r cr3
cr3=00000000238c1000

找各级页表的索引:

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
kd> !dq 00000000238c1000 + 1f0 * 8
#238c1f80 00000000`00784063 00000000`00000000
#238c1f90 00000000`31d14863 00000000`00000000
#238c1fa0 00000000`00000000 00000000`3fedf863
#238c1fb0 80000000`00203863 80000000`00203863
#238c1fc0 80000000`00203863 80000000`00203863
#238c1fd0 00000000`3fedd863 00000000`3fedb863
#238c1fe0 00000000`00000000 00000000`00000000
#238c1ff0 00000000`00000000 00000000`007a4063

kd> !dq 00000000`00784000 + e * 8
# 784070 00000000`00785063 00000000`00000000
# 784080 00000000`00000000 00000000`00000000
# 784090 00000000`00000000 00000000`00000000
# 7840a0 00000000`00000000 00000000`00000000
# 7840b0 00000000`00000000 00000000`00000000
# 7840c0 00000000`00000000 00000000`00000000
# 7840d0 00000000`00000000 00000000`00000000
# 7840e0 00000000`00000000 00000000`00000000

kd> !dq 00000000`00785000 + 147 * 8
# 785a38 00000000`007a2063 00000000`00000000
# 785a48 00000000`00000000 00000000`00000000
# 785a58 00000000`00000000 00000000`00000000
# 785a68 00000000`00000000 00000000`00000000
# 785a78 00000000`00000000 00000000`00000000
# 785a88 00000000`00000000 00000000`00000000
# 785a98 00000000`00000000 00000000`00000000
# 785aa8 00000000`00000000 00000000`00000000

kd> !dq 00000000`007a2000 + 10a * 8
# 7a2850 00000000`0930a963 00000000`0930b963
# 7a2860 00000000`00000000 80000000`0930d963
# 7a2870 80000000`0930e963 80000000`0930f963
# 7a2880 80000000`09310963 80000000`09311963
# 7a2890 80000000`09312963 80000000`09313963
# 7a28a0 00000000`00000000 80000000`09315963
# 7a28b0 80000000`09316963 80000000`09317963
# 7a28c0 80000000`09318963 80000000`09319963

验证一下是否转化成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kd> !dq 00000000`0930a000
# 930a000 00000000`00000000 00000000`00000000
# 930a010 00209b00`00000000 00409300`00000000
# 930a020 00cffb00`0000ffff 00cff300`0000ffff
# 930a030 0020fb00`00000000 00000000`00000000
# 930a040 a8008bf0`b0700067 00000000`fffff803
# 930a050 3540f3bc`30003c00 00000000`00000000
# 930a060 00000000`00000000 00000000`00000000
# 930a070 a6bd8e00`00108500 00000000`fffff803

kd> dq fffff803a8f0a000
fffff803`a8f0a000 00000000`00000000 00000000`00000000
fffff803`a8f0a010 00209b00`00000000 00409300`00000000
fffff803`a8f0a020 00cffb00`0000ffff 00cff300`0000ffff
fffff803`a8f0a030 0020fb00`00000000 00000000`00000000
fffff803`a8f0a040 a8008bf0`b0700067 00000000`fffff803
fffff803`a8f0a050 3540f3bc`30003c00 00000000`00000000
fffff803`a8f0a060 00000000`00000000 00000000`00000000
fffff803`a8f0a070 a6bd8e00`00108500 00000000`fffff803

页表虚拟地址计算

想要改写页表或者查看页表肯定要知道页表的虚拟地址

在xp中pte虚拟基址固定为:0xc0000000

在win10中pte虚拟基址储存在nt中是随机的,在windbg中查看如下:

1
2
3
4
5
6
kd> !pte 0
VA 0000000000000000
PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00000 PDE at FFFFF6FB40000000 PTE at FFFFF68000000000
contains 0470000039122867 contains 0000000000000000
pfn 39122 ---DA--UWEV contains 0000000000000000
not valid

举个例子,计算gdt表所在页的pte虚拟地址

1
2
kd> r gdtr
gdtr=fffff803a8f0a000

取后48位有效位,因为每页4k所以左移12位,然后因为pte中每项为8字节所以左移3位,最后加上pte虚拟基址

1
2
f803a8f0a000 >> 12 << 3 = 7C01D47850
7C01D47850 + FFFFF68000000000 = FFFFF6FC01D47850

在windbg中验证一下

1
2
3
4
5
kd> !pte fffff803a8f0a000
VA fffff803a8f0a000
PXE at FFFFF6FB7DBEDF80 PPE at FFFFF6FB7DBF0070 PDE at FFFFF6FB7E00EA38 PTE at FFFFF6FC01D47850
contains 0000000000784063 contains 0000000000785063 contains 00000000007A2063 contains 000000000930A963
pfn 784 ---DA--KWEV pfn 785 ---DA--KWEV pfn 7a2 ---DA--KWEV pfn 930a -G-DA--KWEV

pde的虚拟地址是pte表虚拟地址的pte,即对pte的虚拟地址做以上运算,同理pxe和ppe

TLB(Translation Lookaside Buffer)

LA(线性地址) PA(物理地址) ATTR(属性) LRU(统计)
  • 属性为各级页表and的结果
  • 统计记录该页是否常用
  • 不同的CPU表的大小不一样
  • 只要cr3改变,TLB立即刷新,一个CPU核心一个TLB
  • 页表G位与TLB是否刷新有关
  • INVLPG指令可删除某些映射关系

TLB种类

根据页表的种类分为以下4组TLB:

  • 第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB)
  • 第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB)
  • 第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB)
  • 第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)

利用1、2组表缓存不同步绕过crc校验?

CPU缓存

CPU缓存是位于CPU与物理内存之间的临时存储器,它的容量比内存小的多,但是交换速度却比内存要快得多。

CPU缓存与TLB的区别:

  • TLB:线性地址<—>物理地址
  • CPU缓存:物理地址<—>具体内容

关于PWT/PCD

  • PWT = 1 时,写Cache的时候也要将数据写入内存中;反之只写入cache
  • PCD = 1 时,禁止该页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存在CPU中

中断与异常

  • 中断的本质是改变CPU执行的路线
  • 中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU“有事情需要处理”,因此又叫中断请求
  • 中断请求的目的是虚妄CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(由IDT表决定)
  • 80x86有两条中断请求线:
    • 非屏蔽中断线,称为NMI
    • 可屏蔽中断线,称为INTR

非可屏蔽中断

当非可屏蔽中断产生时,CPU在执行完当前指令后会进入中断处理程序

非可屏蔽中断不受EFLAG寄存器中IF位的影响,以但发生,CPU必须处理非可屏蔽中断处理程序位于IDT表中的2号位置(80x86中固定为0x2)

可屏蔽中断

在硬件级,可屏蔽中断是有一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求。为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request)后面加上数字来表示不同的中断。

比如:在windows中,时钟中断的IRQ编号为0,也就是IRQ0(在 设备管理器-> 属性 -> 资源 中查看)

如何处理可屏蔽中断:

IDT中断号 IRQ 说明
0x30 IRQ0 时钟中断
0x31-0x3f IRQ1-IRQ15 其他硬件设备的中断
  • 如果自己的程序执行时不希望CPU去处理中断,可以使用CLI指令清空EFLAG寄存器的IF位;用STI指令设置EFLAG寄存器中的IF位
  • 硬件中断与IDT表中的对应关系并非固定不变,详细参考:APIC(高级可编程中断控制器)

异常

异常通常是CPU在执行指令时检测到的某些错误,比如除零、访问无效页面。

中断与异常的区别:

  • 中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的
  • 异常来自于CPU本身,是CPU主动产生的
  • INT N虽然被称为软件中断,但其本质是异常。EFLAG的IF位对INT N无效

异常处理

无论是由硬件设备触发的中断还是CPU产生的异常,处理程序都在IDT表中

常见的异常处理程序:

错误类型 IDT中断号
页错误 0xE
段错误 0xD
除零错误 0x0
双重错误 0x8

双重错误是指,在某错误的异常处理程序中再次触发异常,即为双重错误

缺页异常

缺页异常的产生:

  • 当页表的P=0时
  • 或者向不可写的内存页写入数据时

一旦发生缺页异常,CPU会执行IDT表中的0xE号中断处理程序,由操作系统来接管

例如:当物理页不够用时,操作系统会将物理页存在文件里并且将页表P位置零;若对该物理页进行访问时,操作系统发现P 为0则会触发缺页异常处理程序,再对页表中11(转移位)位、10(原型位)位进行检查,如果都是0则意味着该物理页被存在索引为PFN(1-4位)的页面文件里,那么此时就会恢复物理页并把P位置1

控制寄存器

控制寄存器用于控制和确定CPU的操作模式

  • Cr1 保留

  • Cr3 页目录表基址 (不同分页形式结构不一样)

Cr0

Bit Name Full Name Description
0 PE Protected Mode Enable If 1, system is in protected mode, else system is in real mode
1 MP Monitor co-processor Controls interaction of WAIT/FWAIT instructions with TS flag in CR0
2 EM Emulation If set, no x87 floating-point unit present, if clear, x87 FPU present
3 TS Task switched Allows saving x87 task context upon a task switch only after x87 instruction used
4 ET Extension type On the 386, it allowed to specify whether the external math coprocessor was an 80287 or 80387
5 NE Numeric error Enable internal x87 floating point error reporting when set, else enables PC style x87 error detection
16 WP Write protect When set, the CPU can’t write to read-only pages when privilege level is 0
18 AM Alignment mask Alignment check enabled if AM set, AC flag (in EFLAGS register) set, and privilege level is 3
29 NW Not-write through Globally enables/disable write-through caching
30 CD Cache disable Globally enables/disable the memory cache
31 PG Paging If 1, enable paging and use the § CR3 register, else disable paging.
  • PE = 0 PG = 0 实地址模式
  • PE = 1 PG = 0 进入保护模式,开启分段机制
  • PE = 0 PG = 1 不存在这种情况
  • PE = 1 PG = 1 开启分页机制

Cr2

Contains a value called Page Fault Linear Address (PFLA). When a page fault occurs, the address the program attempted to access is stored in the CR2 register.

Cr3

Used when virtual addressing is enabled, hence when the PG bit is set in CR0. CR3 enables the processor to translate linear addresses into physical addresses by locating the page directory and page tables for the current task. Typically, the upper 20 bits of CR3 become the page directory base register (PDBR), which stores the physical address of the first page directory entry. If the PCIDE bit in CR4 is set, the lowest 12 bits are used for the process-context identifier (PCID).[1]

Cr4

Bit Name Full Name Description
0 VME Virtual 8086 Mode Extensions If set, enables support for the virtual interrupt flag (VIF) in virtual-8086 mode.
1 PVI Protected-mode Virtual Interrupts If set, enables support for the virtual interrupt flag (VIF) in protected mode.
2 TSD Time Stamp Disable If set, RDTSC instruction can only be executed when in ring 0, otherwise RDTSC can be used at any privilege level.
3 DE Debugging Extensions If set, enables debug register based breaks on I/O space access.
4 PSE Page Size Extension If unset, page size is 4 KiB, else page size is increased to 4 MiBIf PAE is enabled or the processor is in x86-64 long mode this bit is ignored.[2]
5 PAE Physical Address Extension If set, changes page table layout to translate 32-bit virtual addresses into extended 36-bit physical addresses.
6 MCE Machine Check Exception If set, enables machine check interrupts to occur.
7 PGE Page Global Enabled If set, address translations (PDE or PTE records) may be shared between address spaces.
8 PCE Performance-Monitoring Counter enable If set, RDPMC can be executed at any privilege level, else RDPMC can only be used in ring 0.
9 OSFXSR Operating system support for FXSAVE and FXRSTOR instructions If set, enables Streaming SIMD Extensions (SSE) instructions and fast FPU save & restore.
10 OSXMMEXCPT Operating System Support for Unmasked SIMD Floating-Point Exceptions If set, enables unmasked SSE exceptions.
11 UMIP User-Mode Instruction Prevention If set, the SGDT, SIDT, SLDT, SMSW and STR instructions cannot be executed if CPL > 0.[1]
12 LA57 57-Bit Linear Addresses If set, enables 5-Level Paging.[3][4]: 2–18
13 VMXE Virtual Machine Extensions Enable see Intel VT-x x86 virtualization.
14 SMXE Safer Mode Extensions Enable see Trusted Execution Technology (TXT)
16 FSGSBASE Enables the instructions RDFSBASE, RDGSBASE, WRFSBASE, and WRGSBASE.
17 PCIDE PCID Enable If set, enables process-context identifiers (PCIDs).
18 OSXSAVE XSAVE and Processor Extended States Enable
20 SMEP[5] Supervisor Mode Execution Protection Enable If set, execution of code in a higher ring generates a fault.
21 SMAP Supervisor Mode Access Prevention Enable If set, access of data in a higher ring generates a fault.[6]
22 PKE Protection Key Enable See Intel 64 and IA-32 Architectures Software Developer’s Manual.
23 CET Control-flow Enforcement Technology If set, enables control-flow enforcement technology.[4]: 2–19
24 PKS Enable Protection Keys for Supervisor-Mode Pages If set, each supervisor-mode linear address is associated with a protection key when 4-level or 5-level paging is in use.[4]: 2–19

系统调用

API 3环进0环

_KUSER_SHARED_DATA

  • 用于用户态和内核态共享数据
1
2
3
4
5
6
7
8
9
10
11
12
13
.text:000000018009CE20 4C 8B D1                          mov     r10, rcx        ; NtReadFile
.text:000000018009CE23 B8 06 00 00 00 mov eax, 6
.text:000000018009CE28 F6 04 25 08 03 FE+ test byte ptr ds:7FFE0308h, 1 ; 判断CPU是否支持快速系统调用
.text:000000018009CE28 7F 01
.text:000000018009CE30 75 03 jnz short loc_18009CE35 ; Jump if Not Zero (ZF=0)
.text:000000018009CE32 0F 05 syscall ; Low latency system call
.text:000000018009CE34 C3 retn ; Return Near from Procedure
.text:000000018009CE35 ; ---------------------------------------------------------------------------
.text:000000018009CE35
.text:000000018009CE35 loc_18009CE35: ; CODE XREF: NtReadFile+10↑j
.text:000000018009CE35 CD 2E int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
.text:000000018009CE35 ; DS:SI -> counted CR-terminated command string
.text:000000018009CE37 C3 retn ; Return Near from Procedure

windbg中查看:

1
2
3
2:002> dt _KUSER_SHARED_DATA 7FFE0000 SystemCall
ntdll!_KUSER_SHARED_DATA
+0x308 SystemCall : 0

修改寄存器

  1. cs的权限从3变为0
  2. ss与cs权限永远一致,即ss权限也变为0
  3. 权限发生切换,栈也一定需要切换,需要新的esp
  4. 进0环后代码的位置,需要新的eip

中断进0环

  • 固定中断号为0x2e
  • cs eip 由IDT提供,esp ss 由TSS提供
  • 进入0环后执行的内核函数:nt!KiSystemService

sysenter进0环

  • cs esp eip由MSR寄存器提供(ss为cs + 8)
  • 进入0环后执行的内核函数:nt!KiFastCallEntry

sysenter 和 中断 进0环的区别

相同点:

  • 目的都是找到内核态需要的cs ss eip esp

sysenter/快速调用的优点:

  • 中断需要的cs eip在IDT表中,需要查内存(ss与esp由TSS提供),速度慢
  • 如果cpu支持快速系统调用,则会在启动时提前把内核态的cs ss esp eip储存到MSR寄存器中,sysenter执行时,直接交换寄存器的值,速度相对快

64位syscall

  • STAR (0xC0000081) - Ring 0 and Ring 3 Segment bases, as well as SYSCALL EIP. Low 32 bits = SYSCALL EIP, bits 32-47 are kernel segment base, bits 48-63 are user segment base.

  • LSTAR (0xC0000082) - The kernel’s RIP SYSCALL entry for 64 bit software.

  • CSTAR (0xC0000083) - The kernel’s RIP for SYSCALL in compatibility mode.

  • SFMASK (0xC0000084) - The low 32 bits are the SYSCALL flag mask. If a bit in this is set, the corresponding bit in rFLAGS is cleared.

1
2
3
4
5
6
7
8
9
10
11
12
kd> rdmsr c0000082
msr[c0000082] = fffff803`18159d00
kd> u fffff803`18159d00
nt!KiSystemCall64:
fffff803`18159d00 0f01f8 swapgs
fffff803`18159d03 654889242510000000 mov qword ptr gs:[10h],rsp
fffff803`18159d0c 65488b2425a8010000 mov rsp,qword ptr gs:[1A8h]
fffff803`18159d15 6a2b push 2Bh
fffff803`18159d17 65ff342510000000 push qword ptr gs:[10h]
fffff803`18159d1f 4153 push r11
fffff803`18159d21 6a33 push 33h
fffff803`18159d23 51 push rcx

保存现场

_Ktrap_frame

进入0环后,存入所有用户态的寄存器

位于kthread中

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
0:000> dt _Ktrap_frame
ntdll!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c TempSegCs : Uint2B
+0x00e Logging : UChar
+0x00f FrameType : UChar
+0x010 TempEsp : Uint4B
+0x014 Dr0 : Uint4B
+0x018 Dr1 : Uint4B
+0x01c Dr2 : Uint4B
+0x020 Dr3 : Uint4B
+0x024 Dr6 : Uint4B
+0x028 Dr7 : Uint4B
+0x02c SegGs : Uint4B
+0x030 SegEs : Uint4B
+0x034 SegDs : Uint4B
+0x038 Edx : Uint4B
+0x03c Ecx : Uint4B
+0x040 Eax : Uint4B
+0x044 PreviousPreviousMode : UChar
+0x045 EntropyQueueDpc : UChar
+0x046 NmiMsrIbrs : UChar
+0x046 Reserved1 : UChar
+0x047 PreviousIrql : UChar
+0x048 MxCsr : Uint4B
+0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+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

ETHREAD

描述线程相关状态,其中_KTHREAD相对重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:000> dt _ETHREAD
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x280 CreateTime : _LARGE_INTEGER
+0x288 ExitTime : _LARGE_INTEGER
+0x288 KeyedWaitChain : _LIST_ENTRY
+0x290 ChargeOnlySession : Ptr32 Void
+0x294 PostBlockList : _LIST_ENTRY
+0x294 ForwardLinkShadow : Ptr32 Void
+0x298 StartAddress : Ptr32 Void
+0x29c TerminationPort : Ptr32 _TERMINATION_PORT
+0x29c ReaperLink : Ptr32 _ETHREAD
+0x29c KeyedWaitValue : Ptr32 Void
+0x2a0 ActiveTimerListLock : Uint4B
+0x2a4 ActiveTimerListHead : _LIST_ENTRY
+0x2ac Cid : _CLIENT_ID
·
·
·

KPCR(processor control region)

CPU控制区,描述当前CPU的状态

查看CPU数量:

1
dd KeNumberProcessors

查看其他CPU的KPCR: (此处显示结尾地址,应减去相应KPCR结构大小)

1
dq KiProcessorBlock

KiSystemCall64

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
.text:000000014014CD00                   KiSystemCall64  proc near               ; DATA XREF: .pdata:0000000140344818↓o
.text:000000014014CD00 ; KiInitializeBootStructures+12E↓o
.text:000000014014CD00
.text:000000014014CD00 var_58 = byte ptr -58h
.text:000000014014CD00 var_30 = qword ptr -30h
.text:000000014014CD00 var_28 = qword ptr -28h
.text:000000014014CD00 var_20 = qword ptr -20h
.text:000000014014CD00 var_18 = qword ptr -18h
.text:000000014014CD00 var_10 = qword ptr -10h
.text:000000014014CD00 arg_78 = byte ptr 80h
.text:000000014014CD00 arg_F8 = qword ptr 100h
.text:000000014014CD00
.text:000000014014CD00 ; __unwind { // KiSystemServiceHandler
.text:000000014014CD00 0F 01 F8 swapgs ; Exchange GS base with KernelGSBase MSR
.text:000000014014CD03 65 48 89 24 25 10+ mov gs:10h, rsp
.text:000000014014CD03 00 00 00
.text:000000014014CD0C 65 48 8B 24 25 A8+ mov rsp, gs:1A8h
.text:000000014014CD0C 01 00 00
.text:000000014014CD15 6A 2B push 2Bh ; '+' ; ss
.text:000000014014CD17 65 FF 34 25 10 00+ push qword ptr gs:10h ; rsp
.text:000000014014CD17 00 00
.text:000000014014CD1F 41 53 push r11 ; eflags
.text:000000014014CD21 6A 33 push 33h ; '3' ; cs
.text:000000014014CD23 51 push rcx ; rip
.text:000000014014CD24 49 8B CA mov rcx, r10
.text:000000014014CD27 48 83 EC 08 sub rsp, 8 ; Integer Subtraction
.text:000000014014CD2B 55 push rbp ; rbp
.text:000000014014CD2C 48 81 EC 58 01 00+ sub rsp, 158h ; rsp = _KTRAP_FRAME
.text:000000014014CD2C 00
.text:000000014014CD33 48 8D AC 24 80 00+ lea rbp, [rsp+arg_78] ; rbp = _KTRAP_FRAME + 0x80
.text:000000014014CD33 00 00
.text:000000014014CD3B 48 89 9D C0 00 00+ mov [rbp+0C0h], rbx ; rbx
.text:000000014014CD3B 00
.text:000000014014CD42 48 89 BD C8 00 00+ mov [rbp+0C8h], rdi ; rdi
.text:000000014014CD42 00
.text:000000014014CD49 48 89 B5 D0 00 00+ mov [rbp+0D0h], rsi ; rsi
.text:000000014014CD49 00
.text:000000014014CD50
.text:000000014014CD50 KiSystemServiceUser: ; DATA XREF: KiSystemService+2F↑o
.text:000000014014CD50 C6 45 AB 02 mov byte ptr [rbp-55h], 2 ; ExceptionActive = 2

KiSystemService

KiSystemCall64的阉割版

ss eflags cs rip 已经在中断时保存,rsp通过TSS取得

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
.text:000000014014CA00                   KiSystemService proc near               ; DATA XREF: .pdata:00000001403447E8↓o
.text:000000014014CA00 ; INITDATA:0000000140716308↓o
.text:000000014014CA00
.text:000000014014CA00 var_E8 = byte ptr -0E8h
.text:000000014014CA00
.text:000000014014CA00 0F 01 F8 swapgs ; Exchange GS base with KernelGSBase MSR
.text:000000014014CA03 49 8B CA mov rcx, r10
.text:000000014014CA06 48 83 EC 08 sub rsp, 8 ; Integer Subtraction
.text:000000014014CA0A 55 push rbp
.text:000000014014CA0B 48 81 EC 58 01 00+ sub rsp, 158h ; Integer Subtraction
.text:000000014014CA0B 00
.text:000000014014CA12 48 8D AC 24 80 00+ lea rbp, [rsp+168h+var_E8] ; 80h
.text:000000014014CA12 00 00
.text:000000014014CA1A 48 89 9D C0 00 00+ mov [rbp+0C0h], rbx
.text:000000014014CA1A 00
.text:000000014014CA21 48 89 BD C8 00 00+ mov [rbp+0C8h], rdi
.text:000000014014CA21 00
.text:000000014014CA28 48 89 B5 D0 00 00+ mov [rbp+0D0h], rsi
.text:000000014014CA28 00
.text:000000014014CA2F 4C 8D 1D 1A 03 00+ lea r11, KiSystemServiceUser ; ExceptionActive = 2
.text:000000014014CA2F 00
.text:000000014014CA36 41 FF E3 jmp r11 ; Indirect Near Jump
.text:000000014014CA39 ; ---------------------------------------------------------------------------
.text:000000014014CA39 C3 retn ; Return Near from Procedure
.text:000000014014CA39 KiSystemService endp

关于后面的分析需要提前知道下面两个东西

系统服务表

1
2
3
4
5
6
typedef struct _SYSTEM_SERVICE_TABLE{
PVOID ServiceTableBase;//地址表
PVOID ServiceCounterTableBase;//该表被访问次数
ULONGLONG NumberOfServices;//函数个数
PVOID ParamTableBase;//参数个数表
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;

SSDT

SSDT全称为System Service Descriptor Table,系统服务描述表

1
2
3
4
5
6
7
8
9
10
11
kd> dq nt!KeServiceDescriptorTable
fffff802`6f3fe780 fffff802`6f341150 00000000`00000000
fffff802`6f3fe790 00000000`000001bc fffff802`6f341f34
fffff802`6f3fe7a0 00000000`00000000 00000000`00000000
fffff802`6f3fe7b0 00000000`00000000 00000000`00000000

kd> dq nt!KeServiceDescriptorTableShadow
fffff802`6f3fe740 fffff802`6f341150 00000000`00000000
fffff802`6f3fe750 00000000`000001bc fffff802`6f341f34
fffff802`6f3fe760 fffff961`efd89000 00000000`00000000
fffff802`6f3fe770 00000000`0000046f fffff961`efd8b7ec

在xp中SSDT有4个表,后面3个都是空的,这里盲猜有2个表

SSDT中包含ntoskrl中的函数,SSDT Shadow中额外包含win32k中的函数

KiSystemServiceUser

可以看到不管是中断还是快速调用都会跳到KiSystemServiceUser

这里主要判断是否处于调试模式,如果是则保存调试相关寄存器Dr0-Dr7等一系列操作,如果不是则继续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:000000014014CD50                   KiSystemServiceUser:                    ; DATA XREF: KiSystemService+2F↑o
.text:000000014014CD50 C6 45 AB 02 mov byte ptr [rbp-55h], 2 ; ExceptionActive = 2
.text:000000014014CD54 65 48 8B 1C 25 88+ mov rbx, gs:188h ; _KTHREAD
.text:000000014014CD54 01 00 00
.text:000000014014CD5D 0F 0D 8B 90 00 00+ prefetchw byte ptr [rbx+90h] ; Trap_Frame
.text:000000014014CD5D 00
.text:000000014014CD64 0F AE 5D AC stmxcsr dword ptr [rbp-54h] ; Trap_Frame.MxCsr
.text:000000014014CD68 65 0F AE 14 25 80+ ldmxcsr dword ptr gs:180h ; KPCRB.MxCsr
.text:000000014014CD68 01 00 00
.text:000000014014CD71 80 7B 03 00 cmp byte ptr [rbx+3], 0 ; DebugActive
.text:000000014014CD75 66 C7 85 80 00 00+ mov word ptr [rbp+80h], 0 ; Dr7 = 0
.text:000000014014CD75 00 00 00
.text:000000014014CD7E 0F 84 9A 00 00 00 jz loc_14014CE1E ; 不处于调试就跳转
.text:000000014014CD84 48 89 45 B0 mov [rbp-50h], rax
.text:000000014014CD88 48 89 4D B8 mov [rbp-48h], rcx
.text:000000014014CD8C 48 89 55 C0 mov [rbp-40h], rdx
.text:000000014014CD90 F6 43 03 03 test byte ptr [rbx+3], 3 ; Logical Compare
.text:000000014014CD94 4C 89 45 C8 mov [rbp-38h], r8
.text:000000014014CD98 4C 89 4D D0 mov [rbp-30h], r9
.text:000000014014CD9C 74 05 jz short loc_14014CDA3 ; Jump if Zero (ZF=1)
.text:000000014014CD9E E8 AD 5E FF FF call KiSaveDebugRegisterState ; Call Procedure

这里首先保存FirstArgument SystemCallNumber TrapFrame

然后判断SystemCallNumber的第13位是否为1,如果是则是一个win32k调用走SSDT Shadow

接着获取SSDT中的函数地址表,在64位系统中这里并不是真正的地址而是需要经过一段计算FunAddr = ssdt[0] + *(ssdt[0]+4 * Index) >> 4

经过一些判断后最终跳转到KiSystemServiceCopyEnd

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
.text:000000014014CE1E                   loc_14014CE1E:                          ; CODE XREF: KiSystemCall64+7E↑j
.text:000000014014CE1E FB sti ; Set Interrupt Flag
.text:000000014014CE1F 48 89 8B 88 00 00+ mov [rbx+88h], rcx ; FirstArgument
.text:000000014014CE1F 00
.text:000000014014CE26 89 83 80 00 00 00 mov [rbx+80h], eax ; SystemCallNumber
.text:000000014014CE2C 0F 1F 40 00 nop dword ptr [rax+00h] ; No Operation
.text:000000014014CE30
.text:000000014014CE30 KiSystemServiceStart: ; DATA XREF: KiServiceInternal+5A↑o
.text:000000014014CE30 ; .data:00000001402C4338↓o
.text:000000014014CE30 48 89 A3 90 00 00+ mov [rbx+90h], rsp ; TrapFrame
.text:000000014014CE30 00
.text:000000014014CE37 8B F8 mov edi, eax
.text:000000014014CE39 C1 EF 07 shr edi, 7 ; Shift Logical Right
.text:000000014014CE3C 83 E7 20 and edi, 20h ; Logical AND
.text:000000014014CE3F 25 FF 0F 00 00 and eax, 0FFFh ; Logical AND
.text:000000014014CE44
.text:000000014014CE44 KiSystemServiceRepeat: ; CODE XREF: KiSystemCall64+490↓j
.text:000000014014CE44 4C 8D 15 35 29 23+ lea r10, KeServiceDescriptorTable ; Load Effective Address
.text:000000014014CE44 00
.text:000000014014CE4B 4C 8D 1D EE 28 23+ lea r11, KeServiceDescriptorTableShadow ; Load Effective Address
.text:000000014014CE4B 00
.text:000000014014CE52 F7 43 78 40 00 00+ test dword ptr [rbx+78h], 40h ; KTHREAD.GuiThread
.text:000000014014CE52 00
.text:000000014014CE59 4D 0F 45 D3 cmovnz r10, r11 ; Move if Not Zero (ZF=0)
.text:000000014014CE5D 42 3B 44 17 10 cmp eax, [rdi+r10+10h] ; typedef struct _SYSTEM_SERVICE_TABLE{
.text:000000014014CE5D ; PVOID ServiceTableBase;//地址表
.text:000000014014CE5D ; PVOID ServiceCounterTableBase;//该表被访问次数
.text:000000014014CE5D ; ULONGLONG NumberOfServices;//函数个数
.text:000000014014CE5D ; PVOID ParamTableBase;//参数个数表
.text:000000014014CE5D ; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
.text:000000014014CE62 0F 83 EF 02 00 00 jnb loc_14014D157 ; Jump if Not Below (CF=0)
.text:000000014014CE68 4E 8B 14 17 mov r10, [rdi+r10]
.text:000000014014CE6C 4D 63 1C 82 movsxd r11, dword ptr [r10+rax*4] ; 64位下需要计算得到真实的地址FunAddr =ssdt[0] + *(ssdt[0]+4 * Index)>>4
.text:000000014014CE70 49 8B C3 mov rax, r11
.text:000000014014CE73 49 C1 FB 04 sar r11, 4 ; Shift Arithmetic Right
.text:000000014014CE77 4D 03 D3 add r10, r11 ; Add
.text:000000014014CE7A 83 FF 20 cmp edi, 20h ; ' ' ; Compare Two Operands
.text:000000014014CE7D 75 51 jnz short loc_14014CED0 ; Jump if Not Zero (ZF=0)
.text:000000014014CE7F 4C 8B 9B F0 00 00+ mov r11, [rbx+0F0h] ; teb
.text:000000014014CE7F 00
.text:000000014014CE86
.text:000000014014CE86 KiSystemServiceGdiTebAccess: ; DATA XREF: KiSystemServiceHandler+D↑o
.text:000000014014CE86 41 83 BB 40 17 00+ cmp dword ptr [r11+1740h], 0 ; teb.GdiBatchCount
.text:000000014014CE86 00 00
.text:000000014014CE8E 74 40 jz short loc_14014CED0 ; Jump if Zero (ZF=1)
.text:000000014014CE90 48 89 45 B0 mov [rbp-50h], rax
.text:000000014014CE94 48 89 4D B8 mov [rbp-48h], rcx
.text:000000014014CE98 48 89 55 C0 mov [rbp-40h], rdx
.text:000000014014CE9C 49 8B D8 mov rbx, r8
.text:000000014014CE9F 49 8B F9 mov rdi, r9
.text:000000014014CEA2 49 8B F2 mov rsi, r10
.text:000000014014CEA5 B9 07 00 00 00 mov ecx, 7
.text:000000014014CEAA 33 D2 xor edx, edx ; Logical Exclusive OR
.text:000000014014CEAC 4D 33 C0 xor r8, r8 ; Logical Exclusive OR
.text:000000014014CEAF 4D 33 C9 xor r9, r9 ; Logical Exclusive OR
.text:000000014014CEB2 E8 E9 0F 2D 00 call PsInvokeWin32Callout ; Call Procedure
.text:000000014014CEB7 48 8B 45 B0 mov rax, [rbp-50h]
.text:000000014014CEBB 48 8B 4D B8 mov rcx, [rbp-48h]
.text:000000014014CEBF 48 8B 55 C0 mov rdx, [rbp-40h]
.text:000000014014CEC3 4C 8B C3 mov r8, rbx
.text:000000014014CEC6 4C 8B CF mov r9, rdi
.text:000000014014CEC9 4C 8B D6 mov r10, rsi
.text:000000014014CECC 0F 1F 40 00 nop dword ptr [rax+00h] ; No Operation
.text:000000014014CED0
.text:000000014014CED0 loc_14014CED0: ; CODE XREF: KiSystemCall64+17D↑j
.text:000000014014CED0 ; KiSystemCall64+18E↑j
.text:000000014014CED0 83 E0 0F and eax, 0Fh ; Logical AND
.text:000000014014CED3 0F 84 B7 00 00 00 jz KiSystemServiceCopyEnd ; Jump if Zero (ZF=1)
.text:000000014014CED9 C1 E0 03 shl eax, 3 ; Shift Logical Left
.text:000000014014CEDC 48 8D 64 24 90 lea rsp, [rsp-70h] ; Load Effective Address
.text:000000014014CEE1 48 8D 7C 24 18 lea rdi, [rsp+70h+var_58] ; Load Effective Address
.text:000000014014CEE6 48 8B B5 00 01 00+ mov rsi, [rbp+100h] ; rsp
.text:000000014014CEE6 00
.text:000000014014CEED 48 8D 76 20 lea rsi, [rsi+20h] ; Load Effective Address
.text:000000014014CEF1 F6 85 F0 00 00 00+ test byte ptr [rbp+0F0h], 1 ; cs
.text:000000014014CEF1 01
.text:000000014014CEF8 74 16 jz short loc_14014CF10 ; Jump if Zero (ZF=1)
.text:000000014014CEFA 48 3B 35 FF 22 23+ cmp rsi, cs:MmUserProbeAddress ; 用户态最大地址
.text:000000014014CEFA 00
.text:000000014014CF01 48 0F 43 35 F7 22+ cmovnb rsi, cs:MmUserProbeAddress ; Move if Not Below (CF=0)
.text:000000014014CF01 23 00
.text:000000014014CF09 0F 1F 80 00 00 00+ nop dword ptr [rax+00000000h] ; No Operation
.text:000000014014CF09 00
.text:000000014014CF10
.text:000000014014CF10 loc_14014CF10: ; CODE XREF: KiSystemCall64+1F8↑j
.text:000000014014CF10 4C 8D 1D 79 00 00+ lea r11, KiSystemServiceCopyEnd ; Load Effective Address
.text:000000014014CF10 00
.text:000000014014CF17 4C 2B D8 sub r11, rax ; Integer Subtraction
.text:000000014014CF1A 41 FF E3 jmp r11 ; Indirect Near Jump

KiSystemServiceCopyEnd:

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:000000014014CF90                   KiSystemServiceCopyEnd:                 ; CODE XREF: KiSystemCall64+1D3↑j
.text:000000014014CF90 ; DATA XREF: KiSystemServiceHandler+27↑o
.text:000000014014CF90 ; KiSystemCall64:loc_14014CF10↑o
.text:000000014014CF90 F7 05 EE 22 23 00+ test dword ptr cs:PerfGlobalGroupMask+8, 40h ; Logical Compare
.text:000000014014CF90 40 00 00 00
.text:000000014014CF9A 0F 85 55 02 00 00 jnz loc_14014D1F5 ; Jump if Not Zero (ZF=0)
.text:000000014014CFA0 41 FF D2 call r10 ; Indirect Call Near Procedure
.text:000000014014CFA3
.text:000000014014CFA3 loc_14014CFA3: ; CODE XREF: KiSystemCall64+54A↓j
.text:000000014014CFA3 65 FF 04 25 38 2E+ inc dword ptr gs:2E38h ; KeSystemCalls
.text:000000014014CFA3 00 00
.text:000000014014CFAB
.text:000000014014CFAB KiSystemServiceExit: ; CODE XREF: KiSystemCall64+B2↑j

进程与线程

EPROCESS进程结构体

每个windows进程在0环都有一个对应的结构体:EPROCESS,这个结构体包含了进程所有重要的信息。

KPROCESS

  • Header :_DISPATCHER_HEADER
    • 内核对象结构体带有此字段即为可等待对象,比如Mutex互斥体,Event事件等(WaitForSingleObject)
  • DirectoryTableBase
    • 页目录表的基址,进程切换时就是使用另外的一个进程的该字段填入CR3中
  • KernelTime/UserTime
    • 统计信息,记录一个进程在内核模式/用户模式下所花的时间
  • Affinity
    • 规定进程里的所有线程能在哪个CPU上跑,如果值为1,那么只能在0号CPU上跑(00000001);如果值为3,那么可以在0、1号CPU上跑(00000011)
  • BasePriority
    • 基础优先级或最低优先级,该进程中的所有线程最起码的优先级

EPROCESS其他成员

  • CreateTime/ExitTime
    • 进程的创建,退出时间
  • UniqueProcessId
    • PID
  • ActiveProcessLinks
    • 双向链表,所有轰动滚成都连接在一起构成的一个链表,对其解链可以达到隐藏进程的目的。PsActiveProcessHead指向全局链表头
  • QuetaUsage/QuotaPeak
    • 物理页相关的统计信息
  • CommitCharge/PeakVirtualSize/VirtualSize
    • 虚拟内存相关的统计信息
  • VadRoot
    • 平衡二叉树,标识0-2G用户空间哪些地址被分配,模块隐藏相关
  • DebugPort/ExceptionPort
    • 调试相关,调试器和被调试程序的桥梁进行通信
  • ObjectTable
    • 句柄表,反调试:查询其他进程的句柄表,若发现自身的EPROCESS则说明该进程正在对我调试
  • ImageFileName
    • 进程镜像文件名,最多16字节
  • ActiveThreads
    • 活动线程的数量
  • PEB
    • 参考 潘爱民老师《windows内核原理与实现》第三章
    • BeingDebugged
    • LDR 进程模块信息,模块链表

ETHREAD 线程结构体

KTHREAD

  • Header :_DISPATCHER_HEADER
    • 内核对象结构体带有此字段即为可等待对象,比如Mutex互斥体,Event事件等(WaitForSingleObject)
  • InitialStack,StackLimit,KernelStack
    • 线程切换相关,将此字段赋值给TSS储存0环栈
  • Teb
    • Thread Environment Block 线程环境块,三环FS:[0] -> TEB,在其中可以找到PEB
  • DebugActive
    • 如果被调试就不是-1,如果是-1,则不能使用调试寄存器Dr0-Dr7
  • ApcState,ApcQueueLock,ApcStatePointer,SavedApcState
    • APC相关
  • State
    • 线程状态:就绪、等待还是运行
  • BasePriority
    • 初始值是所属进程的BasePriority的值(KPROCESS->BasePriority),以后可以通过KeSetBasePriorityThread()函数重新设定
  • WaitBlock
    • 等待哪个对象(WaitForSingleObject)
  • ServiceTable
    • 指向系统服务表基址
  • TrapFrame
    • 进0环时保存环境
  • PreviousMode
    • 某些内核函数会判断程序是0环调用还是3环调用的
  • ThreadListEntry
    • 双向链表,一个进程所有的线程都处在一个链表中,一共有两个这样的链表,EPROCESS中同样有

ETHREAD其他成员

  • Cid
    • 当前线程的ID和当前进程PID,进程隐藏相关
  • ThreadsProcess
    • 指向自己所属的进程EPROCESS,进程隐藏相关
  • ThreadListEntry
    • 双向链表,一个进程所有的线程都处在一个链表中,一共有两个这样的链表,EPROCESS中同样有

KPCR CPU控制区(Processor Control Rregion)

  • 当线程进入0环时,FS:[0]指向KPCR(3环时指向TEB)
  • 每个CPU都有一个KPCR结构体(一个核心一个)
  • KPCR中储存了CPU本身要用的一些重要数据:GDT、IDT、以及线程相关的一些信息

_NT_TIB主要成员

  • ExceptionList
    • 储存0环异常处理程序链表SEH,对比TEB中同样有这个结构储存3环异常处理程序链表SEH
  • StackBase,StackLimit
    • 0环线程栈底和栈大小
  • Self
    • 指向TIB自身

KPCR其他成员

  • SelfPcr
    • 指向自己,方便寻址
  • Prcb
    • 指向拓展结构体PRCB
  • IDT
    • IDT表基址
  • GDT
    • GDT表基址
  • TSS
    • 指向TSS,每个CPU都有一个TSS
  • Number
    • CPU编号
  • PrcbData:_KPRCB
    • 拓展结构体

KPRCB

  • CurrentThread
    • 当前CPU正在跑的线程
  • NextThread
    • 下一个切换的线程
  • IdleThread
    • 空闲线程,如果没有要跑的线程,就跑这个空闲线程

等待链表、调度链表

在进程中对线程链表进行断链,OD等调试器查询不到该线程,但线程仍然在跑。说明两个问题,OD查询线程使用的API是通过线程链表去查询的;CPU在调度线程执行的时候不是使用这个链表。

线程有3仲状态:

  • 运行
  • 就绪
  • 阻塞

等待链表

KiWaitListHead 双向链表指向KTHREAD.WaitListEntry,线程调用Sleep()或者WaitForSingleObject()等函数时,就插入这个链表

33个链表

处于运行状态的链表在KPCR中,就绪和等待全在另外33个链表中,1个等待链表,32个就绪链表

调度(就绪)链表

KiDispatcherReadyListHead 存储32个双向链表的链表头,按优先级为下标排序

版本差异

32位只有33个双向链表,多核也只有33个

64位有65个

服务器版本:KiWaitListHead只有一个,但KiDispatcherReadyListHead这个数组有几个CPU就有几组

模拟线程切换

  • 线程不是被动切换而是主动切换
  • 线程切换并没有使用TSS来保存寄存器,而是使用堆栈
  • 线程切换的过程就是堆栈切换的过程

主动切换

  • windows中大多情况下通过调用SwapContext函数进行线程切换
  • 线程切换时会比较是否属于同一进程,如果不是则切换cr3寄存器

如果不调用API,就可以一致占用CPU吗?

时钟中断切换

时间片管理切换

线程切换之TSS

Intel设计TSS的目的是为了任务切换(线程切换),但Windows与Linux并没有使用,而是采用栈来保存线程的各种寄存器。

一个CPU只有一个TSS(保存在KPCR中),但是线程很多,如何用一个TSS来保存所有线程的ESP0呢?

切换线程时会将从TEB中取出目标线程的esp0 和 cr3 存到TSS中,其他不动

线程切换之FS

系统中同时存在许多个线程,这就意味着fs在3环时指向的TEB要有多个(每个线程一个)

但在实际的使用中我们发现,当我们在3环查看不同线程的FS寄存器时,FS的段选择子都是相同的,那是如何实现通过一个FS寄存器指向多个TEB呢?

线程切换时会取出目标进程的TEB存入FS段描述符基址中

线程优先级

有三种情况会导致线程切换:

  • 当前线程主动调用API:KiSwapThread KiSwapContext
  • 当前线程时间片稻妻:KiDispatchInterrupt KiQuantumEnd
  • 有备用线程(KPCR.PrcbData.NextThread):KiDispatchInterrupt

在KiSwapThread与KiQuantumEnd 函数中都是通过KiFindReadyThread来找下一个要切换的线程,那么KiFindReadyThread时根据什么条件来选择下一个要执行的线程呢?

根据之前说到的KiDispatcherReadyListHead 数组中的双向链表的下标作为优先级进行调度

进程挂靠