이전 _IO_FILE Arbitrary Read에 이어 특정영역의 값을 읽을 수 있는 공격
_IO_FILE Arbitrary Write 기법에 대해 정리한 글입니다.
드림핵 자료와 여러 자료를 참고해 정리했으며 다소차이점이 존재할수 있습니다.
(잘못된 부분이나 틀린 부분이 있다면 댓글로 지적해주세요.)
먼저 이전에 작성한_IO_FILE의 구조체 분석과 _IO_FILE Arbitrary Read를 통해 해당 공격흐름을 분석했습니다.
https://skysquirrel.tistory.com/270
[Pwn] _IO_FILE Arbitrary Read
이전 _IO_FILE vtable Overwrite에 이어 특정영역의 값을 읽을 수 있는 공격 _IO_FILE Arbitrary Read 기법에 대해 정리한 글입니다. 드림핵 자료와 여러 자료를 참고해 정리했으며 다소차이점이 존재할수 있
skysquirrel.tistory.com
_IO_FILE Arbitrary Read의 흐름을 이해했다면
_IO_FILE Arbitrary Write도 크게 다르지 않습니다.
이전의 정리한 _IO_FILE Arbitrary Read의 흐름을 보면
fwrite()함수 호출에서 최종적으로 _IO_new_do_write()를 호출하고 해당 함수의 코드를 활용합니다.
_IO_FILE Arbitrary Write기법도 함수의 이름은 다르지만 비슷한 흐름을 가지고 있고 코드 흐름에 따라
최종적으로 호출할 함수의 값을 조작합니다.
흐름 분석
드림핵자료의 예시코드를 통해 흐름을 분석해보겠습니다.
// gcc -o file_aw2 file_aw2.c
#include <stdio.h>
int main()
{
char overwrite_me[256];
char buf[256];
FILE *fp;
fp = fopen("testfile","r");
fp->_IO_buf_base = overwrite_me;
fp->_IO_buf_end = overwrite_me + 256;
fp->_fileno = 0;
fread(buf, 1, 5, fp);
printf("overflow_me: %s\n",overwrite_me);
return 0;
}
코드의 흐름을 보면 fread()를 호출하고 있습니다.
호출되는 코드의 흐름을 보면
_IO_sgetn을 호출하고 _IO_FILE_underflow를 호출합니다.
이는 fwrite()를 호출했을때 _IO_sgetn을 호출한뒤 _IO_FILE_overflow를 호출하는 것과 동일한 흐름입니다.
하지만, Read공격에서는 읽는 것이 목표였다면 Write공격은 쓰는 것이 목표이기에 필요한 함수가 다른것이 당연합니다.
해서 최종적으로 호출되는 underflow()를 분석해 어떤것을 호출해야 하는지 코드를 통해 보면 다음과 같습니다.
int _IO_new_file_underflow(_IO_FILE *fp)
{
_IO_ssize_t count;
#if 0
/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & _IO_EOF_SEEN)
return (EOF);
#endif
if (fp->_flags & _IO_NO_READS)
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno(EBADF);
return EOF;
}
if (fp->_IO_read_ptr < fp->_IO_read_end)
return *(unsigned char *)fp->_IO_read_ptr;
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);
}
/* Flush all line buffered files before reading. */
/* FIXME This can/should be moved to genops ?? */
if (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
{
#if 0
_IO_flush_all_linebuffered();
#else
/* We used to flush all line-buffered stream. This really isn't
required by any standard. My recollection is that
traditional Unix systems did this for stdout. stderr better
not be line buffered. So we do just that here
explicitly. --drepper */
_IO_acquire_lock(_IO_stdout);
if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
== (_IO_LINKED | _IO_LINE_BUF))
_IO_OVERFLOW(_IO_stdout, EOF);
_IO_release_lock(_IO_stdout);
#endif
}
_IO_switch_to_get_mode(fp);
/* This is very tricky. We have to adjust those
pointers before we call _IO_SYSREAD () since
we may longjump () out while waiting for
input. Those pointers may be screwed up. H.J. */
fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
fp->_IO_read_end = fp->_IO_buf_base;
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
= fp->_IO_buf_base;
count = _IO_SYSREAD(fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);
if (count <= 0)
{
if (count == 0)
fp->_flags |= _IO_EOF_SEEN;
else
fp->_flags |= _IO_ERR_SEEN, count = 0;
}
fp->_IO_read_end += count;
if (count == 0)
{
/* If a stream is read to EOF, the calling application may switch active
handles. As a result, our offset cache would no longer be valid, so
unset it. */
fp->_offset = _IO_pos_BAD;
return EOF;
}
if (fp->_offset != _IO_pos_BAD)
_IO_pos_adjust(fp->_offset, count);
return *(unsigned char *)fp->_IO_read_ptr;
}
상당히 긴 코드인데 해당 Read와 비슷하게 모든 코드를 분석하진 않고 중요한 코드만 뽑아보면 다음과 같습니다.
//Read
count = _IO_SYSWRITE (fp, data, to_do);
//Write
count = _IO_SYSREAD(fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);
Write에서는 Read와 동일한 count라는 멤버에 _IO_SYSWRITE() 대신 _IO_SYSREAD()함수를 사용하고 있습니다.
해당 함수의 역할을 인자값과 함께 보면 fp와
buf_base와 buf_end-buf_base계산을 통해 원하는 영역을 지정 하고 입력된 값을 읽을수 있습니다.
그리고 여기서 사용되는 buf_base와 buf_end는 IO_FILE의 구조체인것또한 알수 있습니다.
즉, IO_FILE의 구조체를 조작할 수 있다면 buf_base와 buf_end를 조작해 원하는 영역을 지정해 함수를 호출하고나면
공격자의 마음대로 값을 쓰는 것이 가능하게 됩니다.
하지만, Read때와 동일하게 Write에서도 해당 함수를 바로 호출할수는 없고 우회해야 할
조건문이 존재합니다.
해당 조건문들은 다음과 같습니다.
if (fp->_flags & _IO_NO_READS) ...
if (fp->_IO_read_ptr < fp->_IO_read_end) ...
if (fp->_IO_buf_base == NULL) ...
if (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED)) ...
위의 조건문들을 보면 모두 IO_FILE 구조체에 해당 하는 값들을 조건문으로 지정했기에
IO_FILE구조체를 조작해 해당 조건문들을 모두 false로 우회한다면
_IO_SYSREAD()함수의 인자값으로 Write할 영역을 지정하고
원하는 값을 입력한다면 원하는 영역에 Write할수 있게 됩니다.
그러나 실제 익스플로잇으로 구성하려면 Write에서는 _flags를 유의해야하는 점이 있는데
이는 드림핵 워게임 문제를 풀면서 함께 정리하도록 하겠습니다.
'Reference > Pwnable_Study' 카테고리의 다른 글
[Pwn] House of spirit (0) | 2024.01.19 |
---|---|
[Pwn] House of Force (2) | 2024.01.10 |
[Pwn] _IO_FILE Arbitrary Read (0) | 2023.07.03 |
[Pwn] _IO_FILE_Plus & _IO_FILE vtable Overwrite (0) | 2023.07.01 |
[Pwn] SROP(Sig Return Oriented Programming) (0) | 2023.05.02 |