CPU的3个模式:
1、实模式:
DOS系统下的CPU用的就是实模式,里面用的地址就是真实的物理地址。现在的系统已经没有使用实模式的了,只是在启动的瞬间是实模式,然后进入保护模式。
2、保护模式:
保护模式中的地址是假的,如果要从保护模式到实模式的地址,无论是低2G还是高2G都要经过转换。除了CR3寄存器存储的是物理地址,其他的寄存器全是线性地址。
3、虚拟8086模式:
如果在保护模式下,想运行实模式的代码,也是可以运行的,就是虚拟8086模式。虚拟8086模式仍然是保护模式,而不是实模式。
保护模式
作用
我们学习保护模式,肯定要知道保护模式有啥用,它保护的是资源,也就是寄存器,它与段和页都有很大的联系,我们接下来的学习任务都是学习段和页。
段寄存器
ES 堆栈段 CS 代码段 SS 堆栈段 DS 堆栈段
FS 标志段寄存器 GS 全局段寄存器 LDTR TR
段
段是具有属性的
CS 代码段,可读可执行,不可以写
DS 数据段,可读也可写,不可执行
#include<Windows.h>
int val = 0x10;
int main(){
_asm{
mov ax, cs;
mov ds, ax;
mov ebx, 0x11;
mov dword ptr[val], ebx;
}
return 0;
}
由于ds数据段是存放数据的,但是我们现在给ds寄存器赋值了cs寄存器的属性,那么就会导致ds现在不可以写入,也就是会赋值失败,下图就是报了访问异常的错误
探究段的基址base和长度limit
#include<Windows.h>
int val = 0x10;
int main(){
_asm{
mov eax, 0x1;
mov dword ptr ds:[0], eax;
}
return 0;
}
当我们尝试写入ds:[0]会触发异常,但是我们改成下面的代码
#include<Windows.h>
int val = 0x10;
int main(){
_asm{
mov eax, 0x1;
mov dword ptr fs:[0], eax;
}
return 0;
}
这串代码和前面的区别在于由ds改为了fs,说明我们写入fs的0位置是没有问题的,这个就是与段的基址base有关
我们可以发现除了fs,其他的段base都是从0开始的,而fs之所以可以正常访问,是因为fs指向TEB表,我们写入数据,相当于改了TEB表的便宜为0的地址上的数据。另外如果我们想得到fs的base,不能直接 lea eax,dword ptr fs:[0] ,这个会使eax=0,因为lea实际上取的是[]之间的东西
通过Windbg可以查看一下TEB结构体
在偏移0x18的地方存放着指向自己的首地址,直接mov eax,dword ptr fs:[0x18]
#include<Windows.h>
int val = 0x10;
int main(){
_asm{
xor eax, eax;
mov eax, dword ptr fs : [0x18];
mov val, eax;
}
printf("%X", val);
system("pause");
return 0;
}
保护模式寻址方式
段.base+offset(逻辑地址)=线性地址,取值最大长度limit
段选择子
保护模式下的段寄存器其实由16位的段选择子和64位的段描述符寄存器构成,我们OD看到的是段选择子
段选择子:存储段描述符的索引,也就是我们前面在OD里面看到的类似0x1B,0x23这些
段描述符寄存器: 存储段描述符,里面包含一些段的属性,段的base,段的limit等
在16位汇编中,段寄存器中放段基址,IP中放偏移。CS:IP
在32位汇编中,段寄存器中放段选择子,我们需要根据下图对段选择子进行拆分
举例
CS:1B 二进制为: 0001 1011 ,按图片拆分得到:00011 0 11
Index:3
Ti:0 //在windows中LDT表没有用,所以Ti一直为0
RPL:3 也就是说明我们现在是在R3层
如何查GDT表
GDT表的地址存放在gdtr寄存器中,GDT表的大小存放在gdtl中
我们用 dq 80b95000 (dq表示四字,即64位 )
从0开始,索引为3,所以我们找到:
CS段描述符为:00cffb00`0000ffff
段描述符
我们已经通过索引找到了段描述符,现在我们开始分析和拆解段描述符
分为高32位和低32位,根据上表拆解CS的段描述符:
我们把CS的段描述符转换成二进制
0000 0000 1100 1111 1111 1011 0000 0000 //高32位
0000 0000 0000 0000 1111 1111 1111 1111 //低32位
Base:0000 0000 0000 0000 0000 0000 0000 0000(十六进制00000000)
Limit:1111 1111 1111 1111 1111(十六进制fffff)
TYPE:1011(十六进制B)
S:1
DPL:11
P:1
AVL:0
D/B:1
G:1
同理也可以拆解其他的段描述符,这里可以用C语言去实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct low_word {
unsigned int limit_0_15 : 16;
unsigned int base_0_15 : 16;
};
struct high_word {
unsigned int base_16_23 : 8;
unsigned int type : 4;
unsigned int s : 1;
unsigned int DPL : 2;
unsigned int p : 1;
unsigned int limit_16_19 : 4;
unsigned int avl : 1;
unsigned int l : 1;
unsigned int d_b : 1;
unsigned int g : 1;
unsigned int base_24_31 : 8;
};
void seg_des(struct high_word* ph, struct low_word* pl) {
unsigned int seg_base;
seg_base = (ph->base_24_31 << 24) | (ph->base_16_23 << 16) | (pl->base_0_15);//段基址
printf("seg_base=0x%08x\n", seg_base);
unsigned int seg_limit;
seg_limit = (ph->limit_16_19 << 16) | (pl->limit_0_15);
printf("seg_limit=0x%x\n", seg_limit);
printf("Type=0x%x\n", ph->type);
printf("S=%x\n", ph->s);
printf("DPL=%x\n", ph->DPL);
printf("P=%x\n", ph->p);
printf("AVL=%x\n", ph->avl);
printf("DEF=%x\n", ph->l);
printf("D/B=%x\n", ph->d_b);
printf("G=%x\n", ph->g);
}
int main() {
printf("please input the segment descriptor\n");
struct high_word* high;
struct low_word* low;
unsigned int l_word = 0;
unsigned int h_word = 0;
//请求用户输入描述符,先是低32位,再是高32位
printf("low:\n");
scanf("%x", &l_word);
printf("high:\n");
scanf("%x", &h_word);
printf("-----------------------\n");
high = (struct high_word*)&h_word;
low = (struct low_word*)&l_word;
seg_des(high, low);
printf("------------------------\n");
return 0;
}
Type位
有四位,通过值在表中进行索引
G位
颗粒度
G = 1 Limit单位为页
G = 0 Limit单位为字节
页分为两种
大页:4MB
小页:4KB=4096B=0x1000Byte(我们使用)
所以Limit=Limit*0x1000,既ffffff*0x1000=ffffff000
因为我们从第0页开始,所以ffffff000+0x1000
又因为地址从0开始,所以还得ffffff000+0x1000-1=ffffffff
所以最后base为00000000,limit为ffffffff
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com