源码防护-FORTIFY-SOURCE

源码防护-fortify_source 由于第一篇FORTIFY刚接触也没咋碰到,了解的也不多 现在回过头来发现当时写的糊里糊涂, 所以本篇继续深入FORTIFY防护 Fortify Source 是什么 本质:GCC 的编译时/运行时缓冲区溢出检测机制,属于源码级保护(不是二进制层面的 NX/Canary 那种) 保护对象:标准库中的高风险函数(strcpy、memcpy、sprintf、gets 等),一般主要是和字符串相关的函数。 FORTIFY_SOURCE 是 GCC/glibc 提供的一种编译时 + 运行时联合防护机制,通过将危险的 C 库函数替换为带边界检查的安全版本(__*_chk 后缀函数),在编译期和运行期检测缓冲区溢出。 核心思想: 编译器在编译时尽可能推断出缓冲区大小,如果能在编译期判定溢出就直接报错;如果编译期无法确定,就插入运行时检查代码,在溢出发生的瞬间终止程序。 Fortify Source干了什么 要想知道Fortify Source干了什么,那就要先看看如果没有Fortify Source会发生什么 char buf[32]; strcpy(buf, user_input); //① memcpy(buf, src, len); //② sprintf(buf, "%s", str); //③ read(fd, buf, 1024); //④ 先来看这段源码,毫无疑问的,①②③④这四条语句都有安全漏洞: ①完全没有长度检查,溢出 ②len作为变量可能会>32 ③str也可能长度>32 ④1024>32毫无疑问的溢出 而编译器对这些代码没有任何反应,也就是说,对生成的代码不会做任何的边界检查 Fortify Source的工作机制 工作流程: 源代码:strcpy(buf, src) │ ▼ ┌───────────────────────────┐ │ 预处理阶段(宏替换) │ │ #include <string.h> │ │ FORTIFY 宏将 strcpy 替换为 │ │ __builtin___strcpy_chk() │ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────────────┐ │ 编译阶段(GCC 内置函数处理) │ │ │ │ GCC 尝试用 __builtin_object_size() │ │ 推断 buf 的大小 │ │ │ ├──── 能确定大小 ────────────────────┤ │ │ │ │ ├─ 编译期可判定溢出 │ │ │ → 编译错误/警告 │ │ │ │ │ ├─ 编译期可判定安全 │ │ │ → 优化为普通 strcpy(零开销) │ │ │ │ │ └─ 编译期无法确定 │ │ → 插入运行时检查 │ │ → 调用 __strcpy_chk() │ │ │ ├──── 不能确定大小 ──────────────────┤ │ → 退化为普通 strcpy(无法保护) │ └──────────────────────────────────┘ 而工作流程中的三个关键组件: ...

February 24, 2026 · 4 min · 747 words · Chenjx12

FORTIFY保护

FORTIFY fortify是轻微的检查,用于检查是否存在缓冲区溢出的错误。适用于程序采用大量的字符串或者内存操作函数,如: memcpy(): 描述:void *memcpy(void str1, const void str2, size_t n) 从存储区str2复制n个字符到存储区str1 参数:str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void 指针 str2 – 指向要复制的数据源,类型强制转换为 void 指针 n – 要被复制的字节数 返回值:该函数返回一个指向目标存储区 str1 的指针 memset(): 描述:void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符 参数:str – 指向要填充的内存块 c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式 n – 要被设置为该值的字节数 返回值:该值返回一个指向存储区 str 的指针 strcpy(): 描述:char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest,容易出现溢出 参数:dest – 指向用于存储复制内容的目标数组 src – 要复制的字符串 返回值:该函数返回一个指向最终的目标字符串 dest 的指针 ...

February 1, 2025 · 2 min · 322 words · Chenjx12

RELRO保护

RELRO RELRO(RELocation Read Only) 先从名字上看,翻译一下:重定位只读 只读很好理解,重定位又是什么呢 重定位(RELocation) 在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是 存储函数指针的区域 。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。 GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为 只读 . 设置符号重定向表格为只读或在程序启动时就解析并 绑定所有动态符号 ,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。 那么GOT表又是干什么的呢? GOT表,PLT表 这涉及到 动态链接 的知识了: 动态链接,是 提高程序空间效率的重要方法 。通过动态链接, 我们可以调用外部共享库中的函数,而不需要将其编译在 可执行文件 中。在运行时动态链接的过程中,PLT表和GOT表起到了至关重要的作用。 GOT: Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后) PLT: Procedure Link Table, 过程链接表 ,包含调用外部函数的 跳转指令 (跳转到GOT表中),以及 初始化外部调用指令 (用于链接器动态绑定dl_runtime_resolve) .plt 段的工作流程: ...

