RSP(Stack Pointer Register):
1.始终指向当前栈的顶部(最后入栈的数据的位置)
2.当执行push、pop、call、ret等指令时,RSP的值会自动变化。
RBP(Base Pointer Register):
1.通常用作栈桢的基地址(指向当前的函数栈帧的起点)。
2.在函数执行中,RBP的值一般保持不变,方便通过偏移量访问局部变量或者参数。
mov rbp,rsp
的作用是将栈指针寄存器(RSP)的当前的之复制到基址寄存器(RBP)。(为当前函数建立一个固定的栈帧基址(RBP),便于安全的管理局部变量和参数)
1 | push rbp ; 保存旧的 RBP 值到栈中 |
sub rsp,40h
的作用是在站上分配64字节(十进制)的空间,通常用于储存函数的局部变量、临时数据,或为后续函数调用预留空间。这是函数调用建立栈帧的关键步骤之一。
寄存器们
EAX(Accumulator Register)
累加器寄存器,常用于算术运算和返回值的存储。
EBX(Base Register)
基址寄存器,通常用于基址指针或存储在内存中的数据地址。
ECX(Counter Register)
计数寄存器,常用于循环操作或字符串操作中的计数。
EDX(Data Register)
数据寄存器,通常用于输入/输出操作,也常与 EAX 一起使用处理乘法和除法操作。
ESI(Source Index)
源索引寄存器,常用于字符串操作或指向内存块的源地址。
EDI(Destination Index)
目标索引寄存器,常用于字符串操作或指向内存块的目标地址。
ESP(Stack Pointer)
栈指针寄存器,指向当前栈顶,管理函数调用时的堆栈。
EBP(Base Pointer)
基址指针寄存器,指向栈帧的基址,常用于存储调用函数时的栈帧信息。
EIP(Instruction Pointer)
指令指针寄存器,指向将要执行的下一条指令的地址。
xor eax eax
恢复栈空间
在函数结束时,必须恢复RSP的原始值,否则会导致栈不平衡。
1.常见操作
1 | mov rsp,rbp ;恢复RSP到分配之前的状态(RBP是旧的栈顶) |
2.加法释放
1 | add rsp, 40h ;释放分配的64字节空间 |
fs
段寄存器
在x86/x64架构中,fs是段寄存器之一,用于访问线程特定的内存区域。
**Windows系统:**fs指向当前线程TIB,包括线程的异常处理链、栈信息等。
**Linux系统:fs可以用于线程局部存储(TLS)**或者其他内核数据结构。
mov和lea
1.lea可以优化代码(他只计算地址而不涉及数据传输,再某种情况下可以减少内存访问次数)
mov涉及实际的数据复制,可能会增加内存访问次数
2.lea用于循环、数组操作和函数参数处理,因为他可以快速计算地址。
mov用于一般的数据处理(变量处理、函数返回值)
lea
lea指令用于计算操作数的有效地址,并将其存储在寄存器里。不涉及实际的数据传输,只计算地址。
常用于获取数组、结构体或者变量地址,以及在寻呼那和数组操作中计算偏移地址。
eg:
1 | lea edi, [lpBuffer] ; 将变量 lpBuffer 的地址加载到 edi 寄存器中 |
mov
用于将数据从一个位置移动到另一个位置、数据的粘贴复制。
可以移动立即数、寄存器中的值或内存中的数据。
1 | mov eax, [lpBuffer] ; 将内存地址 lpBuffer 处的数据移动到 eax 寄存器中 |
Canary
Canary 值(Stack Canary/Cookie)是计算机安全中用于检测栈溢出攻击(Stack Buffer Overflow)的一种防御机制。它的核心思想是在栈的关键位置插入一个随机值,并在函数返回前验证该值是否被篡改。以下是关于 Canary 值的详细解析:
1. 核心作用
- 检测栈溢出:
Canary 值位于函数栈帧的返回地址之前。如果攻击者试图通过缓冲区溢出覆盖返回地址,会先破坏 Canary 值,从而触发安全检测。 - 防止控制流劫持:
通过检测 Canary 值的完整性,阻止攻击者通过覆盖返回地址或函数指针来执行任意代码(如 ROP 攻击)。
2. 工作原理
2.1 函数执行时的工作流程
函数序言(Prologue):
在函数开始时,从线程安全区域(如fs:0x28
)读取 Canary 值,并将其插入栈的特定位置。1
2mov rax, fs:0x28 ; 读取 Canary 值到 rax
mov [rbp-8], rax ; 将 Canary 插入栈中函数尾声(Epilogue):
在函数返回前,检查栈中的 Canary 值是否与原始值一致。1
2
3mov rcx, [rbp-8] ; 从栈中读取 Canary
xor rcx, fs:0x28 ; 与原始值比较
jne stack_check_fail ; 不一致则终止程序
2.2 触发保护
- 如果攻击者通过缓冲区溢出覆盖了返回地址,必定会覆盖 Canary 值。
- 函数返回前的检查会发现 Canary 值被篡改,程序将立即终止(如调用
__stack_chk_fail
)。
3. Canary 值的类型
3.1 终结符 Canary(Terminator Canary)
- 特点:使用包含
0x00
、0x0A
、0x0D
、0xFF
等特殊字符的固定值(如0x000A0D00
)。 - 防御场景:阻止基于字符串操作(如
strcpy
)的溢出攻击,因为这些字符会终止字符串复制。 - 缺点:容易被攻击者猜测或绕过。
3.2 随机 Canary(Random Canary)
- 特点:在程序启动时生成一个随机值,存储在全局区域(如
fs:0x28
)。 - 防御场景:每个进程的 Canary 值不同,防止攻击者通过内存泄漏获取。
- 现代实现:主流操作系统(Linux、Windows)默认使用此类 Canary。
3.3 异或 Canary(XOR Canary)
- 特点:将随机 Canary 值与栈上的其他数据(如返回地址)进行异或运算。
- 防御场景:即使攻击者泄漏了 Canary 值,仍需知道异或的密钥才能构造有效载荷。
- 复杂度:实现复杂,较少使用。
4. Canary 的生成与存储
4.1 生成时机
- 程序启动时:由操作系统或运行时库生成,存储在线程局部存储(TLS)中。
- 例如,在 Linux 中,
glibc
通过_dl_setup_stack_chk_guard
初始化 Canary。
- 例如,在 Linux 中,
- 每次函数调用时:某些实现会动态生成 Canary(成本较高)。
4.2 存储位置
- x86-64 Linux:通过
fs
段寄存器访问,fs:0x28
存储 Canary。 - x86-64 Windows:类似,通过
gs
段寄存器访问。
5. 如何绕过 Canary?
尽管 Canary 是有效的防护措施,但仍有潜在绕过方法:
5.1 信息泄漏(Information Leak)
- 原理:通过漏洞(如格式化字符串漏洞)泄漏 Canary 值。
- 防御:将 Canary 值存储在不可读的内存区域(现代系统已实现)。
5.2 覆盖其他指针
- 原理:不覆盖返回地址,而是劫持函数指针或异常处理机制(如 SEH)。
- 防御:结合其他防护(如 ASLR、CFG)。
5.3 逐字节爆破(Brute Force)
- 原理:在 fork 型服务中,通过多次尝试猜测 Canary 值。
- 防御:使用足够长的随机 Canary(如 64 位)。
6. 实际应用
6.1 编译器选项
- GCC/Clang:通过
-fstack-protector
启用 Canary(-fno-stack-protector
禁用)。 - MSVC:通过
/GS
编译选项启用。
6.2 检查是否启用 Canary
在二进制中,可以通过以下特征识别:
- 函数序言:存在
mov reg, fs:0x28
指令。 - 函数尾声:存在
xor reg, fs:0x28
和检查跳转。
7. 总结
- 优点:高效、低开销,能防御大多数栈溢出攻击。
- 缺点:无法防御堆溢出或非栈溢出漏洞。
- 最佳实践:需结合其他防护(如 ASLR、DEP、CFI)。
Canary 值是现代软件安全的基石之一,理解其原理对逆向工程、漏洞分析和防御设计至关重要。