聊聊动态链接和dl_runtime_resolve
写在前面
linux下的动态链接相关结构,重新回顾_dl_runtime_resolve的流程以及利用方法
动态链接相关结构
为了高效率的利用内存,多个进程可以共享代码段、程序模块化方便更新维护等,动态链接技术自然就出现了。不详细介绍位置无关代码和位置无关可执行程序这些基本知识,这里着重记录一下ELF实现运行时重定位为了提高效率做的各种工作和用到的结构。动态链接的可执行文件装载过程和静态链接基本一样,OS读取可执行文件的头部信息,检查文件合法性后从Program Header中读取每一个Segment的虚拟地址、文件地址和属性,然后把他们映射到进程虚拟空间的相对位置。但是OS接下来不能把控制权交给可执行文件,因为动态链接中还有很多依赖于共享对象的无效地址,需要进一步处理。映射完成后,OS会启动一个动态链接器(Dynamic Linker)——linux下就是ld.so。这个动态链接器实际上也是一个共享对象,OS也会通过映射的方式把它载入进程的地址空间中。然后OS就会把控制权交给DL的入口地址,它会进行一系列初始化操作,根据当前环境参数对可执行文件进行动态链接工作。完成之后再把控制权转交给可执行文件。
pwn常见的so hell
动态链接器并非由系统配置或者环境参数决定,而是由ELF可执行文件自己决定!动态链接的ELF可执行文件中,有一个专门的段叫做.interp,这个段里就保存了一个字符串(可执行文件所需要的动态链接器的路径)一般就是这个路径
$ readelf -x .interp RNote3
Hex dump of section '.interp':
0x00000238 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
0x00000248 7838362d 36342e73 6f2e3200 x86-64.so.2.
比赛中遇到一个和系统ld不匹配的libc.so时,由于ELF中的动态链接器路径指向系统默认的ld,然后就会出现修改LD_PRELOAD仍然无法加载指定libc的情况。一个做法是找到题目给的libc版本然后找一个匹配的ld,通过change_ld来加载指定libc。
def change_ld(binary, ld):
"""
Force to use assigned new ld.so by changing the binary
"""
if not os.access(ld, os.R_OK):
log.failure("Invalid path {} to ld".format(ld))
return None
if not isinstance(binary, ELF):
if not os.access(binary, os.R_OK):
log.failure("Invalid path {} to binary".format(binary))
return None
binary = ELF(binary)
for segment in binary.segments:
if segment.header['p_type'] == 'PT_INTERP':
size = segment.header['p_memsz']
addr = segment.header['p_paddr']
data = segment.data()
if size <= len(ld):
log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
return None
binary.write(addr, ld.ljust(size, '\0'))
if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
if os.access(path, os.F_OK):
os.remove(path)
info("Removing exist file {}".format(path))
binary.save(path)
os.chmod(path, 0b111000000) #rwx------
success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
return ELF(path)
#example
elf = change_ld('./pwn', './ld.so')
p = elf.process(env={'LD_PRELOAD':'./libc.so.6'})
.dynamic段
ELF里专门用于动态链接的段还有几个,首先是.dynamic。这个段里保存了动态链接器所需的基本信息,比如依赖于哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址。.dynamic段的结构由一个类型变量加上一个附加的数值或者指针组成。
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
这里的d_tag表示这个表项的类型,可供取值范围(参考程序员的自我修养p205)
这个section给动态链接器提供动态链接的各种信息入口,用readelf -d bin
可以看到详细的关于此模块的详细动态链接信息。
$ readelf -d test
Dynamic section at offset 0xe18 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400468
0x000000000000000d (FINI) 0x4006a4
0x0000000000000019 (INIT_ARRAY) 0x600e08
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e10
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400350
0x0000000000000006 (SYMTAB) 0x4002c0
0x000000000000000a (STRSZ) 95 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 48 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400438
0x0000000000000007 (RELA) 0x4003f0
0x0000000000000008 (RELASZ) 72 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x4003c0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4003b0
0x0000000000000000 (NULL) 0x0
重定位表(.rel.plt和.rel.dyn)
使用readelf -r bin
可以查看elf文件的重定位section.
$ readelf -r test
Relocation section '.rela.dyn' at offset 0x3f0 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600fe8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 printf@GLIBC_2.2.5 + 0
000000600ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x438 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
.rel.dyn 包含了需要重定位的变量的信息, 叫做变量重定位表
.rel.plt 包含了需要重定位的函数的信息, 叫做函数重定位表
32位和64位使用的重定位表有一点区别,都是结构体数组但是一般32位使用Rel,64位使用Rela.比如上面readelf解析出来的就是一个64位程序的重定位表。结构体长成下面这个样子。
#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 */
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_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 */
好像32位一般用来函数重定位就是R_386_JMP_SLOT,64位函数重定位R_X86_64_JUMP_SLOT类型,是看源码的注释也是Create PLT entry。转而也能理解重定位表项里的r_offset的含义,r_offset为重定位对象的入口,用readelf做实验可以发现对于函数重定位其实就是指向了.got.plt的对应项。后面会说.got.plt是全局偏移表中存储重定位函数地址的地方。那么我们大概知道了,dl_runtime_resolve就是通过这个offset得知把解析出来的地址写到哪里。
全局偏移表(.got和.got.plt)
GOT 表在 ELF 文件中分为两个部分
- .got,存储全局变量的引用。
- .got.plt,存储函数的引用
在 Linux 的实现中,.got.plt 的前三项的具体的含义如下 - GOT[0],.dynamic 的地址。
- GOT[1],指向内部类型为 link_map 的指针,只会在动态装载器中使用,包含了进行符号解析需要的当前 ELF 对象的信息。每个 link_map 都是一条双向链表的一个节点,而这个链表保存了所有加载的 ELF 对象的信息。
- GOT[2],指向动态装载器中 _dl_runtime_resolve 函数
之后的got表项存的是函数的真实地址(解析过后),解析前存的是对应plt表项中那段胶水代码的第二条指令地址。整个解析过程就是在各自的plt传递reloc_arg, 在plt0传递link_map_obj.接着调用_dl_runtime_resolve.要注意的是,32位的reloc_arg和64位的有区别:32位使用reloc_offset, 64位使用reloc_index
#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,如果这个符号被导出,这个符号保存着对应的虚拟地址。
利用原理
动态链接下第一次调用glibc的函数需要通过plt表中的一段代码解析函数的真实地址,这也是linux的lazy bind的特点。具体的解析方式就是_dl_runtime_resolve(link_map_obj, reloc_arg) ,如果我们可以控制整个解析过程中的参数,那么就能解析我们想要的函数地址。回顾一下整个流程:
- call printf@plt
- jmp *(printf@got) -> (第一次会jmp回来,之后就直接jmp到解析出来的地址了) -> push n -> jmp &plt[0] (跳到公共表项)
- push got[1] (link_map 可以理解为模块ID) -> jmp *got[2] (跳转到dl_runtime_resolve函数)
- 以上步骤相当于调用了dl_runtime_resolve(link_map_obj, reloc_arg)
- 解析完毕后会把解析出来的地址写回reloc_arg定位到的.rel.plt表项中r_offset指向的位置(其实就是.got.plt的对应项)
- 弄懂dl_runtime_resolve的解析过程后,就可以通过伪造reloc_arg来解析出我们想要的libc函数地址并且写回可控区域了
dl_runtime_resolve
- 通过link_map_obj访问.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
- 最后调用这个函数
32位情况下构造payload
构造payload
- 思路:伪造reloc_arg,使得函数重定位表项落在可控内存段,就可以伪造r_offset和r_info让动态链接符号表表项落在可控区域,接着伪造st_name让动态链接字符串表项值为目标函数名称。这些参数的构造需要拿到.dynstr,.dynsym,.rel.plt的地址。pwntools的ELF函数提供了这个接口:
elf= ELF(name)
rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr #0x8048330
dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr #0x80481d8
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr #0x8048278
伪造reloc_arg指向fake—rel,fake-rel里伪造好r_offset指到可控区域,构造r_info指向fake-sym,同时r_info要的低8位必须是7.fake-sym里伪造好st_name,让.dynstr+st_name指向伪造好的system字符串,就完成了整个构造过程。例子和exp很容易找到,之后有空再补64位情况下的一些注意事项。
聊聊动态链接和dl_runtime_resolve的更多相关文章
- Mach-O 的动态链接(Lazy Bind 机制)
➠更多技术干货请戳:听云博客 动态链接 要解决空间浪费和更新困难这两个问题最简单的方法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态的链接在一起.简单地讲,就是不对那些组成程序的目标文 ...
- 原创 C++应用程序在Windows下的编译、链接(四)动态链接
4动态链接 4.1概述 在静态链接阶段,链接器为PE文件生成了导入表,导出表,符号表,并调整了Call指令后面的操作数,在程序调用的时候,能够直接地或者间接地定位到IAT中的某个位置,在PE文件中,该 ...
- ELF动态链接
为什么要使用动态链接? 在现代的linux系统中,假设一个普通的程序会使用到c语言静态库至少1MB以上,那么,如果我们的机器运行100个这样的程序,就用浪费近100MB的内存:如果磁盘有2000个这样 ...
- Delphi之DLL知识学习2---静态链接和动态链接
静态连接 静态链接是指Delphi 编译器把要调用的函数和过程编译成可执行代码.函数的代码可存留在应用程序的 .dpr文件或一单元中.当链接用户的应用程序时,这些函数与过程便成为最终的可执行文件的一部 ...
- discuz x3在DIY模块中调用伪静态不成功,显示动态链接的解决办法
discuz x3在DIY模块中调用伪静态不成功,显示动态链接,然而其他的链接正常显示伪静态. 后台启用伪静态后,发现论坛版块.帖子点击链接,伪静态正常显示,然后在门户首页DIY显示的帖子,点进去后发 ...
- 微信支付开发(4) 动态链接Native支付
关键字:微信支付 微信支付v3 动态native支付 统一支付 Native支付 prepay_id 作者:方倍工作室原文: http://www.cnblogs.com/txw1958/p/wxpa ...
- 程序的链接和装入及Linux下动态链接的实现
http://www.ibm.com/developerworks/cn/linux/l-dynlink/ 程序的链接和装入及Linux下动态链接的实现 程序的链接和装入存在着多种方法,而如今最为流行 ...
- [CSAPP-II] 链接[符号解析和重定位] 静态链接 动态链接 动态链接接口
1 平台 转http://blog.csdn.net/misskissc/article/details/43063419 1.1 硬件 Table 1. 硬件(lscpu) Architecture ...
- 实例分析ELF文件动态链接
参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第6章 可执行文件的装载与进程 第7章 动态链接 <Linux GOT与PLT> 开发平台 ...
随机推荐
- sql datetime类型数据如果进行模糊查询
select * from Table1 where CONVERT(nvarchar(50),CreateTime,120) like '%2019'
- php yield关键字以及协程的实现
php的yield是在php5.5版本就出来了,而在初级php界却很少有人提起,我就说说个人对php yield的理解 Iterator接口 在php中,除了数组,对象可以被foreach遍历之外,还 ...
- 转:动态库路径配置- /etc/ld.so.conf文件
Linux 共享库 Linux 系统上有两类根本不同的 Linux 可执行程序.第一类是静态链接的可执行程序.静态可执行程序包含执行所需的所有函数 — 换句话说,它们是“完整的”.因为这一原因,静态可 ...
- 解析mysql慢日志
mysql慢日志太多,需要分析下具体有哪些慢日志 mysql可以直接记录所有慢日志,现在的问题是将日志文件sql进行去重 想了老半天该怎样将sql的查询字段去掉进行排序,没有get到重点.后来发现my ...
- tomcat如何正确的开启远程调试功能(转)
转自:http://blog.csdn.net/mhmyqn/article/details/49209541 版权声明:本文为博主原创文章,未经博主允许不得转载. 在日常开发中,有时需要对远程服务器 ...
- 如何用Word制作斜线表头?
如何用Word制作斜线表头?遇到这种问题,你一般是如何操作?本期企业网盘坚果云干货分享与大家分享有关斜线表头的制作方法. 斜线表头分单斜线表头和多斜线表头,下面分情况来了解相关的解决办法. 单斜线表头 ...
- 21.与重入锁相关联的Condition
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * ...
- Vue学习笔记【27】——Vue路由(设置路由)
设置路由高亮 css: .router-link-active, /* vue-router*/ .myactive { color: red; font-weigh ...
- javaweb判断当前请求是否为移动设备访问的方法
由于移动端和pc端还是稍微有些区别的,我觉得最好是在一个地儿统一判断,而且不要改动原先的代码,这样可以从一定程度上减少bug的数量.我的想法是首先应该判断当前请求是否为移动端,然后设一个标识到sess ...
- AI加持的阿里云飞天大数据平台技术揭秘
摘要:2019云栖大会大数据&AI专场,阿里云智能计算平台事业部研究员关涛.资深专家徐晟来为我们分享<AI加持的阿里云飞天大数据平台技术揭秘>.本文主要讲了三大部分,一是原创技术优 ...