GDB
查看信息
1 2
| info functions #查看所有function plt #查看外部调用函数
|
程序运行参数
1 2
| set args 指定运行时参数 (set args "xxx") show args 查看设置好的运行参数
|
设置断点
1 2 3
| break 设置断点,简写为b(b *0x401000) info break 查看设置好的断点,简写为i b delete 删除断点,简写为d(d 1删除第一个断点)
|
调试程序
1 2 3 4 5 6
| run 运行程序 简写为r next 单步跟踪 一行一行的执行 简写为n step 步入 进入被调用函数 简写为s finish 退出循环 简写为fin until 在循环内单步跟踪时,可跳出循环,简写为u continue 继续运行程序到下一个断点 简写为c
|
查看运行数据
1 2 3
| print 打印有符号的变量、字符串、表达式等的值,可简写为p stack 查看栈数据,后可跟数字输出指定行数 x 以格式化的形式打印内存数据,格式为x/FMT address,格式字符串有o(八进制),x(十六进制),d(十进制),u(无符号十进制),t(二进制),f(浮点数),a(地址),i(指令),c(字符),s(字符串),z(十六进制对齐)。同时在FMT后面还可以加上每个单元
|
1. 全局与环境配置
在脚本开头设置目标架构和调试级别,能省去后续生成 shellcode 或 ROP 链时的很多麻烦。
1 2 3 4 5
| from pwn import *
# 核心配置:指定目标程序的架构、操作系统及日志级别 context(arch='amd64', os='linux', log_level='debug') # log_level='debug' 是写 exp 时最关键的配置,它会将所有收发的数据(包括不可见字符)以十六进制形式打印在终端,方便定位程序阻塞在哪一步。
|
2. 建立交互对象
无论是本地调试还是远程打靶机,都会返回一个统一的 IO 管道对象(通常命名为 io, p 或 sh),后续的所有收发操作都基于此对象。
1 2 3 4 5
| # 本地进程交互 (常用于调试) io = process('./pwn_file')
# 远程网络交互 (打靶机) io = remote('192.168.1.100', 1337)
|
3. 数据接收
接收目标程序的输出,用于同步执行流或泄漏(Leak)内存地址。注意:在 Python 3 中,强烈建议使用字节串 b'...' 进行匹配。
1 2 3 4 5 6
| io.recv(numb=2048) # 基础接收:最多接收 numb 个字节的数据。 io.recvline() # 按行接收:持续接收直到遇到换行符 '\n'。常用于读取程序打印的一整行提示或 Leak 的地址。
# 精确同步 (最常用) io.recvuntil(b"Input your name: ") # 阻塞程序,直到接收到指定的特征字符串后才继续执行。这是保证 exp 稳定性的关键,避免因网络延迟导致发送的数据被丢弃。
|
4. 数据发送
将 Payload 发送给目标程序。发送时同样需要注意是否需要附带换行符。
1 2 3 4 5 6 7
| io.send(payload) # 基础发送:直接发送 payload,不附加任何额外字符。常用于程序使用 read() 等按字节读取的情况。 io.sendline(payload) # 按行发送:在 payload 末尾自动加上换行符 '\n'。常用于 gets(), scanf() 等需要回车触发的输入。
# 组合技:接收并发送 (推荐) io.sendafter(b"name:", payload) # 等同于 recvuntil(b"name:") + send(payload) io.sendlineafter(b"choice:", b"1") # 等同于 recvuntil(b"choice:") + sendline(b"1") # 使用 after 系列函数可以使代码更简洁,且极大降低时序引发的玄学 Bug。
|
5. ELF 文件解析 (ELF Parsing)
避免在脚本中硬编码地址(Hardcoding),利用 ELF 模块动态获取函数、符号的地址,提高脚本在不同环境下的兼容性。
1 2 3 4 5 6 7 8
| elf = ELF('./pwn_file') libc = ELF('./libc.so.6') # 如果题目提供了对应的 libc 版本
# 常用地址获取 puts_plt = elf.plt['puts'] # 获取 plt 表中 puts 函数的跳板地址 (常用于泄露真实地址) puts_got = elf.got['puts'] # 获取 got 表中 puts 函数的实际加载地址 main_addr = elf.sym['main'] # 获取 main 函数的起始地址 bss_addr = elf.bss() # 获取 .bss 段的首地址 (常用于 ROP 链中存放伪造的输入数据)
|
6. 交互与辅助 (Interaction & Utilities)
取得 Shell 后,或者在开发过程中需要切入动态调试。
1 2 3 4 5 6 7 8 9
| io.interactive() # 将控制权交还给用户,允许你在终端直接与目标程序进行标准输入输出交互。通常放在 exp 的最后一步(拿到 shell 之后)。
# 附加调试 (GDB) gdb.attach(io, ''' b *main c ''') # 在 process() 启动的本地进程上自动挂载 GDB 并执行 GDB 脚本(如打断点、继续执行)。仅在本地调试阶段使用。
|
附:标准 Exploit 脚本模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from pwn import *
# 1. 基础配置 file_name = './pwn' elf = ELF(file_name) context(arch=elf.arch, os=elf.os, log_level='debug')
# 2. 运行环境切换 local = True if local: io = process(file_name) libc = elf.libc else: io = remote('node.example.com', 12345) libc = ELF('./libc.so.6') # 替换为题目提供的 libc
def debug(): if local: gdb.attach(io) pause() # 暂停脚本,等待 GDB 附加完成
# 3. 核心漏洞利用逻辑 (Payload 构造区) # io.recvuntil(b"something") # payload = b'A' * 0x20 + p64(elf.sym['main']) # io.sendlineafter(b"input:", payload)
# 4. 获取交互 io.interactive()
|
IDA使用
快捷键
1 2 3 4 5 6 7 8 9 10 11
| r: 转换成字符/字符串 h: 转换成十六进制 d: 转换成数据 (db/dw/dd) c: 转换成代码 a: 转换成 ASCII 字符串 u: 取消定义 (恢复原始字节) n: 重命名变量或函数 y: 修改变量或函数类型 x: 查看交叉引用 F5: 生成伪代码 shift + e:提取数据
|