Windows 内核之保护模式

  • A+
所属分类:逆向工程

在早期8086的年代,处理器只存在实模式,在实模式下,内存是以 :段内偏移 的方式寻址的,所操作的地址都是物理地址,并且所有的段都是可以读、写、执行的,就相当于直接运行在机器之上的程序,没有任何保护措施,可以认为当一个程序修改了 0x1000的内存地址,另一个程序读取0x1000的地址会是被修改后的数据。

而在其之后,为了将程序之间的(内存)空间隔离开,出现了保护模式,在保护模式下运行的程序,每个进程拥有者单独的内存空间,即进程与进程之间的内存地址互相隔离。在一个进程修改0x401000地址的内存时,另外一个进程并不会收到任何影响(共享内存除外)。在保护模式下,存在着两种寻址方式:分段、分页。同时段寻址的方式也与实模式下的方式完全不同。

寻址方式

实模式段寻址:

mov 0x23:[0x80], 1

段基址:0x2316位)

段内偏移:0x8016位)

在实模式下的段寻址算法为 0x23 << 4 + 0x800 (段基址<<4+段内偏移) = 0x1f0(物理地址)

因此在此模式下所能访问到的内存仅仅止步于20

0xffff:[0xf] = 0xffff<<4+0xf = 0xfffff

如果寻址超过20位(> 0xf ffff)则会从0开始访问内存。

0xffff:[0x10] = 0xffff<<4+0x10 = 0

保护模式段寻址(不同时包含分页的情况):

由于保护模式下寻址不在是直接映射到物理地址,所以保护模式中新增了一个GDT 用于区分开每个程序之间的地址与物理地址之间的关系。寻址方式仍然是 :段内偏移。但是此时的 段描述符(0x23),不再是作为一个段基址,而是一个选择子(其中包含GDT表的索引),需要查找到真正的地址需要通过GDT表项中记录的地址作为基地址来寻址,并且表项中还存放着对段的其他描述,包括段是否可读、可写、可执行,是否是32位的段还是64位、段长度等,因此寻址方式变成了

GDT<0x23>.Base + 0x80

(暂时忽略GDTLDT的存在,在今后章节详细讲解GDT的格式):

由此可看出只要每个进程的段描述符不同,其映射到的物理地址也可能不相同。

[提示]: 如果读者发现自己windows 中的程序每个程序的段描述符都是一样的,属于正常现象,因为当今windows 下已经不再使用段进行内存管理了,而仅采用分页(并不代表分段已经无效,只是没有使用)

保护模式页寻址:

段寻址优先于页寻址,页寻址是将段寻址后得到的地址再次对照一张页表,通过将地址切割成几段二进制作为下标查找几级页表后获得表项位置,有实际的物理地址、这块内存的读写权限等。由于分页的算法机制,每一个页面都是固定大小的单元,权限也仅涉及当前一个页面内的内存,与段不同的是段的属性影响的是整个段的内存。同时也代表着内存的分配与释放也必须遵循着这个单位,默认情况下一个页面的大小是4KB

几种不同的地址

逻辑地址

逻辑地址即是每个程序对于其来讲相对的地址,比如某个程序访问了 0x23:[0x401000]的地址,那么0x401000就是这个程序的逻辑地址,该地址也就是常使用C语言指针所指向的地址。

线性地址

线性地址是经过一次转换后的地址,例如程序访问0x23:[0x401000],GDT<0x23>得到其基址(假设为0x795c0000),通过 0x795c0000 + 0x401000 = 0x799c1000就是线性地址。

如果不存在分页线性地址就是物理地址,如果同时存在分页,则还需要通过分页寻址到真正的物理地址。

物理地址

物理地址是在内存中真正存放的地址,对应着的就是物理内存意义上的地址。

从实模式进入保护模式

但是由于向下兼容的问题,CPU在加电后仍然先进入实模式,因此操作系统需要主动去启用保护模式,主要是为了兼容部分实模式的程序或者系统。同时由于保护模式的段寻址需要通过GDT表,页寻址需要一张页表,因此操作系统在进入保护模式之前需要为这些结构进行创建与初始化。在一切准备就绪后通过修改CR0PE位进入保护模式,但是要注意一旦启用保护模式,从下一条语句开始就已经通过保护模式下的段寻址了。因此需要通过jmp far 跨段跳转,跳转到保护模式下的下一条指令上,且段描述符为GDT的选择子。

本文始发于微信公众号(锋刃科技):Windows 内核之保护模式

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: