Return to dl resolve

728x90

요약

Lazy binding을 악용하여 원하는 함수를 호출할 수 있는 공격.

 _dl_rumtime_resolve 함수를 조작된 reloc_arg 로 호출 하여 원하는 함수를 호출 하도록 하는 기법

 

사전지식

Lazy binding

Dynamic Linking 방식으로 컴파일 된 ELF 바이너리는 공유 라이브러리 내에 위치한 함수의 주소를 동적으로 알아오기 위해 GOT(Global Offset Table) 테이블을 이용하는데, 이렇게 모든 외부 함수의 주소를 한 번에 로딩하지 않고, 함수 호출 시점에 해당 함수의 주소만 공유 라이브러리로부터 알아오는 것은 Lazy Binding이라고 합니다.
Lazy binding을 사용하면 라이브러리의 내용을 전부 메모리에 매핑하는게 아닌 실행때 필요한 함수의 주소만 알아오는 것.
Lazy binding 과정 중, PLT와 GOT를 이용한다.

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 이라고도 하는거 같다.

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에서 저장한 함수의 이름을 가리키도록 함.

 

출처:&amp;amp;nbsp;https://www.lazenca.net/display/TEC/01.Return-to-dl-resolve+-+x86

 

최종적인 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

 

728x90