一、段寄存器有哪些 ?
段寄存器有ES、CS、SS、DS、FS、GS、LDTR、TR共8个。
ES:扩展段。在串操作时(比如cmovs)目标操作数的基址是ES,源操作数是DS。
CS:代码段,配合EIP使用。
SS: 堆栈段,凡是基址是EBP或ESP的,段前缀就是SS。
DS:数据段,默认的都是DS。
FS、GS:80386 之后定义的。
段寄存器结构:
段寄存器的大小是 96 位
段寄存器结构可以抽象成以下结构
struct Segment
{
WORD Selector; //16位段选择子,可见部分. 使用OD 或者X64dbg看段寄存器只会显示16位的段选择子可见部分.当读段寄存器(如mov ax,CS)的时候,只会返回这16位。或者push seg 操作针对的都是这16位。如果目标操作数是32位(如mov eax ,CS),则将16位零扩展成32位赋给目标操作数。但是写的时候,就会涉及到96位
WORD Attribute; //16位表示的段属性, 表示了当前段寄存器是可读的可写的还是可执行的
DWORD Base; //32位表示的基址,表示段从哪里开始
DWORD limit; //32位表示,表示的是基址的长度. base + limit 可以确定一个段的大小
}
在x86下.我们可以看如下寄存器表示图.
寄存器名称 | 段选择子(Select) | 段属性(Attributes) | 段基址(Base) | 段长(Limit) |
ES(附加扩展段) | 0x0023 | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
CS(代码段) | 0x001B | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
SS(堆栈段) | 0x0023 | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
DS(数据段) | 0x0023 | 可读,可写 | 0x00000000 | 0xFFFFFFFF |
FS(分段机制) | 0x003B | 可读,可写 | 0x7FFDF000 | 0xFFF |
GS | 未使用 | 未使用 | 未使用 | 未使用 |
二、段寄存器读写
可以使用MOV指令对段寄存器进行读写。
读操作时,可以读到段寄存器的段选择子部分的16位。例如 mov ax,es 指令会把es寄存器的段选择子读到ax。
写操作时,会写入96位,其中源操作数的16位写入到段寄存器的段选择子部分,另外80位会根据段选择子从GDT表(全局描述表)中获取。因此,
1 .mov ax,cs
2. mov ds,ax
实际上是把cs完整的复制给了ds。
三、段属性探测
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.则会出现错误.
说明权限确实是不可写.
其它代码段段属性探测
int Var = 0; int main()
{
__asm
{
mov ax,ss
mov ds,ax
mov dword ptr ds:[Var],eax
}
}
段基地址探测
通常我们用汇编读写某一个地址时,如下:
Mov dword ptr ds:[0x123846],eax
这时我们把eax 的值往地址去写,写的地址是:
ds.base + 0x123456
int main()
{
__asm
{
mov ax,fs
mov gs,ax
mov eax,gs:[0]
}
}
段长探测
在段地址探测中,访问有效地址等价于段.base + 偏移地址
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 读取
}
}