被忽视的ds
1 | mov dword ptr ds:[0x003f048], eax |
在进行ring3逆向时,海哥让我们不去管ds寄存器的作用,只需要理解,这条语句的作用是将eax的值,写入0x003f048这个地址处即可;但是到了保护模式,这种说法就不再准确了,接下来一步步探寻ds的本质
段寄存器
ds 是 CPU 中的一个寄存器,这种寄存器称为段寄存器,除了ds,还有cs、es、ss、fs、gs 、ldtr、tr共八个。
打开OllyDbg,任意附加一个.exe文件,可以在右侧窗口看到如下一块区域
这些是OllyDbg调试器显示出当前程序运行时段寄存器的各部分属性的值。接下来分析这些值的来源和含义。
段寄存器的读写
在后面的部分会经常用到段寄存器的读写,这里先说明一下:
读:
1 | mov ax, fs |
写:
1 | mov ds, ax |
段寄存器在读的时候,只读了16位,但是写的时候会写入96位。
注意:ldtr和tr段寄存器不能用mov指令进行读写
段寄存器结构
1 | struct SegmentReg { |
由段寄存器的结构可知,段寄存器共96位,由16位的段选择子,16位的段属性,32位的base和32位的limit组成。
打印ds寄存器的值,发现只能显示0x0023,也就是段选择子那16位。不是说好的共96位吗?实际上,剩下来80位是不可见的部分,只不过OD也展示出来了,接下来证明每个属性的存在。
段基址
1 | mov eax, dword ptr ds:[0] |
理论上,上面这条语句是无法执行成功的,因为零地址是不允许访问的(因为没有给零地址挂物理页)
但是上述程序可以成功执行(这里不使用ds,原因是vc6作者对ds做过优化,写成ds将编译不过去),说明了这里访问的不是零地址,而是其它地址,也就是说,段寄存器修改了写入数据的地址,证明了段基址的存在。
这里真正的将数据写入eax的地址是:
1 | gs.base + 0x0 |
以下是常见段的基址
段寄存器 | Base |
---|---|
ES | 0 |
CS | 0 |
SS | 0 |
DS | 0 |
FS | 0x7FFDE000 |
GS | - |
由于将fs段的值赋给了gs段,因此写入eax寄存器的是0x7FFDE000地址上的值。
段属性
上面两段程序的差别仅仅在于插入的汇编的第一条指令,mov ax, cs 和 mov ax, ss。造成结果不同的原因是,ss段寄存器是可读、可写的,而cs段寄存器是可读、可执行,但是不可写;因此在试图向cs段寄存器所指向的基址+偏移(既[ ]内的值)是会发生访问违例的,这也说明了,不同的段寄存器,属性是不同的,证明了段属性的存在。
段限长
又出现了访问违例的情况,此处var的值为0x1000,超过了fs段寄存器的Limit:0xFFF,所以此时已经不能通过fs段来访问fs.base+0x1000这个地址了,这说明段寄存器也有一定的管辖范围,超出这个范围,就没有权限访问了
总结
这次的笔记主要探究了段寄存器的属性和结构,大致整理如下
段寄存器 | 段选择子 | 属性 | 基址 | 限长 |
---|---|---|---|---|
ES | 0x0023 | RW | 0 | 0xFFFFFFFF |
CS | 0x001B | RX | 0 | 0xFFFFFFFF |
SS | 0x0023 | RW | 0 | 0xFFFFFFFF |
DS | 0x0023 | RW | 0 | 0xFFFFFFFF |
FS | 0x003B | RW | 0x7FFDE000 | 0xFFF |
GS | - | - | - | - |
参考文章:https://blog.csdn.net/q1007729991/article/details/52537943