struct Segment{
WORD Selector;
WORD Attribute;
DWORD Base;
DWORD Limit;
}
段寄存器的属性
拿OD随便载入一个程序,观察寄存器窗口:

得到了当前的计算机的段寄存器信息(不同计算机段寄存器信息不一定相同)

段寄存器的读写
对于段寄存器可以使用MOV指令进行读写(LDTR和TR除外)
读段寄存器
#include <stdio.h>
#include <windows.h>
int main(){
WORD selector=0;
_asm{
mov selector, es
}
printf("%x\n",selector);
return 0;
}
对段寄存器的读操作只能读取段寄存器的16位Selector部分(可见部分)
运行结果

能够正确地读出es段寄存器的selector
写段寄存器
#include <stdio.h>
#include <windows.h>
WORD data=0x0610;
WORD readData=0;
__declspec(naked) void fuction(){
__asm{
//保留调用前堆栈
push ebp
//提升堆栈
mov ebp,esp
sub esp,0x40
//保护现场
push ebx
push esi
push edi
//初始化提升的堆栈,填充缓冲区
mov eax,0xCCCCCCCC
mov ecx,0x10
lea edi,dword ptr ds:[ebp-0x40]
rep stosd
//函数核心功能
push ds //保存ds段寄存器
mov ax,cs //将cs段寄存器的段选择子赋值给ax
mov ds,ax //使用cs段寄存器覆盖ds段寄存器
mov ax,word ptr ds:[data] //使用修改后的段寄存器ds读取,这里相当于mov ax,word ptr cs:[data]
pop ds //还原ds段寄存器
mov readData,ax //将读出来的数据赋值给变量
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
//返回
ret
}
}
int main(){
fuction();
printf("%X\n",readData);
return 0;
}
运行结果

可以看到代码是能够正常执行,并且输出对应的data
说明
上述代码使用了裸函数,避免了编译器的干扰;关于裸函数可以回顾:大神论坛 逆向脱壳分析基础学习笔记九 C语言内联汇编和调用协定
截取出关键代码:
push ds //保存ds段寄存器
mov ax,cs //将cs段寄存器的段选择子赋值给ax
mov ds,ax //使用cs段寄存器覆盖ds段寄存器
mov ax,word ptr ds:[data] //使用修改后的段寄存器ds读取,这里相当于mov ax,word ptr cs:[data]
pop ds //还原ds段寄存器
mov readData,ax //将读出来的数据赋值给变量
代码注释如上,就是个简单的覆盖段寄存器的操作
为什么明明替换了段寄存器,仍然能够正常运行呢?
首先要注意到,替换和被替换的段寄存器分别是:cs和ds;它们的base是相同的都为0,因此所访问的内存自然也是相同的
再来看权限问题:无论是cs还是ds,它们都具有可读的权限;这里也只对数据进行了读操作,于是可以正常运行
如果这里将读取data的代码修改为写data的代码,则会报错:
mov ax,word ptr ds:[data] //使用cs段寄存器覆盖过的ds段寄存器,读取data
//将上面的代码修改为:
mov word ptr ds:[data],ax //使用cs段寄存器覆盖过的ds段寄存器,修改data
为什么会报错?因为此时的ds段寄存器已经被覆盖为了cs段寄存器,而cs段寄存器的权限为可读、可执行,没有可写的权限,所以会报错
报错截图:

可以看到,此时的data的地址明明是有效的,先前也验证了可以正确读取,但是在这里就会报错:Acccess Violation(非法访问)
就这里就是因为段寄存器权限不足导致的,也是为什么先前都是使用ds段寄存器来赋值,而不是用cs段寄存器
mov word ptr ds:[address],data //使用ds段寄存器修改数据,可以正常修改
mov word ptr cs:[address],data //使用cs段寄存器修改数据,会报错
和前面对段寄存器的读操作不同,写寄存器是对整个96位的段寄存器进行修改
但是这里明明只给出了16位的段选择子Selector,剩下的80位呢?
这个就段描述符有关了,这里暂且不谈,留作之后自会知晓,先记住写寄存器是对整个段寄存器进行修改即可
验证Limit
在前面的读写中,或多或少都验证了段寄存器的几个属性:Base、Selector、Attribute
现在最后验证一下Limit
#include <stdio.h>
#include <windows.h>
int main(){
unsigned char base;
_asm{
mov al,fs:[0x1000] //超过limit:0xfff,无法正常运行
mov base,al
}
printf("%x\n",base);
return 0;
}

#include <stdio.h>
#include <windows.h>
int main(){
unsigned char base;
_asm{
mov al,fs:[0xfff] //在临界点可以正常运行
mov base,al
}
printf("%x\n",base);
return 0;
}

总结
- 段寄存器共96位,其中16位为可见部分,后80位为不可见部分
- 不同计算机段寄存器信息不一定相同
- FS和GS两个段寄存器分别在32位程序和64位程序发挥作用