22-从段寄存器开始

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段寄存器的段界限,所以这段代码是可以执行成功。通过以上的实验学习,我们知道段寄存器的这些成员是真实存在的,只是这些部分不可见而已。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值