_dl_rumtime_resolve 함수를 조작된 reloc_arg 로 호출 하여 원하는 함수를 호출 하도록 하는 기법
사전지식
Lazy binding
Dynamic Linking 방식으로 컴파일 된 ELF 바이너리는 공유 라이브러리 내에 위치한 함수의 주소를 동적으로 알아오기 위해 GOT(Global Offset Table) 테이블을 이용하는데, 이렇게 모든 외부 함수의 주소를 한 번에 로딩하지 않고, 함수 호출 시점에 해당 함수의 주소만 공유 라이브러리로부터 알아오는 것은 Lazy Binding이라고 합니다.
Lazy binding을 사용하면 라이브러리의 내용을 전부 메모리에 매핑하는게 아닌 실행때 필요한 함수의 주소만 알아오는 것.
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;
ELF32_Rel의 r_info는 2개의 값으로 분류
/* How to extract and insert information held in the r_info field. */
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_SYM(info) ((info)>>8)
#define ELF32_R_TYPE(info) ((unsigned char)(info))
#define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
JMPREL은 각 항목을 기호에 매핑하는 재배치 테이블을 저장
ELF32_R_TYPE값과 ELF32_R_INFO의 값은 다음과 같이 계산한다.ex) 0x304 라는 값이 있으면, ELF32_R_TYPE은 0x304 >> 8 = 0x3ELF32_R_INFO = 0x304 & 0xff = 0x4
ELF32_Sym -> SYMTAB
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;
Return to dl resolve
요약
Lazy binding을 악용하여 원하는 함수를 호출할 수 있는 공격.
_dl_rumtime_resolve 함수를 조작된 reloc_arg 로 호출 하여 원하는 함수를 호출 하도록 하는 기법
사전지식
Lazy binding
https://rafaelchen.wordpress.com/2017/09/25/pwn%E7%9A%84%E4%BF%AE%E7%85%89%E4%B9%8B%E8%B7%AF-lazy-binding/
PLT와 GOT
PLT(Procedure Linkage Table) -> 외부 프로시저를 연결해 주는 테이블이며, 함수를 호출할때 PLT를 호출하는것과 동일하다.
GOT(Global Offset Table) -> PLT가 참조하는 테이블. 처음 함수를 호출했을때는 plt를 호출하여 함수의 got를 알아내고, 두번째 호출때는 바로 got를 호출
과정
Lazy binding을 위해
_dl_runtime_resolve() -> _dl_fixup() -> _dl_lookup_symbol_x() -> do_lookup_x() -> check_match() 순으로 진행됨.
ELF32_Rel -> JMPREL 이라고도 하는거 같다.
ELF32_Rel의 r_info는 2개의 값으로 분류
JMPREL은 각 항목을 기호에 매핑하는 재배치 테이블을 저장
ELF32_R_TYPE값과 ELF32_R_INFO의 값은 다음과 같이 계산한다.ex) 0x304 라는 값이 있으면, ELF32_R_TYPE은 0x304 >> 8 = 0x3ELF32_R_INFO = 0x304 & 0xff = 0x4
ELF32_Sym -> SYMTAB
ELF32_Sym 구조체에서 가장 중요한 값은 Elf32_Word st_name 값이다.
st_name은 STRTAB에서 오프셋을 제공
STRTAB
_dl_runtime_resolve
인자를 레지스터(rsi, rdi)에 저장후, _dl_fixup함수를 호출
_dl_fixup
1. D_PTR 메크로 사용하여 link_map 구조체에서 DT_SYMTAB, DT_STRTAB 영역의 주소 값을 symtab, strtab 변수에 저장
2. reloc_offset은 _dl_fixup() 함수에 전달 된 reloc_arg인수
3. reloc 구조체 변수에서 r_info의 값을 이용하여 R_SYM정보를 추출
4. 찾고자 하는 함수의 Symbol table의 주소를 sym변수에 저장
Lazy binding = 찾고자하는 함수의 이름을 이용하여 동적 라이브러리에서 해당 함수의 코드 영역 찾음
Return_to_dl_resolve는 다음과 같은 흐름을 통해 원하는 함수를 호출
1. 메모리 영역에 fake Elf32_Rel, Elf32_Sym 구조체, 찾고자 하는 함수의 이름을 저장
2. reloc_offset을 이용하여, fake Elf32_Rel 구조체 영역에 접근
3. fake Elf32_Rel 구조체를 이용하여 fake Elf32_Sym 구조체 영역에 접금
4. Elf32_Sym->st_name을 이용하여, 1에서 저장한 함수의 이름을 가리키도록 함.
최종적인 Exploit 순서
1. ROP를 이용하여 .bss 영역에 새로운 return-to-resolve 코드 저장. 이때 fake_reloc_arg, 호출할 함수의 인자, Fake Elf32_Rel, Fake Elf32_Sym, 원하는 함수의 이름(ex. system, execve...)
2. .bss 영역으로 이동하여 return-to-resolve 실행
.bss, .dynsym, .dynstr, .rel.plt.. 등등의 주소는 readelf -S '바이너리'로 찾을 수 있지만, pwntoools를 이용할때는 다음과 같이 찾을 수 있다.
관련 문제
2018 0ctf quals babystack
2015 codegate yocto
2017 codeblue simple memo pad
2021 seccon ctf kasu_bof
참고자료
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 을 사용할 경우 활용이 가능한 기법입니
www.lazenca.net
https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/
PLT와 GOT 자세히 알기 1
Dynamic Linking 과정을 추적해 PLT와 GOT를 이해해보자 :) 시스템 해킹을 공부하시는 분들이라면 PLT와 GOT에 대해 알고 있을 것입니다. 이제 막 시스템 해킹 공부를 시작한 분들도 한 번 쯤 들어보셨을
bpsecblog.wordpress.com
'Technique' 카테고리의 다른 글