Lord of BOF_18_Nightmare


핵심 알고리즘은 다음과 같다.
- return address에는 스택 주소나 실행파일 주소 사용 불가
- return address를 따라갔을 때 leave나 ret 코드가 있는지 체크 → nop-nop 패턴이 나올 때까지 체크
- return address를 제외한 스택의 위아래를 삭제
- LD 환경변수 찌꺼기 제거
시도
고안한 방법은 다음과 같다.
- LD_PRELOAD를 이용해 올린 라이브러리에 nop-nop 코드를 넣고, 그 뒤에 셸코드를 넣음. 그 주소를 이용.
- LD_PRELOAD를 이용해 heap을 할당, 여기 셸 코드를 넣음. 그 주소를 이용.
- LOB에는 ASLR이 걸려있지 않으니, 라이브러리 주소를 이용한다. 다만, 함수 시작 주소가 아니라 함수를 구분하는 nop 사이를 이용한다.
- 코드에서 이용하는
\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