概述

寄存器相关整理

相关参考

参考书籍:

  • 《INTEL 开发手册卷 3》

相关教程

[toc]

寄存器的易失性和保存方式

下表说明了每种寄存器在整个函数调用过程中的使用方法:

展开表

注册状态使用
RAX易失的返回值寄存器
RCX易失的第一个整型自变量
RDX易失的第二个整型自变量
R8易失的第三个整型自变量
R9易失的第四个整型自变量
R10: R11易失的必须根据需要由调用方保留;在 syscall/sysret 指令中使用
R12: R15非易失的必须由被调用方保留
RDI非易失的必须由被调用方保留
RSI非易失的必须由被调用方保留
RBX非易失的必须由被调用方保留
RBP非易失的可用作帧指针;必须由被调用方保留
RSP非易失的堆栈指针
XMM0、YMM0易失的第一个 FP 参数;使用 __vectorcall 时的第一个矢量类型参数
XMM1、YMM1易失的第二个 FP 参数;使用 __vectorcall 时的第二个矢量类型参数
XMM2、YMM2易失的第三个 FP 参数;使用 __vectorcall 时的第三个矢量类型参数
XMM3、YMM3易失的第四个 FP 参数;使用 __vectorcall 时的第四个矢量类型参数
XMM4、YMM4易失的必须根据调用方的需要保留;使用 __vectorcall 时的第五个矢量类型参数
XMM5、YMM5易失的必须根据调用方的需要保留;使用 __vectorcall 时的第六个矢量类型参数
XMM6: XMM15、YMM6: YMM15非易失的 (XMM),易失的(YMM 的上半部分)必须由被调用方保留。 YMM 寄存器必须根据需要由调用方保留。

当函数进入和退出 C 运行时库调用和 Windows 系统调用时,CPU 标志寄存器的方向位标志将被清除。

段寄存器

[段寄存器](【汇编】【实验楼】汇编教程笔记 #8086 寄存器组),分别是 CS 代码段寄存器、DS 数据段寄存器、SS 堆栈段寄存器,还有另外三个数据段寄存器(ES 附加段寄存器、FS 和 GS)供进程使用。

在调试器中,段寄存器基本上如下所示:

image-20241030225242507

段寄存器的结构

IMG-20241009160522952

  • Selector: 16 bits
  • Attribute: 16 bits
  • Base: 32 bits
  • Limit: 32 bits

每个段寄存器都由“可见”部分和“不可见”部分组成。(有时,不可见部分也被称为“描述符缓存”或者“影子缓存器”)。当段选择符被加载到一个段寄存器的可见部分时,处理器也通过段选择符所指向的段描述符获取了这个段寄存器的不可见信息:段基址、段限长和访问权限。 段寄存器保存的信息(可见或不可见的)使得处理器在进行地址转换时,不需要花费额外的总线周期来从段描述符中获取段基址和段限长。 在一个允许多个进程访问同一个描述符表的系统中,当描述符表被改变后,软件应该重新载入段寄存器。如果不这么做,当存储位置信息(its memory-resident version)发生变化后,用到的将是缓存在段寄存器中的旧的描述符信息。

段寄存器成员简介

如下所示为测试环境中的一个可能情况(红色部分可变):

段寄存器SelectorAttributeBaseLimit
ES0023可读、可写00xFFFFFFFF
CS001B可读、可执行00xFFFFFFFF
SS0023可读、可写00xFFFFFFFF
DS0023可读、可写00xFFFFFFFF
FS003B可读、可写0x7FFDE0000xFFF
GS----

段描述符

image-20241009164657966

CS

存放当前正在运行的程序代码所在段的段基址,表示当前使用的指令代码可以从该段寄存器指定的存储器段中取得,相应的偏移量则由 IP 提供。

SS

堆栈寄存器

DS

数据段寄存器

ES

附加段寄存器

FS

fs 是 80386 起增加的两个辅助段寄存器之一, 在这之前只有一个辅助段寄存器 ES FS 寄存器指向当前活动线程的 TEB 结构(线程结构)

偏移说明
00只想 SEH 链表指针
04线程堆栈顶部(地址最小)
08线程堆栈底部(地址最大)
0cSubSystemTib
10FiberData
14ArbitraryUserPointer
18FS 段寄存器在内存中的镜像
20进程 PID
24线程 ID
2c指向线程局部存储的指针
30PEB 结构地址(进程结构)
34上一个错误(LastError)

GS

在 x64 Windows 中,GS 段寄存器取代了 x86 下的 FS,指向当前线程的 TEB(线程环境块)。x64 下指针宽度为 8 字节,因此偏移与 x86 不同。

关键偏移速查:

GS 偏移说明
GS:[0x30]TEB 自引用指针
GS:[0x40]进程 PID
GS:[0x48]线程 TID
GS:[0x60]PEB 指针
GS:[0x68]LastError
GS:[0x08]堆栈基址
GS:[0x10]堆栈上限

详见:【汇编】x64 GS 寄存器与 TEB/PEB/堆栈访问

段寄存器的读写

段寄存器的读写指令只有几个,如下所示:

  • MOV 指令:
    • MOV AX,ES ,将 ES 寄存器拷贝到 AX 寄存器,只能读到 16 位的可见部分
    • MOV ES,AX,将 AS 寄存器的值拷贝到 ES 段寄存器,拷贝 96 位。
  • SLDT / LLDT 指令:读写 LDTR
  • STR / LTR 指令: 读写 TR

段寄存器属性探测

前文提到段寄存器有 96 位,可见部分内容只有 16 位,那么剩下的 80 位内容如何查看?

根据前文段寄存器结构图,段寄存器中 Attribute 、Base、Limit 是不可见的。那么接下来操作下如何查看相关内容。

环境说明:

  • WinXP SP3
  • Visual Basic 6.0

探测 Attribute

新建控制台工程并输入以下代码:

C++cpp
#include "stdafx.h"
 
int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov ax,cs;
        mov dword ptr ds:[a],10;
        mov ds,ax;
        mov dword ptr ds:[a],20;
    }
    return 0;
}

