Duet

分析

这个题目又是一个PLT表损坏,ida无法分析的题目。修复方法是将.got中的偏移修改为extern的位置,之后ida就会自动识别相应的函数,此时我们将.got中对应的DATA XREF中函数(.plt.sec)的命名更改之后,ida就可以正常的分析函数了。具体的修复方法点这

程序一共提供了四种功能add, delete, show, edit。存储两个堆块指针,可分配的堆块的大小为0x80-0x400。只能使用一次edit,并且在该函数中存在一个off-by-one的漏洞。

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ca73a599-951f-4e11-a5d5-0312a71120eb/Untitled.png

同时设置了sandbox

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/af65e6b9-f462-43f7-b29b-40e31004559a/Untitled.png

只能通过orw进行,因此我们需要执行ropchain

利用

libc2.24之后的版本中增加了对vtable的检查,而libc2.29相对libc2.27之前的IO_strfile来说,其分配和释放内存的函数都被修改为了标准的malloc,free函数,因此没有办法直接getshell

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5797959c-c138-4cb2-adce-2ff7a7406814/Untitled.png

EXP1

0CTF/TCTF 2020 Quals PWN

直接覆写io_list_all或者stderr->chain。伪造io_file链表,利用_io_str_overflow函数中的mallocmemcpy函数实现任意写。看一下源码

int
_IO_str_overflow (FILE *fp, int c)
{
  int flush_only = c == EOF;
  size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
	return EOF;
      else
	{
	  char *new_buf;
	  char *old_buf = fp->_IO_buf_base;
	  size_t old_blen = _IO_blen (fp);
	  size_t new_size = 2 * old_blen + 100;
	  if (new_size < old_blen)
	    return EOF;
	  new_buf = malloc (new_size);
	  if (new_buf == NULL)
	    {
	      /*	  __ferror(fp) = 1; */
	      return EOF;
	    }
	  if (old_buf)
	    {
	      memcpy (new_buf, old_buf, old_blen);
	      free (old_buf);
	      /* Make sure _IO_setb won't try to delete _IO_buf_base. */
	      fp->_IO_buf_base = NULL;
	    }
	  memset (new_buf + old_blen, '\\\\0', new_size - old_blen);

	  _IO_setb (fp, new_buf, new_buf + new_size, 1);
	  fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
	  fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
	  fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
	  fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

	  fp->_IO_write_base = new_buf;
	  fp->_IO_write_end = fp->_IO_buf_end;
	}
    }

  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}

注意到这里会申请一个new_size=2 * old_blen + 100大小的new_buf,并执行memcpy (new_buf, old_buf, old_blen);操作,这里的old_blen的计算方式是#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base),也就是说如果我们控制了IO_FILE结构体,并伪造_IO_buf_end_IO_buf_base就可以申请任意大小的堆块,如果提前布置好堆布局,就可以分配到任意的内存地址,利用memcpy执行任意地址写。需要绕过几个条件

1. ((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
	   ) //执行IO_OVERFLOW
2. (fp->_flags & _IO_NO_WRITES) == FALSE
// _IO_blen (fp): ((fp)->_IO_buf_end - (fp)->_IO_buf_base)`
3. (fp->_IO_write_ptr - fp->_IO_write_base) > (fp)->_IO_buf_end - (fp)->_IO_buf_base
4. fp->_IO_buf_base != NULL //当然这也是进行memcpy的地址