浅析ret2dl_reslove
前言
这几天抽空学了一下ret2dl,发现网上大多都是对32位程序的讲解和利用,这里我从64位角度出发记录一下我对ret2dl的理解。
注:glibc源码版本为glibc-2.23
ret2dl_reslove
ret2dl_reslove巧妙地利用了ELF格式以及动态装载器的弱点,不需要泄露内存信息,就可以直接标识关键函数的位置并调用。
原理
动态链接的elf文件由于采用了延迟绑定技术,在首次调用外部函数时会去解析该函数的真实地址,即调用_dl_runtime_reslove进行地址解析,得到实际地址后会去写got表并执行。ret2dl_reslove就是在这一过程中对相关结构体进行伪造,利用函数_dl_runtime_resolve的内部逻辑使得程序解析出一个恶意函数例如execve/system并执行。
前置知识
ELF文件中动态链接相关段简述
.dynamic
该节存放了许多Elf64_Dyn结构体,保存了动态链接器所需要的基本信息,比如存放了ELF文件其他节的标识和起始地址。
Elf64_Dyn结构体定义:
其中关键字d_tag定义如下(不重要):
.rel.plt
该节存放了众多Elf64_Rela结构体,是对函数引用的修正,修正的位置在got.plt表,每个libc库函数都有自己的Elf64_Rela结构体。在程序入口附近,位于LOAD段。
Elf64_Rela结构体定义:
其中我们需要记住r_info是一个复合值,其高32位表示该重定位项在动态链接符号表.dynsym中对应项的下标,低32位表示该重定位项的重定向类型。
重定位类型:
.dynsym
该节存放了很多Elf64_Sym结构体,每个libc函数都会有自己的Elf64_Sym结构体。
Elf64_Sym结构体定义:
Elf64_Sym内部各个部分的大小(重要):
.plt
我们先来看看在ida中其结构:
PLT[0](通用调用解析表项):
不存储任何外部函数的跳转信息,保存got[1](内存放有link_map)的地址和got[2]的地址。
PLT[1],[2],[3]:
分成两个部分:
ida内:
.got
got[0]:
第一项指向dynamic段。
got[1]:
link_map地址。
got[2]:
dl_runtime_resolve地址。
got[3]-got[...]:
plt[1]-plt[...]的第二条指令。
总结
这里给个表:
延迟绑定
在学习ret2libc时我们已经对延迟绑定有所了解,这里我以系统调用read函数来对延迟绑定进行一个详细的讲解。
在第一次调用外部引用函数read时,程序先跳转到read的GOT表,
而此时那里并不是read的真实地址,而是read的plt表地址,同时还有一个注意的点是如果你使用当前rip+0x2fe2你会得到一个错误的地址,因为x64下的RIP相对寻址需要跳过当前RIP,也就是其实是下一条指令的地址+0x2fe2。详细看这篇文章REX(Register EXtension) 前缀,所以我们先来看看0x401030这个地方存储的指令长度(jmp指令是0xff25)
可以看到这条指令的长度为6(0xff 0x25 0xe2 0x2f 0x00 0x00),接下来看jmp所跳转的地址
因此程序又跳转到了read@plt,执行了push num(num为read的重定位表项相对于重定位段的偏移) jmp PLT[0],再执行push GOT[1]-->link_map,jmp GOT[2]->_dl_runtime_reslove,
由_dl_runtime_reslove进行符号解析与重定位。
_dl_runtime_resolve
_dl_runtime_resolve是一段汇编代码,在我所用的libc源码中存放于/sysdeps/x86_64/dl-trampoline.h中,代码如下:
源码看的实在是头疼,简述一下_dl_runtime_resolve函数的这段汇编代码的功能,其就是保存寄存器的值到栈中,然后调用_dl_fixup函数执行具体的功能,然后从栈中恢复寄存器。
link_map
直接上代码,只需要记住其中的l_addr,l_info两个定义即可,
_dl_fixup
_dl_fixup函数存在于/elf/dl-runtime.c内,代码如下:
简单概括一下,_dl_fixup函数传入的两个参数一个是rdi寄存器中存储的link_map,一个是rsi寄存器中存储的GOT表中关于PLT重定位的索引值,后面要根据该索引值写入新的地址。
顺便提一下,这里的reloc_arg就是后面用的reloc_offset:
以及这里的ELFW的定义和其操作:
之后通过宏D_PTR从link_map结构中获得符号表symtab和字符串表strtab,以及reloc
reloc_offset即是传入的参数reloc_arg,其代表在plt表中的第几项,保存在reloc中。
reloc的r_offset表示需要修改的函数地址在GOT表中的地址,加上装载地址l_addr得到的rel_addr就是最终要修改的函数的绝对地址。
接下来的st_other描述符号的可见性,先简介一下 __builtin_expect (long exp, long c)函数:
你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小,如果为1,则执行if分支的可能性很大。
也就是说如果st_other==0则直接将装载地址加上st_value即得到函数的最终地址value,将其写入rel_addr即可。
大部分情况下,会进入if语句,首先获得符号的version信息,然后调用_dl_lookup_symbol_x函数从已装载的共享库中查找最终的符号地址,查找到符号sym后,对其进行重定位,即加上装载地址,保存在value中。
最后调用elf_machine_fixup_plt函数进行修正。
总结
到这里,前置知识就基本讲完了,在这里给个总结:
- Dynmic节由Elf64_Dyn结构体构成
- Dynsym节由Elf64_Sym结构体构成
- .rel.plt节由Elf64_Rela结构体构成
-
_dl_runtime_resolve(link_map,reloc_arg)实际上就是执行了_dl_fixup
- 进行动态链接的流程:
1.根据想要连接的函数确定偏移reloc_arg(体现在plt[1]表里面的push)
2.找到.rel.plt里对应的ELF64_Rela表项
3.根据ELF64_Rela里的r_info确定该函数对应.dynsym里的哪一个Elf64_sym项(在64位中 r_info>>32即为表项下标)
4.之后进行一系列检查
再给出一张图:
ret2dl_runtime_resolve
目标:伪造link_map和相关字段值,调用dl_runtime_reslove函数进行解析。
前提条件:1.需要有可写的内存区域
2.需要知道libc版本
这里结合一个函数板子来详细讲解一下我们所做的工作。
板子:
这段代码的作用就是在于他伪造了如下图所示的结构在一个我们可以控制的地方:
我们来分析一下,如果我们能够在程序的一个地址上写入这段代码,并且我们可以让程序跳转调用_dl_fixup函数的话,那么我们就可以控制函数的执行流,让其执行我们希望他执行的函数。
具体执行流程如下:
1._dl_fixup接受两个参数,一个是我们伪造的fake_link_map的地址,一个是关于PLT重定位的索引值0。
2.开始_dl_fixup函数内部处理,将symtab,strtab,reloc赋值:
根据上面提到的link_map的结构体中的l_info部分、_fix_up函数内的宏操作和d_tag关键字:
我们不难得到symtab,strtab,reloc的赋值
再来看看下面的赋值情况:
得到sym,rel_addr的值:
这个时候sym的值就被放在了已经解析的函数的got表-8的地方,这样sym->st_value就被解析为一个已经解析过的函数的真实地址。
之后经过两个判断:
将value赋值为sym->st_value+offset,最终执行到我们希望执行的函数。
例题
这里结合一道例题去加深一下理解,题目链接放在文末
主函数很简单,就是给了一个很大的溢出,没有输出函数,所以我们先想到利用溢出修改返回地址,能够将我们伪造的link_map输出到bss段上,那么再利用jmp_dl指令强行跳转到fixup函数执行即可。
详细exp如下;
题目链接:
链接:https://pan.baidu.com/s/1h0OMUN_fIGOm2B-NYS6LgA?pwd=1111
提取码:1111
--来自百度网盘超级会员V4的分享
__EOF__

本文链接:https://www.cnblogs.com/mazhatter/p/18475606.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端