调试代码查看变量 a 的赋值情况,如下所示:

image-20241030223342964

上述操作时将 cs 寄存器赋值给 ax 寄存器,之后又将常量 10 复制给段寄存器中的变量 a,再往下执行就会报错,因为段寄存器 cs 刻度不可写。原来的 ds 是可读可写的,但将 cs 通过 ax 赋值给 ds 的时候,ds 已经不是原来的 ds,而是 cs,所以会报异常。

image-20241030223905767

探测 Base

修改代码如下所示:

C++cpp
#include "stdafx.h"
 
int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov ax,fs;
        mov es,ax;
        mov eax,es:[0];        //不要用DS,否则编译不过去
        mov dword ptr ds:[a],eax;
    }
    return 0;
}

代码说明,主要是将 fs 寄存器通过 ax 赋值到 es 寄存器,然后获取 es 寄存器的 Base 再通过 eax 赋值给段寄存器中的变量 a。执行结果如下所示:

image-20241030225005327

探测 Limit

修改代码如下所示:

C++cpp
#include "stdafx.h"
 
int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov eax,fs:[0x1000];
        mov dword ptr ds:[a],eax;
    }
    return 0;
}

补充:x64 寄存器

Register状态请使用
RAX易失的返回值寄存器
RCX易失的第一个整型参数
RDX易失的第二个整型参数
R8易失的第三个整型参数
R9易失的第四个整型参数
R10: R11易失的必须根据需要由调用方保留;在 syscall/sysret 指令中使用
R12: R15非易失的必须由被调用方保留
RDI非易失的必须由被调用方保留
RSI非易失的必须由被调用方保留
RBX非易失的必须由被调用方保留
RBP非易失的可用作帧指针;必须由被调用方保留
RSP非易失的堆栈指针
XMM0易失的第一个 FP 参数
XMM1易失的第二个 FP 参数
XMM2易失的第三个 FP 参数
XMM3易失的第四个 FP 参数
XMM4: XMM5易失的必须根据需要由调用方保留
XMM6: XMM15非易失的必须根据需要由被调用方保留。