_IO_FILE结构体利用
good good study,day day up~
翻阅的师傅们的文章
1 |
|
File结构
日常使用中setvbuf,stdin、stdout、stderr四个结构体一般位于libc数据段,其他的保存在栈上
构造偏移
1 |
|
定义
1 |
|
而FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,64位程序下其偏移为0x60
链表头部用_IO_list_all指针表示。
_IO_list_all->stderr->stdout->stdin构成链表
在正常情况下,程序启动会伴随着:stdin,stdout,stderr三个文件流一起开启,这三个流位于libc.so的数据段
但是我们使用fopen等文件输入输出函数时所创建的文件流位于堆上
但是其实_IO_File结构体外面还有一层1
2
3
4
5struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}
而IO_jump_t *vtable保存了很多的函数指针,偏移为0xd81
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};
利用手法
vtable hijacker
一些小函数
为什么可以通过劫持vtable指针来控制程序执行流呢?我们来看几个日常使用的函数调用方法,这里ctf-wiki也有讲,我就不过多阐述:1
2
3
4
5fread(从文件流中读数据)
fwrite(向文件流中写数据)
fopen(打开文件)
fclose(关闭文件)
printf/puts(输出)#这里要多嘴一句,没有变量的printf优化后就是去了'\n'的puts
- fread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// libio/iofread.c
_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); // 调用 _IO_sgetn 函数
_IO_release_lock (fp);
return bytes_requested == bytes_read ? count : bytes_read / size;
}
而其中的_IO_sgetn函数原型是:1
2
3
4
5
6
7
8_IO_size_t
_IO_sgetn (fp, data, n)
_IO_FILE *fp;
void *data;
_IO_size_t n;
{
return _IO_XSGETN (fp, data, n);
}
而_IO_XSGETN则是vtable的变量之一(9),调用这个函数前会先取出vtable中的指针然后调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// libio/libioP.h
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
# define _IO_vtable_offset(THIS) 0
#endif
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
1 |
|
- fwrite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// libio/iofwrite.c
_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_sputn 函数
_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;
}
1 |
|
- fwrite会调用_IO_sputn,而这个对应了_IO_new_file_xsputn
- 调用vtabled的_IO_OVERFLOW,对应了_IO_new_file_overflow
- 调用系统调用write
1
2
3
4
5// libio/fileops.c
_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
- fopen(借用ctf-wiki的总结)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40_IO_FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); // 为 FILE 结构分配空间
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps; // 设置 vtable = &_IO_file_jumps
_IO_file_init (&new_f->fp); // 调用 _IO_file_init 函数进行初始化
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL) // 打开目标文件
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
_IO_FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
return __fopen_internal (filename, mode, 1);
}
1 |
|
1 |
|
- 使用 malloc 分配 FILE 结构
- 设置 FILE 结构的 vtable
- 初始化分配的 FILE 结构
- 将初始化的 FILE 结构链入 FILE 结构链表中
- 调用系统调用打开文件
- fclose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54// libio/iofclose.c
int
_IO_new_fclose (_IO_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->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp); // 将 fp 从链表中取出
_IO_acquire_lock (fp);
if (fp->_IO_file_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)
{
#if _LIBC
/* 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);
#endif
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp); // 释放 FILE 结构体
}
return status;
}
- 调用_IO_unlink_it将指定的结构体从链表中移除
- 调用_IO_file_close_it 函数,调用系统调用关闭文件
- 调用vtable中的_IO_FINISH即(_IO_file_finish函数)
- free掉之前的File结构体
- printf/puts(和fwrite类似)
- vfprintf+11
- _IO_file_xsputn
- _IO_file_overflow
- funlockfile
- _IO_file_write
- write
利用
利用手法分为两种:
- 通过任意地址写直接改写vtable的函数指针
- 覆盖vtable的函数指针,让其指向我们能控制的内存
我们要知道的是在libc2.23之前才可以更改虚表,libc-2.24之后加入了防御机制
FSOP(libc版本<2.24)
利用任意地址写来覆盖_IO_list_all让链表指向我们能控制的区域,从而改写虚表vtable
然后通过调用_IO_flush_all_lockup()来触发漏洞
该函数的调用条件:
- 当 libc 执行 abort 流程时。
- 执行 exit 函数时,当执行流从 main 函数返回时
- 当执行流从 main 函数返回时
而当glibc检测到内存错误时,会依次调用这样的函数路径:malloc_printerr ->
libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
如果我们需要控制执行流,我们就得伪造 fp->_mode = 0, fp->_IO_write_ptr > fp->_IO_write_base来通过验证1
2
3
4
5
6
7
8if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
libc版本>2.24
由于libc-2.24版本新增了检查机制,而新增的两个函数IO_validate_vtable 和 _IO_vtable_check的代码如下:
IO_validate_vtable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// libio/libioP.h
/* 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;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __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;
}_IO_vtable_check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37// libio/vtables.c
void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if (flag == &_IO_vtable_check)
return;
/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
{
Dl_info di;
struct link_map *l;
if (_dl_open_hook != NULL
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}
#else /* !SHARED */
/* We cannot perform vtable validation in the static dlopen case
because FILE * handles might be passed back and forth across the
boundary. Therefore, we disable checking in this case. */
if (__dlopen != NULL)
return;
#endif
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}
这个新增的防御机制会检查边界,从而使得我们一旦将vtable指向堆地址就会报错甚至退出程序
但是道高一尺魔高一丈,我们可以不转移地址,而是在vtable选择一个能完成我们任务的东西:
IO_str_jumps
虽然不能把vtable改到堆上了,但是我们依旧可以改 vtable为 _IO_str_jump来绕过检测
因为其中使用的IO_str_overflow 函数会调用 FILE+0xe0处的地址。这时只要我们将虚表覆盖为 IO_str_jumps将偏移0xe0处设置为one_gadget即可。
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// libio/strops.c
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
1 |
|
而这个vtable中包含了函数_IO_str_overflow,这个函数有相对地址引用,也就是可以伪造引用的地址1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28int
_IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_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 >= (_IO_size_t) (_IO_blen (fp) + flush_only)) // 条件 #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
{
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);
_IO_size_t new_size = 2 * old_blen + 100; // 通过计算 new_size 为 "/bin/sh\x00" 的地址
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); // 在这个相对地址放上 system 的地址,即 system("/bin/sh")
1 |
|
因此,伪造方式如下:
伪造方式
- fp->_flags = 0
- fp->_IO_buf_base = 0
- fp->_IO_buf_end = (bin_sh_addr - 100) / 2#如果bin/sh地址以奇数结尾可以+1以避免向下取整
- fp->_IO_write_ptr = 0xffffffff
- fp->_IO_write_base = 0
- fp->_mode = 0
完整的调用过程如下:
malloc_printerr -> libc_message -> GI_abort -> _IO_flush_all_lockp -> GIIO_str_overflow
但是我们不需要知道heap的地址,因为vtable位于LIBC,故只要libc地址已知即可1
2
3
4
5
6
7
8
9void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) // 条件
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); // 在这个相对地址放上 system 的地址
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
这个利用我们只要在 fp->_IO_buf_base 放上 “/bin/sh” 的地址,然后设置 fp->_flags = 0 就可以绕过函数要去里的条件。
那么进入函数的方法:
- fclose(fp)可以让程序进入_IO_str_finish执行
- 异常处理,我们伪造传递给 _IO_OVERFLOW(fp) 的 fp 是 vtable 的地址减去 0x8,那么根据偏移,程序将找到 _IO_str_finish 并执行
这里还有一个函数可以用:
_IO_wstr_jumps
可以用IO_finish,o_finish会以 IO_buf_base处的值为参数跳转至 FILE+0xe8处的地址然后执行 fclose( fp)时会调用此函数,但是大多数情况下可能不会有 fclose(fp),这时我们还是可以利用异常来调用 io_finish,异常时调用 IO_OVERFLOW是根据IO_str_overflow在虚表中的偏移找到的, 我们可以设置vtable为IO_str_jumps-0x8异常时会调用io_finish函数。
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// libio/wstrops.c
const struct _IO_jump_t _IO_wstr_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
这个利用了函数_IO_wstr_overflow,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32_IO_wint_t
_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
{
int flush_only = c == WEOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : WEOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
}
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only)) // 条件 #define _IO_wblen(fp) ((fp)->_wide_data->_IO_buf_end - (fp)->_wide_data->_IO_buf_base)
{
if (fp->_flags2 & _IO_FLAGS2_USER_WBUF) /* not allowed to enlarge */
return WEOF;
else
{
wchar_t *new_buf;
wchar_t *old_buf = fp->_wide_data->_IO_buf_base;
size_t old_wblen = _IO_wblen (fp);
_IO_size_t new_size = 2 * old_wblen + 100; // 使 new_size * sizeof(wchar_t) 为 "/bin/sh" 的地址
if (__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX / sizeof (wchar_t)))
return EOF;
new_buf
= (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size
* sizeof (wchar_t)); // 在这个相对地址放上 system 的地址
利用函数_IO_wstr_finish,代码如下:1
2
3
4
5
6
7
8
9void
_IO_wstr_finish (_IO_FILE *fp, int dummy)
{
if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF)) // 条件
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base); // 在这个相对地址放上 system 的地址
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
例子
libc-2.23版本
HITB-XCTF 2018 GSEC once
libc版本有点低,先咕咕咕了
libc未知版本(无leak)
blind
代码分析
main函数(我给函数都起了别名)
1 |
|
很普遍的一个菜单题,但是少了输出这一项
new_onw
1 |
|
我们每创建一个,就会分配0x68的内存给他,最多创建五个,然后输入内容
edit_one
1 |
|
修改功能
dele_one
1 |
|
这里是free函数,可以看到没有置零,漏洞点就在这里
程序主体很简单,唯一就是没有可以输出的地方,而这样就没办法去leak了,这次我就用两种方法来做这道题,一种方法是panda师傅的,另一个就是网上比较多的通过劫持.bss段上的stdout然后通过printf函数触发
先看看防护1
2
3
4
5
6
7
8
9
10
11╭─azeral@Azeral /mnt/d/pwn/blind
╰─$ checksec blind
[*] Checking for new versions of pwntools
To disable this functionality, set the contents of /home/azeral/.pwntools-cache/update to 'never'.
[*] You have the latest version of Pwntools (3.12.2)
[*] '/mnt/d/pwn/blind/blind'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
因为开了FULL RELRO,所以这里就没办法劫持GOT表了,但是我又翻了翻程序,发现居然有直接给的system,就简化了很多步骤1
2
3
4int sub_4008E3()
{
return system("/bin/sh");
}
并且.bss段包含了stdout1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23.bss:0000000000602020 public stdout
.bss:0000000000602020 ; FILE *stdout
.bss:0000000000602020 stdout dq ? ; DATA XREF: LOAD:0000000000400330↑o
.bss:0000000000602020 ; sub_400882+22↑r
.bss:0000000000602020 ; Copy of shared data
.bss:0000000000602028 align 10h
.bss:0000000000602030 public stdin
.bss:0000000000602030 ; FILE *stdin
.bss:0000000000602030 stdin dq ? ; DATA XREF: LOAD:00000000004003C0↑o
.bss:0000000000602030 ; sub_400882+4↑r
.bss:0000000000602030 ; Copy of shared data
.bss:0000000000602038 align 20h
.bss:0000000000602040 public stderr
.bss:0000000000602040 ; FILE *stderr
.bss:0000000000602040 stderr dq ? ; DATA XREF: LOAD:0000000000400420↑o
.bss:0000000000602040 ; sub_400882+40↑r
.bss:0000000000602040 ; Copy of shared data
.bss:0000000000602048 byte_602048 db ? ; DATA XREF: sub_400850↑r
.bss:0000000000602048 ; sub_400850+12↑w
.bss:0000000000602049 align 20h
.bss:0000000000602060 ; void *ptr[7]
.bss:0000000000602060 ptr dq ? ; DATA XREF: newone+6A↑r
.bss:0000000000602060 ; newone+87↑w ...
利用思路
因为没有泄露的地方,并且在使用的时候不能劫持got表,因此我们考虑修改stdout的跳转函数,把函数指向system函数
因为我们需要绕过一些限制,所以要更改flag使得
flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0
而这个0x8000即是_IO_USER_LOCK,相关代码上面都有,可以自行翻阅Hhh
后面部分先咕咕咕
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!