January 29, 2025 · 2 min · 349 words · Chenjx12

PIE/ASLR保护机制

PIE/ASLR 区别 ASLR 是什么? ASLR 是 Linux操作系统 的功能选项,作用于程序(ELF)装入内存运行时。是一种 针对缓冲区溢出的安全保护技术,通过对加载地址的随机化 ,防止攻击者直接定位攻击代码位置,到达 阻止溢出攻击 的一种技术。 开启、关闭ASLR 查看当前系统ASLR的打开情况: sudo cat /proc/sys/kernel/randomize_va_space ASLR 有三个安全等级: 0: ASLR 关闭 1:随机化栈基地址(stack)、共享库(.so\libraries)、mmap 基地址 2:在1基础上,增加随机化堆基地址(chunk) PIE 是什么? PIE 是 gcc 编译器 的功能选项,作用于程序(ELF)编译过程中。是一个针对代码段( .text )、数据段( .data )、未初始化全局变量段( .bss )等固定地址的一个防护技术,如果程序开启了PIE保护的话,在_每次加载程序时都变换加载地址_,从而不能通过 ROPgadget 等一些工具来帮助解题。 开启 PIE: 在使用 gcc 编译时加入参数-fPIE。 gcc -o test test.c // 默认情况下,不开启PIE gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1 gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2 gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE -no-pie / -pie (关闭 / 开启) PIE 开启后会随机化代码段( .text )、初始化数据段( .data )、未初始化数据段( .bss )的加载地址。 ...

January 22, 2025 · 1 min · 206 words · Chenjx12

Canary保护

Canary Canary(金丝雀) 保护原理 Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。 Canary保护的原理是在栈上 rbp 附近放置一个随机数,等到函数执行结束,就会 检测 该位置存放的随机数是否被改动(覆盖),如果是说明发生了栈溢出,可能导致rbp,ret address 被修改导致异常行为,会中止程序的运行。 High Address | | +-----------------+ | args | +-----------------+ | return address | +-----------------+ rbp => | old ebp | +-----------------+ rbp-8 => | canary value | +-----------------+ | local variables | Low Address | | 具体细节 如何开启/关闭canary保护: gcc -o test test.c // 默认情况下,不开启Canary保护 gcc -fno-stack-protector -o test test.c //禁用栈保护 gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码 gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码 -fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启) 编译一个例子来看看: ...

January 19, 2025 · 2 min · 347 words · Chenjx12

NX(DEP)保护

NX(DEP) 全名为No-eXecute(不可执行) 开启/关闭方法: gcc -o test test.c // 默认情况下,开启NX保护 gcc -z execstack -o test1 test.c // 禁用NX保护 gcc -z noexecstack -o test2 test.c // 开启NX保护 -z execstack / -z noexecstack (关闭 / 开启) 看看例子 现在用gdb调试一下看看 这个是默认开启了NX保护的,可以通过 vmmap 指令,看到栈的部分权限是没有x(execute)执行权限的 而这里呢,test1是禁用了NX保护的,栈区的权限多了一个X 具体细节 ​ 了解**冯·诺依曼结构的师傅应该知道,它将程序指令存储器和数据存储器合并在一起,使数据和程序以同等地位存放在内存当中**。 ​ NX是**数据与程序的分水岭,栈溢出核心思想是通过局部变量覆盖函数返回地址来修改EIP和注入 Shellcode,在函数返回时跳到Shellcode去执行。要防止这种攻击,最有效的办法就是让攻击者注入的Shellcode无法执行,这就是数据执行保护(Data Execution Prevention, DEP)安全机制的初衷。NX策略是使栈区域的代码无法执行**。 ​ 该保护机制用于防范:栈溢出 + shellcode ​ 正常在栈溢出时通过跳转指令跳转至shellcode,但是NX开启后CPU会对数据区域进行检查,当发现正常程序不执行,并跳转至其他地址后会抛出异常,接下来不会继续执行shellcode,而是去转入异常处理,处理后会禁止shellcode继续执行。

January 9, 2025 · 1 min · 54 words · Chenjx12