개념

 : Unsorted Bin Attack 의 업그레이드 버전.. 매우 복잡

>> 조건

 : Top Chunk 를 덮어쓸 수 있도록 힙 오버플로우가 일어나야 함 (경계 검사 X)

 

>> _IO_list_all 변수

struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;

 

>> _IO_FILE_plus 구조체

struct _IO_FILE_plus
{
	FILE file;
	const struct _IO_jump_t *vtable;
};

 

>> FILE = _IO_FILE 구조체

typedef struct _IO_FILE FILE;
struct _IO_FILE 
{
	int _flags;       /* High-order word is _IO_MAGIC; rest is 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;
    int _flags2;
    _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
 
    /* 1+column number of pbase(); 0 is unknown. */
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
 
    _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

 

>> _IO_jump_t 구조체

#define JUMP_FIELD(TYPE, NAME) TYPE NAME


struct _IO_jump_t
{
   JUMP_INIT_DUMMY,
   JUMP_INIT(finish, _IO_file_finish),
   JUMP_INIT(overflow, _IO_file_overflow),
   JUMP_INIT(underflow, _IO_file_underflow),
   JUMP_INIT(uflow, _IO_default_uflow),
   JUMP_INIT(pbackfail, _IO_default_pbackfail),
   JUMP_INIT(xsputn, _IO_file_xsputn),
   JUMP_INIT(xsgetn, _IO_file_xsgetn),
   JUMP_INIT(seekoff, _IO_new_file_seekoff),
   JUMP_INIT(seekpos, _IO_default_seekpos),
   JUMP_INIT(setbuf, _IO_new_file_setbuf),
   JUMP_INIT(sync, _IO_new_file_sync),
   JUMP_INIT(doallocate, _IO_file_doallocate),
   JUMP_INIT(read, _IO_file_read),
   JUMP_INIT(write, _IO_new_file_write),
   JUMP_INIT(seek, _IO_file_seek),
   JUMP_INIT(close, _IO_file_close),
   JUMP_INIT(stat, _IO_file_stat),
   JUMP_INIT(showmanyc, _IO_default_showmanyc),
   JUMP_INIT(imbue, _IO_default_imbue)
};

 

전체 과정

  • 힙 오버플로우로 Top Chunk 크기를 변조      * 반드시 페이지 단위를 맞출 것 *
  • 변조된 크기보다 큰 크기를 할당 (Top Chunk becomes Free Chunk)
  • freed Top Chunk 변조
    • prev size = "/bin/sh\x00" (딱 8바이트)
    • size of chunk = small_bin[4] 에 들어가는 90~98 바이트
    • bk = &_IO_list_all - 0x10 영역으로 수정
    • 나머지 영역은 freed Top Chunk 의 시작주소를 기준으로 _IO_FILE_plus 구조체로 만듬
    • 오프셋 계산해서 ptr of vtable = ptr of  _IO_jump_t 위치만 이동할 주소로 수정
    • _IO_jump_t 구조체는 별도 영역에 생성하지 않고 freed Top Chunk 에 있는 _IO_FILE_plus 구조체를 덮어써도 됨
    • _IO_jump_t 구조체에서 0x18 위치에 _IO_overflow_t 대신 이동할 주소를 씀 (보통 system())
    • _IO_FILE 에서 몇몇 필드는 _IO_wide_data 구조체로 덮어씌워짐 (아래 내용 참고)
_IO_flush_all_lockp() 함수에서 사용하지 않는 "fp"변수의 "_freeres_list", "_freeres_buf" 영역 다음과 같이 활용합니다.
fp→_wide_data 변수에 fp→_offset의 주소 값을 저장합니다.
fp→_freeres_list = _wide_data->_IO_write_ptr
fp→_freeres_buf = _wide_data->_IO_write_base
  • 새로운 힙 영역 할당
  • 아래의 함수 호출 순서를 따라 _IO_overflow_t 영역의 함수 (변조한 주소)로 이동한다.
    • _int_malloc() → malloc_printerr() → __libc_message → __FI_abort() → _IO_flush_all_lockp()

 

Proof Of Concept

 

Reference

https://www.lazenca.net/display/TEC/House+of+Orange

 

House of Orange - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Conditions 해당 기술은 다음과 같은 조건에서 동작합니다.공격자에 의해 Top chunk 영역에 값을 저장 할 수 있어야 합니다.공격자에 의해 Top chunk 의 값 보다 큰 값을 생성 할 수 있어야 합니다.이로 인해 기존의 Top chunk는 Free chunk가 됩니다.공격자에 의해 Free chunk 영역에 값을 저장 할 수 있어야 합

www.lazenca.net

hitctf 2016 : http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html

 

HITCON CTF Qual 2016 - House of Orange Write up

HITCON CTF Qual 2016 - House of Orange Write up Program Build the house Create a house which contains an orange with the chosen...

4ngelboy.blogspot.com

 

'Security > System' 카테고리의 다른 글

CTF Summary  (0) 2020.02.07
linux system call table  (0) 2019.07.03
[Linux] Lazy Binding  (0) 2019.06.30
ROP Gadget Dictionary  (0) 2019.06.30
BROP (Blind Return Oriented Programming)  (0) 2019.06.26

'Security > System' 카테고리의 다른 글

CTF Summary  (0) 2020.02.07
[Heap Overflow] House Of Orange  (0) 2019.07.06
[Linux] Lazy Binding  (0) 2019.06.30
ROP Gadget Dictionary  (0) 2019.06.30
BROP (Blind Return Oriented Programming)  (0) 2019.06.26

 

Lazy Binding

리눅스 ELF 바이너리에서 라이브러리 함수의 시작주소를 구하지 않다가, 함수를 처음 호출할 때 해당 주소를 구하는 것

이후 호출에는 함수의 실제 주소를 구하지 않고 바로바로 이동한다.

* plt, got 에 대한 개념을 알아야 함

 

ex) puts() 함수를 호출하는 경우

