保护模式 -段 -段寄存器结构
一丶保护模式学习
保护模式,主要学习的就是段 与 页 的关系. 学习段的时候先学习段寄存器
- 何为段 根据Intel 手册第三卷所属. 分段和分页是操作系统提供的机制. 这种机制可以为每个 程序或者任务提供单独的代码. 数据 和栈. 也就是我们 常常所说的进程隔离 保证了多个任务何以运行在一个处理器之上,且不会互相影响. 这就是分段.
- 何为页 分页机制 是实现了传统的请求 调用 页虚拟机内存系统. 简单来说就是内存是如何管理的. 分页机制同样可以实现任务隔离. 在保护模式下. 分段机制 是必须的 而分页是可选的.
所以先从段开始学.
二丶段寄存器
2.1 什么是段寄存器
先看一段汇编 代码如下
代码语言:javascript复制mov eax,fs:[0]
mov eax,fs:[eax 0x30]
上面的代码学过 TEB PEB 结构的人应该知道.是在做什么. 而我么你这里所说不讲 PEB TEB
看汇编代码.我们操作了FS; FS就是段寄存器 我们这里操作的地址分别就是 FS:[0] 与 fs:[0x30]
而其实展开来说. 我们真正的操作的地址是 FS.base 0 或者 FS.base 0x30 这样来操作的.
出了FS寄存器.还有 ES CS SS DS GS LDTR TR等段寄存器
我们上面所说的 段.base 其实是段寄存器的一个成员. 可以理解为段寄存器就是一个结构体
PS: 在inter手册中也称为段寄存器为段描述符
2.2 段寄存器结构
在我们x86平台下.我们知道一个寄存器是 4个字节. 32位. 可以表达一个32位的数据. 但是我们的段
却很少有人关注 其实我们的段 是有96位的.是一个结构体.
结构如下:
代码语言:javascript复制struct Segment
{
WORD Selecter; //16位段选择子 可见部分. 使用OD 或者X64dbg看段寄存器只会显示16位的段选择子可见部分.
WORD Attribute; //16位表示的段属性, 表示了当前段寄存器是可读的可写的还是可执行的
DWORD Base; //32位表示的基址,表示段从哪里开始
DWORD limit; //32位表示,表示的是基址的长度. base limit 可以确定一个段的大小
}
2.3 段寄存器的读写
使用汇编可以对段寄存器进行读写.
代码语言:javascript复制读取
mov ax,ss 段寄存器的可见部分只有16位.所以读出来之后只能放到16位寄存器中
写入
mov ss,ax 读寄存器只是读了可见部分的16位.而写入寄存器则是写入了96位
inter手册对段寄存器的操作寄存器指令有以下
- 直接载入的指令
mov pop LDS LES LSS LGS LFS L是Load的意思
- 其它隐含改变的指令 Call JMP RET Sysenter sysExit iret intn int0 int3
2.4 段寄存器的探测
2.4.1 段寄存器结构
在x86下.我们可以看如下寄存器表示图.
寄存器名称 | 段选择子(Select) | 段属性(Attributes) | 段基址(Base) | 段长(Limit) |
---|---|---|---|---|
ES(附加扩展段) | 0x0023 | 可读,可写 | 0x0000000 | 0xFFFFFFFF |
CS(代码段) | 0x001B | 可读,可执行 | 0x00000000 | 0xFFFFFFFF |
SS(堆栈段) | 0x0023 | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
DS(数据段) | 0x0023 | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
FS(分段机制) | 0x003B | 可读,可写 | 0x7FFDF000 | 0xFFF |
GS | 未使用 | 未使用 | 未使用 | 未使用 |
2.4.2 段寄存器中的 段属性探测
我们可以巧妙的利用汇编.来探测一下段属性 是否存在的. 依靠段寄存器结构所知. 出了CS段 不能写之外.其余段都是可以写的.
那么看一下内敛汇编代码.
- CS代码段 段属性探测 int Var = 0; int main() { __asm { mov ax,cs mov ds,ax mov dword ptr ds:[Var],eax ;等价于 mov dword ptr cs:[Var],eax } } 将CS赋值到AX中. AX赋值给DS. 此时DS就代表CS了. 如果以把eax之给 CS.则会出现错误. 可以将代码放到VS2019中.直接编译生成. 运行之后会崩溃. 而下方的则不会崩溃. 说明权限确实是不可写.
- 其它代码段段属性探测 int Var = 0; int main() { __asm { mov ax,ss mov ds,ax mov dword ptr ds:[Var],eax } }
2.4.3 段基地址探测
根据上面我们可以轻而一举的探测出 CS段 带有不可写的属性. 那么同样我们可以探测一下基地址.
在上图我们得知. 有基地址的只有FS. 看如下代码.
代码语言:javascript复制int main()
{
__asm
{
mov ax,fs
mov gs,ax
mov eax,gs:[0]
}
}
看汇编我们可以看到. 为啥访问 gs:[0]不会出错那. 有基础的都应该知道. 0地址是不可读的. 其实我们说过. 我们对任何一个地址的操作,操作的都是它的 段.base 偏移的方式. 在实模式下.这个概念应该知道.到了保护模式下. 段base为0了. 所以偏移就是我们看到的虚拟地址.
上面的汇编是一样的. FS是有基地址的. 当其赋值给gs的时候. gs代表的就是fs. 所以用 gs去操作[0]地址是有效的. 等价于操作 fs:[0]
而FS:[0] 就是我们的TEB结构.
2.4.4 段长探测
在段地址探测中,我们学习到了 访问有效地址 都等价于 段.base 偏移地址 x86下.偏移地址就是我们所看到的虚拟地址. 也称为逻辑地址.
那么我们如何探测 段长? 根据段寄存器表格得知. FS的 limit 只有0xFFF大小. 我们可以写汇编代码验证一下
代码语言:javascript复制int main()
{
__asm
{
mov ax, fs
mov gs, ax
mov eax, gs: [0] //fs.base 0 读取
mov eax,gs:[0x1000]//fs.base 0x1000
mov eax, dword ptr ds : [eax 0xFFF] ;
// mov eax,gs:[0x1000] //fs.base 0x1000 读取
}
}
可能是我们win764上测试的. 读取长度.越界读取.都会导致程序崩溃.