保护模式笔记六 代码跨段跳转
# 前言所有保护模式索引链接:[保护模式笔记一 保护模式介绍](https://www.52pojie.cn/thread-1415096-1-1.html)
在先前的[保护模式笔记三 段描述符和段选择子](https://www.52pojie.cn/thread-1415961-1-1.html#37995569_%E5%8A%A0%E8%BD%BD%E6%AE%B5%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%87%B3%E6%AE%B5%E5%AF%84%E5%AD%98%E5%99%A8)中提到了可以使用MOV、LES、LSS、LDS、LFS、LGS指令修改段寄存器
但CS段寄存器不能通过上面的指令修改;CS段寄存器为代码段寄存器,**改变CS的同时必然要修改EIP**
`EIP寄存器`,用来存储CPU要读取指令的地址,CPU通过`EIP寄存器`读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,`EIP寄存器`的值就会增加
# 代码跨段跳转
## 同时修改CS和EIP的指令
| 指令 | 含义 |
| ---------------------- | ---------------------------- |
| JMP FAR | 远跳转 |
| CALL FAR | 远调用 |
| RETF(return far) | 远返回 |
| INT(interrupt) | 中断 |
| IRET(interrupt return) | 执行到中断程序或过程的远返回 |
------
## 只改变EIP的指令
| 指令 | 含义 |
| ------------------------ | ----------------------- |
| JMP | 跳转 |
| CALL | 调用 |
| JCC(jump condition code) | 跳转指令状态码/条件跳转 |
| RET | 返回 |
------
## JMP FAR 指令
### 指令格式
JMP Selector:Offset
形如:JMP 0x20:0x00452610
- Selector为段选择子
- Offset为要跳转的偏移
------
### CPU执行指令流程
以上面的 JMP 0x20:0x00452610为例,探究CPU的执行流程
执行流程为:
1. 拆分段选择子
2. 根据段选择子查表得到段描述符
3. 权限检查
4. 加载段描述符
5. 代码执行
------
#### 拆分段选择子
段选择子为:0x001B
将其转换成二进制得到:0000 0000 0001 1011
然后根据段选择子的结构得到:(有关段选择子的结构可回顾[保护模式笔记三 段描述符和段选择子](https://www.52pojie.cn/thread-1415961-1-1.html#37995569_%E6%AE%B5%E9%80%89%E6%8B%A9%E5%AD%90%E7%9A%84%E7%BB%93%E6%9E%84))
| | Index | TI | RPL |
| :------- | :--------------- | :-------- | --------------- |
| 二进制值 | 0000 0000 0001 1 | 0 | 11 |
| 十进制值 | 3 | 0 | 3 |
| 含义 | 索引为3 | 查询GDT表 | 请求特权等级为3 |
------
#### 根据段选择子查表得到段描述符
根据前面得到的段选择子的TI 可以确定要查询的表为GDT表
**对应的段描述符地址 = GDT表首地址 + 索引× 段描述符长度 = GDT表首地址 + 索引 × 8**
所以:对应的段描述符地址 = 0x8003f000 + 3×8= 0x8003f000 + 24 = 0x8003f000 + 0x18 = 0x8003f018
使用`windbg`查看对应的段描述符地址
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210411225609994.png)
------
得到对应的段描述符为:00cffb00`0000ffff
将其转换为二进制得到:0000 0000 1100 1111 1111 1011 0000 0000 ` 0000 0000 0000 0000 1111 1111 1111 1111
根据段描述符的结构得到:(有关段描述符的结构可回顾[保护模式笔记四 段描述符结构](https://www.52pojie.cn/thread-1416456-1-1.html))
| 数据位 | 31-24 | 23 | 22 | 21 | 20 | 19-16 | 15 | 14-13 | 12 | 11-8 | 7-0 |
| :----- | :------- | :--- | :----------- | :------ | :--------------- | :--------- | :----- | :------- | :--------- | :----- | :------ |
| 含义 | Base | G | D/B | 0 | AVL | Seg.Limit| P | DPL | S | Type | Base |
| 解释 | 基地址 | 粒度 | 默认操作大小 | 固定为0 | 用于系统软件使用 | 段大小限制 | 有效位 | 特权等级 | 描述符类型 | 段类型 | 基地址|
| 数值 | 00000000 | 1 | 1 | 0 | 0 | 1111 | 1 | 11 | 1 | 1011 | 0000000 |
------
| 数据位 | 31-16 | 15-0 |
| :----- | :--------------- | :--------------- |
| 含义 | Base Adress | Segment Limit |
| 解释 | 基地址 | 段大小限制 |
| 数值 | 0000000000000000 | 1111111111111111 |
这里要关注S位和Type域
**S位**
此时的S位为1,表明该段描述符为代码段描述符或者数据段描述符
**Type域**
此时的Type域为1011,表明该段描述符为代码段描述符,且可执行可读,可访问
------
根据索引得到的段描述符只有4种情况可以实现跳转:**代码段**、调用门、TSS任务段、任务门
此时的段描述符为代码段描述符,因此可以看下一步
------
#### 权限检查
有关权限检查的内容在[保护模式笔记五 段权限检查](https://www.52pojie.cn/thread-1422916-1-1.html)中已详细介绍,这里不过多赘述
**判断 DPL是否满足:EPL=max(RPL,CPL)<=DPL是否成立**
此时EPL=3,DPL=3;满足EPL<=DPL,**权限检查通过**
PS:权限检查还需要关注Type域的属性,如果Type域的属性**允许从较低特权级别调用**(表明该段为**一致代码段(共享段)**),则CPL>=DPL即可;该例中的Type域表明此段为**非一致代码段**,于是需要进行权限检查
- 如果是非一致代码段,要求:EPL=max(RPL,CPL)<=DPL
- 如果是一致代码段,要求:CPL>=DPL
------
#### 加载段描述符
通过以上的检查后,CPU会将段描述符加载到CS段寄存器中
#### 代码执行
CPU将CS.Base+Offset的值写入EIP 然后执行CS:EIP处的代码,段间跳转结束
根据前面的段描述符结构,可以得到CS.Base为0,于是EIP会被修改为Offset:0x00452610
------
## 测试JMP FAR指令
前面了解了JMP FAR指令的执行流程,接下来验证JMP FAR指令执行的结果
因为CS段寄存器原本的段选择子就是0x001B,所以如果使用前面的例子会导致效果不明显
因此要自己构造一个段描述符,并使用对应的段选择子进行测试
### 确定段选择子
使用Windbg找到一处未被使用的段描述符
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421142619680.png)
确定了要被构造的段描述符的地址为:0x8003f048
根据 **段描述符地址 = GDT表首地址 + 索引× 段描述符长度 = GDT表首地址 + 索引 × 8**可以逆推出
索引 =( 段描述符地址 - GDT表首地址)÷ 8
即 索引 = (0x8003f048-0x8003f000) ÷ 8 = 0x48 ÷ 8 = 72 ÷ 8 = 9
| | Index | TI | RPL |
| :------- | :--------------- | :-------- | :-------------- |
| 二进制值 | 0000 0000 0100 1 | 0 | 11 |
| 十进制值 | 9 | 0 | 3 |
| 含义 | 索引为9 | 查询GDT表 | 请求特权等级为3 |
将二进制拼接起来得到:0000 0000 0100 1000,对应十六进制为0x4B
即段选择子为0x4B
------
### 构造段描述符
确定了段选择子以后,就可以构造段描述符了
| 数据位 | 31-24 | 23 | 22 | 21 | 20 | 19-16 | 15 | 14-13 | 12 | 11-8 | 7-0 |
| :----- | :------- | :--- | :----------- | :------ | :--------------- | :--------- | :----- | :------- | :--------- | :----- | :------ |
| 含义 | Base | G | D/B | 0 | AVL | Seg.Limit| P | DPL | S | Type | Base |
| 解释 | 基地址 | 粒度 | 默认操作大小 | 固定为0 | 用于系统软件使用 | 段大小限制 | 有效位 | 特权等级 | 描述符类型 | 段类型 | 基地址|
| 数值 | 00000000 | 1 | 1 | 0 | 0 | 1111 | 1 | 00 | 1 | 1111 | 0000000 |
------
| 数据位 | 31-16 | 15-0 |
| :----- | :--------------- | :--------------- |
| 含义 | Base Adress | Segment Limit |
| 解释 | 基地址 | 段大小限制 |
| 数值 | 0000000000000000 | 1111111111111111 |
------
主要修改了DPL为0;段类型设置为可执行可读,可从较低特权级别调用,可访问的 代码段描述符
将上面的二进制拼接得到:00cf9f00`0000ffff
------
### 写入段描述符
将构造好的段描述符写入
在windbg中使用指令
```
eq8003f048 00cf9f00`0000ffff
```
eq :edit qword,以qword的数据宽度编辑修改指定地址的数据
指令格式为:eq address data
------
修改后再用windbg查看
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421144222620.png)
确认写入成功后继续下一步
------
### OD测试指令
使用OD随便打开一个软件,这里用的demo为EverEdit.exe
修改要执行的指令为:
```assembly
jmp far 0x4b:0056AF36
```
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421145806413.png)
------
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421145936444.png)
------
修改后,按F8单步步过,观察CS和EIP的变化
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421150125416.png)
可以看到EIP被修改为了Offset(偏移),CS被修改为了Selector(段选择子);EIP和CS被同时修改了
------
### 尝试修改CPL
原本的CPL为3,CPL为当前特权级别,等于CS和SS的RPL(请求特权级别)
前面的其它操作不变,将段选择子的请求特权级别修改为0
即将段选择子修改为
| | Index | TI | RPL |
| :------- | :--------------- | :-------- | :-------------- |
| 二进制值 | 0000 0000 0100 1 | 0 | 00 |
| 十进制值 | 9 | 0 | 0 |
| 含义 | 索引为9 | 查询GDT表 | 请求特权等级为0 |
得到新的段选择子为0x0048
使用OD执行新的指令
```assembly
jmp far 0x48:0056AF36
```
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210421150945583.png)
可以看到,CS仍然是0x4B,CPL仍然没有发生改变,当前特权等级依旧为3
也就是说CPL无法通过这种方式改变
------
# 总结
**对于一致代码段(共享段)**
- 特权级高的程序不允许访问特权级低的数据:核心态不允许访问用户态的数据
- 特权级低的程序可以访问到特权级高的数据,但特权级不会改变:用户态还是用户态
------
**对于普通代码段(非一致代码段)**
- 只允许同级访问
-绝对禁止不同级别的访问
------
直接对代码段进行JMP 或者 CALL的操作,无论目标是一致代码段还是非一致代码段,**CPL都不会发生改变**
**如果要提升CPL的权限,只能通过调用门(这也就是所谓的提权操作)**
------
为了对数据进行保护,普通代码段是**禁止不同级别进行访问**的。用户态的代码不能访问内核的数据,同样,内核态的代码也不能访问用户
态的数据。这种**数据隔离**的性质更好地保障了系统的安全性和稳定性
------ 学习了!最近在了解些底层的东西 我最近忙死了,头发都掉了 学到啦,了解底层知识 谢谢大神分享!好好学习好好学习! 用心讨论,共获提升! 请教一下给位大佬,我按照步骤做,od却报错说没有权限
页:
[1]