吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7998|回复: 31
收起左侧

[系统底层] 保护模式笔记二 段寄存器

  [复制链接]
lyl610abc 发表于 2021-4-11 19:04
本帖最后由 lyl610abc 于 2021-4-12 13:25 编辑

前言

所有保护模式索引链接:保护模式笔记一 保护模式介绍

先前提到了保护模式下的两大特点:段的机制和页的机制

先从段的机制开始学习,而要学习段的机制,首先要了解的便是段寄存器


段寄存器

什么是段寄存器

在先前的逆向基础笔记五 标志寄存器中,有提到过段寄存器的概念

当使用汇编来操作一个内存地址时,就会涉及到段寄存器,只不过先前并没有太过在意

如:

mov dword ptr ds:[0x123456],eax

注意汇编语句中的 ds,它就是一个段寄存器

实际上真正读取的内存地址为:ds.base+0x123456


段寄存器有哪些

段寄存器共8个: CS DS ES SS  FS GS LDTR TR

CS

代码段寄存器,用于存放代码段的段基址

DS

数据段寄存器,用于存放数据段的段基址

ES

附加段寄存器,用于存放附加段的段基址

SS

堆栈段寄存器,用于存放堆栈段的段基址,指示堆栈段区域的位置

FS

附加段寄存器,F为上一个附加段寄存器字母E后的字母,没有对应的名称

指向一种被称为线程信息块(TIB)的结构,这种结构是由内核在创建线程时创建的,用于支持操作系统相关功能、服务和API

GS

附加段寄存器,G为上一个附加段寄存器字母F后的字母,没有对应的名称

在32位Windows上GS保留供将来使用

在x64模式下,FSGS段寄存器已交换

Win64使用GS的原因是该FS寄存器用于32位兼容性层(称为Wow64)

32位应用程序永远不会导致GS更改,而64位应用程序永远不会导致FS更改

注意,在Win64和Wow64中GS是非零的,这可以用来检测一个32位应用程序是否在64位Windows中运行,在一个“真正“的32位Windows中GS总是零

IDTR

中断描述符表寄存器,用于存放中断描述符表IDT的32位线性基地址和16位表长度值

TR

任务寄存器,用于存放当前任务TSS段的16位段选择符、32位基地址、16位段长度和描述符属性值


段寄存器的结构

组成 Base Limit Attribute Selector
数据宽度 32位 32位 16位 16位
是否可见 不可见 不可见 不可见 可见
描述 基地址(当前段的起始地址) 大小限制(当前段的整个长度) 属性(当前段是否可读可写可执行) 段选择子

struct Segment{
    WORD Selector;
    WORD Attribute;
    DWORD Base;
    DWORD Limit;
}

段寄存器的属性

拿OD随便载入一个程序,观察寄存器窗口:

image-20210411163725387

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


段寄存器 Selector Attribute Base Limit
ES 0023 可读、可写 0 0xFFFFFFFF
CS 001B 可读、可执行 0 0xFFFFFFFF
SS 0023 可读、可写 0 0xFFFFFFFF
DS 0023 可读、可写 0 0xFFFFFFFF
FS 003B 可读、可写 0x7FFDE000 0xFFF
GS - - - -

段寄存器的读写

对于段寄存器可以使用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部分(可见部分)


运行结果

image-20210411174259638

能够正确地读出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;
}

运行结果

image-20210412124347258

可以看到代码是能够正常执行,并且输出对应的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段寄存器的权限为可读、可执行,没有可写的权限,所以会报错

报错截图:

image-20210412132518630

可以看到,此时的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;
}

image-20210411190011540


#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;
}

image-20210411185454452


总结

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

免费评分

参与人数 13吾爱币 +14 热心值 +12 收起 理由
ICEY + 2 + 1 我很赞同!
junjia215 + 1 + 1 用心讨论,共获提升!
tiger3018 + 1 用心讨论,共获提升!
Alutemurat + 1 + 1 谢谢@Thanks!
bailemenmlbj + 1 + 1 谢谢@Thanks!
非雾 + 1 + 1 热心回复!
会飞的kaka + 1 + 1 热心回复!
shiina0831 + 1 + 1 谢谢@Thanks!
tocabd + 1 + 1 谢谢@Thanks!
莫莫 + 1 + 1 讲的真好
努力加载中 + 1 + 1 用心讨论,共获提升!
研究技术 + 2 + 1 感谢分享,先收藏,有空再细细品尝。
sam喵喵 + 1 比汇编书籍讲的还清楚,赞

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Otoboku 发表于 2021-8-31 12:39
在windows模式下看不出来段选择器的作用,换个16位的汇编编译器,然后再去dosbox模拟器上才有用。
Heartsoft 发表于 2021-4-11 19:38
请问一下,有哪些编译器支持“_asm”呢?
膜拜大神!
xiaohao521 发表于 2021-4-11 20:29
小阔厮 发表于 2021-4-11 21:03
感谢楼主分享
Allen666 发表于 2021-4-11 21:11
感谢大佬分享
bigdawn 发表于 2021-4-11 21:23
感谢分享学习资料
keyan2 发表于 2021-4-11 21:40
感谢分享资料
qqjaf110 发表于 2021-4-11 21:58
感谢分享!!!
三和老哥 发表于 2021-4-11 22:08
哇 好厉害 永远支持男神
c617681957 发表于 2021-4-11 22:41
学习了谢谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 15:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表