--- title: ret2dl_resolve Sections 關係表 description: 簡單紀錄在利用 ret2dl_resolve 時,相關 Sections 之間的關係 tags: security lang: zh_tw --- [TOC] # ret2dl_resolve Sections 關係表 ## 32bits ``` .rel.plt: base(.rel.plt) + reloc_arg Elf32_Rel <804A010h, 507h> ; R_386_JMP_SLOT write r_offset (4): 804A010h r_info (4): 507h .dynsym: base(.dynsym) + (r_info >> 8) * 0x10 Elf32_Sym <offset aWrite - offset byte_8048268, 0, 0, 12h, 0, 0> ; "write" st_name (4): 0x54 st_value (4): 0 st_size (4): 0 st_info (1): 0x12 st_other (1): 0 st_shndx (2): 0 .dynstr: base(.dynstr) + st_name "write" ``` ## 64bits ``` .rela.plt: base(.rela.plt) + reloc_arg * 0x18 Elf64_Rela <601018h, 100000007h, 0> ; R_X86_64_JUMP_SLOT printf r_offset (8): 0x601018 r_info (8): 0x100000007 r_addend (8): 0 .dynsym: base(.dynsym) + (r_info >> 32) * 0x18 Elf64_Sym <offset aPrintf - offset byte_400330, 12h, 0, 0, 0, 0> ; "printf" st_name (4): 0xb st_info (1): 0x12 st_other (1): 0 st_shndx (2): 0 st_value (8): 0 st_size (8): 0 .dynstr: base(.dynstr) + st_name "printf" ``` ## description - `st_info` 可以為 0 - `r_info` 低位必須是 `07` - 解析出的 function 真正位址會放回 `r_offset` - `r_offset` 指向的就是 `GOT` * 參考: http://blog.eonew.cn/archives/934 > 漏洞利用方式 > 1.控制eip为PLT[0]的地址,只需传递一个index_arg参数 > 2.控制index_arg的大小,使reloc的位置落在可控地址内 > 3.伪造reloc的内容,使sym落在可控地址内 > 4.伪造sym的内容,使name落在可控地址内 > 5.伪造name为任意库函数,如system > 绕过 在执行ret2dl-solve时,还有一点必须绕过,那就是.gnu.version+ELF32_R_SYM(rel_entry->r_info) * 2必须是一个较小的值,否则下面的代码就会执行错误。 * Source Code: https://elixir.bootlin.com/glibc/glibc-2.23/source/elf/dl-runtime.c#L215 * Assembly 截圖: ![](https://i.imgur.com/5BA1B9C.png) # Source code reading * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/x86_64/dl-trampoline.h#L60 ```c= .globl _dl_runtime_resolve .hidden _dl_runtime_resolve .type _dl_runtime_resolve, @function .align 16 cfi_startproc _dl_runtime_resolve: cfi_adjust_cfa_offset(16) # Incorporate PLT _CET_ENDBR # if DL_RUNTIME_RESOLVE_REALIGN_STACK # if LOCAL_STORAGE_AREA != 8 # error LOCAL_STORAGE_AREA must be 8 # endif pushq %rbx # push subtracts stack by 8. cfi_adjust_cfa_offset(8) cfi_rel_offset(%rbx, 0) mov %RSP_LP, %RBX_LP cfi_def_cfa_register(%rbx) and $-STATE_SAVE_ALIGNMENT, %RSP_LP # endif # ifdef REGISTER_SAVE_AREA sub $REGISTER_SAVE_AREA, %RSP_LP # if !DL_RUNTIME_RESOLVE_REALIGN_STACK cfi_adjust_cfa_offset(REGISTER_SAVE_AREA) # endif # else # Allocate stack space of the required size to save the state. # if IS_IN (rtld) sub _rtld_local_ro+RTLD_GLOBAL_RO_DL_X86_CPU_FEATURES_OFFSET+XSAVE_STATE_SIZE_OFFSET(%rip), %RSP_LP # else sub _dl_x86_cpu_features+XSAVE_STATE_SIZE_OFFSET(%rip), %RSP_LP # endif # endif # Preserve registers otherwise clobbered. movq %rax, REGISTER_SAVE_RAX(%rsp) movq %rcx, REGISTER_SAVE_RCX(%rsp) movq %rdx, REGISTER_SAVE_RDX(%rsp) movq %rsi, REGISTER_SAVE_RSI(%rsp) movq %rdi, REGISTER_SAVE_RDI(%rsp) movq %r8, REGISTER_SAVE_R8(%rsp) movq %r9, REGISTER_SAVE_R9(%rsp) # ifdef USE_FXSAVE fxsave STATE_SAVE_OFFSET(%rsp) # else movl $STATE_SAVE_MASK, %eax xorl %edx, %edx # Clear the XSAVE Header. # ifdef USE_XSAVE movq %rdx, (STATE_SAVE_OFFSET + 512)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8)(%rsp) # endif movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 2)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 3)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 4)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 5)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 6)(%rsp) movq %rdx, (STATE_SAVE_OFFSET + 512 + 8 * 7)(%rsp) # ifdef USE_XSAVE xsave STATE_SAVE_OFFSET(%rsp) # else xsavec STATE_SAVE_OFFSET(%rsp) # endif # endif # 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. ``` * 呼叫 `_dl_fixup`, 為解析器 ## \_dl_fixup * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L59 ```c= 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) { // 取得 symbol table 位址 (.dynsym) const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); // 取得 string table 位址 (.dynstr) const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); // reloc_offset 從 reloc_arg 得來, // 取得 PLTREL, 為 rel 或 rela // 詳細請看尾部補充 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 取得對應 Sym, 從 .dynsym 和 r_info 推算出來 // ELFW(R_SYM) 最終展開成 ELF32_R_SYM 或 ELF64_R_SYM // ELF32_R_SYM(i) 展開為 ((i) >> 8) // ELF64_R_SYM(i) 展開為 ((i) >> 32) const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; const ElfW(Sym) *refsym = sym; // GOT 位址, 解析完成後會將結果放回來 void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); lookup_t result; DL_FIXUP_VALUE_TYPE value; // ELFW(R_TYPE) 最終展開成 ELF32_R_TYPE 或 ELF64_R_TYPE // ELF32_R_TYPE(i) 展開為 ((i) & 0xff) // ELF64_R_TYPE(i) 展開為 ((i) & 0xffffffff) /* Sanity check that we're really looking at a PLT relocation. */ assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // st_other 我們設為 0, 步入 if /* Look up the target symbol. If the normal lookup rules are not used don't look in the global scope. */ if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) { const struct r_found_version *version = NULL; if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; } /* We need to keep the scope around so do some locking. This is not necessary for objects which cannot be unloaded or when we are not using any threads (yet). */ int flags = DL_LOOKUP_ADD_DEPENDENCY; if (!RTLD_SINGLE_THREAD_P) { THREAD_GSCOPE_SET_FLAG (); flags |= DL_LOOKUP_GSCOPE_LOCK; } #ifdef RTLD_ENABLE_FOREIGN_CALL RTLD_ENABLE_FOREIGN_CALL; #endif // 解析 symbol name // strtab + sym->st_name result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); /* We are done with the global scope. */ if (!RTLD_SINGLE_THREAD_P) THREAD_GSCOPE_RESET_FLAG (); #ifdef RTLD_FINALIZE_FOREIGN_CALL RTLD_FINALIZE_FOREIGN_CALL; #endif /* Currently result contains the base load address (or link map) of the object that defines sym. Now add in the symbol offset. */ value = DL_FIXUP_MAKE_VALUE (result, SYMBOL_ADDRESS (result, sym, false)); } else { /* We already found the symbol. The module (and therefore its load address) is also known. */ value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true)); result = l; } /* And now perhaps the relocation addend. */ value = elf_machine_plt_value (l, reloc, value); if (sym != NULL && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); /* Finally, fix up the plt itself. */ if (__glibc_unlikely (GLRO(dl_bind_not))) return value; // 最終將解析結果放回 GOT return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value); } ``` * 裡面頻繁使用到各種 Macro 例如 `ElfW`, `D_PTR` * PLTREL 選擇是用 Rela 或 Rel: * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L34 ```c= #if (!ELF_MACHINE_NO_RELA && !defined ELF_MACHINE_PLT_REL) \ || ELF_MACHINE_NO_REL # define PLTREL ElfW(Rela) #else # define PLTREL ElfW(Rel) #endif ``` ## Macro reloc_offset * x64: * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/x86_64/dl-runtime.c#L6 ```c= #define reloc_offset reloc_arg * sizeof (PLTREL) ``` * x86: * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-runtime.c#L45 ```c= #ifndef reloc_offset # define reloc_offset reloc_arg # define reloc_index reloc_arg / sizeof (PLTREL) #endif ``` ## Macro ElfW * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/link.h#L28 ```c= /* We use this macro to refer to ELF types independent of the native wordsize. `ElfW(TYPE)' is used in place of `Elf32_TYPE' or `Elf64_TYPE'. */ #define ElfW(type) _ElfW (Elf, __ELF_NATIVE_CLASS, type) #define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) #define _ElfW_1(e,w,t) e##w##t ``` * 另外有大寫版本: * https://elixir.bootlin.com/glibc/glibc-2.31/source/include/link.h#L368 ```c= /* We use this macro to refer to ELF macros independent of the native wordsize. `ELFW(R_TYPE)' is used in place of `ELF32_R_TYPE' or `ELF64_R_TYPE'. */ #define ELFW(type) _ElfW (ELF, __ELF_NATIVE_CLASS, type) ``` * 幾個範例: ```c= ElfW(Word) reloc_arg; // Elf__ELF_NATIVE_CLASS_Word reloc_arg; ``` * 接著看 * https://elixir.bootlin.com/glibc/glibc-2.31/source/bits/elfclass.h ```c= #define __ELF_NATIVE_CLASS __WORDSIZE ``` * 於是乎 macro 繼續展開: ```c= ElfW(Word) reloc_arg; // Elf__ELF_NATIVE_CLASS_Word reloc_arg; // Elf__WORDSIZE_Word reloc_arg; ``` * 接著每個 architecture 就能自定義 `__WORDSIZE` 多大 * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/x86/bits/wordsize.h#L4 ```c= /* Determine the wordsize from the preprocessor defines. */ #if defined __x86_64__ && !defined __ILP32__ # define __WORDSIZE 64 #else # define __WORDSIZE 32 #define __WORDSIZE32_SIZE_ULONG 0 #define __WORDSIZE32_PTRDIFF_LONG 0 #endif #ifdef __x86_64__ # define __WORDSIZE_TIME64_COMPAT32 1 /* Both x86-64 and x32 use the 64-bit system call interface. */ # define __SYSCALL_WORDSIZE 64 #else # define __WORDSIZE_TIME64_COMPAT32 0 #endif ``` * 假設在 64 bit, macro 展開變成以下 ```c= ElfW(Word) reloc_arg; // Elf__ELF_NATIVE_CLASS_Word reloc_arg; // Elf__WORDSIZE_Word reloc_arg; // Elf64_Word reloc_arg; ``` * 而這些類型都有基本定義 * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/elf.h ```c= /* Type for a 16-bit quantity. */ typedef uint16_t Elf32_Half; typedef uint16_t Elf64_Half; /* Types for signed and unsigned 32-bit quantities. */ typedef uint32_t Elf32_Word; typedef int32_t Elf32_Sword; typedef uint32_t Elf64_Word; typedef int32_t Elf64_Sword; /* Types for signed and unsigned 64-bit quantities. */ typedef uint64_t Elf32_Xword; typedef int64_t Elf32_Sxword; typedef uint64_t Elf64_Xword; typedef int64_t Elf64_Sxword; /* Type of addresses. */ typedef uint32_t Elf32_Addr; typedef uint64_t Elf64_Addr; /* Type of file offsets. */ typedef uint32_t Elf32_Off; typedef uint64_t Elf64_Off; /* Type for section indices, which are 16-bit quantities. */ typedef uint16_t Elf32_Section; typedef uint16_t Elf64_Section; /* Type for version symbol information. */ typedef Elf32_Half Elf32_Versym; typedef Elf64_Half Elf64_Versym; ``` * 但大寫的結局就不太相同: ```c= ELFW(R_SYM) (reloc->r_info); // ELF__ELF_NATIVE_CLASS_R_SYM // ELF__WORDSIZE_R_SYM // ELF64_R_SYM ``` * https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/elf.h#L673 ```c= /* 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_INFO(sym, type) (((sym) << 8) + ((type) & 0xff)) #define ELF64_R_SYM(i) ((i) >> 32) #define ELF64_R_TYPE(i) ((i) & 0xffffffff) #define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type)) ... /* How to extract and insert information held in the st_other field. */ #define ELF32_ST_VISIBILITY(o) ((o) & 0x03) /* For ELF64 the definitions are the same. */ #define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o) ``` ## Macro D_PTR * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/generic/ldsodefs.h#L78 ```c= /* All references to the value of l_info[DT_PLTGOT], l_info[DT_STRTAB], l_info[DT_SYMTAB], l_info[DT_RELA], l_info[DT_REL], l_info[DT_JMPREL], and l_info[VERSYMIDX (DT_VERSYM)] have to be accessed via the D_PTR macro. The macro is needed since for most architectures the entry is already relocated - but for some not and we need to relocate at access time. */ #ifdef DL_RO_DYN_SECTION # define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr) #else # define D_PTR(map, i) (map)->i->d_un.d_ptr #endif ``` * 幾個在 \_dl_fixup 用到的地方: ```c= 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(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ``` * 展開後: ```c= const Elf64_Sym *const symtab = (const void *) (l)->l_info[DT_SYMTAB]->d_un.d_ptr; const char *strtab = (const void *) (l)->l_info[DT_STRTAB]->d_un.d_ptr; const PLTREL *const reloc = (const void *) ((l)->l_info[DT_JMPREL]->d_un.d_ptr + reloc_offset); const Elf64_Half *vernum = (const void *) (l)->l_info[VERSYMIDX (DT_VERSYM)]->d_un.d_ptr; ``` * l_info 定義如下: ```c= ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM]; ``` * ElfW(Dyn) 展開後如下: ```c= /* Dynamic section entry. */ typedef struct { Elf32_Sword d_tag; /* Dynamic entry type */ union { Elf32_Word d_val; /* Integer value */ Elf32_Addr d_ptr; /* Address value */ } d_un; } Elf32_Dyn; typedef struct { Elf64_Sxword d_tag; /* Dynamic entry type */ union { Elf64_Xword d_val; /* Integer value */ Elf64_Addr d_ptr; /* Address value */ } d_un; } Elf64_Dyn; ``` * 至於 index 的那些 Macro: ```c= #define DT_STRTAB 5 /* Address of string table */ #define DT_SYMTAB 6 /* Address of symbol table */ #define DT_JMPREL 23 /* Address of PLT relocs */ #define DT_NUM 35 /* Number used */ #define DT_VERSYM 0x6ffffff0 ``` * Macro VERSYMIDX: ```c= #define VERSYMIDX(sym) (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (sym)) #define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */ #define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */ #define DT_THISPROCNUM 0 ``` * 此 macro 能處理 relocation 問題 ## Macro DL_FIXUP_MAKE_VALUE * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/generic/dl-lookupcfg.h#L23 ```c= /* Construct a value of type DL_FIXUP_VALUE_TYPE from a code address and a link map. */ #define DL_FIXUP_MAKE_VALUE(map, addr) (addr) ``` ## Macro SYMBOL_ADDRESS * https://elixir.bootlin.com/glibc/glibc-2.31/source/sysdeps/generic/ldsodefs.h#L90 ```c= #define LOOKUP_VALUE_ADDRESS(map, set) ((set) || (map) ? (map)->l_addr : 0) #define SYMBOL_ADDRESS(map, ref, map_set) \ ((ref) == NULL ? 0 \ : (__glibc_unlikely ((ref)->st_shndx == SHN_ABS) ? 0 \ : LOOKUP_VALUE_ADDRESS (map, map_set)) + (ref)->st_value) ```