ret2_dl_runtime_resolve

遇到无法leak的题目的时候..日常roputils一把梭..这次就打算从原理学习一下”Return_to_dl_resolve“这项技术。可能写的会有些杂乱

借鉴和参考学习的师傅们的文章

1
2
3
4
5
6
https://veritas501.space/2017/10/07/ret2dl_resolve%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
https://www.hirworld.xyz/posts/3bfd0449/
https://www.anquanke.com/post/id/177450#h3-7
http://www.reversing.win/2017/08/29/%E4%BA%8C%E6%A0%88%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8-ret2resolve/
https://xz.aliyun.com/t/5111#toc-2

看了这么多才看懂。。实在是tcl…

原理部分

ret2_dl技术利用了函数的延迟绑定机制

动态链接

ELF将GOT表拆成了两个表,一个是“.got”(保存变量),”.got.plt”(保存函数)
而.got.plt表的前三项为:

  1. .dynamic 段的地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    .dynamic段:
    Elf32_Dyn由一个类型值加上一个附加的数值或指针。
    /*typedef struct {
    Elf32_Sword d_tag;
    union {
    Elf32_Word d_val;
    Elf32_Addr d_ptr;
    }
    } Elf32_Dyn*/
    DT_SYMTAB,动态链接符号表的地址,d_ptr表示.dynsym的地址
    DT_STRTAB,动态链接字符串表地址,d_ptr表示.dynstr的地址
    DT_HASH,动态链接哈希表大小,d_val表示大小
    DT_SONAME,本共享对象的SO-NAME
    DT_RPATH,动态链接共享对象搜索路径
    DT_INIT,初始化代码地址
    DT_FINIT,结束代码地址
    DT_NEED,依赖的共享对象文件,d_ptr表示依赖的共享对象文件名
    DT_REL,动态链接重定位表地址
    DT_RELA
    DT_RELENT,动态重读位表入口数量
    DT_RELANET
  2. 本模块的 ID

  3. _dl_runtime_resolve()的地址

我们可以查看任意程序的节区:

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
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 00002c 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481d8 0001d8 0000a0 10 A 6 1 4
[ 6] .dynstr STRTAB 08048278 000278 00006b 00 A 0 0 1
[ 7] .gnu.version VERSYM 080482e4 0002e4 000014 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080482f8 0002f8 000020 00 A 6 1 4
[ 9] .rel.dyn REL 08048318 000318 000018 08 A 5 0 4
[10] .rel.plt REL 08048330 000330 000028 08 AI 5 24 4
[11] .init PROGBITS 08048358 000358 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048380 000380 000060 04 AX 0 0 16
[13] .plt.got PROGBITS 080483e0 0003e0 000008 00 AX 0 0 8
[14] .text PROGBITS 080483f0 0003f0 000232 00 AX 0 0 16
[15] .fini PROGBITS 08048624 000624 000014 00 AX 0 0 4
[16] .rodata PROGBITS 08048638 000638 000008 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 08048640 000640 000034 00 A 0 0 4
[18] .eh_frame PROGBITS 08048674 000674 0000f4 00 A 0 0 4
[19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000020 04 WA 0 0 4
[25] .data PROGBITS 0804a020 001020 000008 00 WA 0 0 4
[26] .bss NOBITS 0804a040 001028 00000c 00 WA 0 0 32
[27] .comment PROGBITS 00000000 001028 000035 01 MS 0 0 1
[28] .shstrtab STRTAB 00000000 001798 00010a 00 0 0 1
[29] .symtab SYMTAB 00000000 001060 0004b0 10 30 47 4
[30] .strtab STRTAB 00000000 001510 000288 00 0 0 1

  • 其中.rel.plt是用于函数重定位的
    其结构体定义如下:
    1
    2
    3
    4
    5
    6
    7
    8
    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))

而在最前面我们也提及过got.plt中保存了函数偏移,而这个正好对应的是Rel结构体中的r_offset,而LF32_R_TYPE=7,这也是R_386_JUMP_SLOT的值

  • .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;
  • .dynstr节则包含了字符串,其以\x00为开头结尾
    Elf32_Sym[num]->st_name=.dynsym + Elf32_Sym_size * num
    而字符串的地址就在:Elf32_Sym[num]->st_name+.dynstr的地方

  • .plt节为过程链接表
    用来确定函数的绝对地址

而动态链接的过程即为:

  1. 第一次调用函数时,将rel_arg压入栈中然后跳转到函数的PLT[0]位置
  2. PLT[0]将link_map压入栈中
  3. 调用_dl_runtime_resolve(link_map, reloc_arg)函数,将函数的绝对地址写入GOT表中,从而完成调用

这其中还调用了_dl_fixup函数,这里引用师傅的函数解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_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);
}

到这里动态链接的过程就结束了

漏洞利用

在上面的链接过程中我们了解到
在.dynsym里第一个是name的地址,而这个地址加上.dynstr就是字符串的位置,如我的示例程序中.dynsym地址为:0x80481d8,.dynstr为0x08048278:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/32x 0x80481d8
0x80481d8: 0x00000000 0x00000000 0x00000000 0x00000000
0x80481e8: 0x00000033 0x00000000 0x00000000 0x00000012
0x80481f8: 0x00000027 0x00000000 0x00000000 0x00000012
0x8048208: 0x00000052 0x00000000 0x00000000 0x00000020
0x8048218: 0x00000020 0x00000000 0x00000000 0x00000012
0x8048228: 0x0000003a 0x00000000 0x00000000 0x00000012
0x8048238: 0x0000004c 0x00000000 0x00000000 0x00000012
0x8048248: 0x0000002c 0x0804a044 0x00000004 0x001a0011
pwndbg> x/s 0x08048278 +0x33
0x80482ab: "setbuf"
pwndbg> x/s 0x08048278 +0x27
0x804829f: "read"

综上,我们可以得到一个利用的思路(这里调整综合了一些师傅的思路):

  • 根据.dynamic段是否可写分为两种:

    • 当.dynamic段可写时,我们可以把.dynstr段劫持到.bss段,而在.bss段里伪造目标字符串

    • 当.dynamic不可写时,我们可以把传入的rel_offset改为目标函数的偏移大小.

  • 因为.rel.plt段中不一定存在我们需要的函数,所以我们可以将rel_offset修改为一个比较大的值,然后伪造一个.rel.plt段

  • 因为程序是根据r_info来找到.dynsym[num]的,所以我们可以伪造Elf32_Sym -> st_name,使.dynstr + st_name可控,指向我们伪造的函数字符串(函数名)

  • 假设从bss + 0×80处伪造.rel.plt项,rel_offset = bss_address + 0x80 - Addr[.rel.plt].

  • r_offset为函数对应.got.plt项的地址.r_info保存了对应.dynsym中的index和flag.可以在这段数据后伪造.dynsym,例如:

    • bss + 0x100处,即index = (bss + 0×100 - Addr[.dynsym]) / 0×10(SYMENT为16字节),类型必须为7(LF32_R_TYPE)
    • r_info = (index << 8 ) | 0x7.
    • 因为.dynsym包含四个字段,而我们只需要更改st_name部分即可,其余的值完全可以不更改,因为st_name表示了字符串相对strtab的偏移,所以我们可以将字符串写在这段数据后.

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!