[pwn基础]动态链接原理

动态链接概念

为了解决空间浪费和更新困难问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不是将它们静态链接在一起。

简单的说:不对那些组成程序的目标文件进行链接,等到程序要运行时候才进行链接。

把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想。

动态链接调用so例子

LibTest.h LibTest.c

#ifndef LIBTEST_H
#define LIBTEST_H void foobar(int i); #endif
#include "LibTest.h"
#include <stdio.h> void foobar(int i)
{
printf("Printing from Lib.so %d\n",i);
}

编译成.so(动态链接库)

gcc -fPIC -shared LibTest.c -o libtest.so
#-fPIC是与地址无关选项
#-shared 是编译成so 动态联机
#-o 输出,so文件名必须以lib开头

Program1.c

#include "LibTest.h"

int main(int argc,char *argv[])
{
foobar(1);
return 0;
}

Program2.c

#include "Lib.h"

int main(int argc,char *argv[])
{
foobar(2);
return 0;
}

分别编译Program1 和Program2动态调用libtest.so

gcc Program1.c -L. -ltest -o Program1
gcc Program2.c -L. -ltest -o Program2
export LD_LIBRARY_PATH=/home/pwn/testdemo:$LD_LIBRARY_PATH #上面命令的意思分别是
#-L. 代表的是so在本地当前目录查找
#-ltest 动态调用so有一套自己的命名规则,一般必须是lib带头,然后才是so名字.所以-l后面跟的是lib之后的so名,忽略后缀。
#export LD_LIBRARY_PATH代表的是把动态链接目录加入环境变量,默认是/usr/lib下
~/testdemo » ldd Program1
linux-vdso.so.1 (0x00007ffd25b6e000)
libtest.so => /home/pwn/testdemo/libtest.so (0x00007f3ae466f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3ae446a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3ae467b000) #ldd命令可以用来查看当前程序所调用的动态so。

运行结果:

~/testdemo » ./Program1
Printing from Lib.so 1 ~/testdemo » ./Program2
Printing from Lib.so 2

GOT(全局偏移表)

GOT表的全称是Global Offset Table(全局偏移表)

可以把它理解成为了动态链接,把所有的符号偏移量或(绝对地址)都放入到了一个表里这就是GOT表

  • .got表(一般放的是全局变量和static变量)
  • .got.plt表(一般放的就是引用so的函数,即导入函数)

下面我们来做个实验加深下理解。

/*a.c源码*/
extern int shared; //外部符号,跨模块 int main()
{
int a = 100;
swap(&a,&shared);//外部符号,调用外部模块的swap函数
}
/*b.c源码*/
extern int shared = 1; void swap(int *a,int *b)
{
*a ^= *b ^= *a ^= *b;
}
#编译成.so
gcc -fPIC -shared b.c -o libb.so
export LD_LIBRARY_PATH=/home/pwn/got:$LD_LIBRARY_PATH
#编译a可执行程序
gcc a.c -L. -lb -o a

从下图中可以看到shared变量的访问和之前我没静态链接篇的访问方式是一模一样的,用的是当rip+偏移这种间接寻址的方式来访问三方模块的全局变量,而函数swap在这里则变成了swap@plt

利用断点跟入swap@plt函数,然后跟到了plt表,后面会将plt表的用途,可以看到有个jmp是间接跳转,加上偏移后刚好就是got表的位置,对应的是存放swap函数的绝对地址。

got表劫持小实验

#include <stdio.h>
void fun()
{
system("id");
}
int main()
{
//下面演示:Printf("id") 变成shell命令
printf("id");
return 0;
}

最后成功劫持,将printf劫持成了system函数,输出了当前id

PLT(延迟绑定)

PLT概念

首先, 我们要知道, GOT和PLT只是一种重定向的实现方式. 所以为了理解他们的作用, 就要先知道什么是重定向, 以及我们为什么需要重定向.

重定向我在静态链接文章中已经介绍过,就是编译成.o文件时候,那些外部符号变量和函数无法确定时候,预留的填充值,比如用0填充,然后等待链接时候才真实的被写入。

之前介绍的是静态链接的情况,那么动态链接时候会怎么样呢?一遍实战一遍学习。

#include <stdio.h>

void print_banner()
{
printf("Welcome to World of PLT and GOT\n");
} int main(int argc,char *argv[])
{
print_banner();
return 0;
}
 #编译分别生成.o 和可执行程序
gcc -c plt.c -o plt.o -m32
gcc -o plt plt.c -m32

编译后产生了.o和plt可执行程序,我们先用objdump来看看plt.o的汇编源码,命令是objdump -M intel -dw plt.o

可以看到call printf的这个地址是填0的,因为这时候编译器并不知道printf的函数真实地址,printf函数是需要程序被装载后才能确定地址,那么动态链接器为什么不在程序运行起来后,装载起来后

再把真实的printf地址填进去呢?因为这个call printf的语句是在.text代码段的,运行起来后代码段是无法被修改的,只能修改.data数据段。

