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. 学习Java第五周

    通过这一段时间的学习发现Java和C++虽然都是面向对象的编程语言,有相似之处也有不同之处,相似的地方总会感觉易于接受,不同之处或者新接触的有些知识不是很好理解和掌握. 前一段时间学的内部类和接口便是 ...

  2. Python14_中TK模块使用总结

    事件的绑定: https://www.cnblogs.com/jerryspace/p/9836142.html https://www.cnblogs.com/progor/p/8505599.ht ...

  3. Kubernetes Clusters

    1. 创建集群 Kubernetes集群  Kubernetes协调一个高可用的计算机集群,作为一个单独的单元来一起工作.有了这种抽象,在Kubernetes中你就可以将容器化的应用程序部署到集群中, ...

  4. vue文章学习路线

    vue学习笔记(一)入门 Vue实现简单的购物车功能 vue学习笔记(二)vue的生命周期和钩子函数 使用webstorm搭建vue-cli项目 vue-cli项目中引入第三方插件 vue-cli项目 ...

  5. DEVOPS技术实践_23:判断文件下载成功作为执行条件

    在实际生产中,我们经常会需要通过判断一个结果作为一个条件去执行另一个内容,比如判断一个文件是否存在,判官一个命令是否执行成功等等 现在我们选择其中一个场景进行实验,当某个目录下存在,则执行操作 1. ...

  6. Windows To Go 企业版2019 LTSC 开发环境部署

    Windows To Go 是一项非常实用的功能,与传统方式安装Windows 10相比更具有灵活性,会根据每次接入的硬件型号保留不同版本驱动. 由于博主是一名全栈程序员(截至发稿处于菜鸟级别),对灵 ...

  7. Markdown数学符号

    上标 语法: x^2 效果: \(x^2\) 下标 语法: x_i 效果: \(x_i\) 整体 语法: x^{2y} 效果: \(x^{2y}\) 大括号 语法: \{\} 效果: \(\{\}\) ...

  8. 【接口测试】使用httpClient获取cookies+携带获取的cookies访问get接口

    数据准备 在本机或者远端机器安装部署moco-runner(参考:https://blog.csdn.net/qq_32706349/article/details/80472445) 这里我们只需要 ...

  9. 小小知识点(四十九)——SCMA

    SCMA的实现基于两步: 1.实现码本资源到物理频域资源映射的映射矩阵(matrix mapping): 2.实现二进制比特流到码本映射的复数域星座图(complex domain constella ...

  10. SpringBoot介绍与使用

    SpringBoot介绍与使用 1.什么是SpringBoot SpringBoot是Spring项目中的一个子工程,与我们所熟知的Spring-framework 同属于spring的产品: 我们可 ...