1. ds寄存器
保护模式有2种非常重要的机制:段和页,这2种机制都是非常复杂的,无论是学习段或者页的机制,在此之前都要先了解段寄存器。
来看下面这一段代码:
mov dword ptr ds:[0x123456],eax
在上面的代码中,有一个ds寄存器,根据前面学习实模式我们知道ds是一个数据段寄存器。eax是一个32位的寄存器,dword表示4字节,正好也是32位。换句话说,这段代码的意思就是将eax里的数据写入到ds里的地址加上0x123456地址,然而事实真的是这样吗?实际上这段代码真正读写的地址是:ds.base + 0x123456。
2. 段寄存器
ds是一个段寄存器,另外段寄存器还有以下几个:ES CS SS DS FS GS LDTR TR ,通过OllyDbg软件随便打开一个程序,可以从右侧的寄存器窗口中看到段寄存器:
ds.base是什么意思呢?其实base是ds寄存器的一个成员,当然,ds寄存器的其他成员我们将会在后面一一介绍。
3. 段寄存器结构
32位下的通用寄存器大小都是32位的,段寄存器则复杂的多,其大小有96位,如下图所示:
有16位是可见的,剩下的80位是不可见的,段寄存器的结构如下所示:
struct SegMent {
//可见
WORD selector;
//不可见
WORD attribute;
DWORD base;
DWORD limit;
}
selector:表示可见的部分,有16位。
在OllyDbg中展示了ds寄存器后面跟了一个数字0023(16位可见部分),该数字后面部分是不可见的,不过OllyDbg程序也把这部分展示了出来。
现在问题来了,段寄存器的这些成员到底是什么意思呢?
attribute:该属性表示段寄存器是可读的或者可写的,还是可执行的。
base:表示这个段是从哪里开始的
limit:表示这个段的长度是多少
4. 段寄存器属性探测
OllyDbg软件把这些段寄存器的所有成员的值都列出来了,但只有Selector部分是可见的,那我们应该要如何证明Attribute、Base、Limit的存在呢?
4.1 探测Attribute成员
int var = 0;
int main(int argc, char* argv[])
{
__asm
{
mov ax,ss //ss是可读,可写的
mov ds,ax //此时ds是修饰的ss
mov dword ptr ds:[var],eax //执行成功
}
return 0;
}
在VC里执行以上代码后,我们发现并没有报错,确实能执行成功,原因在于ss段寄存器是可读可写的,因此将eax寄存器里的内容写入ss段寄存器中是可行的。
int var = 0;
int main(int argc, char* argv[])
{
__asm
{
mov ax,cs //cs是不可写的
mov ds,ax //此时ds是修饰的cs
mov dword ptr ds:[var],eax //执行失败,因为cs是不可写的
}
return 0;
}
如果我们将mov ax,ss语句替换成mov ax,cs,由于cs是不可写的,当执行mov dword ptr ds:[var],eax语句时就会报错,通过以上的例子可以确定Attribute是存在的。
4.2 探测Base成员
int main(int argc, char* argv[])
{
__asm
{
mov ax,fs
mov gs,ax //将fs段选择子带入到gs段寄存器中,注意不能带入到ds中,否则会编译失败
mov eax,gs:[0] //读写0地址,执行成功
}
return 0;
}
0地址是不可读,也不可写的,但是以上代码在对gs:[0]地址进行读操作却能执行成功,原因在于gs:[0]地址并不是一个0地址,真正读写的实际上是fs.base+0这个地址,而fs寄存器的base成员的值是0x7FFDE000,fs.base+0等价于0x7FFDE000地址,这个地址是可读的。
4.3 探测Limit成员
int main(int argc, char* argv[])
{
__asm
{
mov ax, fs
mov gs, ax // 将 fs 段选择子代入 gs 段寄存器。注意不能代入到 ds,否则会编译失败
mov eax, gs:[0x1000] // 执行失败,0x1000越界了
}
return 0;
}
注意,我们对gs:[0x1000]地址进行读操作,实际上是读取fs段寄存器,但是fs段寄存器的段界限是0xFFF,而0x1000超过了fs段寄存器的段界限,会导致读取越界。
接下来,我们来看另一个例子:
int main(int argc, char* argv[])
{
__asm
{
mov eax, dword ptr ds:[0x7FFDF000+0x1000] //执行成功
}
return 0;
}
因为ds段寄存器的段界限是0xFFFFFFFF,没有超过ds段寄存器的段界限,所以这段代码是可以执行成功。通过以上的实验学习,我们知道段寄存器的这些成员是真实存在的,只是这些部分不可见而已。