_dl_runtime_resolve () 를 호출하기 전

맨 처음에 puts() 를 호출하면, puts@plt 영역으로 이동한다. 그리고 got 영역 (0x804a00c) 으로 이동(1번)하나, 첫 호출이기 때문에 plt+6 의 주소가 저장되어 있다. (2번) 따라서 plt + 6 에서 값을 스택에 넣고 점프(3번)하는데, 두 영역의 주소를 보면 가깝게 위치한다. 그 이유는 Lazy Binding 을 위해 push ; jmp 명령어를 놓기 위한 여분의 자리가 있는 상태에서 그 자리에 여분의 코드가 들어갔기 때문이다. 결국 컴파일 방식에 따라 없을 수도 있다.

아무튼 중요한건 노란색 박스에 보여진 값인데, 0x0은 reloc_offset 이고 0x804a004 는 link_map 구조체의 포인터로 자세한 설명은 아래를 참고하고, 두 값은 모두 _dl_runtime_resolve() 함수의 인자이다.

 

실제 GOT 영역에 주소가 저장되는 과정

>> 함수 호출 순서

// 깊이 들어갈수록 나중에 호출되는 것

_dl_runtime_resolve(int reloc_offset, struct link_map *l) ;

	_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) ;

		_dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
                     const ElfW(Sym) **ref,
                     struct r_scope_elem *symbol_scope[],
                     const struct r_found_version *version,
                     int type_class, int flags, struct link_map *skip_map) ;

			do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
                            unsigned long int *old_hash, const ElfW(Sym) *ref,
                            struct sym_val *result, struct r_scope_elem *scope, size_t i,
                            const struct r_found_version *const version, int flags,
                            struct link_map *skip, int type_class, struct link_map *undef_map)


>> reloc_offset

: 아래와 같은 구조체의 시작주소를 얻기 위해 이용된다.

 (32-bit) JMPREL(.rel.plt) 영역에서 해당 함수의 Elf32_Rel 구조체의 오프셋 (주소 오프셋)

 (64-bit) RELA(.rela.plt) 영역에서 해당 함수의 Elf32_Rela 구조체의 오프셋 (구조체 배열의 인덱스)

구조체의 크기가 다르기 때문에, 32비트인 경우 주소의 오프셋을 사용하나, 64비트인 경우 배열의 인덱스를 사용한다.

 

>> link_map 구조체

 : 링커가 런타임에서 라이브러리 함수들을 메모리에 매핑시킬 때 사용하는 구조체

struct link_map
{
	ElfW(Addr) l_addr;		  /* Difference between the address in the ELF
    						file and the addresses in memory.  */
	char *l_name;			  /* Absolute file name object was found in.  */
	ElfW(Dyn) *l_ld;		  /* Dynamic section of the shared object.  */
	struct link_map *l_next, *l_prev; /* Chain of loaded objects. link_map 앞,뒤 노드 */
};

- link_map 은 이중 연결 리스트 구조이며, GOT 영역에서 2번째 원소에 첫번째 노드의 주소가 있다.
- 의미는 각 주석 참조
- l_name 은 문자열 포인터인데 STRTAB(.dynstr) 에 있는 라이브러리의 전체 경로 문자열을 참조한다.
- 찾고자 하는 함수의 메모리 상의 실제 주소를 구할 때 사용 (라이브러리의 시작주소부터 알아야 하니, 필요하다.)

 

GOT 영역과 link_map 구조체

0x804a000 은 GOT 영역이며, 다음과 같은 정보를 가진다.

GOT[0] = .dynsym 시작주소

GOT[1] = link_map 연결 리스트의 첫번째 노드의 시작주소

GOT[2] = _dl_runtime_resolve() 의 시작주소

GOT[3] = PLT 에서 참조하는 GOT 영역 (0x804a000 + 0x10 이후)

아래에 있는 link_map 이 이중 연결 리스트 구조인 것을 알 수 있다. 그림에서 마지막 노드의 경우 l_name은 "/lib32/libc.so.6"이다.

 

