Return to dl resolve
요약
Lazy binding을 악용하여 원하는 함수를 호출할 수 있는 공격.
_dl_rumtime_resolve 함수를 조작된 reloc_arg 로 호출 하여 원하는 함수를 호출 하도록 하는 기법
사전지식
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 이라고도 하는거 같다.
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;
ELF32_Sym 구조체에서 가장 중요한 값은 Elf32_Word st_name 값이다.
st_name은 STRTAB에서 오프셋을 제공
STRTAB
//
// .dynstr
// SHT_STRTAB [0x804822c - 0x804827b]
// ram:0804822c-ram:0804827b
//
__DT_STRTAB XREF[2]: 08049f58(*),
_elfSectionHeaders::000000fc(*)
0804822c 00 ?? 00h
0804822d 6c 69 62 ds "libc.so.6"
63 2e 73
6f 2e 36 00
08048237 5f 49 4f ds "_IO_stdin_used"
5f 73 74
64 69 6e
08048246 72 65 61 ds "read"
64 00
0804824b 61 6c 61 ds "alarm"
72 6d 00
08048251 5f 5f 6c ds "__libc_start_main"
69 62 63
5f 73 74
08048263 5f 5f 67 ds "__gmon_start__"
6d 6f 6e
5f 73 74
08048272 47 4c 49 ds "GLIBC_2.0"
42 43 5f
32 2e 30 00
_dl_runtime_resolve
_dl_runtime_resolve:
...
# Copy args pushed by PLT in register.
# %rdi: link_map, %rsi: reloc_index
mov (LOCAL_STORAGE_AREA + 8)(%BASE), %RSI_LP
mov LOCAL_STORAGE_AREA(%BASE), %RDI_LP
call _dl_fixup # Call resolver.
mov %RAX_LP, %R11_LP # Save return value
# Get register content back.
...
인자를 레지스터(rsi, rdi)에 저장후, _dl_fixup함수를 호출
_dl_fixup
#ifndef reloc_offset
# define reloc_offset reloc_arg
# define reloc_index reloc_arg / sizeof (PLTREL)
#endif
...
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
...
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
...
}
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를 이용할때는 다음과 같이 찾을 수 있다.
addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr']
addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr']
addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr']
addr_plt = elf.get_section_by_name('.plt').header['sh_addr']
addr_bss = elf.get_section_by_name('.bss').header['sh_addr']
addr_plt_read = elf.plt['read']
addr_got_read = elf.got['read']
log.info('Section Headers')
log.info('.dynsym : ' + hex(addr_dynsym))
log.info('.dynstr : ' + hex(addr_dynstr))
log.info('.rel.plt : ' + hex(addr_relplt))
log.info('.plt : ' + hex(addr_plt))
log.info('.bss : ' + hex(addr_bss))
log.info('read@plt : ' + hex(addr_plt_read))
log.info('read@got : ' + hex(addr_got_read))
관련 문제
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