段寄存器
根据下图了解相关段寄存器
为了减少地址转换的时间和代码复杂度,处理器提供了 6 个段寄存器来保存段选择符
代码段寄存器(CS),数据段寄存器(DS)和堆栈段寄存器(SS)赋予有效的段选择符
,3 个数据段寄存器(ES,FS 和 GS)供进程使用。
段基址,段限长和访问权限
:::tips
DS:数据段
CS:代码段
ES:扩展段
SS:堆栈段
FS:暂时不管
:::
DS数据段
要想操作eax的值,使用纯C语言是不行,我们在虚拟机VS2008中,在微软X86编译器语法中使用__asm 内嵌的方式去操作。
微软支持以下该写法
#include <Windows.h>
int g_value = 10;
int main()
{
__asm
{
mov eax,g_value;
}
return 0;
}
但标准写法应该是这样的
#include <Windows.h>
int g_value = 10;
int main()
{
__asm
{
mov eax,dword ptr:ds [g_value];
}
return 0;
}
我们将ds删掉
#include <Windows.h>
int g_value = 10;
int main()
{
__asm
{
mov eax,dword ptr [g_value];
}
return 0;
}
在我们调式该代码,反汇编的时候发现编译器自动加上了ds段这个标识符
这个段的值的术语叫做段选择子
SS堆栈段
我们将全局变量 移到局部变量里,就会发现使用的是堆栈段ss
#include <Windows.h>
int main()
{
int g_value = 10;
__asm
{
mov eax,dword ptr [g_value];
}
return 0;
}
CS代码段
//全局变量可读可写
int g_value = 10;
/*
*/
int main()
{
g_value = 100;
__asm
{
//当我们将只有可读可执行的属性的cs时,
//在后续代码中使用写入修改就会发生段错误
mov ax,cs;
mov ds,ax;
mov eax,100;
mov dword ptr ds:[g_value],eax;
mov ax,es;
mov ds,ax;
}
system("pause");
return 0;
}
写入修改失败
可得出 段是有属性的,内存是跟段属性息息相关的
windbug实验
:::tips
r gdtr
#d:查看内存 db:一字节 dw:两字节 dd:四字节 dq:八字节
dq 80b97000
dq 80b97000 L40 #查看多少项
eq #修改
:::
r 只能查看通用寄存器 段寄存器 flg 标志
r eax
r gdtr
d 是看内存
byte word dword qword
段选择符
:::tips
DS:数据段 0023
CS:代码段 001B
ES:扩展段 0023
SS:堆栈段 0023
FS:暂时不管
:::
段选择符是一个16位的段标识符,它并不直接指向该段,而是指向定义该段的段描述符。
LDT: Local descriptor table (局部描述符表)
GDT:Global descriptor table(全局描述符表)
Index (位3~15),选中GDT或LDT中8192个描述符中的某个描述符。处理器将索引值乘以8(段描述符的字节数),然后加上GDT或LDT的基地址(基地址在GDTR或者LDTR寄存器中)。
TI (table indicator) 标记 ( 位 2 )。确定使用哪一个描述符表:将这个标记置0,表示用GDT。将这个标记置1,表示用LDT。
请求的特权级(RPL)(位 0 和 1)。确定该选择符的特权级。特权级从0-3, 0为最高特权级。
GDT 中的第一项是不用的。指向 GDT 中第一项的段选择符(即选择符中的索引为 0,TI标记置为 0)被视为空(null)段选择符。当段寄存器(CS 或 DS)被赋值为空选择符时,处理器并不产生一个异常。然而当使用值为空选择符的寄存器来访问内存时,处理器会产生一个异常。空选择符可以用来初始化未使用的段寄存器。对 CS 或者 SS 赋予一个空选择符会导致处理器产生一个通用保护异常 (#GP)。对应用程序而言,段选择符作为指针变量的一部分,是可见的。但是其值由连接程序赋予或者更改,而不是应用程序。
DS段:0023
:::tips
0023 拆分二进制 0000000000100 0 11
RPL:11 ->请求特权级别为3
Ti:0 ->查找GDT表
index:0000000000100 ->索引为4,查找GDT表索引为4的段描述符
:::
意思就是我要查第四项的GDT表的值
段描述符
段描述符是GDT或LDT中的一个数据结构,它为处理器提供诸如段基址,段大小,访问权限
及状态等信息,段描述符主要是由编译器,连接器,装载器或者操作系统构造的,而不是
由应用程序产生的。
段描述符:00cff300`0000ffff
二进制:
0000 0000 1100 1111 1111 0011 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
:::tips
基地址域 base:00 00 0000 基址 上图有三个base
段限长字段 limt:f ffff
Type:3
S(描述符类型)标志 s:1
DPL(描述符特权级)域 DPL:3
P(段存在)标志 P:1
AVL:0
D/B( 默认操作数大小/默认栈指针大小和/或上限)标志 d/b:1
G(粒度)标志 g:1
:::
P(段存在)标志
段的第一位 代表这个段是否有效,
标志指出该段当前是否在内存中(1表示在内存中,0表示不在)。
:::tips
P = 1: 有效
P= 0: 无效
:::
S(描述符类型)标志
确定段描述符是系统描述符(S 标记为0)或者代码,数据段描述符(S 标记为1)。
:::tips
S = 0 表示系统段 调用门、中断们、任务段
S = 1 表示代码段或者数据段
:::
Type “是否可写”标志
代码和数据段描述符类型域的解码描述
描述符的类型域的低3位(位8,9,10)被解释为访问控制(A),是否可写(W),扩展方向(E)。
再举个例子:
CS段:001B
在前面讲述中 我们发现 RPL、TI占3位
RPL:11 ->请求特权级别为3
Ti:0 ->查找GDT表
index:0000 0000 0010 0
所以我们可以直接用与运算,快速的求出index
我们只关心前13位,& 上 1 ,就会得出13位是多少
001B & 0xFFF8 = 0000 0000 0001 1011 & 1111 1111 1111 1000
= 0000 0000 0001 1000 = 0x18
index = gdtr得出的地址+0x18
index = 0000000000011 = 3
或者001B直接 >>3就可以了
段描述符:00cffb00`0000ffff
二进制:
0000 0000 1100 1111 1111 1011 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
:::tips
base:00 00 0000 基址
limt:f ffff
Type:B
s:1
DPL:3
P:1
AVL:0
d/b:1
g:1
:::
所以根据上表CS段不支持写入修改的操作。
实验:Base段基址
修改段属性
将虚拟机设置为单核
将虚拟机设置 处理器内核数改为1
int g_value = 0x100;
int g_value1 = 0;
int main()
{
__asm
{
mov eax,dword ptr ds:[g_value];
mov g_value1,eax;
mov ax,es;
mov ds,ax;
}
printf("g_value1 = %x\r\n",g_value1);
system("pause");
return 0;
}
以上代码测试结果为:
修改代码为
int g_value = 0x100;
int g_value1 = 0;
int main()
{
__asm
{
mov ax,0x4b;
mov ds,ax;
mov eax,dword ptr ds:[g_value];
mov g_value1,eax;
mov ax,es;
mov ds,ax;
}
printf("g_value1 = %x\r\n",g_value1);
system("pause");
return 0;
}
测试结果为:
我们修改段属性,将DS段的属性复制到0x48的位置
使用eq指令修改该地址
eq 80b99000+48 00cff300
0000ffff`
image.png
就会使得0x48的位置可以有写入权限
手动加点佐料-修改base段基址的偏移
使用eq指令修改该地址
eq 80b99000+48 00cff300
0001ffff`
再次运行程序,调试发现eax的值变了
但是执行结果仍是0x100
很诡异???
逻辑地址:010F7014h
根据前面的操作,我们修改0x48地址的值为DS段的段描述符 00cff300
0000ffff,根据前面我们的分析DS段的base段基址 ,这个逻辑地址要与我们的base相加,没修改之前base的值应是
0000 0000 + 010F7014h ,base的值为0,那么该地址就是线性地址,而
eq 80b99000+48 00cff3000001ffff
,人为修改后的值应是 0000 0001 + 010F7014h = 0000 0001 + 010F7015h
。所以在mov eax,dword ptr ds:[g_value];
取的地址的值是错误的。
可以通过这种方式hook.
例子:
base +idt.offset =0x48;
0x12345678
0x12345678 - idt.offset = value
gdt.48.base = valueX64系统没有了base和 limit
:::tips
int g_value = 0x100;
int g_value1 = 0;
int main()
{
__asm
{
mov ax,0x4b;
//修改了ds段的地址,原本ds段的值为23,现在改为了4b,
mov ds,ax;
//原本我们手动修改的ds的段描述符原封不动的赋值给了4b的地址,但是我们做了一个手脚
//00cff300
0001ffff` 将base段基地址加了个1,所以在寻址ds段的地址时,会往前寻址一位
mov eax,dword ptr ds:[g_value];
//安装正常的流程,寻址到的值是0x100,赋值给eax
//但是我们base段基地址加了个1,所以寻址的地址的值是 FE 00 00 01
mov g_value1,eax;
mov ax,es;
//es段存的值是0x23
//最后ds的值恢复原来的0x23
mov ds,ax;
}
//所以在调用输出这个函数的时候使用的是ds段的值0x23的地址,而这个地址存放的值是0x100,赋值给eax,在push eax,输出的值是0x100
printf(“g_value1 = %x\r\n”,g_value1);
system(“pause”);
return 0;
}
:::
实验:P(段存在)标志
将p设置为0,说明该段无效
修改指令eq 80b99000+48 00cf7300
0000ffff`
0x48这个位置是无效段,写入失败