LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

一般情况下,其加载顺序为LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib。几米夜空转载的文章《LD_PRELOAD作用》程序调用流图和代码例子值得一看。

1. 简单举例

我们以Rafał Cieślak的一篇文章的例子为例说明它的用法,翻译自Rafał Cieślak的博客,译者qhwdw

random_num.c:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
int i = 10;
while(i--)
printf("%d\n", rand()%100);
return 0;
}

我不使用任何参数来编译它,如下所示:

gcc random_num.c -o random_num

我希望它输出的结果是明确的:从 0-99 中选择的十个随机数字,希望每次你运行这个程序时它的输出都不相同。

现在,让我们假装真的不知道这个可执行程序的出处。甚至将它的源文件删除,或者把它移动到别的地方 —— 我们已不再需要它了。我们将对这个程序的行为进行重大的修改,而你并不需要接触到它的源代码,也不需要重新编译它。

因此,让我们来创建另外一个简单的 C 文件:

unrandom.c:

int rand() {
// the most random number in the universe
return 42;
}

我们将编译它进入一个共享库中:

gcc -shared -fPIC unrandom.c -o unrandom.so

因此,现在我们已经有了一个可以输出一些随机数的应用程序,和一个定制的库,它使用一个常数值 42 实现了一个 rand() 函数。现在……就像运行 random_num 一样,然后再观察结果:

LD_PRELOAD=$PWD/unrandom.so ./random_num

如果你想偷懒或者不想自动亲自动手(或者不知什么原因猜不出发生了什么),我来告诉你 —— 它输出了十次常数 42。

如果先这样执行:

export LD_PRELOAD=$PWD/unrandom.so

然后再以正常方式运行这个程序,这个结果也许会更让你吃惊:一个未被改变过的应用程序在一个正常的运行方式中,看上去受到了我们做的一个极小的库的影响……

当我们的程序启动后,为程序提供所需要的函数的某些库被加载。我们可以使用 ldd 去学习它是怎么工作的:

ldd random_num

linux-vdso.so.1 => (0x00007fff4bdfe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f48c03ec000)
/lib64/ld-linux-x86-64.so.2 (0x00007f48c07e3000)

它列出了被程序 random_num 所需要的库的列表。这个列表是构建进可执行程序中的,并且它是在编译时决定的。在你的机器上的具体的输出可能与示例有所不同,但是,一个 libc.so 肯定是有的 —— 这个文件提供了核心的 C 函数。它包含了 “真正的” rand()。

我使用下列的命令可以得到一个全部的函数列表,我们看一看 libc 提供了哪些函数:

nm -D /lib/libc.so.6

这个 nm 命令列出了在一个二进制文件中找到的符号。-D 标志告诉它去查找动态符号,因为 libc.so.6 是一个动态库。这个输出是很长的,但它确实在列出的很多标准函数中包括了 rand()。

现在,在我们设置了环境变量 LD_PRELOAD 后发生了什么?这个变量为一个程序强制加载一些库。在我们的案例中,它为 random_num 加载了 unrandom.so,尽管程序本身并没有这样去要求它。下列的命令可以看得出来:

LD_PRELOAD=$PWD/unrandom.so ldd random_nums

linux-vdso.so.1 =>  (0x00007fff369dc000)
/some/path/to/unrandom.so (0x00007f262b439000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f262b044000)
/lib64/ld-linux-x86-64.so.2 (0x00007f262b63d000)

注意,它列出了我们当前的库。实际上这就是代码为什么得以运行的原因:random_num 调用了 rand(),但是,如果 unrandom.so 被加载,它调用的是我们所提供的实现了 rand() 的库。

2. 打开文件显示路径

编写inspect_open.c:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h> typedef int (*orig_open_f_type) (const char *pathname, int flags); int open(const char *pathname, int flags, ...)
{
// remember to include stdio.h!
printf("open():%s\n", pathname);
/* Some evil injected code goes here. */
orig_open_f_type orig_open;
orig_open = (orig_open_f_type) dlsym(RTLD_NEXT, "open");
return orig_open(pathname, flags);
} FILE *fopen(const char *path, const char *mode) {
printf("fopen():%s\n", path);
FILE* (*original_fopen) (const char*, const char*);
original_fopen = dlsym(RTLD_NEXT, "fopen");
return (*original_fopen)(path, mode);
}

编译为动态库:

gcc -shared -fPIC inspect_open.c -o inspect_open.so -ldl

编写读取文件的例子test.c:

#include<stdlib.h>
#include<stdio.h> int main()
{
FILE*fp;
fp=fopen("file01.txt","r");
if(fp==NULL)
{
printf("Can not openthe file!\n");
exit(0);
}
fclose(fp);
return 0;
}

编译:

gcc test.c -o test

加载库运行:

LD_PRELOAD=$PWD/inspect_open.so ./test

结果如下,成功注入了函数,打印文件路径:

inspect_open.c中还重写了open,不过这个实现是不完美的,使用会导致异常。有一些参考文章可以学习:

https://catonmat.net/simple-ld-preload-tutorial-part-two

https://stackoverflow.com/questions/35771395/why-doesnt-ld-preload-trick-catch-open-when-called-by-fopen

