Lord of BOF_18_Nightmare

핵심 알고리즘은 다음과 같다.

  • return address에는 스택 주소나 실행파일 주소 사용 불가
  • return address를 따라갔을 때 leave나 ret 코드가 있는지 체크 nop-nop 패턴이 나올 때까지 체크
  • return address를 제외한 스택의 위아래를 삭제
  • LD 환경변수 찌꺼기 제거

시도

고안한 방법은 다음과 같다.

  1. LD_PRELOAD를 이용해 올린 라이브러리에 nop-nop 코드를 넣고, 그 뒤에 셸코드를 넣음. 그 주소를 이용.
  2. LD_PRELOAD를 이용해 heap을 할당, 여기 셸 코드를 넣음. 그 주소를 이용.
  3. LOB에는 ASLR이 걸려있지 않으니, 라이브러리 주소를 이용한다. 다만, 함수 시작 주소가 아니라 함수를 구분하는 nop 사이를 이용한다.
  4. 코드에서 이용하는 \x90\x90, \xc9, \xc3 문자열을 이용

조사 결과 1번과 2번 방법은 권한이 맞지 않아 실패한다. 권한이 없는 라이브러리는 로드되지 않는다. 3번은 일부 함수 주소에는 \x00이 들어간다. 또한 인자를 넘길 수 없어 정상적으로 함수 호출이 불가하다. 4번은 파일 이름 길이에 제한이 있어 불가하다. 255글자가 한계이다. 5번은 \x90\x90 뒤 내용이 없고, 그 뒤에 내용을 넣을 수 없으며, 이 또한 바이너리 이미지 주소 범위에 들어간다. \xc9\xc3도 마찬가지이다. 인자를 넘길 수 없으므로 셸 코드를 이용해야 한다.

참고

fgets 함수 내에서 사용하는 내부 버퍼에 셸코드를 뿌려놓고 이용할 수 있다. fgets 함수의 정의는 다음과 같다. FILE structure 의 포인터를 인자로 받는다. 이 문제에서는 stdin이다.

char *fgets (char *string, int n, FILE *stream)

다음은 FILE 구조체의 모습이다. _IO_read_base 는 이 파일 스트림에서 사용하는 임시 버퍼의 주소가 저장된다. 이 위치를 이용할 수 있다.

struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */
  struct _IO_marker *_markers;
  struct _IO_FILE *_chain;
  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];
  /*  char* _save_gptr;  char* _save_egptr; */
  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
 

fgets 에서 FILE 구조체 포인터로 넣는 값은 0x8049a3c이다. 이는 stdin의 FILE 구조체 포인터이다.

이 포인터에 저장된 주소를 따라가면 stdin의 FILE 구조체를 찾을 수 있다.

따라가면 위에서 살펴 본 구조체가 나온다. 빨간 박스는 _IO_read_base 멤버로, read할 버퍼의 시작 주소를 담고 있다.

read base로 가서 stdin 버퍼의 시작 부분을 보면 입력 값으로 준 a들을 찾을 수 있다. 즉, 이 버퍼에 셸 코드를 넣을 수 있다.

이제 실제로 셸 코드를 올려보자. return address를 0x40015000으로 잡으면 stdin 버퍼의 시작 주소로 갈 수 있다. 하지만 문제점이 있다. return address로 \x00\x50\x01\x40을 올려야 하는데, \x00이 문제가 된다.

따라서 입력값에 약간의 더미 값을 넣어 return address로 다른 주소를 이용할 수 있게 했다. 또한 return address가 라이브러리인지 체크하는 루틴을 통과할 수 있도록 셸 코드 뒤에 \x90\x90을 넣었다.

이를 종합한 페이로드이다.

xavius의 비밀번호는 throw me away 이다.


tags: writeup, pwnable, buffer overflow, stack overflow, memory corruption, elf file, linux, c lang, x86asm