BJDCTF 2nd secret Pwn题解析:栈溢出与栈对齐实战
1. 从一道Pwn题看栈溢出利用的完整链路拿到一道Pwn题最怕的不是保护全开而是保护没开全但你就是找不到利用点。BJDCTF 2nd的secret这题我第一次做的时候卡了整整一个下午不是因为技术难度有多高而是因为一个很隐蔽的细节——程序里藏了一个后门函数但它的触发条件不是简单的覆盖返回地址就行。这题后来我跟几个朋友交流发现不少人都在同一个地方翻车所以决定把完整的分析过程和踩坑记录整理出来。这篇文章适合已经了解栈溢出基本原理、会用pwntools写基本exp、但还不太熟悉复杂利用链构造的读者。我会从二进制保护检查开始一步步走到最终getshell把每一步的思考过程和踩坑经验都摊开来讲。如果你正在刷CTF Pwn题或者想系统梳理栈溢出利用的完整流程这篇内容应该能帮你省下不少时间。先交代一下这题的基本情况64位ELF动态链接保护情况需要实际检查。题目名字叫secret暗示程序里藏了什么东西——可能是后门函数可能是未初始化的变量也可能是某个特殊的判断逻辑。这类题目的核心考点通常不是复杂的堆利用或者格式化字符串而是对栈布局的精确理解和返回地址的巧妙控制。2. 保护检查与二进制信息收集2.1 用checksec快速摸清底细拿到二进制文件的第一件事永远是检查保护机制。我习惯用pwntools自带的checksecchecksec ./secret输出大概长这样Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)这个结果信息量很大。No canary意味着栈溢出不会被检测可以直接覆盖返回地址。No PIE意味着代码段的地址是固定的后门函数的地址可以直接硬编码。NX enabled意味着不能直接在栈上执行shellcode必须用ROP或者调用现有函数。Partial RELRO说明GOT表可写但这里大概率用不上。注意checksec的输出格式在不同版本的pwntools中可能略有差异但关键字段是一样的。如果显示NX disabled那就可以考虑直接写shellcode但大多数现代题目都会开启NX。2.2 用file和readelf补充信息checksec给的是宏观保护情况还需要确认一些细节file ./secret readelf -h ./secret | grep Type readelf -s ./secret | grep FUNCfile命令确认是64位ELF、动态链接。readelf -h看ELF类型通常是EXEC非PIE或DYNPIE。readelf -s列出符号表这一步很关键——如果二进制没有strip可以直接看到函数名后门函数可能就藏在里面。我在这题里用readelf -s看到了一个名为get_flag或者类似名字的函数地址是固定的。这就是题目名字secret的暗示——程序里藏了一个正常情况下不会被调用的函数里面直接读取flag文件。2.3 用IDA或objdump做静态分析静态分析工具的选择看个人习惯。我一般先用objdump快速看一眼main函数的反汇编objdump -d -M intel ./secret | grep -A 50 main:但objdump的输出可读性一般真正要理清逻辑还是得上IDA或者Ghidra。用IDA打开后重点看几个地方main函数的栈帧大小、输入函数是什么read/gets/scanf、缓冲区到返回地址的偏移、有没有明显的后门函数。这题的main函数逻辑不复杂大概是这样int main() { char buf[64]; puts(Tell me your secret:); read(0, buf, 0x100); // 明显的栈溢出 return 0; }read函数读了0x100字节到64字节的缓冲区溢出是板上钉钉的事。但问题在于直接覆盖返回地址到后门函数能不能拿到shell这里就是第一个坑。3. 栈布局的精确计算与偏移量确定3.1 为什么偏移量不能靠猜很多人做栈溢出题喜欢用pattern_create和pattern_offset来定位偏移这本身没问题但前提是你得确认溢出确实能触发崩溃。这题我一开始用cyclic模式跑了一遍发现崩溃时的RIP确实是cyclic的值偏移量算出来是7264字节缓冲区8字节saved rbp。但这里有个细节如果程序在read之后还有其他操作比如调用puts输出什么栈上的数据可能会被修改导致偏移量变化。所以最稳妥的办法是结合静态分析和动态调试来确认。3.2 用GDBpeda/pwndbg精确观察栈帧我习惯用pwndbg在read函数调用前下断点gdb ./secret b *main0x?? # 在read调用后下断点 r然后查看栈布局stack 30或者用telescope $rsp查看栈上的数据。确认缓冲区起始地址到saved rbp的距离再加上8就是到返回地址的偏移。这题的实际偏移是72但我在第一次做的时候算成了80原因是把saved rbp的位置搞错了。后来用info frame确认了栈帧结构才纠正过来。提示不同编译器的栈对齐策略可能不同有时候会有额外的padding。不要完全依赖静态分析的数字一定要用GDB动态验证。3.3 后门函数的地址确认确认了偏移量之后下一步是找到后门函数的地址。如果二进制没有PIE直接用readelf或者IDA里看到的地址就行。这题的后门函数地址是0x400xxx具体数字记不清了但关键是这个函数里面做了什么。我反编译看了一下后门函数大概是这样的void secret_function() { system(/bin/sh); }看起来很简单直接覆盖返回地址到这个函数就能getshell。但实际跑的时候发现不行——程序崩溃了。这就是第二个坑栈对齐问题。4. 栈对齐一个容易被忽略的致命细节4.1 为什么调用system会崩溃x86_64架构下System V ABI要求调用函数时栈指针RSP必须是16字节对齐的。具体来说在call指令执行之前RSP必须是16的倍数call指令会把返回地址压栈所以进入函数体时RSP是16的倍数减8。如果直接覆盖返回地址到后门函数程序从main函数返回时RSP的值可能不满足对齐要求。当后门函数内部调用system时system内部的某些指令比如movaps要求16字节对齐不对齐就会触发SIGSEGV。这个坑我在很多题里都遇到过但每次遇到还是容易忘。判断方法很简单在GDB里跑到后门函数入口看RSP的值是不是16的倍数。如果不是就需要在ROP链里加一个ret gadget来调整栈。4.2 ret gadget的选取与使用ret gadget的地址很好找用ROPgadget或者ropperROPgadget --binary ./secret | grep ret$或者直接在IDA里找一个单独的ret指令的地址。这题里我找到了一个0x400xxx的ret gadget。构造payload的时候在返回地址之前插入这个ret gadget的地址payload bA * 72 p64(ret_addr) p64(backdoor_addr)这样程序先从main返回到ret gadgetret gadget执行一次ret相当于多弹了一次栈RSP增加8从而调整了对齐。注意有时候需要加多个ret gadget才能对齐具体加几个要看GDB里RSP的实际值。一般来说加一个就够了但极端情况下可能需要加两个。4.3 验证栈对齐是否成功调整之后在GDB里重新跑一遍在后门函数的system调用处下断点确认RSP的值是16的倍数。如果还是不对就再加一个ret gadget。我在这题里加了一个ret gadget就成功了。但有个朋友跟我说他加了两个才行原因是他的GDB版本和编译器的栈对齐策略略有不同。所以这一步一定要自己验证不要照抄别人的exp。5. 完整EXP的编写与调试过程5.1 pwntools模板的搭建我写exp习惯用一个固定的模板省得每次重新写from pwn import * context.log_level debug context.arch amd64 elf ELF(./secret) # libc ELF(./libc.so.6) # 这题不需要 if args.REMOTE: p remote(target, port) else: p process(./secret) # 偏移量 offset 72 # 后门函数地址 backdoor elf.symbols[secret_function] # 或者硬编码地址 # ret gadget ret 0x400xxx # 实际地址 payload bA * offset payload p64(ret) payload p64(backdoor) p.sendline(payload) p.interactive()这个模板的关键点是用elf.symbols自动获取函数地址如果符号表没被strip用args.REMOTE控制本地还是远程。5.2 本地调试中遇到的意外第一次跑exp的时候程序确实拿到了shell但输出的flag是空的。我一开始以为是后门函数的问题后来发现是本地没有flag文件。这种问题在本地调试时很常见解决办法是在本地创建一个假的flag文件echo flag{test_flag} /flag或者修改后门函数让它输出别的内容。但更稳妥的做法是直接看后门函数的反汇编确认它到底读了哪个文件。5.3 远程打通的注意事项远程打的时候首先要确认目标环境的libc版本和本地是否一致。这题因为直接调用了后门函数不涉及libc地址泄露所以不用管libc版本。但如果题目需要泄露libc基址再算system地址那就必须确认libc版本。另外远程连接可能会因为网络延迟导致交互超时。我一般会在sendline之后加一个短暂的sleep或者用p.recvuntil等待特定输出再发送payload。p.recvuntil(bTell me your secret:) p.sendline(payload)这样能确保payload在正确的时间点发送避免因为时序问题导致利用失败。6. 从这题延伸出的通用Pwn解题思路6.1 后门函数类题目的快速识别这类题目的特征很明显二进制里有一个不会被正常调用的函数函数内部直接调用了system(/bin/sh)或者execve(/bin/sh, ...)。识别方法就是看符号表里有没有名字可疑的函数比如get_flag、secret、backdoor、win之类的。如果符号表被strip了那就看函数列表里有没有哪个函数的反汇编特别短而且内部有system调用。用IDA的Function Window按地址排序通常后门函数会在main函数附近或者文件末尾。6.2 栈溢出偏移量的三种确认方法确认偏移量有三种方法我一般会交叉验证第一种是静态分析在IDA里看栈帧结构直接读出缓冲区到saved rbp的距离。这种方法最快但可能因为编译器优化而不准确。第二种是动态调试用cyclic模式跑崩溃看RIP的值。这种方法最准但需要程序确实能崩溃。第三种是源码审计如果有源码的话直接看数组大小和read的长度。CTF题一般不给源码但如果有这是最可靠的。三种方法的结果如果一致那就可以放心用。如果不一致以动态调试的结果为准。6.3 栈对齐问题的通用解决方案栈对齐问题不是这题独有的任何64位栈溢出题只要涉及到调用libc函数或者system都可能遇到。通用的解决方案就是在ROP链里加ret gadget。判断是否需要加ret gadget的方法在GDB里跑到目标函数入口执行p $rsp看值是不是16的倍数。如果不是就需要加。加几个ret gadget的判断方法加一个之后再看RSP如果还不是16的倍数就再加一个。因为每个ret gadget会让RSP增加8所以最多加一个就能从8 mod 16变成0 mod 16。6.4 本地调试环境的搭建建议本地调试Pwn题我建议装一个pwndbg或者peda能大幅提升效率。另外创建一个假的flag文件放在根目录方便验证后门函数是否真的能读到flag。如果题目依赖特定的libc版本可以用patchelf修改二进制的解释器和rpathpatchelf --set-interpreter ./ld-linux-x86-64.so.2 ./secret patchelf --set-rpath . ./secret这样就能用指定的libc运行程序避免因为libc版本不一致导致的偏移量差异。7. 我在实战中总结的几条经验做Pwn题最忌讳的就是拿到题目直接上exp模板不分析二进制。我见过太多人因为跳过了保护检查和静态分析导致在错误的方向上浪费大量时间。这题如果没注意到栈对齐问题可能跑十遍exp都是崩溃然后以为是偏移量算错了反复调整偏移量最后心态崩了。另一个经验是GDB的调试信息比任何猜测都可靠。偏移量、栈对齐、函数地址这些关键数据一定要在GDB里确认。静态分析给的是理论值动态调试给的是实际值两者不一致时以动态为准。还有一点关于exp的编写不要一次性写完整个exp再跑要分步验证。先确认能控制RIP再确认能跳到后门函数最后确认能拿到shell。每一步都验证通过之后再往下走这样出问题的时候能快速定位是哪一步错了。最后说一个这题的小细节后门函数的地址在有些版本里可能因为编译选项不同而变化所以不要硬编码地址尽量用elf.symbols自动获取。如果符号表被strip了那就用ROPgadget或者IDA找地址但一定要在GDB里验证地址是否正确。这题的整体难度不高但栈对齐这个点确实卡住了不少人。把这道题吃透之后以后再遇到类似的后门函数题目基本就是几分钟的事。关键是要养成严谨的分析习惯不放过任何一个细节。

相关新闻

强化学习入门:环境与智能体解耦,轻松替换算法

强化学习入门:环境与智能体解耦,轻松替换算法

简介:这份资源面向强化学习初学者与需要快速上手实践的开发者,提供一套可直接运行的乒乓球对战模拟实例,帮助理解智能体如何通过与环境交互不断优化击球策略。压缩包共10个文件,以6个pth模型权重、3个py脚本和1个pyc缓存文件为主&…

2026/10/16 20:08:38 阅读更多 →
C# async/await原理与高并发异步编程实战指南

C# async/await原理与高并发异步编程实战指南

1. 这不是语法糖,是C#并发能力的真正开关“C#异步task任务 await与async的正确打开方式”——这个标题里藏着一个被严重低估的事实:async/await不是让代码“看起来不卡”的美化技巧,而是C#运行时调度模型的一次底层重构。我带过不少刚从Java或…

2026/10/16 20:07:37 阅读更多 →
Spring Boot集成SAP RFC全攻略:JCo连接、Shiro权限与Quartz调度

Spring Boot集成SAP RFC全攻略:JCo连接、Shiro权限与Quartz调度

简介:这是一份基于Spring Boot构建的SAP RFC交互后台管理系统源码,适合需要对接SAP系统的Java开发者、后端工程师及项目架构师。项目完整集成MyBatis、MyBatis-Plus、Shiro、Quartz、thymeleaf以及SAPJCO3,通过调用SAP RFC函数实现SAP与外部系…

2026/10/18 12:55:32 阅读更多 →

最新新闻

前端JavaScript安全漏洞利用实战:从XSS到原型链污染

前端JavaScript安全漏洞利用实战:从XSS到原型链污染

我最近在整理一份安全学习系列v2023的学习笔记,第六十五讲正好落在JS应用安全漏洞利用这个主题上。说实话,这一讲的内容跟我平时做前端代码审查时的场景非常吻合:不需要等下一条注入请求,而是从浏览器里那堆看似平常的JavaScript文…

2026/10/18 18:17:55 阅读更多 →
knowledge-work-plugins 插件开发指南:Claude Code 知识工作流封装与 slash commands 实战

knowledge-work-plugins 插件开发指南:Claude Code 知识工作流封装与 slash commands 实战

1. 从零认识 knowledge-work-plugins:它到底解决什么问题第一次看到knowledge-work-plugins这个仓库名,很多人会以为它又是一个“插件市场”或者“工具合集”。但如果你真的在 Claude Code 或 Claude Cowork 里干过几天活,就会明白它想解决的…

2026/10/18 18:17:55 阅读更多 →
基于MSP430的老人跌倒检测手环:GSM报警与GPS北斗定位设计

基于MSP430的老人跌倒检测手环:GSM报警与GPS北斗定位设计

上个月接了这个老人跌倒手环的活,我一开始觉得这事不难:传感器、定位模块、GSM模块,全是现成货,拼一块板、写几行代码,能发短信就行。真正把样机戴在假人手腕上测了两周,我才发现标题里那串词——MSP430单片…

2026/10/18 18:17:55 阅读更多 →
Keithley 2400源表Python控制框架:SCPI封装与自动化测试胶水层

Keithley 2400源表Python控制框架:SCPI封装与自动化测试胶水层

简介:本资源是专为Keithley 2400系列数字源表(SMU)配套开发的自动化测试软件,面向电子测量工程师、半导体器件研发人员及高校电学特性实验教学用户,解决I-V、I-t、V-t等基础电学曲线的快速采集与可视化分析需求。压缩包…

2026/10/18 18:17:55 阅读更多 →
本地知识库问答系统实践:RAG、混合检索与部署避坑指南

本地知识库问答系统实践:RAG、混合检索与部署避坑指南

1. 内容整体设计与思路拆解1.1 为什么我想做一个本地知识库问答系统几个月前,我在某公司做内部工具链时遇到一个很典型的问题:团队手里有大量文档——技术方案、接口文档、排障手册、历史会议纪要——但散落在不同的网盘、本地文件夹和个人电脑里。每次有…

2026/10/18 18:17:55 阅读更多 →
Java实现可玩数独游戏:盘面生成、实时校验与控制台交互

Java实现可玩数独游戏:盘面生成、实时校验与控制台交互

简介:这是一份面向Java初学者与窗体应用开发者的趣味编程实践资源,提供完整可运行的数独小游戏源码,帮助用户通过经典逻辑游戏理解GUI编程、事件驱动机制与难度分级设计。压缩包共21个文件,含核心Java源文件(.java&…

2026/10/18 18:16:55 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →