P_Squirrel
Squirrel Hack
P_Squirrel
전체 방문자
오늘
어제
  • 분류 전체보기 (429)
    • Wargame(Web) (173)
      • Webhacking.kr Writeup (58)
      • DreamHack Writeup (64)
      • Lord of SQLInjection (40)
      • Websec.fr (2)
      • Webgoat (1)
      • G04T war (7)
    • Wargame(Pwnable) (94)
      • DreamHack Writeup (68)
      • pwnable.xyz Writeup (20)
      • G04T war (6)
    • Wargame(Reversing) (26)
      • DreamHack Writeup (26)
    • Wargame(crypto) (7)
      • DreamHack Writeup (7)
    • Wargame(DigitalForensic) (41)
      • CTF-D Writeup (28)
      • DreamHack Writeup (6)
      • Suninatas Writeup (7)
    • Wargame(misc) (4)
      • DreamHack Writeup (4)
    • DreamHack CTF (21)
    • ångstrom CTF (6)
    • AmateursCTF (2)
    • BDSec CTF (5)
    • Hspace war (1)
    • Reference (42)
      • Language_Study (10)
      • Pwnable_Study (24)
      • Web Hacking_Study (3)
      • Linux (2)
      • Windows Kernel (1)
    • 자격증 공부 (2)
      • 정보처리기사 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 문

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
P_Squirrel

Squirrel Hack

Reference/Pwnable_Study

[Pwn] _IO_FILE Arbitrary Read

2023. 7. 3. 15:28

이전 _IO_FILE vtable Overwrite에 이어 특정영역의 값을 읽을 수 있는 공격

_IO_FILE Arbitrary Read 기법에 대해 정리한 글입니다.

드림핵 자료와 여러 자료를 참고해 정리했으며 다소차이점이 존재할수 있습니다.

(잘못된 부분이나 틀린 부분이 있다면 댓글로 지적해주세요.)

 

먼저 이전에 작성한 _IO_FILE vtable Overwrite 에서 _IO_FILE의 구조체를 분석해보며

해당 _IO_FILE 구조체를 Overwrite하는 것으로 원하는 함수를 실행시키는 것이 가능하다는 것을 보았습니다.

https://skysquirrel.tistory.com/269

 

[Pwn] _IO_FILE_Plus & _IO_FILE vtable Overwrite

이전 Linux File System _IO_FILE에 이어서 _IO_FILE_Plus와 _IO_FILE vtable Overwrite에 대한 정리글로 드림핵 자료위주로 정리하였습니다. https://skysquirrel.tistory.com/268 [Linux] Linux File System _IO_FILE 드림핵 로드맵을

skysquirrel.tistory.com

 

 

_IO_FILE Arbitrary Read

이번에 정리할 _IO_FILE Arbitrary Read또한 동일하게 _IO_FILE 구조체 멤버 조작을 통해

원하는 영역의 값을 출력하는 공격방식입니다.

예시코드와 glibc를 분석해보면 다음과 같습니다.

#include <stdio.h>
#include <string.h>
int main()
{
	char *buf = "THIS IS TEST FILE!\0";
	FILE *fp;
	fp = fopen("testfile","w"); 
	fwrite(buf, 1, strlen(buf), fp);
	return 0;
}

코드에서는 "THIS IS TEST FILE!" 라는 문자열을 testfile에 작성해 저장하려고 합니다.

이전 _IO_FILE vtable Overwrite를 위해 _IO_FILE 구조체를 분석했을때는 fread 함수만을 분석했는데

fread분석했던 아래 내용을 보면

fread함수는 내부적으로 _IO_sgetn 함수를 호출하고 이는 다시

검증코드를 지나 _IO_FILE_xsgetn 을 거쳐

_IO_str_overflow()에 도달하게 됩니다.

 

overwrite가 아닌 read공격에서도 비슷하게 fwrite()를 호출하면 _IO_fwrite를 지나

_IO_new_file_xsputn를 거쳐 _IO_overflow()에 도달하게 됩니다.

이후 해당 함수에서 _IO_FILE 구조체를 조작할수 있는 new_do_write를 호출하게 됩니다.

(여기서 분석된 glibc는 2.27을 기준으로 하였습니다.)

 

흐름 분석

glibc를 보면 설명과 동일한 흐름으로 다음과 같습니다.

위와 같은 흐름을 지나 _IO_new_file_xsputn에 오게되면 해당 코드중에서