使用LD_PRELOAD注入程序的更多相关文章

  1. 调试LD_PRELOAD注入的代码

    LD_PRELOAD提供了平民化的注入方式固然方便,同一时候也有不便:注入库出错后调试比較困难. 我琢磨了几天找到了可行的调试方法,当然未必是最有效的办法.抛出陋文,希望引来美玉~ 首先.写一段代码作 ...

  2. SQL防注入程序 v1.0

    /// ***************C#版SQL防注入程序 v1.0************ /// *使用方法: /// 一.整站防注入(推荐) /// 在Global.asax.cs中查找App ...

  3. SQL防注入程序

    1.在Global.asax.cs中写入: protected void Application_BeginRequest(Object sender,EventArgs e){      SqlIn ...

  4. php之防注入程序绕过浅谈

    <?php/*判断传递的变量是否含有非法字符如:$_POST/$_GET功能:SQL防注入系统*/ //屏蔽错误提示error_reporting(7); //需要过滤的字符 $ArrFiltr ...

  5. sqlmap命令

    -u #注入点 -f #指纹判别数据库类型 -b #获取数据库版本信息 -p #指定可测试的参数(?page=1&id=2 -p "page,id") -D "& ...

  6. 最好的 NMAP 扫描策略

    # 适用所有大小网络最好的 nmap 扫描策略 # 主机发现,生成存活主机列表 $ nmap -sn -T4 -oG Discovery.gnmap 192.168.56.0/24 $ grep &q ...

  7. 渗透测试工具实战技巧 (转载freebuf)

    最好的 NMAP 扫描策略 # 适用所有大小网络最好的 nmap 扫描策略 # 主机发现,生成存活主机列表 $ nmap -sn -T4 -oG Discovery.gnmap 192.168.56. ...

  8. Sql-Server应用程序的高级注入

    本文作者:Chris Anley 翻译: luoluo [luoluonet@hotmail.com] [目 录] [概要] [介绍] [通过错误信息获取信息] [更深入的访问] [xp_cmdshe ...

  9. 安装全局消息钩子实现dll窗体程序注入

    说明{      通过设置全局消息钩子来实现dll注入,然后窗体有相关消息请求的时候就会自动加载注入dll, 然后在入口处做处理就可以了.注入方式简单很多,比代码注入和lsp等注入都简单,就不解释了. ...

随机推荐

  1. bash: : Too many levels of symbolic links

    ln -s 时 bash: : Too many levels of symbolic links改为绝对路径,

  2. "技术框架太多,多的眼花缭乱,如何在众多选择中找到自己的方向?

    "技术框架太多,多的眼花缭乱,如何在众多选择中找到自己的方向?",经常有人这么问我. 咱们从开源项目说起,可以从两个维度来对开源项目进行分类,一方面是编程语言,另一方面是应用领域. ...

  3. 使用 Git和Github

    本地库初始化 git init 效果: 注意: .git 目录中存放的是本地库相关的子目录和文件,不要删除,也不要胡 乱修改. 设置签名 其实就是为了区分不同开发人员的身份需要设置一下自己的信息,包括 ...

  4. $vjudge-$基本算法专题题解

    考完期末又双叒回来刷普及题辣$kk$ 然后放个链接趴还是$QwQ$ [X]$A$ 因为是嘤文($bushi$所以放个题意趴$QwQ$ 就汉诺塔问题,只是说有四个塔$A,B,C,D$,要求输出有1-12 ...

  5. 洛谷$P$2123 皇后游戏 贪心

    正解:贪心 解题报告: 传送门! 心血来潮打算把$luogu$提高历练地及其之前的所有专题都打通关,,,$so$可能会写一些比较水的题目的题解$QAQ$ 这种题,显然就套路地考虑交换相邻两个人的次序的 ...

  6. Kafka原理及应用(一)

    一. Kafka简介 (1) 消息中间件的两种实现模式 JMS (Java Message Service) 对消息的发送和接收定义了两种模式: 点对点模式:消息的生产和消费者均只有一个,消息由生产者 ...

  7. 浅谈Linux下/etc/passwd文件

    浅谈Linux 下/etc/passwd文件 看过了很多渗透测试的文章,发现在很多文章中都会有/etc/passwd这个文件,那么,这个文件中到底有些什么内容呢?下面我们来详细的介绍一下. 在Linu ...

  8. 【FAR 方云研发绩效】助力于解决管理难题

    方云研发绩效(farcloud.com)自发布以来,助力多家企业完成研发管理数字化转型,并有效解决产研绩效这一普遍存在的管理难题. 研发管理是许多企业面临的管理难题,首先,技术构成的信息壁垒,让内部沟 ...

  9. caffe实战笔记

    Caffe简要介绍: Caffe还没有windows版本,所以我需要远程登录linux服务器 Caffe主要处理图片/图片序列 Caffe读取的数据格式 从专用的数据库中读取(lmdb.leveldb ...

  10. 为QLabel增加Clicked信号

    QT为QLabel添加Click事件(如果我们使用组件,我们关心的是信号槽:如果我们自定义组件,我们关心的是事件) 其实就是改写了一个函数:mouseReleaseEvent,当在QLabel放开鼠标 ...