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 의 위치를 알아야 하는데
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 이다.
해당 테이블은
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 함수의
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 구조체 형태로 해석하자는 것인데
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;다음과 같은 형태이다. _s가 보이는 것을 확인할 수 있고
struct _IO_streambuf
{
struct _IO_FILE _f;
const struct _IO_jump_t *vtable;
};_IO_streambuf 은 우리가 아는 그 _IO_FILE_plus 와 같다.
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 값을 설정하면 되는데
#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;다음과 같으며 상당히 쉽게 조작할 수 있다.