资讯动态

ISCC 2023 Pwn方向实战解析:从栈溢出到堆利用的CTF进阶之路

发布时间:2026/10/1 9:02:56 来源:尧图企业网站定制
[ISCC 2023] pwn部分又到了一年一度打ISCC的时候。这个比赛全称是“信息安全与对抗技术竞赛”从2004年办到现在算是国内老牌CTF赛事之一。今年的ISCC 2023我一如既往地报了pwn方向打完整场比赛最大的感受是题目难度梯度拉得比较开从签到题到进阶题都有覆盖而且今年动态容器部署在题目里占了相当大的比例很多玩家都栽在环境交互而不是漏洞利用本身上。这篇文章不打算做标准的赛题writeup汇总更想从一个普通参赛者的视角聊聊今年ISCC pwn部分从入门到进阶的几个典型题目思路以及我在实际做题过程中踩过的一些坑。如果你正准备入门pwn或者想找一场题目质量比较稳定的国内赛事练手这篇文章应该能帮你少走不少弯路。1. 赛题整体观察与命题趋势1.1 今年pwn题目的难度分布先说说整体感受。ISCC 2023的pwn方向一共安排了多个题目难度从入门到进阶都有覆盖。我自己的体感是最基础的题目考察点集中在栈溢出和格式化字符串这是pwn入门必会的两个基础技能中间档次的题目开始出现堆相关的利用手法和沙箱限制最高难度的题目则需要你对glibc堆管理机制有比较深入的理解。对比前几年今年ISCC的pwn题目在数量上不算特别多但每道题的质量都还算在线。没有那种纯送分的题也没有那种脱离现实、纯为难而难的偏题怪题。作为一场兼顾普及和选拔的比赛这个节奏控制得比较合理。1.2 动态容器成为主流交互方式这里要特别提一下动态容器。今年ISCC的pwn题目基本全面采用了动态容器部署也就是你通过题目附带的端口连接上去拿到的是一个独立运行的容器环境。这个做法的好处是每个选手的题目环境隔离不会出现互相干扰的情况同时也方便题目设置不同的libc版本和运行参数。但动态容器也带来了一个新的问题它要求参赛者必须具备基本的netcat使用能力和端口连通性判断能力。很多第一次参加CTF的同学精力都放在学习栈溢出原理和pwntools脚本编写上结果开赛第一天就卡在连不上题、或者连上了但脚本一发数据就断连这种问题上非常可惜。我的建议是大家在开赛前务必自己动手实验一下怎么用nc或者pwntools的remote函数连接一个远程端口并且确认你本地的网络环境能正常访问比赛服务器。这个准备工作看起来简单实际上能帮你避免至少一个小时的无效排错时间。2. 栈溢出题的完整解题链路2.1 从checksec开始的环境摸底不管什么pwn题拿到二进制文件第一件事永远是checksec。这个工具本质上是一个脚本帮你快速检查ELF文件开启了哪些安全防护机制包括栈保护canary、NX非可执行栈、PIE地址随机化以及RELRO重定位表只读等。ISCC 2023的栈溢出题目里面有一道签到级别的题目checksec结果大概是这样的Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)这里最关键的信息是No canary found和No PIE。没有canary意味着你覆盖返回地址的时候不会被程序检测到没有PIE意味着代码段地址是固定的可以直接在脚本里写硬编码地址。唯一需要绕过的就是NX也就是栈不可执行这意味着你不能直接把shellcode放在栈上跳过去执行。读懂这些信息之后解题思路基本就清晰了找一个能溢出的输入点覆盖返回地址到程序里已有的后门函数或者ROP链即可。如果程序本身给了后门函数一般来说这种签到题都会给那就直接ret到后门函数getshell。2.2 IDA静态分析快速锁定危险函数拿到题目给出的二进制文件之后用IDA打开常规做法是先看main函数和函数列表里有没有可疑的函数名比如win、backdoor、shell之类。ISCC这种入门级题目通常不会做太多代码混淆后门函数基本都在名字上写得很直白。定位到后门函数之后要关注两件事一是后门函数的地址二是主函数里漏洞输入点距离返回地址的偏移。偏移的计算我一般直接用cyclic来测快速且准确。具体操作是用pwntools生成一串cyclic发送过去让程序崩溃再把崩溃时的RIP值放进cyclic的find函数里面就能精确算出偏移。这个步骤有个细节值得说生成的cyclic长度一定要大于你预估的偏移量加上返回地址的长度否则测出来的结果可能不准确。2.3 pwntools脚本的完整写法这里直接给出一份我比赛中常用的脚本模板针对的是最简单的ret2text类型题目from pwn import * context.arch amd64 context.log_level debug p remote(127.0.0.1, 9999) # 本地测试时使用 # p process(./pwn) payload ba * 0x28 # 覆盖到返回地址的偏移 payload p64(0x4011b6) # 后门函数地址 p.sendline(payload) p.interactive()偏移部分我写的是0x28这是根据我实际测试的某道ISCC题目来的。一般情况下如果你是64位程序偏移大概率在0x20到0x80这个范围内具体数值还是以cyclic实测为准。这一步做到了最简单的栈溢出题就解决了。实际比赛里大部分人在这个阶段都能做出来签到题真正的分化从进阶题开始。3. 格式化字符串与其他经典考点的处理思路3.1 格式化字符串题目的核心逻辑ISCC 2023中有一道题考察的是格式化字符串漏洞这也是pwn方向的必修课。格式化字符串漏洞的原理一句话就能讲明白程序用printf或者sprintf输出时直接把用户输入当作格式化字符串传了进去导致你输入的%x、%p、%n会被printf解释成格式控制符。利用格式化字符串漏洞能做两件关键的事泄露内存和写内存。泄露内存用%p或者%s就能实现你可以在输入里用%N$p来指定输出第N个参数的内容从而任意读取栈上的数据。写内存则用%n系列工具它会把已经输出的字符数量写入一个地址配合合适的布局就能实现往任意地址写任意值。在实际解题过程中格式化字符串最经典的用法是泄露canary。因为canary在栈上的位置通常是固定的你只要先用%p把栈上第几个参数打印出来把canary的值拿到再结合栈溢出覆盖返回地址就能绕过栈保护。很多同学看到程序开了canary就觉得做不了其实配合格式化字符串漏洞canary并不是不可绕过的。3.2 从ISCC真题看格式化字符串的实操套路我记得今年ISCC有一道格式化字符串的题程序逻辑很简单先让你输入一段内容然后printf直接输出。没有第二次输入机会所以你要在这一次输入里完成信息泄露。这种题的关键是确定格式化字符串在参数列表中的位置通常用%p逐位测试就能找到偏移。from pwn import * p process(./fmt) payload bAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p p.sendline(payload) print(p.recvline())输出里出现0x41414141的位置就是格式化字符串的起始偏移。拿到这个偏移之后后续所有的%n写操作都基于这个位置来布局。这种测试方法非常实用我在几乎所有格式化字符串题目里都会先用它定位。3.3 栈迁移与ROP的入门要点除了单纯的ret2text今年ISCC还有一道题虽然核心是栈溢出但程序可用的代码段里没有现成的后门函数也没有完整打印flag的逻辑。这时候就需要用到ROP也就是通过控制返回地址链把程序里的gadget串联起来实现我们想要的功能。最简单的ROP思路是ret2libc程序在运行时已经加载了libc动态库你只需要泄露libc里某个函数的真实地址再根据libc文件算出system和/bin/sh的偏移就能拼出一条调用system(/bin/sh)的ROP链。这个过程中比较麻烦的是栈布局的计算特别是当你的溢出长度有限、放不下完整ROP链的时候会需要用到栈迁移stack pivot。栈迁移的思路是把栈指针rsp迁移到你控制的内存区域比如bss段上然后在那里继续布置后续的ROP链。这种方式在CTF中很常见但我得提醒一句ISCC这类比赛中栈迁移考得不算特别深如果你刚入门先把ret2libc的完整流程学会栈迁移作为进阶内容了解即可。4. 堆题与沙箱逃逸的实战分析4.1 堆题的glibc版本陷阱今年ISCC的堆题让我印象比较深的是glibc版本问题。做的事情很常规菜单程序有add、delete、edit、show四个功能典型的堆管理器交互题。漏洞点在delete之后没有把指针置空是典型的UAFuse-after-free漏洞。但问题在于题目环境用的是glibc 2.31或者2.35不同版本的glibc在堆块管理上有细微差别比如tcache的key字段检查、safe-linking机制等等。这些差异会导致你在本地打通的exp放到远程容器里完全不生效。所以遇到堆题第一件事一定要确认远程环境的libc版本。ISCC的动态容器通常会给你明确的提示比如“ubuntu 20.04”或者“glibc 2.31”。如果题目说明没写你可以通过连接远程端口后观察泄露的地址再和本地libc对比来判断。4.2 UAF利用的标准化流程以glibc 2.31环境为例UAF利用的标准流程大致是1. 分配若干个chunk 2. free其中一个chunk让它进入tcache或fastbin 3. 通过UAF读取该chunk里的fd指针泄露堆地址或libc地址 4. 继续free其他chunk利用double free构造tcache poisoning 5. 把malloc返回地址劫持到__free_hook写入system这个流程每一步都有对应的检查机制要绕过。比如tcache poisoning需要伪造chunk的size字段如果程序在edit时对大小有限制你可能还需要用large bin来伪造合适的chunk。我知道对刚入门堆利用的同学来说这些内容看起来像天书。我的建议是不要急先找《how2heap》这个项目把里面的每个glibc版本的利用技巧都跑一遍理解每一行代码的意图再回来做CTF堆题你会发现自己突然开了窍。4.3 沙箱题是pwn和reverse的结合ISCC 2023还出了一道带沙箱的题目。所谓沙箱就是题目用seccomp限制了你的系统调用通常常见配置是只允许open、read、write这三个系统调用禁掉了execve。这意味着你不能getshell但可以直接通过open/read/write的组合来读取flag文件。这种限制下的常见解法是ORW也就是open打开flag文件read读取内容write输出到终端。你需要构造一个ROP链来完成这三步操作。难点在于正确设置三个函数的参数特别是调用read时文件描述符要先从open的返回值里拿到。这道题实际上把pwn和系统调用知识做了结合做起来会觉得思路很开阔。如果你对seccomp不熟悉建议先学习一下seccomp-tools这个工具它可以快速检查一个二进制文件的seccomp规则方便你确定能用哪些系统调用。5. 动态容器部署与远程满分技巧5.1 动态容器的工作原理今年ISCC的pwn题全面采用动态容器玩家通过一个端口连接过去实际上是连到了一个由Docker动态生成的容器实例。容器的生命周期从你第一次连接开始到一段时间没有交互后自动销毁。这种方式对比赛主办方有几个明显的好处环境隔离、防作弊、可动态扩容。对选手来说也多了一些平时本地做题不会遇到的变量比如网络延迟、容器启动时间、flag文件路径等。很多选手第一次接触动态容器容易犯的一个错误是在本地址用了process()调试好的exp直接改成remote()就去打远程结果发现时而成功时而失败。实际上远程容器和本地环境在资源消耗、进程调度上都存在细微差异很可能需要微调一些时间相关或状态相关的参数。5.2 远程脚本的稳定性优化这里分享几个我常用的提高远程exp成功率的技巧首先pwntools的recvuntil尽量匹配稳定且唯一的字符串不要用recvline然后猜测行数。其次在发送关键payload之前可以加一个小的sleep(0.5)给远程程序留出足够的时间完成前一步的输出刷新。最后如果在远程环境下发现某个操作和本地表现不一致不要急着改exp逻辑先仔细确认远程环境的libc版本和权限配置是否和本地一致。还有一个小经验ISCC的动态容器有时候首次连接会稍微慢一点如果你的脚本在connect之后立刻send很可能出现数据丢失。建议第一次连接后先recv一段数据确认连通再开始交互。5.3 失败的排查顺序参考如果你连接远程题目失败不要慌按下面的顺序排查端口是否能连通用nc手动试一下确认网络没问题程序是否有输出提示确认容器是否已经正常启动如果连接后掉线确认是否是因为容器生命周期到了导致实例销毁如果脚本报错用context.log_level debug查看收发数据的详细日志我把常见的远程异常情况整理成了一个速查表现象可能原因建议操作连接立即断开容器未启动完成等待几秒重试或重新连接发送数据无响应需要先接收提示字符串使用recvuntil等待提示收到数据不完整网络延迟导致分段用recvuntil替代recv与环境交互异常动态容器libc版本不同确认远程libc调整偏移6. 备赛路线与个人经验总结6.1 从零开始pwn入门的推荐路线如果你看完这篇文章对做pwn题产生了兴趣那我建议你按照下面的路线来学习第一步扎实掌握汇编语言基础尤其是函数调用约定、栈帧结构。这一步很多人会跳过但后面分析ROP链和堆利用的时候汇编功底决定你能走多远。第二步学习pwntools的基本用法至少能写出发送payload、接收数据的脚本。这个工具是CTF pwn做题的标配很多自动化操作都依赖它。第三步把栈溢出和格式化字符串练熟。找一些入门题集比如CTFshow的pwn方向题单或者BUUCTF上的pwn部分把每一种基础题型都过一遍。第四步开始接触堆利用从how2heap的tcache部分开始逐步理解fastbin、unsorted bin、large bin的利用方式。第五步学习沙箱逃逸和内核pwn等进阶内容但这部分对大多数初学者来说属于选学可以在基础和堆利用都比较熟练之后再挑战。6.2 做题时容易被忽略的几个习惯结合这次ISCC的经历我想特别强调几个做题习惯第一个习惯是做题前一定先看保护。很多人一上来就反编译主函数找漏洞点忽视了checksec的重要性。保护机制决定了你利用手法的上限看错或者漏看可能导致你在错误的方向上浪费大量时间。第二个习惯是重视flag的获取方式。有些题目标明flag在/flag文件里有些则是在/home/ctf/flag。如果你在远程环境getshell之后发现找不到flag很可能只是路径不对并不代表利用失败。第三个习惯是勤写笔记。CTF题目数量很大你不可能靠脑力记住每一道题的利用细节。我自己的做法是每做完一道有价值的题就把题目文件、exp和思路总结保存在一个笔记库里下次遇到类似的题目直接翻笔记找思路。这个习惯帮我省下了大量重复摸索的时间。拿这次ISCC来说很多题目的考点和往年题目高度相似及时翻阅之前的笔记能让你快速建立起对题目的直觉判断。注意这不是让你背exp而是让你理解一类题目的共性思路再根据具体题目灵活调整。最后再说一句参与CTF pwn最重要的是搞清楚边界。所有做题都应当在比赛方的授权环境和自己的本地靶机中进行目的是理解漏洞原理、提升安全防护能力。漏洞利用知识本身只是工具把它用在建设性的方向上才能真正在安全这条路上走远。希望ISCC 2023 pwn题目的这些思路能对你接下来打比赛或者学习二进制安全有所帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