从CTF签到题入门栈溢出:逆向分析、动态调试与安全机制解析

发布时间:2026/8/4 7:19:20
从CTF签到题入门栈溢出:逆向分析、动态调试与安全机制解析 1. 项目概述从一道签到题看PWN的攻防本质最近在带新人入门CTF的PWN方向发现很多人拿到一个二进制文件第一反应就是丢进IDA然后对着反汇编代码发呆或者直接运行一下看看程序输出什么。这其实忽略了一个最核心的问题我们到底在分析什么PWN的核心是漏洞利用而漏洞利用的前提是理解程序的运行机制和内存布局。与其一上来就扎进复杂的汇编指令不如从一个最简单的“签到题”入手理解最经典的漏洞模型——栈溢出以及现代系统为了对抗它而部署的层层防御。今天我就以CTFshow平台上的一道典型PWN签到题为引子带大家走一遍完整的逆向分析流程重点不是如何“打穿”这道题而是拆解它背后的防御机制理解为什么现在的栈溢出没那么简单了以及我们作为分析者应该如何思考和应对。这道题通常是一个极简的C程序编译成一个32位或64位的ELF可执行文件。它的核心逻辑可能就十几行代码一个危险的、不检查输入长度的函数如gets或scanf一个局部字符数组以及一个永远无法被正常执行的函数比如system(/bin/sh)或打印flag的函数。我们的目标就是通过溢出覆盖返回地址劫持程序流去执行那个“后门函数”。听起来很直接对吧但当你用gdb载入准备开搞时可能会发现事情没那么简单。程序崩溃了但似乎没跳到我们想要的地方。这就是防御机制在起作用。通过这道题我们实际上是在学习与整个现代操作系统的安全子系统对话。2. 逆向分析前的环境与工具准备工欲善其事必先利其器。PWN题的分析和利用高度依赖于一套稳定、高效的工具链。这里我分享一套我用了多年兼顾新手友好和老手效率的配置并解释每个工具的核心用途避免大家盲目安装一堆用不上的东西。2.1 核心工具链选型与配置我的本地环境通常是Ubuntu 20.04/22.04 LTS因为其软件包更新及时对各类安全工具支持良好。Windows用户强烈建议使用WSL2它能提供一个近乎原生的Linux体验。1. 调试器GDB Peda/Pwndbg/GEF三选一GDB是基石但原生GDB对二进制漏洞利用不太友好。增强插件是必须的。Pwndbg目前社区最活跃对CTF场景优化极好。它集成了堆可视化、ROP链搜索、内存搜索、漏洞利用辅助命令如cyclic生成测试字符串等功能信息展示非常直观。安装也简单通常一条pip install pwndbg加上源码安装就能搞定。GEF功能同样强大界面比Pwndbg更紧凑一些。它的内存检查命令如checksec信息展示方式我个人很喜欢。Peda比较经典的插件有些老教程还在用但近年更新放缓。我的选择与建议新手直接上Pwndbg。它的错误提示更友好命令集成度高。比如它内置的cyclic和cyclic_find命令能自动化完成计算溢出偏移量这个原本很繁琐的工作。2. 反汇编与静态分析IDA Pro / Ghidra / Binary NinjaIDA Pro逆向界的“瑞士军刀”功能最强交互式反汇编体验最好F5伪代码功能对快速理解程序逻辑至关重要。缺点是收费。对于学习其免费版旧版或体验版也足够应付大部分CTF题目。GhidraNSA开源的神器完全免费且功能强悍。它的反编译引擎非常出色有时生成的伪代码可读性甚至优于IDA。支持多人协作、脚本编写。缺点是启动和加载大型项目稍慢UI需要适应。Binary Ninja后起之秀设计现代API极其强大适合编写自动化分析脚本。商业版价格不菲。我的选择与建议Ghidra是绝佳的起点和主力。免费、强大、更新快。用熟Ghidra后如果需要更快的交互体验可以搭配使用IDA。对于这道签到题用Ghidra就绰绰有余了。3. 动态分析辅助strace / ltracestrace跟踪程序执行的系统调用。比如你可以看到程序何时、以什么参数调用了read、write、execve等。这对于理解程序与操作系统的交互尤其是输入/输出逻辑非常有用。ltrace跟踪程序调用的库函数。你可以看到程序何时调用了strcpy、gets、printf等。这对于快速定位危险函数调用点至关重要。实操技巧拿到题目二进制文件不要急着逆向。先strace ./pwn_binary和ltrace ./pwn_binary跑一下看看程序大概做了什么。如果题目有网络交互strace可能会显示socket、bind、accept等调用帮你判断题型。4. 检查安全机制checksec这是pwntools工具包里的一个脚本也可以单独运行。它用于快速检查二进制文件启用了哪些安全编译选项。checksec ./pwn_binary它会输出类似下面的信息Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments这份报告就是我们分析防御机制的“体检表”后面会详细解读每一项。5. 漏洞利用开发框架pwntoolsPython库PWN手必备。它封装了进程交互、网络连接、数据打包p32/p64、汇编/反汇编、日志记录等一系列功能让你能像写普通Python脚本一样编写漏洞利用程序Exploit。from pwn import * context.log_level debug # 设置调试输出 context.arch i386 # 设置架构 io process(./pwn_binary) # 本地启动进程 # 或 io remote(ctf.show, 12345) # 远程连接 payload bA * 偏移量 p32(后门函数地址) io.sendline(payload) io.interactive() # 交互模式2.2 环境配置中的常见坑点Python环境冲突pwntools和某些插件对Python版本有要求。建议使用virtualenv或conda创建独立的虚拟环境。我习惯用python3 -m venv pwn_env然后source pwn_env/bin/activate。GDB插件安装失败最常见的是权限问题或依赖缺失。一定要仔细阅读插件的官方安装指南。对于Pwndbg确保已安装python3-dev、gdb等包。32位程序运行库如果题目是32位i386程序在64位系统上运行需要安装32位运行库sudo apt install gcc-multilib。工具版本滞后安全工具更新很快旧版本可能无法解析新编译器生成的二进制文件。定期更新你的工具链是必要的。3. 静态分析拆解程序结构与漏洞点拿到二进制文件pwn_simple我们首先进行静态分析目的是在不运行程序的情况下摸清它的整体结构、逻辑流程和潜在的危险点。3.1 初步信息收集与安全检查第一步永远是用file和checksec命令。file pwn_simple输出pwn_simple: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]..., not stripped这告诉我们这是一个32位、小端序、动态链接、没有去除符号表的ELF可执行文件。“not stripped”是个好消息意味着函数名、变量名等调试信息还在逆向难度大大降低。接着checksec pwn_simple我们假设得到如下输出Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segmentsArch32位程序这影响我们计算地址和填充数据的大小4字节。RELRO: Partial部分RELRO全局偏移表GOT可写这为GOT覆写攻击留下了可能性但本题不涉及。Stack: No canary栈溢出保护Canary未开启。这是关键意味着我们进行栈溢出时不会触发Canary检查导致的崩溃这是传统栈溢出利用能成功的前提之一。NX: Disabled数据执行保护NX未开启。另一个关键点这意味着栈上的数据比如我们注入的shellcode可以被当作代码执行。如果NX开启栈是不可执行的我们就不能直接跳转到栈上的shellcode必须转向ROP等技术。PIE: No PIE地址空间布局随机化PIE未开启。最重要的信息这意味着程序的加载基地址是固定的0x8048000。我们通过静态分析找到的函数地址如main在0x80484a0后门函数win在0x8048512在程序运行时是不会变的可以直接在Exploit中使用。RWX存在可读、可写、可执行的段这通常就是栈因为NX关闭了。这份报告已经给了我们极大的信心这是一个“裸奔”的二进制没有任何现代栈溢出防御。理论上我们找到溢出点计算好偏移把返回地址覆盖成后门函数地址就能搞定。3.2 使用Ghidra进行核心逻辑逆向用Ghidra打开pwn_simple加载完成后在Symbol Tree里找到main函数并双击Ghidra会自动进行反编译。我们可能会看到类似如下的伪代码undefined4 main(void) { char local_12 [10]; int local_8; local_8 0; puts(Welcome to CTFshow PWN challenge!); printf(Input your name: ); gets(local_12); // 危险函数 if (local_8 0xdeadbeef) { system(/bin/sh); } else { printf(Hello, %s\n,local_12); } return 0; }注变量名local_12、local_8是Ghidra自动生成的基于它们相对于栈帧基址的偏移。逻辑分析定义了一个10字节的字符数组local_12和一个整型变量local_8初始化为0。使用gets(local_12)读取输入。gets函数是著名的“缓冲区溢出杀手”它不检查输入长度会一直读到换行符为止有多少写多少完全不管local_12只能容纳10个字节。检查local_8是否等于0xdeadbeef如果是就调用system(/bin/sh)给我们一个shell。否则打印问候语。漏洞点一目了然gets(local_12)存在栈溢出漏洞。我们的目标是通过溢出不仅覆盖函数返回地址还要顺带覆盖local_8变量的值使其变为0xdeadbeef从而通过if检查拿到shell。关键地址获取在Ghidra的符号表中我们还能找到一个叫win或get_flag的函数地址假设是0x8048512。这是我们的目标地址。main函数的地址也需要有时用于计算偏移但本题更关键的是local_12的缓冲区大小和栈布局。3.3 计算精确的溢出偏移量这是栈溢出利用中最需要精确的一步。我们需要知道从我们输入的缓冲区起始位置到覆盖掉函数返回地址Saved EIP之间需要填充多少字节。方法一理论计算结合汇编查看main函数的汇编代码在Ghidra的汇编窗口push ebp mov ebp, esp sub esp, 0x18 ; 为局部变量分配0x18(24)字节栈空间 ... lea eax, [ebp-0x12] ; local_12的地址是 ebp - 0x12 push eax call gets栈布局从高地址到低地址高地址 ... 旧EBP值 (4字节) -- [ebp] 返回地址 (4字节) -- [ebp4] 【目标覆盖点】 ... (可能有一些对齐空间) local_8 (4字节) -- [ebp-0x8] 【需要覆盖为0xdeadbeef】 ... (2字节对齐填充) local_12 (10字节) -- [ebp-0x12] 【输入起点】 低地址从local_12(ebp-0x12)到返回地址(ebp4)的偏移量计算(ebp - (ebp-0x12)) 4 0x12 4 0x16十进制22。也就是说先填满22个字节接下来的4个字节就会覆盖到返回地址。 同时local_8在ebp-0x8从local_12到local_8的偏移是(ebp-0x12) 到 (ebp-0x8) 0xa十进制10。所以输入的第11-14字节因为local_8是4字节int会覆盖local_8。方法二动态验证使用Pwndbg的cyclic理论计算可能有误比如编译器优化、对齐问题动态验证更可靠。在终端1运行gdb ./pwn_simple在gdb中cyclic 100生成一个100字节的、带特殊模式如aaabaaacaaad...的字符串。运行程序r当提示输入时粘贴这100个字符。程序必然会崩溃。gdb会显示类似0x6161616c in ?? ()的信息。0x6161616c就是覆盖到返回地址的那4个字节laaa的十六进制表示小端序。在gdb中cyclic -l 0x6161616c。这个命令会告诉你这个模式出现在cyclic生成字符串的哪个位置。假设输出是28。这意味着从输入开始到返回地址的偏移是28字节。同时我们也可以检查local_8的值。在崩溃前下断点或者查看崩溃时栈内存找到local_8的位置ebp-0x8看它被覆盖成了cyclic字符串的哪一部分从而验证其偏移是10。我的实操心得永远相信动态验证的结果。编译器的优化等级-O0,-O2、不同的编译器版本gcc, clang都可能导致栈布局产生微小差异。cyclic方法是目前最准确、最通用的方法。对于这道题我们确认偏移量是覆盖local_8需要10字节填充 4字节的0xdeadbeef覆盖返回地址需要总共28字节填充 4字节的目标地址。4. 动态调试验证分析与理解崩溃现场静态分析给了我们蓝图动态调试则是施工和验收。我们用GDB配合Pwndbg来实际运行程序观察内存变化验证我们的计算。4.1 布局漏洞利用并触发崩溃根据上面的分析我们可以先构造一个初步的Payload进行测试from pwn import * context(archi386, oslinux) offset_to_local8 10 offset_to_ret 28 win_addr 0x8048512 deadbeef 0xdeadbeef payload bA * offset_to_local8 payload p32(deadbeef) # 覆盖 local_8 为 0xdeadbeef # 从覆盖完local_8到返回地址之间还有空间28 - 10 - 4 14 字节 payload bB * 14 payload p32(win_addr) # 覆盖返回地址 print(payload.hex())运行这个脚本生成一串十六进制Payload。然后我们启动GDB调试。4.2 使用GDB/Pwndbg跟踪执行gdb ./pwn_simple在main函数和gets调用后设断点b main,b *地址gets调用后的指令地址。r运行程序程序会在main入口暂停。使用stack或telescope $esp等命令查看栈的初始状态。记录下ebp的值。c继续程序提示输入时将我们生成的Payload作为输入发送进去。在Pwndbg中可以直接send命令或者复制粘贴。程序在gets后的断点停下。此时再次检查栈。使用x/20wx $ebp-0x12查看local_12开始的区域应该被我们的A填满。使用x/wx $ebp-0x8查看local_8应该变成了0xdeadbeef。使用x/wx $ebp4查看返回地址应该变成了win函数的地址0x8048512。继续执行c程序应该不会执行原来的printf(Hello...)而是直接跳转到win函数最终获得shell如果win函数是system(/bin/sh)。关键调试技巧关注EIP/RIP寄存器崩溃时eip寄存器的值就是程序试图执行的下一条指令地址。如果它变成了我们Payload中的字符如0x41414141即AAAA说明我们成功覆盖了返回地址但地址值不对。检查栈对齐在64位程序中有时需要关注栈对齐Stack Alignment问题。某些函数如system要求栈指针rsp在调用时是16字节对齐的否则会崩溃。这需要通过添加一个ret指令的地址作为“垫片”来调整。32位程序通常没这个问题。使用search命令在内存中搜索字符串或字节序列非常有用。例如search -t bytes 41 41 41 41可以搜索连续的AAAA帮你定位Payload在内存中的位置。4.3 处理常见的动态调试问题程序输入包含空字节\x00strcpy等函数遇到空字节会终止复制。但gets和read不会。不过在构造Payload时如果目标地址本身包含空字节如0x8048500在32位下这作为字符串的一部分可能被某些函数截断。这时需要调整Payload结构或者寻找不包含空字节的地址通常PIE关闭时地址高位是\x00但0x804xxxx的高位0x80不是空字节没问题。环境变量影响栈地址在调试器GDB中运行和直接运行./pwn_simple时环境变量不同可能导致栈的绝对地址有细微偏移。但这不影响相对偏移的计算。如果你的Exploit在GDB中成功在外面失败可以尝试在GDB中使用unset env清空环境变量再测试或者使用env -i ./pwn_simple来运行。管道缓冲与交互问题用Python的pwntools写Exploit时发送Payload后如果程序不立即崩溃或输出可能是输入缓冲问题。尝试在Payload后加上\n换行符或者使用io.sendline()而不是io.send()。对于交互式shell务必使用io.interactive()。5. 漏洞防御机制深度解析与绕过思路通过这道“裸奔”的签到题我们理解了最原始的栈溢出。但现实中的二进制程序包括CTF中稍难的题目都开启了各种防御机制。理解它们是进阶的必经之路。下面结合checksec的输出逐一拆解。5.1 NXDEP数据执行保护原理将内存页标记为不可执行No-eXecute。栈和堆通常被标记为RW-可读可写但不可执行。这样即使我们将shellcode注入到栈上当程序流跳转到栈地址时CPU会抛出异常Segmentation Fault阻止代码执行。检查checksec中NX enabled。对本题的影响如果本题开启了NX我们上述将返回地址直接覆盖为栈上地址指向我们的shellcode的方法就会失效。程序会崩溃在0x41414141之类的地址而不是执行shellcode。绕过思路ROPReturn-Oriented Programming。既然不能执行自己注入的代码我们就利用程序中已有的、以ret指令结尾的小代码片段称为“gadget”将它们串联起来达到类似执行代码的效果。例如我们可以用gadget来设置系统调用的参数如将/bin/sh字符串地址放入ebx然后调用int 0x8032位或syscall64位来执行execve。这需要更复杂的链式构造和更多的准备工作。5.2 Stack Canary栈金丝雀原理在函数栈帧的返回地址之前低地址方向插入一个随机值Canary。函数返回前会检查这个值是否被改变。如果被改变说明发生了栈溢出程序会立即终止调用__stack_chk_fail。检查checksec中Stack canary found。对本题的影响如果开启Canary我们的溢出在覆盖返回地址之前会先覆盖Canary。函数返回时检查失败程序直接崩溃不会执行到被覆盖的返回地址。绕过思路泄露Canary值如果程序有输出漏洞如格式化字符串漏洞可以读取栈内存就有可能读到Canary的值。然后在构造Payload时在对应位置填入正确的Canary值就能“骗过”检查。覆盖特定的函数指针或局部变量如果溢出长度有限不足以覆盖到返回地址但可以覆盖到某个函数指针比如atexit处理函数或重要的局部变量也可能实现利用但这不属于传统栈溢出。爆破Canary在fork-server模式的题目中常见于网络服务子进程会继承父进程的内存空间包括Canary。可以尝试逐字节爆破。但这需要程序可以多次崩溃重启。5.3 PIEASLR地址空间布局随机化原理程序每次加载到内存时其基地址包括代码段、数据段、堆、栈等都是随机化的。这使得攻击者无法在Exploit中硬编码函数地址如system的地址。检查checksec中PIE enabled。注意PIE主要针对代码段。即使PIE关闭系统的堆栈ASLR可能仍然开启通过/proc/sys/kernel/randomize_va_space控制。对本题的影响如果本题开启了PIE我们在Ghidra里看到的win函数地址如0x555555554512只是一个偏移量。程序实际运行时基地址会变导致绝对地址变化。我们无法直接用这个地址。绕过思路泄露地址利用程序漏洞如格式化字符串、数组越界读泄露出某个已知的代码地址比如puts函数的实际地址。然后根据偏移量计算出基地址进而推算出win函数或其他gadget的实际地址。公式泄露地址 - 已知偏移 实际基地址实际基地址 目标函数偏移 目标函数实际地址部分覆写如果PIE随机化的范围有限比如只随机化低12位或20位而我们知道目标地址的大致范围可以尝试部分覆写返回地址的低位字节。但这需要精确的堆喷Heap Spraying或特定条件难度较高。5.4 RELRO重定位只读原理控制动态链接时重定位数据的读写权限。Partial RELROGOT全局偏移表可写。这是默认设置。Full RELROGOT被标记为只读。这防止了GOT覆写攻击通过修改GOT表项来劫持库函数调用。检查checksec中RELRO状态。对本题的影响RELRO本身不直接影响栈溢出但它影响利用链的构建。如果程序是Partial RELRO在无法直接执行栈代码NX开启且没有现成的system函数时我们可以尝试覆写GOT表例如将printf的GOT项改为system的地址然后通过调用printf来触发system。Full RELRO则封死了这条路。5.5 综合防御下的利用策略演变当多种防御机制同时开启时如NXPIECanary单一的利用技术往往不够。现代CTF的PWN题利用链通常是多种技术的组合信息泄露 ROP这是最经典的组合。先通过一个漏洞如格式化字符串、堆溢出导致的UAF读泄露关键信息Canary值、libc基地址用于计算system地址、程序基地址PIE。然后利用另一个漏洞如栈溢出构造ROP链在已知地址的情况下调用system(/bin/sh)或execve。堆利用技术当栈溢出被防死注意力就转向堆。Use-After-FreeUAF、Double Free、Heap Overflow等漏洞结合堆管理器的特性如glibc的ptmalloc2可以实现任意地址写Write-Anything-Anywhere最终劫持控制流。FSOPFile Stream Oriented Programming利用FILE结构体FILE进行利用。通过伪造FILE结构体控制虚函数表vtable或相关指针在调用如exit、fclose等函数时触发恶意代码执行。回到我们的签到题它之所以简单就是因为checksec显示所有防御都没开。这让我们可以专注于理解栈溢出的最基本原理控制EIP。这是所有漏洞利用的基石无论后续防御多复杂最终目标都是让CPU去执行我们想让它执行的指令。6. 从解题到出题构建自己的PWN实验环境理解了漏洞和防御最好的巩固方式就是自己动手“造”一个漏洞程序并尝试利用它。这能让你从攻击者视角切换到设计者视角理解漏洞产生的每一个细节。6.1 编写有漏洞的C程序创建一个vuln.c文件#include stdio.h #include string.h #include stdlib.h void win() { system(/bin/sh); } void vulnerable_function() { char buffer[16]; printf(Input: ); gets(buffer); // 明显的栈溢出漏洞 printf(You entered: %s\n, buffer); } int main() { setbuf(stdout, NULL); // 关闭输出缓冲方便调试 vulnerable_function(); return 0; }这个程序比签到题更简单只有一个缓冲区和一个后门函数。6.2 使用不同编译选项观察差异我们使用GCC编译通过不同的参数来模拟不同的安全设置完全无保护类似签到题gcc -m32 -fno-stack-protector -z execstack -no-pie vuln.c -o vuln_none-m32: 编译为32位。-fno-stack-protector: 禁用栈保护Canary。-z execstack: 使栈可执行关闭NX。-no-pie: 禁用PIE。开启NXgcc -m32 -fno-stack-protector -z noexecstack -no-pie vuln.c -o vuln_nx-z noexecstack是默认选项这里显式写出。开启Canarygcc -m32 -fstack-protector-all -z noexecstack -no-pie vuln.c -o vuln_canary-fstack-protector-all对所有函数插入保护。开启PIEgcc -m32 -fno-stack-protector -z noexecstack -pie -fPIE vuln.c -o vuln_pie用checksec检查每个生成的文件你会看到不同的保护组合。然后尝试为每个版本编写Exploit。你会发现对于vuln_none简单的跳转到win即可。对于vuln_nx需要构造ROP链可能需要用到ROPgadget工具寻找gadget。对于vuln_canary程序会因__stack_chk_fail而崩溃。对于vuln_pie需要先泄露地址。6.3 利用Python脚本自动化测试使用pwntools可以快速编写测试脚本from pwn import * import sys def exploit(binary_name): context.binary binary ELF(binary_name) io process(binary_name) # 根据checksec结果动态构造payload if binary.checksec[canary]: print([!] Canary found, need to leak it first.) # 这里需要先利用其他漏洞泄露canary return if binary.pie: print([!] PIE enabled, need to leak base address.) # 需要泄露地址 return # 无保护情况下的简单利用 offset 28 # 使用cyclic动态计算 win_addr binary.symbols[win] if win in binary.symbols else 0x8048512 payload flat({ offset: win_addr }) io.sendline(payload) io.interactive() if __name__ __main__: if len(sys.argv) 1: exploit(sys.argv[1]) else: print(Usage: python3 exploit.py binary)这个脚本框架展示了如何根据二进制文件的安全属性动态调整利用策略。7. 总结与进阶学习路径通过这道CTFshow的PWN签到题我们完成了一次完整的栈溢出漏洞分析、利用和防御原理的探索。从工具准备、静态逆向、动态调试到深入理解NX、Canary、PIE、RELRO等现代漏洞缓解技术我们建立了一个清晰的PWN入门知识框架。关键收获思路重于工具工具IDA、GDB、pwntools是帮手核心是理解程序的内存模型、控制流和数据流。动态验证是金标准理论计算必须用动态调试如cyclic来验证。防御机制是阶梯从无保护到NX到CanaryPIE利用技术也从直接跳转发展到ROP再到需要信息泄露的组合利用。理解每一层防御为何被引入以及如何被绕过是能力提升的关键。动手实验是最好的老师自己编译有漏洞的程序尝试在不同保护下利用它能极大地加深理解。给新手的进阶建议刷题平台CTFshow、Pwnable.kr、Pwnable.tw、攻防世界ADWorld都是很好的练习场。从最简单的栈溢出开始逐步挑战开启了不同保护机制的题目。专题学习格式化字符串漏洞学习如何利用%x,%s,%n等格式化符读写任意内存。堆漏洞入门理解glibc的malloc/free机制学习UAF、Fastbin Attack等基础技巧。ROP技术精进学习如何用工具如ROPgadget、ropper寻找gadget构造复杂的调用链。内核PWN在有一定基础后可以尝试内核模块的漏洞这需要对操作系统有更深的理解。阅读材料《CTF竞赛权威指南PWN篇》国内经典的入门书籍。《Hacking: The Art of Exploitation》非常经典的底层安全书籍。Phrack、看雪等安全社区的文章有很多高质量的漏洞分析和技术文章。参与实战尝试分析一些真实世界软件从历史CVE漏洞开始的漏洞公告和利用代码理解漏洞从发现到利用的全过程。PWN的学习曲线可能比较陡峭它需要你对计算机系统尤其是内存和指令执行有扎实的理解。但每解开一道题每绕过一层防御所带来的成就感也是巨大的。记住不要怕复杂的题目把它们拆解成一个个你学过的小知识点这里有个溢出那里有个泄露这里需要ROP那里需要堆风水。当你把这些点连成线最终拿到shell的那一刻你会真正体会到二进制安全的魅力所在。这道“签到题”就是这一切的起点它告诉你最基本的原理而后面广阔的世界正等着你去探索。