티스토리 뷰
도저히 libc leak을 할 벡터가 없을 때, libc leak을 하기 위한 여러가지 테크닉을 익히기 위해서
관련된 문서자료들을 읽어보고, 관련함수들을 분석해 보고, 해당 내용들을 정리하기 위해서 한번 작성해봤당.
hitcon - baby_tcache
pwnable.tw - heap paradise
flow
해당 libc leak은 _IO_FILE struct
의 flag값을 사용자가 임의의 값으로 바꾸고, _IO_write_base
의 일부를 NULL
로 변조함에 따라, puts와 같은 stdout을 사용하는 함수가 사용될 때, 내부 루틴이 꼬이면서
libc가 leak이 되도록 한다.
file struct _flag define
/* Magic number */
/* Don't deallocate buffer on close. */
/* Reading not allowed. */
/* Writing not allowed. */
/* Don't call close(_fileno) on close. */
/* In the list of all open files. */
/* Put and get pointer move in unison. */
/* 0x4000 No longer used, reserved for compat. */
FILE struct
에 정의된 flag들은 다음과 같이 지정되어있으며,
_IO_MAGIC
_IO_IS_FILEBUF
_IO_CURRENTLY_PUTTING
_IO_LINKED
_IO_NO_READS | _IO_UNBUFFERED | _IO_USER_BUF
일반적으로는 해당 flag들로 0xfbad28~~
이 file struct의 flag값으로 존재하게 된다.
우리는 flag를 0xfbad1800
으로 변조할 것이다.
이로써 우리는 puts 의 내부루틴속 조작된 flag값에의해 libc leak이 될것을 유도한다.
puts routine
int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);
if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (_IO_stdout);
return result;
}
puts
의 호출 과정이다.
여기서 포인트는
_IO_sputn
이호출되며 _IO_sputn
은 _IO_xsputn
를 호출한다는 내용이다.
glibc/libio/libioP.h
extern unsigned _IO_adjust_column (unsigned, const char *, int) __THROW;
libc_hidden_proto (_IO_adjust_column)
ssize_t _IO_least_wmarker (FILE *, wchar_t *) __THROW;
libc_hidden_proto (_IO_least_wmarker)
glibc/libio/libioP.h 헤더속의 _IO_sputn정의
_IO_xsputn
의 루틴을 살펴본다.
_IO_xsputn
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
const char *s = (const char *) data;
size_t to_do = n;
int must_flush = 0;
size_t count = 0;
if (n <= 0)
return 0;
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
count = 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;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
if (to_do + must_flush > 0)
{
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)
해당 _IO_xsputn
은 _IO_OVERFLOW
를 호출하고,
_IO_OVERFLOW
는 _IO_new_file_overflow
를 가지고 있으며
_IO_new_file_overflow
는 _IO_do_write
를 호출한다.
_IO_new_file_overflow
int
_IO_new_file_overflow (FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
/* Allocate a buffer if needed. */
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
/* Otherwise must be currently reading.
If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting the
read pointers to all point at the beginning of the block). This
makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving that
alone, so it can continue to correspond to the external position). */
if (__glibc_unlikely (_IO_in_backup (f)))
{
size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
_IO_free_backup_area (f);
f->_IO_read_base -= MIN (nbackup,
f->_IO_read_base - f->_IO_buf_base);
f->_IO_read_ptr = f->_IO_read_base;
}
if (f->_IO_read_ptr == f->_IO_buf_end)
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_IO_write_end = f->_IO_write_ptr;
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
puts -> _IO_sputn -> _IO_xsputn -> _IO_OVERFLOW -> _IO_new_file_overflow -> _IO_do_write
이때, _IO_do_write
는 다음과 같은 인자를 가진다.
_IO_do_write(f, f->_IO_write_base, f->_IO_write_ptr - f -> _IO_write_base)
일반적인 stdout
의 상태는 다음과 같다.
0x7ffff7dd0760 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd07e3
0x7ffff7dd0770 <_IO_2_1_stdout_+16>: 0x00007ffff7dd07e3 0x00007ffff7dd07e3
0x7ffff7dd0780 <_IO_2_1_stdout_+32>: 0x00007ffff7dd07e3 0x00007ffff7dd07e3
0x7ffff7dd0790 <_IO_2_1_stdout_+48>: 0x00007ffff7dd07e3 0x00007ffff7dd07e3
0x7ffff7dd07a0 <_IO_2_1_stdout_+64>: 0x00007ffff7dd07e4 0x0000000000000000
하지만 우리는 NULL
을 25byte정도 넣어주었으므로
0x7ffff7dd0760 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x0000000000000000
0x7ffff7dd0770 <_IO_2_1_stdout_+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd0780 <_IO_2_1_stdout_+32>: 0x00007ffff7dd0700 0x00007ffff7dd07e3
0x7ffff7dd0790 <_IO_2_1_stdout_+48>: 0x00007ffff7dd07e3 0x00007ffff7dd07e3
0x7ffff7dd07a0 <_IO_2_1_stdout_+64>: 0x00007ffff7dd07e4 0x0000000000000000
다음과 같은 상태를 가질 태고,
이를 바탕으로 내부 루틴에서 _IO_do_write가 실행되면,
_IO_do_write(fp, 0x00007ffff7dd0700, 0x00007ffff7dd07e3- 0x00007ffff7dd0700)
로
_IO_do_write(fp, 0x00007ffff7dd0700, 0xe3)
가 실행이 되어서 leak 이 된다.