格式化字符串漏洞小总结(上)
字数 1305 2025-08-24 16:48:16

格式化字符串漏洞详解(上)

前言

格式化字符串漏洞是二进制安全领域中的一个重要漏洞类型,本文将从理论层面详细解析格式化字符串漏洞的原理和利用方法。

格式化字符串基础

格式化字符串的基本格式:

%[parameter][flags][field width][.precision][length]type

关键参数解析

  • parametern$,获取格式化字符串中的指定参数
  • field width:输出的最小宽度
  • precision:输出的最大长度
  • length:输出的长度
    • hh:1-byte
    • h:2-byte
    • l:4-byte
    • ll:8-byte

重要type类型

  • d/i:有符号整数
  • u:无符号整数
  • x/X:16进制unsigned int(x小写,X大写)
  • s:输出以null结尾的字符串
  • c:把int参数转为unsigned char输出
  • p:输出指针值
  • n:不输出字符,但把已成功输出的字符个数写入对应的整型指针参数所指变量
  • %:'%'字面值

漏洞原理

格式化字符串函数根据格式化字符串进行解析,参数个数由格式化字符串控制。当格式化字符串中的格式化字符多于实际提供的参数时,程序会从栈上读取额外的值进行解析,导致信息泄露或内存覆盖。

漏洞利用方法

内存数据泄露

栈上数据泄露

  • 使用%order$p%order$x获取指定参数对应栈的内存值
  • 使用%order$s获取指定变量对应地址的内容(遇到\x00会截断)

任意地址内存泄露

需要将目标地址写入栈中,然后通过格式化字符串引用该地址。

确定偏移

32位程序

  • 参数全部在栈上传递
  • 偏移计算直接对应栈位置

64位程序

  • 前6个参数通过寄存器传递(rdi, rsi, rdx, rcx, r8, r9)
  • 第7个参数开始才在栈上
  • 偏移计算需要考虑寄存器参数
写入地址到栈

32位格式

<address>%<order>$s

改进版(添加标记):

<address>@@%<order>$s@@

64位格式

  • 地址不能放在开头(高位为0)
  • 需要处理字节对齐问题
  • 示例:
payload = '@@%11$s@@'.ljust(0x28,'a') + p64(0x000000601028)

小技巧

  1. 泄露libc基址

    • 通过__libc_start_main + x的返回地址
    • 示例:
    payload = '%21$p'.ljust(0x8,'a')
    libc_base = int(io.recv(12),16) - 240 - libc.symbols['__libc_start_main']
    
  2. 泄露栈地址

    • 利用栈上接近ESP的数据
    • 多层函数调用时留下的EBP值

内存覆盖

任意地址覆盖

使用%n转换指示符,将已输出的字符数写入指定地址。

基本格式:

...[overwrite addr]....%[overwrite offset]$n

格式化字符串写入大小控制

  • %n:写入4字节
  • %hn:写入2字节
  • %hhn:写入1字节

常用%hn%hhn来精确控制写入值。

单次printf多次写入

示例格式:

%xc%offset1$hn %yc%offset2$hn address address+2

写入值控制脚本

单字节写入

def fmt_byte(prev, val, idx, byte=1):
    result = ""
    if prev < val:
        result += "%" + str(val - prev) + "c"
    elif prev == val:
        result += ''
    else:
        result += "%" + str(256**byte - prev + val) + "c"
    result += "%" + str(idx) + "$hhn"
    return result

双字节写入

def fmt_short(prev, val, idx, byte=2):
    result = ""
    if prev < val:
        result += "%" + str(val - prev) + "c"
    elif prev == val:
        result += ''
    else:
        result += "%" + str(256**byte - prev + val) + "c"
    result += "%" + str(idx) + "$hn"
    return result

pwntools fmtstr模块

pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

参数说明:

  • offset:控制的第一个格式化程序的偏移量
  • writes:格式为{addr: value}的字典
  • numbwritten:已由printf函数写入的字节数
  • write_size:'byte'、'short'或'int'

总结

本文详细介绍了格式化字符串漏洞的理论基础,包括:

  1. 格式化字符串的基本语法和关键参数
  2. 漏洞原理和利用方法
  3. 内存泄露技术(栈数据和任意地址)
  4. 内存覆盖技术(包括精确控制写入值)
  5. 实用脚本和工具

下篇将重点介绍实际CTF题目中的利用技巧和高级利用方法。

格式化字符串漏洞详解(上) 前言 格式化字符串漏洞是二进制安全领域中的一个重要漏洞类型,本文将从理论层面详细解析格式化字符串漏洞的原理和利用方法。 格式化字符串基础 格式化字符串的基本格式: 关键参数解析 parameter : n$ ,获取格式化字符串中的指定参数 field width :输出的最小宽度 precision :输出的最大长度 length :输出的长度 hh :1-byte h :2-byte l :4-byte ll :8-byte 重要type类型 d/i :有符号整数 u :无符号整数 x/X :16进制unsigned int(x小写,X大写) s :输出以null结尾的字符串 c :把int参数转为unsigned char输出 p :输出指针值 n :不输出字符,但把已成功输出的字符个数写入对应的整型指针参数所指变量 % :'%'字面值 漏洞原理 格式化字符串函数根据格式化字符串进行解析,参数个数由格式化字符串控制。当格式化字符串中的格式化字符多于实际提供的参数时,程序会从栈上读取额外的值进行解析,导致信息泄露或内存覆盖。 漏洞利用方法 内存数据泄露 栈上数据泄露 使用 %order$p 或 %order$x 获取指定参数对应栈的内存值 使用 %order$s 获取指定变量对应地址的内容(遇到\x00会截断) 任意地址内存泄露 需要将目标地址写入栈中,然后通过格式化字符串引用该地址。 确定偏移 32位程序 : 参数全部在栈上传递 偏移计算直接对应栈位置 64位程序 : 前6个参数通过寄存器传递(rdi, rsi, rdx, rcx, r8, r9) 第7个参数开始才在栈上 偏移计算需要考虑寄存器参数 写入地址到栈 32位格式 : 改进版(添加标记): 64位格式 : 地址不能放在开头(高位为0) 需要处理字节对齐问题 示例: 小技巧 泄露libc基址 : 通过 __libc_start_main + x 的返回地址 示例: 泄露栈地址 : 利用栈上接近ESP的数据 多层函数调用时留下的EBP值 内存覆盖 任意地址覆盖 使用 %n 转换指示符,将已输出的字符数写入指定地址。 基本格式: 格式化字符串写入大小控制 %n :写入4字节 %hn :写入2字节 %hhn :写入1字节 常用 %hn 和 %hhn 来精确控制写入值。 单次printf多次写入 示例格式: 写入值控制脚本 单字节写入 : 双字节写入 : pwntools fmtstr模块 参数说明: offset :控制的第一个格式化程序的偏移量 writes :格式为 {addr: value} 的字典 numbwritten :已由printf函数写入的字节数 write_size :'byte'、'short'或'int' 总结 本文详细介绍了格式化字符串漏洞的理论基础,包括: 格式化字符串的基本语法和关键参数 漏洞原理和利用方法 内存泄露技术(栈数据和任意地址) 内存覆盖技术(包括精确控制写入值) 实用脚本和工具 下篇将重点介绍实际CTF题目中的利用技巧和高级利用方法。