>> Elf32_Rel 그리고 Elf64_Rela 구조체

 : 함수의 재배치 정보를 가지며, _dl_runtime_resolve() 에서 참조된다.

typedef uint32_t Elf32_Word;
typedef uint32_t Elf32_Addr;
 
/* Relocation table entry without addend (in section of type SHT_REL).  */
typedef struct
{
	Elf32_Addr        r_offset;	/* Address */
	Elf32_Word        r_info;	/* Relocation type and symbol index */
} Elf32_Rel;

- r_offset : 재배치된 후 (함수 바인딩이 끝나고) 실제 함수 주소가 저장될 GOT 영역의 주소
- r_info : 첫 1바이트는 재배치 타입(ELF32_R_TYPE), 나머지 3바이트는 심볼 테이블 정보(ELF32_R_SYM)

#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)

 

- 32비트일 때 구조체는 크기가 8바이트이며, 64비트인 경우 크기는 24바이트이다.

typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef int64_t  Elf64_Sxword;
 
typedef struct
{
	Elf64_Addr	r_offset;	/* Address */
	Elf64_Xword	r_info;		/* Relocation type and symbol index */
	Elf64_Sxword	r_addend;	/* Addend */
} Elf64_Rela;

 

>> Elf32_Sym 그리고 Elf64_Sym 구조체

 : 함수의 심볼테이블 엔트리에 해당된다. _dl_fixup() 에서 참조된다.
  (아래 방식으로 주소를 구함, Elf32_Sym 구조체는 크기가 16바이트)

SYMTAB(.dynsym) 시작주소 + r_info->ELF32_R_SYM * 16 = 해당 함수의 Elf32_Sym 구조체 시작주소

typedef uint16_t Elf32_Section;
 
/* Symbol table entry.  */
typedef struct
{
	Elf32_Word	st_name;	/* Symbol name (string tbl index) */
	Elf32_Addr	st_value;	/* Symbol value */
	Elf32_Word	st_size;	/* Symbol size */
	unsigned char	st_info;	/* Symbol type and binding */
	unsigned char	st_other;	/* Symbol visibility */
	Elf32_Section	st_shndx;	/* Section index */
} Elf32_Sym;

STRTAB(.dynstr) 시작주소 + Elf32_Sym->st_name = 함수 이름이 저장된 주소

 

- 64비트인 경우 

typedef uint32_t Elf64_Word;
typedef uint16_t Elf64_Section;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
 
 
typedef struct
{
	Elf64_Word	st_name;	/* Symbol name (string tbl index), 4 Byte */
	unsigned char	st_info;	/* Symbol type and binding, 1 Byte */
	unsigned char	st_other;	/* Symbol visibility, 1 Byte */
	Elf64_Section	st_shndx;	/* Section index, 2 Byte */
	Elf64_Addr	st_value;	/* Symbol value, 8 Byte */
	Elf64_Xword	st_size;	/* Symbol size, 8 Byte */
} Elf64_Sym;

 

>> 이후 라이브러리 주소를 구하는 과정

  1. 함수이름의 시작 주소를 인자로 _dl_lookup_symbol_x() 함수를 호출
  2. 함수 이름을 해쉬값으로 바꿔서 do_lookup_x() 함수에서 검사
  3. 실제 라이브러리 영역에서의 심볼 테이블 인덱스 구함 (do_lookup_x 함수에서 반환됨)
  4. 인덱스를 가지고 라이브러리 상에서의 오프셋을 구함 (do_lookup_x 함수에서 반환됨)
  5. 로드된 라이브러리 파일에서 해당 함수의 Elf32_Sym 구조체 주소에 접근

    "libc의 .dynsym 영역" + "symidx offset" = libc에서 찾고자 하는 함수의 Elf32_Sym 구조체 영역
  6. Elf32_Sym->st_value 는 실제 함수의 상대주소를 가지고 있음

    즉, libc base address + Elf32_Sym->st_value = 실제 주소


    끝으로, Elf32_Rel->r_offset 에 구한 실제 주소를 저장

 

Reference

https://www.lazenca.net/display/TEC/01.Return-to-dl-resolve+-+x86

 

01.Return-to-dl-resolve - x86 - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Return-to-dl-resolve - x86 Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다. Lazy binding Flow Lazy bin

www.lazenca.net

https://www.lazenca.net/pages/viewpage.action?pageId=19300744

 

02.Return-to-dl-resolve - x64(feat.Return-to-csu) - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Return-to-dl-resolve - x64 Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다. Lazy binding Source code -

www.lazenca.net

 

'Security > System' 카테고리의 다른 글

CTF Summary  (0) 2020.02.07
[Heap Overflow] House Of Orange  (0) 2019.07.06
linux system call table  (0) 2019.07.03
ROP Gadget Dictionary  (0) 2019.06.30
BROP (Blind Return Oriented Programming)  (0) 2019.06.26

+ Recent posts