how to bypass IO_validate_vtable by _IO_str_overflow

해당글을 읽고 오면 편하다.

자 그럼 어떻게 IO_validate_vtable 함수에서 안걸리고 vtable을 조작할 수 있을까?

대표적인 방법으로는 _IO_str_overflow 와 _IO_str_finish 가 있는데 이번 글에서는

_IO_str_overflow 방법을 알아볼려고 한다.

먼저, _IO_str_overflow 의 주소를 가지고 있는 테이블인 _IO_str_jumps 의 위치를 알아야 하는데

C
0x7ffff7dca290:	0x0000000000000000	0x0000000000000000
0x7ffff7dca2a0 <_IO_file_jumps>:	0x0000000000000000	0x0000000000000000
0x7ffff7dca2b0 <_IO_file_jumps+16>:	0x00007ffff7a6e330	0x00007ffff7a6f300
0x7ffff7dca2c0 <_IO_file_jumps+32>:	0x00007ffff7a6f020	0x00007ffff7a703c0
0x7ffff7dca2d0 <_IO_file_jumps+48>:	0x00007ffff7a71c50	0x00007ffff7a6d930
0x7ffff7dca2e0 <_IO_file_jumps+64>:	0x00007ffff7a6d590	0x00007ffff7a6cb90
0x7ffff7dca2f0 <_IO_file_jumps+80>:	0x00007ffff7a70990	0x00007ffff7a6c850
0x7ffff7dca300 <_IO_file_jumps+96>:	0x00007ffff7a6c6d0	0x00007ffff7a60100
0x7ffff7dca310 <_IO_file_jumps+112>:	0x00007ffff7a6d910	0x00007ffff7a6d190
0x7ffff7dca320 <_IO_file_jumps+128>:	0x00007ffff7a6c910	0x00007ffff7a6c840
0x7ffff7dca330 <_IO_file_jumps+144>:	0x00007ffff7a6d180	0x00007ffff7a71dd0
0x7ffff7dca340 <_IO_file_jumps+160>:	0x00007ffff7a71de0	0x0000000000000000
0x7ffff7dca350:	0x0000000000000000	0x0000000000000000
0x7ffff7dca360 <_IO_str_jumps>:	0x0000000000000000	0x0000000000000000
0x7ffff7dca370 <_IO_str_jumps+16>:	0x00007ffff7a72300	0x00007ffff7a71f60
0x7ffff7dca380 <_IO_str_jumps+32>:	0x00007ffff7a71f00	0x00007ffff7a703c0
0x7ffff7dca390 <_IO_str_jumps+48>:	0x00007ffff7a722e0	0x00007ffff7a70420
0x7ffff7dca3a0 <_IO_str_jumps+64>:	0x00007ffff7a705d0	0x00007ffff7a72430
0x7ffff7dca3b0 <_IO_str_jumps+80>:	0x00007ffff7a70990	0x00007ffff7a70860
0x7ffff7dca3c0 <_IO_str_jumps+96>:	0x00007ffff7a70c50	0x00007ffff7a70a00
0x7ffff7dca3d0 <_IO_str_jumps+112>:	0x00007ffff7a71db0	0x00007ffff7a71dc0
0x7ffff7dca3e0 <_IO_str_jumps+128>:	0x00007ffff7a71d90	0x00007ffff7a70c50
0x7ffff7dca3f0 <_IO_str_jumps+144>:	0x00007ffff7a71da0	0x00007ffff7a71dd0
0x7ffff7dca400 <_IO_str_jumps+160>:	0x00007ffff7a71de0	0x0000000000000000
0x7ffff7dca410:	0x0000000000000000	0x0000000000000000

다음과 같이 주소 _IO_file_jumps 다음 테이블이 _IO_str_jumps 이다.

해당 테이블은

C
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,  //0x8 * 2
  JUMP_INIT(finish, _IO_str_finish),
  JUMP_INIT(overflow, _IO_str_overflow),
  JUMP_INIT(underflow, _IO_str_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_str_pbackfail),
  JUMP_INIT(xsputn, _IO_default_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_str_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_default_setbuf),
  JUMP_INIT(sync, _IO_default_sync),
  JUMP_INIT(doallocate, _IO_default_doallocate),
  JUMP_INIT(read, _IO_default_read),
  JUMP_INIT(write, _IO_default_write),
  JUMP_INIT(seek, _IO_default_seek),
  JUMP_INIT(close, _IO_default_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

다음과 같이 정의가 되어있다.

따라서 _IO_str_overflow 은 _IO_str_jumps 테이블로 부터 + 0x16 위치에 있다. 해당 위치로 맞추면 된다.

이후에 _IO_str_overflow 함수로 control flow를 돌릴 수 있었다면, 문제를 각각의 분기를 통과해야한다는 점이다. 그리고 _flags 와 _IO_buf_base 등 에 따라 원하는 코드에 도달하지 못할 수 있다. 따라서 적절히 조정하며,

최종 목적지는 _IO_str_overflow 함수의

C
  new_buf
	    = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

이 부분이다. 이 코드에서는 기존에 받았던 fp 에서 특정 위치에 존재하는 값을 함수를 실행하고 new_size 값을 인자로 가진다. 이 형태는 누가봐도 system(”/bin/sh”)을 실행하기에 적절한 형태이다.

먼저, (*((_IO_strfile *) fp)->_s._allocate_buffer) 이 부분을 해석하자면,

fp 를 _IO_strfile 구조체 형태로 해석하자는 것인데

C
typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;

다음과 같은 형태이다. _s가 보이는 것을 확인할 수 있고

C
struct _IO_streambuf
{
  struct _IO_FILE _f;
  const struct _IO_jump_t *vtable;
};

_IO_streambuf 은 우리가 아는 그 _IO_FILE_plus 와 같다.

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;
};

_IO_str_fields는 2개의 필드가 있는데 각각 8바이트이다. 따라서

(*((_IO_strfile *) fp)->_s._allocate_buffer) 이 말은

우리가 사용하는 _IO_FILE_plus 형태에서 다음 8바이트 위치에 존재하는 값을 실행한다는 뜻이다.

이 위치에 system 함수의 값을 넣으면 된다.

그럼 이제 인자인 new_size 값을 설정하면 되는데

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;

다음과 같으며 상당히 쉽게 조작할 수 있다.