_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (const char *) data;
  _IO_size_t to_do = n;
  int must_flush = 0;
  _IO_size_t count = 0;

  if (n <= 0)
    return 0;
  /* This is an optimized implementation.
     If the amount to be written straddles a block boundary
     (or the filebuf is unbuffered), use sys_write directly. */

  /* First figure out how much space is available in the buffer. */
  if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
    {
      count = f->_IO_buf_end - f->_IO_write_ptr;
      if (count >= n)
	{
	  const char *p;
	  for (p = s + n; p > s; )
	    {
	      if (*--p == '\n')
		{
		  count = p - s + 1;
		  must_flush = 1;
		  break;
		}
	    }
	}
    }
  else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  /* Then fill the buffer. */
  if (count > 0)
    {
      if (count > to_do)
	count = to_do;
      f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
      s += count;
      to_do -= count;
    }
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)
	/* If nothing else has to be written we must not signal the
	   caller that everything has been written.  */
	return to_do == 0 ? EOF : n - to_do;

      /* Try to maintain alignment: write a whole number of blocks.  */
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

      if (do_write)
	{
	  count = new_do_write (f, s, do_write);
	  to_do -= count;
	  if (count < do_write)
	    return n - to_do;
	}

      /* Now write out the remainder.  Normally, this will fit in the
	 buffer, but it's somewhat messier for line-buffered files,
	 so we let _IO_default_xsputn handle the general case. */
      if (to_do)
	to_do -= _IO_default_xsputn (f, s+do_write, to_do);
    }
  return n - to_do;
}

 

_IO_oveflow()를 호출하게 됩니다.

...
if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      /* Next flush the (full) buffer. */
      if (_IO_OVERFLOW (f, EOF) == EOF)
...

이를 통해 _IO_new_file_overflow가 호출되면서 _IO_new_do_write를 호출하게 되는 것입니다.

(디버깅시 _IO_do_write라고 하는데 해당 함수를 glibc의 define을 보면 _IO_new_do_write로 확장된것을 알수 있습니다.)

 

_IO_new_do_write

그럼 _IO_new_do_write코드를 보면 다음과 같습니다.

_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0
	  || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos
	= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
	return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
		       && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
		       ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

해당 코드중 아래 코드가 전달되는 값들을 조작하여 원하는 값을 출력할 수 있는 코드입니다.

...
count = _IO_SYSWRITE (fp, data, to_do);
...

 

정리하면 _IO_FILE Arbitrary Read 공격도 _IO_FILE vtable Overwrite와 마찬가지로 흐름을 따라 호출 과정을 정리하면

fwrite() -> _IO_fwrite() -> _IO_new_file_xsputn() -> _IO_new_file_overflow() -> _IO_new_do_write() 입니다.

 

이러한 흐름에서 _IO_FILE 구조체 멤버 조작이 가능하다면

_IO_new_do_write()를 통해 원하는 값을 출력하는 Arbitrary Read 공격으로 이어지게 할수 있는 것입니다.

 

하지만 해당코드가 실행되려면 조건문을 통과해야합니다.

해당 조건문들을 보면 다음과 같습니다.

if (fp->_flags & _IO_IS_APPENDING)
...
else if (fp->_IO_read_end != fp->_IO_write_base){
...}
count = _IO_SYSWRITE (fp, data, to_do);

이는 _IO_FILE 구조체 멤버중 _flasg의 값에 _IO_IS_APPENDING값이 설정되지 않도록 하고,

_IO_read_end값과 _IO_write_base값을 같게 만들면 해당 조건문을 피할 수 있습니다.

 

이렇게 조건문을 피하게되면 다음 구문인 _IO_SYSWRITE가 실행되면서 임의의 메모리 영역을 볼수 있게되는 것입니다.

 

추가로 회피해야할 조건문이 존재하는데 이는 현재 예시코드보다

더 좋은 예시코드로 워게임문제가 있어 해당 워게임 문제를 해결하면서 정리하도록 하겠습니다.

(잘못된 부분이 있거나 틀린점이 있다면 댓글로 지적해주세요.)


Reference

1. https://dreamhack.io/learn/11#55

2. https://aidencom.tistory.com/503

3. https://elixir.bootlin.com/glibc/glibc-2.27/source

 

'Reference > Pwnable_Study' 카테고리의 다른 글

[Pwn] House of Force  (2) 2024.01.10
[Pwn] _IO_FILE Arbitrary Write  (0) 2023.07.24
[Pwn] _IO_FILE_Plus & _IO_FILE vtable Overwrite  (0) 2023.07.01
[Pwn] SROP(Sig Return Oriented Programming)  (0) 2023.05.02
[Pwn] gdb offset Tip  (0) 2023.04.01
    'Reference/Pwnable_Study' 카테고리의 다른 글
    • [Pwn] House of Force
    • [Pwn] _IO_FILE Arbitrary Write
    • [Pwn] _IO_FILE_Plus & _IO_FILE vtable Overwrite
    • [Pwn] SROP(Sig Return Oriented Programming)
    P_Squirrel
    P_Squirrel

    티스토리툴바