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后面还可以加上每个单元

pwntools

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, psh),后续的所有收发操作都基于此对象。

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:提取数据