PWN——ret2dl_resolve
PWN——ret2dl_resolve
ret2dl_resolve是栈溢出中,底层且高级的一种利用手段,这里特此做一篇笔记学习一下。
个人认为,要掌握这种方法,自己去写demo来多次调试分析是不二法门。
demo是XDCTF 2015的一道例题。
#include<unistd.h>
#include<stdio.h>
#include<string.h> void vuln()
{
char buf[100];
setbuf(stdin,buf);
read(0,buf,256);
} int main()
{
char buf[100]="Welcome to XDCTF2015~!\n";
setbuf(stdout,buf);
write(1,buf,strlen(buf));
vuln();
return 0;
}
gcc -g -o XDCTF2015 -m32 -fno-stack-protector XDCTF2015.c
为了方便调试,也可以提前关闭一下aslr
//关闭aslr
echo 0 > /proc/sys/kernel/randomize_va_space
//开启aslr
echo 2 > /proc/sys/kernel/randomize_va_space
1.ELF文件前置知识
ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部将包含类型为PT_DYNAMIC段。PT_DYNAMIC段中包含很多在动态链接中所需要的节。每个节都是一个相应的结构体数组。
借助readelf,我们可以分析这些节中的信息。readelf -d命令可以显示PT_DYNAMIC段中的信息。
下图是Bamboofox的社课中的图片,标注了几个漏洞利用相关的节
JMPREL节中存放了动态链接中的重定位信息
有.rel.plt节和.rel.dyn节。.rel,plt用于函数重定位,.rel.plt节用于变量重定位。
.rel.plt节相关的ELF32_REL结构如下:
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
} Elf32_Rel; #define ELF32_R_SYM(info) ((info)>>8)
#define ELF32_R_TYPE(info) ((unsigned char)(info))
#define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))
/*
r_offset是该函数在.got.plt中的地址。
ELF32_R_SYM(Elf32_Rel->r_info)=(Elf32_Rel->r_info)>>8
SYMTAB节中包含了动态链接符号表。
.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;
注:为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有一个叫做动态符号表的段来保存这些信息,这个段叫做“.dynsym”段,动态符号表只保存了与动态链接相关的符号,对于那些模块内部的符号,比如模块私有变量则不保存。
STRTAB节中包含了动态链接的字符串。这个节以"\x00"结尾,中间每个字符串也以"\x00"间隔。
2.延迟绑定技术
程序在执行前,如果对整个动态链接库函数进行符号解析的话,是非常浪费资源的,因为一个程序不可能调用动态链接库中所有的函数。我们最好能做到只对用到的函数进行函数解析,这样可以大大提高文件链接的效率,加快程序的启动速度。
这时候,就出现了延迟绑定技术,我们通过plt表(过程链接表),在第一次调用函数的时候,来确定函数的地址,把函数的实际地址存储在got表相对的偏移处。
试想一下,在这个过程中,当我们第一次调用函数,要向got表写入函数真实地址的时候,我们是不是需要一个管理的工具?这个管理的工具就是_dl_runtime_resolve()函数。函数需要两个参数,一个是要被绑定的函数所在的模块,一个是要被绑定函数的符号名。
函数原型:_dl_runtime_resolve(link_map,reloc_arg)
注:got表实际上是分为.got表和got.plt表,got.plt表(全局函数偏移表)中存放的是动态链接库函数,.got表(全局变量偏移表)里面的偏移主要是全局变量。我们在这里讨论的是got.plt表。
.got.plt表的前三项的含义分别如下:
1.got[0],第一项保存的是".dynamic"段的地址,这个段描述了本模块动态链接相关的信息;
2.got[1],第二项保存的是本模块的ID;
3.got[2],第三项保存的是_dl_runtime_resolve()函数的地址。
lazy binding是通过plt表和got表之间巧妙的代码跳转实现的,我们通过例子来研究一下。
一个简单的demo,在gdb下用info function查看他的函数信息,看到了plt表中函数的地址。
然后查看plt表的汇编代码
以read函数为例,他这里有三条指令
jmp DWORD PTR[ebx+0x10]
push 0x8
jmp 0x56556020
jmp这里表示一个跳转,综合我们上面说的,它应该是跳转到了read函数在got.plt表的偏移处,但是这里是[ebx+0x10]。我们来看一下ebx寄存器中存储的值是多少。
ebx中存储的值是0x56559000,那么我们看一下0x56559000+0x10地址处存储着什么。
0x56559010处确实是read@got.plt的地址,说明我们的猜想是正确的。我们同时看一下这里存储的值,可以看出,这里存储的是前面read函数中push 8在plt表的地址,说明跳转到got表之后,got表并没有实际地址,这时候,程序又跳转回plt表的下一条指令处执行。
这里的push 8又是什么呢,我们这里先猜测一下:8应该是read函数对应的Elf32_Rel结构体在.rel.plt节中的相对偏移。
执行完push 8之后,执行下一条语句:jmp 0x56556020。
我们现在回忆一下前面提到的_dl_runtime_resolve()函数,它有两个参数,link_map和reloc_arg。
第一条指令是将[ebx+0x4]入栈,第二条指令是跳转到0x56559000+0x8地址处。0x56559000+0x8存放的是_dl_runtime_resolve函数的地址。
算上前面的push的参数,正好push了两个参数,按照参数入栈的顺序,我们知道[ebx+0x4]就是link_map的指针,0x8就是reloc_arg的偏移。这时候,填入的正好是got表项的前三项,got[0]是0x8(reloc_arg用来计算函数重定位的入口),这个参数就是Elf_Rel条目在重定位表.rel.plt段中对应当前请求函数的偏移,动态链接器将这个值加上.rel.plt的基地址来得到目标Elf_Rel结构的绝对地址;got[1]是link_map;got[2]是_dl_runtime_resolve函数的入口地址。
_dl_runtime_resolve是在glibc-2.23/sysdeps/i386/dl-trampoline.S中用汇编实现的。调用_dl_fixup函数(call 0xf7fe37f0),通过寄存器传参。_dl_fixup是在glibc-2.23/elf/dl_runtime.c实现的。
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]);
// 通过参数 reloc_arg 计算重定位入口,这里的 DT_JMPREL 即 .rel.plt,reloc_offset 即 reloc_arg
//DT_JMPREL存放着.rel.plt节的地址,.rel.plt是Elf32_Rel结构体数组
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 根据函数重定位表中的动态链接符号表索引,即 reloc->r_info,获取函数在动态链接符号表中对应的条目
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);
/*Elf32_R_SYM(Elf32_Rel->r_info)=(Elf32_Rel->r_info)>>8*/
/*eg:Elf32_R_TYPE(0x607)=7*/
/* 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
// 根据 strtab+sym->st_name 在字符串表中找到函数名,然后进行符号查找获取 libc 基址 result
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. */
// 将要解析的函数的偏移地址加上 libc 基址,得到函数的实际地址
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 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;
}
/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);
// 将已经解析完成的函数地址写入相应的 GOT 表中
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);
}
3.漏洞利用
简单来想,我们要利用这种漏洞,肯定要想办法伪造_dl_runtime_resolve()函数的第二个参数。定义在动态链接库中的函数,在导入的时候,在装载阶段作为外部符号导入可执行文件。导入符号的解析需要重定位的支持,重定位项以Elf_Rel结构体来描述。这个结构的实例保存在.rel.plt段(用于导入函数)和.rel.dyn段中(用于导入全局变量),这里的重定位可以类比静态链接时候的.rel.text段,只不过静态链接的重定位都是在链接阶段完成的。
当程序导入一个正常函数是,连接器会在.dynstr段中包含一个函数名称的字符串,在.dynsym段中包含一个指向它的符号:Elf_Sym,在.rel.plt段中包含一个指向这个符号的重定位项:Elf_Rel。
dl_runtime.c中,首先分别获取动态链接符号表和动态链接字符串的基址,然后通过dl_runtime_resolve函数的第二个参数reloc_arg计算重定位入口
重定位的目标(即Elf_Rel结构中的r_offset域)将会是got表中的一个条目。GOT表保存于.got.plt段,由能够解析.rel.plt段中的重定位的动态链接器来填写。
.dynstr段是不可写的,所以我们想要覆写.dynstr段来进行任意函数的调用是不可能的。动态链接器是从.dynamic段的DT_STRTAB表中获得.dynstr段的地址的,而且DT_STRTAB地址是已知且可写的(可以通过readelf这种elf文件解析器获取DT_STRTAB和DT_SYMTAB地址)。所以图(a)中将.dynamic段中DT_STRTAB条目覆写,将.dynstr的地址写到bss段,然后再bss段布置一段假的字符串表。当它尝试解析某个函数的时候会使用不同的基址来寻找函数名,最终执行execve,这种方式非常简单,但仅当二进制程序的.dynamic段可写时有效,当RELRO部分开启的时候,.dynamic段将会被标记为只读,这时候上述方法无法使用。
图(b)中,r_info表示该函数在.rel.plt段的偏移,.rel.plt段的基址加上r_info的偏移,得到目标结构体的绝对地址,当_dl_runtime_resolve第二个参数reloc_indoex超出.rel.plt段,最终落在.bss段中时,攻击者可以在这个位置伪造一个Elf_Rel结构,并填写r_offset的值为一个可写的内存地址来将解析后的函数地址写在那里。r_info也会是一个将动态链接器导向攻击者控制内存的下标。这个下标就只想一个位于它后面的Elf_Sym结构体(我们前面说到过,这个结构体中的st_name会指向.dynstr段中对应的一个函数名称)。所以我们可以在bss段布置一个Elf_Sym结构体,然后控制st_name指向我们要构造的函数名。这是一般通用的方法,在可以绕过部分RELRO的检测。
我们用readelf和gdb查看一下dynsym节
可以看出,.dynsym节遵循16字节对齐,前四个字节就是st_name,这里Elf32_Sym[6]处保存着write函数的符号表信息,Elf32_Sym[6]->st_name=0x4c,.dynstr节的基地址再加上0x4c的偏移量,就是字符串write。
这时候,我们就可以利用图(b)中的方法进行漏洞利用。
PWN——ret2dl_resolve的更多相关文章
- CTF必备技能丨Linux Pwn入门教程——调整栈帧的技巧
Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...
- ret2dl_resolve
ret2dl_resolve是一种比较复杂的高级ROP技巧,利用它之前需要先了解动态链接的基本过程以及ELF文件中动态链接相关的结构. 我根据raycp师傅的文章,动手调试了一下: https://r ...
- Pwn~
Pwn Collections Date from 2016-07-11 Difficult rank: $ -> $$... easy -> hard CISCN 2016 pwn-1 ...
- iscc2016 pwn部分writeup
一.pwn1 简单的32位栈溢出,定位溢出点后即可写exp gdb-peda$ r Starting program: /usr/iscc/pwn1 C'mon pwn me : AAA%AAsAAB ...
- i春秋30强挑战赛pwn解题过程
80pts: 栈溢出,gdb调试发现发送29控制eip,nx:disabled,所以布置好shellcode后getshell from pwn import * #p=process('./tc1' ...
- SSCTF Final PWN
比赛过去了两个月了,抽出时间,将当时的PWN给总结一下. 和线上塞的题的背景一样,只不过洞不一样了.Checksec一样,发现各种防护措施都开了. 程序模拟了简单的堆的管理,以及cookie的保护机制 ...
- pwn学习(1)
0x00 简介 入职之后,公司发布任务主搞pwn和re方向,re之前还有一定的了解,pwn我可真是个弟弟,百度了一番找到了蒸米大佬的帖子,现在开始学习. 0x01 保护方式 NX (DEP):堆栈不可 ...
- pwn学习之四
本来以为应该能出一两道ctf的pwn了,结果又被sctf打击了一波. bufoverflow_a 做这题时libc和堆地址都泄露完成了,卡在了unsorted bin attack上,由于delete ...
- pwn学习之三
whctf2017的一道pwn题sandbox,这道题提供了两个可执行文件加一个libc,两个可执行文件是一个vuln,一个sandbox,这是一道通过沙盒去保护vuln不被攻击的题目. 用ida打开 ...
随机推荐
- 堆&&优先队列&&TreeMap
题目描述 5710. 积压订单中的订单总数 题解 题目不难,主要是要读懂题意,一步步模拟,代码较长,需要细心检查. 坑较多,比如我犯了很多傻逼问题:想都不想就拿1<<9+7当作100000 ...
- 其他:IntelliJ IDEA设置运行内存
1. 打开idea的安装路径,进去bin目录 2. 修改idea.exe.vmoptions 将idea内存设置为-Xms512m -Xmx2048m -XX:ReservedCodeCacheS ...
- PHP 跨域问题 (转)
通过设置Access-Control-Allow-Origin来实现跨域. 例如:客户端的域名是client.runoob.com,而请求的域名是server.runoob.com. 如果直接使用aj ...
- APP 抓包(应用层)
0x01 前言: app抓包是逆向协议的前提,也是一个爬虫工程师的基本要求,最近发现这块知识非常欠缺就抓紧补补了(我太菜了) 然后接下来是通过vpn将流量导出到抓包软件的方式,而不是通过wifi设置代 ...
- leetcode第156场周赛5205
思路分析:先用哈希表统计各个数字的次数,再将每个值放进set集合中,之后如果有重复得,说明比原个数少了,如果都是独一无二的,那么个数是相同的 这波把collection集合,强转成set是真的灵性.想 ...
- XCTF Normal_RSA
这题本来算是很常规的rsa了,下载附件 发现有个公钥文件,还有一个加密文件,这种题之前有遇到一次,做法和这个类似,上次那个是用rsa的库,直接解的,这次直接用常规的,好像更简单,记录下模板 记事本打开 ...
- Linux下使用Ansible处理批量操作
Ansible介绍: ansible是一款为类unix系统开发的自由开源的配置和自动化工具.它用python写成,类似于saltstack和puppet,但是不同点是ansible不需要再节点中安装任 ...
- ESP32-http server笔记
基于ESP-IDF4.1 #include <esp_wifi.h> #include <esp_event.h> #include <esp_log.h> #in ...
- 《快来为你的 .NET 应用加个监控吧!》更新版本啦
目录 导读 三种方式处理监控数据 主动推送 ASP.NET Core 自定义URL .NET diagnostics 自定义监控指标 导读 CZGL.ProcessMetrics 是一个 Metric ...
- 关于hive的基础
Hive基础 1.引入原因 对存在HDFS上的文件或HBase中的表进行查询时,是要手工写一堆MapReduce代码 对于统计任务,只能由懂MapReduce的程序员才能搞定 事实上,许多底层细节实际 ...