????????那怎么搞啊,都不能修改代码段,那搞什么。

只能羡慕大佬么的技巧,大佬么总是那么骚,还是有办法搞的,动态链接器生成了一段额外的小代码判断,通过这段代码获取printf函数地址,并完成对它的调用。

延迟绑定(PLT表)

用来存放这小片段代码的地方就是PLT表,下面是伪代码片段。

.text
....
//调用printf的call指令
call printf_plt
.... printf_plt:
mov rax,[printf函数的存储地址] //GOT表中
jmp rax //跳过去执行printf函数 .got.plt
.....
printf下标
这里存储了printf函数重定位后的真实地址

链接阶段发现printf定义在动态库 glibc时,链接器生成一段小代码 print@plt,然后printf@plt地址取代原来的printf。因此转化为链接阶段对printf@plt做链接重定位,而运行时才对printf做运行时重定位,具体调用流程图,可以参考如下:

实战学习

好的,接下来我们继续用上面的例子,详细对的PLT表进行分析,首先我们用命令objdump -M intel -dw plt查看每个段的数据,有汇编则反汇编。

000003a0 <.plt>:
3a0: ff b3 04 00 00 00 push DWORD PTR [ebx+0x4]
3a6: ff a3 08 00 00 00 jmp DWORD PTR [ebx+0x8]
3ac: 00 00 add BYTE PTR [eax],al
...
000003b0 <puts@plt>:
3b0: ff a3 0c 00 00 00 jmp DWORD PTR [ebx+0xc]
3b6: 68 00 00 00 00 push 0x0
3bb: e9 e0 ff ff ff jmp 3a0 <.plt>
...
0000051d <print_banner>:
51d: 55 push ebp
51e: 89 e5 mov ebp,esp
...
53a: e8 71 fe ff ff call 3b0 <puts@plt>
...
547: c3 ret +0000 0x56556fd8 e0 1e 00 00 00 00 00 00 00 00 00 00 90 cd e4 f7 │....│....│....│....│
+0010 0x56556fe8 b0 de df f7 00 00 00 00 80 58 e1 f7 00 00 00 00

OK,我们对上面的代码进行分析,收我们关注printf_banner函数调用的printf,这里因为编译优化的缘故printf变成了puts,在53a这里可以看到调用了

puts@plt,puts@plt这里有3句汇编代码,分别是jmp到ebx+0xC值的地址,然后又push0,又jmp到0x3a0,因为这里我们不知道ebx是什么值,所以需要动态调试来一步步详细的观察下,用命令pwndbg,gdb pltstart,b printf_banner c,然后单步到call puts@plt

从上图中可以发现,ebx的值是0x56556fd8,这其实是got表装载到内存后的地址,我们可以用readelf -SW plt查看文件中got表的偏移。

可以发现偏移正好是fd8,虚拟内存的起始地址加上fd8就是0x56556fd8

那么如何查看程序加载的起始地址呢?可以借助强大的pwndbg中的vmmap命令来查看内存分布。

正好是(起始地址)0x56555000+偏移(0xfd5)=0x56556fd8

OK现在回归正题,我们已经知道puts@plt中的jmp是要跳转到GOT表中偏移0xC的位置,那么这个位置存放的是什么值呢?

聪明的你已经猜到了,他其实就是puts函数的真实地址,但是!为了不影响程序运行的速度,因为我们程序一运行就把所有符号地址都确定,然后都填入got表,那一但我们调用到非常的动态库时候,性能肯定会受影响的。所以,采用了延迟绑定机制。

000003b0 <puts@plt>:
3b0: ff a3 0c 00 00 00 jmp DWORD PTR [ebx+0xc]
3b6: 68 00 00 00 00 push 0x0
3bb: e9 e0 ff ff ff jmp 3a0 <.plt>

延迟绑定机制原理

我们先来看看,这个got表偏移+0xC位置,在文件位置中的值是多少,可以看到他的值是0x3B6,你可以仔细看看puts@plt函数,jmp后下一句汇编地址是多少?

00001ee0
00000000
00000000
000003B6 [ebx+0xC]

刚好是0x3b6,对应的汇编语句是push 0,接着又跳到了jmp 0x3a0 <.plt>,跳到了plt表。

 3b6:   68 00 00 00 00          push   0x0

plt表中的汇编如下:

这几句汇编代码会调用内核的_dl_runtime_resolve()函数,把puts函数在动态库中的真实地址放入到got表中。

000003a0 <.plt>:
3a0: ff b3 04 00 00 00 push DWORD PTR [ebx+0x4]
3a6: ff a3 08 00 00 00 jmp DWORD PTR [ebx+0x8]
3ac: 00 00 add BYTE PTR [eax],al

所以延迟绑定机制的原理:就是第一次在调用函数时候,才把真实的地址放入got表(进行绑定),之后再调用这函数则直接jmp到真实地址。

