~/Code Archive by Genarch_
stackoverflow.md TECHNICAL ARTICLE

~/archive/entries / stackoverflow

Stack Overflow 栈溢出-基础

0 名词辨析

本文我们主要讨论的是栈缓冲区溢出,而非栈空间耗尽。

1 Intro

先看一个程序:

#include <stdio.h>
#include <string.h>

void f(void) {
    char buffer[16];

    printf("Input: ");
    gets(buffer);

    printf("You entered: %s\n", buffer);
}

int main(void) {
    f();
    return 0;
}

如果你把这个程序提交给AI,他绝对会说:

图片.png

这是为什么呢?因为程序在内存中主要是由以下方式排列的:

高地址-低地址排序
Stack 栈(局部变量)Heap 堆(动态分配)
BSS 未初始化全局变量
Data 已初始化全局变量
Read-Only Data字符串和只读常量
Code 机器指令

Text 段 i.e. 代码段,主要保存程序的机器指令。

例如:

mov eax, 1
add eax, 2
ret

Data和BSS主要存储已初始化(赋值后)和未初始化(赋值前)的变量。

回到程序。如果你输入"Hello"时,我们大一的C语言课程学过了,它实际输入的是:
'H' 'e' 'l' 'l' 'o' '\n'
6个char,这是符合buffer数组的定义的。

但是我们如果输入:
"Zeraora_Is_My_Favorite_Pokemon"
由于gets函数不知道我要读取多少个字符,所以他就疯狂读取直到结束,于是Stack后方的程序段就会被我们的输入覆盖,这就是栈(缓冲区)溢出

如果说我们需要构造一些函数调用让程序执行,这是一个最基础的操作。

同样的,read函数也有可能溢出,这就会导致读取到其他地方的信息,从而导致信息泄露。

2 Canary

Canary的重要思想是:

在局部缓冲区和重要控制数据之间放置一个特殊值,在函数返回前检查该值是否被修改。

叫这个名字的原因是,早期矿工会携带金丝雀(canary)进入矿井。金丝雀对有毒气体更加敏感。如果金丝雀先出现异常,就说明环境存在危险。

加入后的程序运行方式就是:

Canary 的检查过程

函数开始时:

  1. 从安全位置读取 Canary
  2. 将 Canary 保存到当前栈帧

函数结束前:

  1. 读取栈帧中的 Canary
  2. 与原始 Canary 比较
  3. 如果不同,则调用失败处理函数
  4. 程序终止

gcc编译也会使用这种方法,例如编译时的常见选项包括:

-fstack-protector
-fstack-protector-strong
-fstack-protector-all

-fstack-protector

只保护编译器认为较危险的函数,例如含有较大字符数组的函数。

-fstack-protector-strong

保护范围更广,通常是较推荐的选择。

-fstack-protector-all

几乎为所有函数添加 Canary 检查。保护范围更大,但也可能增加一定运行开销和代码体积。

但是这也有一个缺陷——我不读取到canary不就行了?

或者说,我知道canary了,这不也形同虚设吗?

所以我们还有另一种方法:

3 NX (No-Execute) & DEP (Data Execution Prevention)

它用于将某些内存区域标记为不可执行

例如栈通常需要:

可读:是
可写:是
可执行:否

堆通常也类似:

可读:是
可写:是
可执行:否

但是NX阻止的是,在不可执行内存中执行新代码。它不阻止攻击复用已有代码。

(未完待续)

DISCUSSION.LOG

No comments yet

new_comment.json