linux增加动态链接目录,Linux下动态链接流程简要分析

elf_entry = load_elf_interp(&loc->interp_elf_ex,interpreter,&interp_map_addr,load_bias, interp_elf_phdata);/* 入口地址是解释器映像的入口地址 */} else{/* 入口地址是目标程序的入口地址 */elf_entry = loc->elf_ex.e_entry;}}

填写目标文件的参数环境变量等必要信息

通过create_elf_tables,为目标映像和解释器准备一些有关的信息,包括argc、envc等,这些信息需要复制到用户空间,使它们在CPU进入解释器或目标映像的程序入口时出现在用户空间堆栈上。

install_exec_creds(bprm);retval = create_elf_tables(bprm, &loc->elf_ex,load_addr, interp_load_addr);if(retval < 0)gotoout;/* N.B. passed_fileno might not be initialized? */current->mm->end_code = end_code;current->mm->start_code = start_code;current->mm->start_data = start_data;current->mm->end_data = end_data;current->mm->start_stack = bprm->p;

start_thread宏准备进入新的程序入口

start_thread这个宏操作会将eip和esp改成新的地址,就使得CPU在返回用户空间时就进入新的程序入口。如果存在解释器映像,那么这就是解释器映像的程序入口,否则就是目标映像的程序入口。那么什么情况下有解释器映像存在,什么情况下没有呢?如果目标映像与各种库的链接是静态链接,因而无需依靠共享库、即动态链接库,那就不需要解释器映像;否则就一定要有解释器映像存在。

解释器完成动态链接

前面的工作都是在内核完成的,接下来会回到用户空间。

接下来按照解释器的工作流程进行分析

1.解释器检查可执行程序所依赖的共享库

根据上文讲的内核会读取ELF文件头部的INTERP字段,这里面存储着程序所需要的解释器名称

06193c0596a8bacf73619ef5aba8203a.png

ELF 文件有一个特别的段:.dynamic,它存放了和动态链接相关的很多信息,比如依赖于哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址等,动态链接器通过它找到该文件使用的动态链接库。

4ab506b22588d7f7ca50e781db1d500f.png

Linux下可以用ldd命令查看文件所需要的共享库信息

2.解释器对程序的外部引用进行重定位

解释器对程序的外部引用进行重定位,并告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内。动态链接还有一个延迟定位的特性,即只有在“真正”需要引用符号时才重定位,这对提高程序运行效率有极大帮助。延迟定位有些地方也叫延迟绑定,这个在后面讲PLT和GOT的时候再详细讲。

符号,也就是可执行程序代码段中的变量名、函数名等。重定位是将符号引用与符号定义进行链接的过程,对符号的引用本质是对其在内存中具体地址的引用,所以本质上来说,符号重定位要解决的是当前编译单元如何访问外部符号这个问题。动态链接是在程序运行时对符号进行重定位,也叫运行时重定位(而静态链接则是在编译时进行,也叫链接时重定位)

动态符号表:

为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有个动态符号表.dynsym。它只保存与动态链接相关的符号,对于哪些模块内部的符号,比如模块私有变量则不保存。很多动态链接模块同时拥有.symtab和.dynsym。.symtab保存了所有符号,包含.dynsym中的符号。

对应还有动态符号字符串表.dynstr和为了加快符号查找的符号哈希表。

5c96ed346c5655d7d7d36bec78b24511.png

ca5054eb454e0b0825f942d95256ebaa.png

3.延迟绑定

前面已经讲过了动态链接和静态链接的定义。动态链接比静态链接灵活,但牺牲了性能,优点就是二进制文件的体积明显减小了,而动态链接速度慢的主要原因是,动态链接下对于全局和静态数据的访问都要进行复杂的GOT定位,然后间接寻址,对于模块间的调用也要先定位GOT,然后进行间接跳转。

另外,动态链接的链接过程是在运行时完成的,动态链接器会寻找并转载所需要的对象,然后进行符号查找地址重定位等工作。

因为很多函数可能在程序执行完时都不会被用到,比如错误处理函数或一些用户很少用到的功能模块等,那么一开始就把所有函数都链接好实际是一种浪费,因此ELF采用了一种延迟绑定(Lazy Binding),就是在当函数第一次被用到时才进行绑定(符号查找,重定位等),如果没有用到则不进行绑定。

