格式化串漏洞利用姿势

确定偏移

  • 利用pwntools提供的FmtStr(exec_fmt),获取offset

读/写栈上数据

  • 读:计算出要读的地址是第xxx个不定参数,然后利用%xxx$x 读取(x-十六进制读,lx长整读取(64位))
  • 写:首先泄露栈上rbp的值,然后根据rbp与返回地址之间的差值,得出返回地址所在的栈地址,利用任意地址写即可覆盖返回地址,如下图所示,可泄露show函数栈帧中rbp1的值,然后利用rbp1与printf函数返回地址之间的差值,计算可到printf返回地址所在的地址,覆盖之(绕过FULL RELRO,泄露show函数的返回地址,可以绕过PIE,泄露main函数的返回地址,即__libc\_start_main_ret,可以leak libc)

读/写任意地址数据

  • 读:

    • 发送:payload = START + %xx$s + END+ addr

    • 接收:

      1
      2
      3
      4
      recvuntil(START)   
      data = recvuntil(END, drop=1, timeout=1)
      if not data:
          data = '\x00'
    • 读取addr地址处的字符串,xxx是根据addr在栈上的地址计算出的不定参数的值recvuntil(START)

  • 写:
    • payload = %yyyc+%xxx$hn+\[padding\]+addr
    • xxx计算方法同上,yyy为要写入的数据,可以用fmtstr_payload(offset, writes)实现

DynELF时leak函数至少返回1字节,并且addr地址中可以出现\x00,

格式化串不在栈上

要实现任意地址读写,只能通过其他操作控制栈上的数据(比如输入数字或者字符串来构造想要的地址),然后确认偏移

printf_chk

编译时如果开启了FORTIFY_SOURCE=2的编译选项,glibc在调用printf时,更改为调用printf_chk函数,将开启两个保护:

  1. 限制%n, 如果%n写入一个拥有写权限的内存空间,如栈、bss段,就会终止报错
  2. 无法使用%N$来跳过之前的不定参数,但可以采用%4​$x%3$x%2$x%1$x 这种方式