最后,在其他大佬博客上偷了张详细的函数调用plt表延迟绑定的流程图。

参考文献:

https://yjy123123.github.io/2021/12/06/延迟绑定过程分析/

https://evilpan.com/2018/04/09/about-got-plt/ 非常完整详细的讲解博客

《程序员的自我修养 链接、装载与库》 这本书,真的是神书,全部仔细看完肯定有帮助。

[pwn基础]动态链接原理的更多相关文章

  1. java基础--动态代理实现与原理详细分析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式                     ...

  2. linux 下动态链接实现原理

    符号重定位 讲动态链接之前,得先说说符号重定位. c/c++ 程序的编译是以文件为单位进行的,因此每个 c/cpp 文件也叫作一个编译单元(translation unit), 源文件先是被编译成一个 ...

  3. 再探Linux动态链接 -- 关于动态库的基础知识

      在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台 ...

  4. JAVA基础复习与总结<一>(2) 父类引用指向子类对象(向上转型、动态链接)

    先来看看下列代码 public class Animal { public static void main(String[] args){ Animal animal = new Cat(); // ...

  5. 再探Linux动态链接 -- 关于动态库的基础知识(Dynamic Linking on Linux Revisited)

      在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台 ...

  6. [pwn基础] Linux安全机制

    目录 [pwn基础] Linux安全机制 Canary(栈溢出保护) 开启关闭Cannary Canary的种类 Terminator canaries(终结者金丝雀) Random cannarie ...

  7. [pwn基础]Pwntools学习

    目录 [pwn基础]Pwntools学习 Pwntools介绍 Pwntools安装 Pwntools常用模块和函数 pwnlib.tubes模块学习 tubes.process pwnlib.con ...

  8. http 基础与通讯原理

    目录 http 基础与通讯原理 Internet 与中国 1990年10月 注册CN顶级域名 1993年3月2日 接入第一根专线 1994年4月20日 实现与互联网的全功能连接 1994年5月21日 ...

  9. 聊聊动态链接和dl_runtime_resolve

    写在前面 linux下的动态链接相关结构,重新回顾_dl_runtime_resolve的流程以及利用方法 动态链接相关结构 为了高效率的利用内存,多个进程可以共享代码段.程序模块化方便更新维护等,动 ...

随机推荐

  1. VMware下ubuntu 20.04扩容/磁盘

    最近搞zabbix监控,发现搭建的监控server主机磁盘告警.提示/超过阈值80%. 有实在VMware软件下的虚拟机,首先我就是给虚机磁盘增加容量. 增加后发现没什么改变,看来还需要其他操作. 在 ...

  2. Find the Maximum - 题解【思维,贪心】

    题面 这是2022年ICPC昆明站的F题.在赛场上,我一开始敲了个贪心,但是出锅了,改敲树形DP,但是时间来不及了.在队友的提醒下补过了这个题,知道解法的我发现我就是个纯纯的老坛-- 原题链接在牛客网 ...

  3. K8S+Jenkins自动化构建微服务项目(后续)

    因为之前写过基于K8S部署jenkins master/slave平台,在这个的基础上构建微服务到K8S集群中 Jenkins-slave构建微服务项目到K8S集群 1.微服务项目上传到git仓库 这 ...

  4. 我向PostgreSQL社区贡献的功能:空闲会话超时

    经过约八个月的努力,终于完成了 PostgreSQL 空闲会话超时断开的功能. 该功能将在版本 14 中发布. 这是我第一次向 PostgreSQL 提供功能,虽然之前也有向社区提供过补丁,但是这次整 ...

  5. python学习-Day33

    目录 今日内容详细 socket socket套接字简介 socket模块 服务端 客户端 通信循环 服务端 客户端 链接循环 半连接池 概念 产生半连接的两种情况 黏包问题 多次发送被并为一次 TC ...

  6. python学习-Day8

    目录 作业讲解 数据类型内置方法3 字典 ( dict ,可变类型) 类型转换 -- dict() 需要掌握的方法 按key取值(可存可取) 统计字典中键值对的个数(len) 成员运算 删除键值对 获 ...

  7. Springboot中整合knife4j接口文档

    在项目开发过程中,web项目的前后端分离开发,APP开发,需要由前端后端工程师共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发. 什么是knife4j 简单说knife4j就swagge ...

  8. Docker Compose 的介绍、安装与使用

    什么是 Docker Compose? Compose 是 Docker 官方的开源项目,负责实现Docker容器集群的快速编排,开源代码在 https://github.com/docker/com ...

  9. 生命周期和作用域 & mybatis执行流程

    流程 sqlSessionFactory 实例化后  --> transactional事务管理-->创建executor执行器-->创建SqlSession-->实现增删改查 ...

  10. IOC容器--1.12. 基于 Java 的容器配置

    用Java的方式配置Spring ,不使用Spring的XML配置,全权交给Java来做 JavaConfig是Spring的一个子项目,在Sring 4  之后成为核心功能 这种纯Java的配置方式 ...