我使用一个简单的小程序演示一下延迟绑定

# includeintmain{inta;scanf( "%d",&a);printf( "%dn",a);intb = 1;printf( "%dn",b);}

gcc -o testa.c

ELF使用PLT(Procedure Linkage Table)的方法来实现延迟绑定,使用一些很精妙的指令序列来完成。

e520125eb1c588fd558a5005066a8435.png

先使用objdump查看二进制的汇编,我们可以看到在scanf,printf位置call指令的操作数明显不是一个函数的地址,这是因为程序还没有运行起来,所有还不知道具体的函数位置,先放一个符号在这里。

那么程序在运行的时候就可以将这里的符号修改成真正的地址,就要用到两个表,存放函数地址的数据表,称为重局偏移表(GOT, Global Offset Table),而那个额外代码段表,称为程序链接表(PLT,Procedure Link Table)。

我们使用GDB来调试下

32831ff2e247d2606a4743c8c87337cc.png

这时候程序运行到了scanf函数的位置,这里显示的是scanf_plt这就说明了这其实不是真正的scanf函数地址,而是scanf_plt的地址,那我们看看这个plt里面有什么

简单来说就是两个跳转一个压栈,第一个跳转实质是跳转到了GOT

c70e2d788916fe5e3629af936a44f6f2.png

我们可以看到这个时候GOT里面存的就是PLT跳转时下一条指令的地址,也就是压栈的地址

5314e24c1aff729a8df64fa60ebe7c26.png

然后程序跳转到了0x8048300的位置

c9f4ec441a417b2b99ba316ec981de72.png

这里是为了执行_dl_runtime_resolve函数,_dl_runtime_resolve会讲真正的scanf函数的地址写到scanf函数GOT的位置

继续向下执行,程序会同样进行延迟绑定第一次执行的printf函数,当我们第二次来到printf函数的时候,情况就会和上面的不同

61d9d1203a895b56dfd290f0a91b934b.png

我们看到GOT表里已经存储了printf函数真正的位置

下面我来说下我在实际调试时遇到的坑

首先我使用的环境是Ubuntu18.04 可以看到下面这个图

如果我直接使用gcc这个命令,程序的保护是全开的,所以就会遇到下面这个情况

282eaae076a59b3c9cdb4c33c8cba95a.png

第一次执行scanf的时候就会发现GOT里填写的就已经是真正的地址了

所以我在上面的调试中,加入了不开启任何保护的这个命令,就可以验证延迟绑定

gcc-o test -fno-stack-protector -z execstack - no-pie -z norelro -m32 a.c

2ebde6aa540b35e574b21d59de88e532.png

CTF中的延迟绑定考点

不开启RELRO

如果没有开启RELRO,就代表我们可以对GOT表进行修改,所以就有了很多常见的攻击方式,比如GOT表劫持,实现的方法可以是触发堆中的漏洞,实现任意地址写任意内容,我们可以将一些函数的GOT表里填写system函数的地址

开启RELRO

如果开启了RELRO,GOT表字段就是只读的,我们就不能再用上面的方法,常用的方法是修改malloc_hook或者free_hook,将这两个中的一个修改成one_gadget的地址

任意切换程序libc版本

因为在CTF题中,可能有的题是使用glibc2.27有的题是使用2.23还有使用2.26的,不同版本对一些细节是不一样的,有时候这些细节就是考点,比如teache等,但是如果现在只有Ubuntu18的环境,要想同时进行上面那么多版本的调试是很困难的,通常就要搭好几个环境。我在这里介绍一个很方便的方法

GitHub上有一个 gfree-libc的项目

安装

1,git clonegit@github.com:dsyzy/gfree-libc.git2, cdgfree-libc3,sudo sh ./install.sh

添加想要源码级别调试的libc版本

build2 .27(2 .27可以换成你需要的版本)

这个过程很慢,因为需要在本地编译好整个libc环境,通常要等5-10分钟

指定加载版本

gclibc程序名 libc版本 [指定libc]其中,指定 libc需放在和程序一样目录下示例gclibctest2 .24这样 test就加载了 libc-2.24版本,并且是 libc-2.24版本的源代码

如果需要指定 libc,如 libc.so,前提你已经知道 libc版本示例gclibctest2 .24libc.so返回搜狐,查看更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值