Linux 使用LD_PRELOAD 进行hook

使用LD_PRELOAD 进行hook

本文介绍了LD_PRELOAD特性,以及它如何对动态链接的可执行文件进行逆向工程。这种技术允许你劫持函数/注入代码来操纵程序流。

0x01 什么是LD_PRELOAD

LD_PRELOAD 是Linux系统的一个环境变量,它可以影响程序的运行时的链接,允许定义在程序运行前优先加载的动态链接库。

这个功能主要是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

一方面,我们可以利用此功能来使用自己的或者更好的函数(不需要别人的源码)。

另一方面,我们也可以向别人的程序注入程序,从而达到特定的目的。

LD_PRELOAD用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序位

LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib

程序中我们经常要调用一些外部的函数,以open() 和 execve()为例,如果我们自定义这两个函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用open函数时,调用的其实是我们自定义的函数。

Linux 用的都是glibc ,有一个叫libc.so.6库文件,这几乎是Linux 命令的动态链接库,其中也有标准C的各种函数

0x02 什么是程序链接

程序的链接主要有以下三种:

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

对于动态链接来说,需要动态链接库,起作用在于当动态库中的函数发生变化来说,对于可执行程序是透明的,可执行程序无需重新编译,方便程序的发布/维护/更新,但是由于程序是运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致一些非预期的执行结果或者绕过某些安全设置。

什么是HOOK

HOOK(钩子) 通过拦截函数调用,消息或软件组件之间传递的时间来改变或增强操作系统,应用程序或其他软件组件的行为

HOOK 使用场景

程序开发

可以在程序开发的时候hook一些运行时的信息

Windows/Linux/Mac Hook

Android/IOS Hook

在脱壳的时候都会使用Hook


title: 使用LD_PRELOAD进行hook

固件模拟

在固件模拟的时候在hook一些硬件需要的配置

0x03 LD_PRELOAD 进行HOOK

由于LD_PRELOAD可以指定在程序运行前优先加载动态链接库,那我们可以重写程序运行过程中所调用的函数并编译成动态链接库文件,然后通过执行LD_PRELOAD让程序优先加载这个恶意的动态链接库,最后当程序再次运行时便会加载动态链接库中的恶意函数。具体操作步骤如下:

  1. 定义与目标函数完全一样的函数,包括名称、变量及类型、返回值及类型等。
  2. 将包含替换函数的源码编译为动态链接库
  3. 通过命令 export LD_PRELOAD=”库文件路径”,设置要优先替换动态链接库即可。
  4. 替换结束,要还原函数的调用关系,用命令uset LD_PRELOAD解除,或者 export LD_PRELOAD=NULL 卸载库。

使用LD_PRELOAD 进行hook 有个限制:只能hook 动态链接的库,对静态链接的库是无效的,因为静态链接的代码都写到可执行文件了。

可以使用 man 命令查看函数原型和定义,比如 man strcmp

0x04 hook 案例1

passcheck.c , 这里来hook strcmp 函数,绕过密码的验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>

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

image-20211126221603854

上面的代码使用到了标准C函数strcmp函数来进行比较,这是一个外部函数。下面来尝试一下重新编写一个strcmp同名的函数,并编译成动态链接库,实现劫持原函数的功能。

hook_strcmp.c

1
2
3
4
5
6
7
8
9
#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;
}
1
gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so

问题1(已解决)

运行时出现如下错误,看样子是编译的问题。

1
2
3
tigerortiger@ubuntu ~/t/learn_LD_PRELOAD> LD_PRELOAD=/hook_strcmp.so ./passcheck paa
ERROR: ld.so: object '/hook_strcmp.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
Password Wrong!

但是使用export LD_PRELOAD=”hook_strcmp.so”可以成功hook

image-20211126223530371

解决方案,可能是没有加载到库,路径错误。

image-20211126224044388

问题2

不理解编译的时候为什么加-share -fPIC
在编译动态链接库时使用 -shared 参数可以告诉编译器生成动态链接库,而使用 -fPIC 参数则是为了生成位置无关的代码。这意味着编译出的目标文件中的函数和数据的地址都相对独立于加载它们的位置,这使得动态链接库可以被加载到任意内存地址上,而不会由于地址冲突而导致问题。因此,在编译动态链接库时,通常需要同时使用 -shared 和 -fPIC 参数。

0x05 hook案例2 修改系统命令

查看 ls 系统命令会调用那些库函数:

查看二进制文件会调用那些库文件是使用 readelf

或者使用 ltrace 来查看调用库函数(待验证)

ltrace 的作用

ltrace 的功能是能够跟踪进程的库函数调用,首先ltrace打开elf文件,对其进行分析。在elf文件中,出于动态连接的需要,需要在elf文件中保存函数的符号,供连接器使用。

ltrace 就能够获得该文件中,所有系统调用的符号,以及对应的执行指令。

ldd 的作用

用于打印程序或者文件所依赖的共享库列表

image-20211130220612840

1
readelf -Ws /usr/bin/ls

image-20211126225301655

编写hook_strncmp.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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();
}

编译的源代码strncmp函数我故意少了个形参,就会出现如下错误,大概的意思是命名的函数的参数与string.h中定义的函数的参数保持一致

image-20211126230354134

成功劫持了strncpy函数,并且改变了ls命令执行。

image-20211126231104066

0x06 hook 案例3 制作系统后门

制作一个隐藏的Linux 后门,当管理员执行 上面的 ls 命令时反弹shell。

只需要改上面的hook_strncmp.c 文件,只需要更改payload()函数中的system 函数。

然后在 .bashrc 中写入 “ export LD_PRELOAD=/root/hook_strncmp.so”

在执行的时候就可以反弹shell。

0x07 hook 案例4

typedef 的用法

本次hook 的用法是使用typedef 给函数指针类型定义一个别名。

dlopen() 的作用

dlopen 是一个库函数,该函数会打开一个新库,并把它装入内存。该函数主要用来加载库中的符号。

dlopen 以指定模式打开指定的动态链接库文件,并返回一个句柄给调用的进程,dlsym通过句柄和连接符名称获取函数名或者变量名

dlopen打开模式如下:

​ RTLD_LAZY暂缓决定,等有需要时再解出符号
​ RTLD_NOW 立即决定,返回前解除所有未决定的符号。

dlopen() 在dlfcn.h 中定义,并在dl库中实现。它需要两个参数:一个文件名和一个标志。

当库被装入后,可以把dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址,使用这个地址,就可以获得库中特定函数的指针,并调用装载库中的相应函数。

编译的时候要加入 -ldl (指定 dl 库)

dlsym() 函数作用

dlsym() 的函数原型是

void *dlsym(void *handle, const chat *symbol)

handle 是由dlopen 打开动态链接库后返回的指针;symbol 就是要获取的函数的名称;函数返回值是 void* , 指向要获取的函数的地址,供调用使用。

这个案例还是hook 案例1 的 passcheck.c

因为 hook 的目标是拿到strcmp,所以typedef 了一个STRCMP函数指针。由于hook的目的是要控制函数行为,所以需要从原库libc.so.6 中拿到“正版” strcmp指针,保存成old_strcmp以备调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

// hook_strncmp_1.c
// 使用typedef 给函数指针类型一个别名,
// tpyedef 没有定义新的类型,给已经有的类型起一个别名,减少输入。
// * 类型,STRCMP 存储函数地址
// 定义 STRCMP 函数指针
typedef int(*STRCMP)(const char*,const char*);

int strcmp(const char *s1,const char *s2)
{
static void *handle = NULL;
// 创建函数指针
static STRCMP old_strcmp = NULL;

if(!handle)
{
// 打开动态链接库
handle = dlopen("libc.so.6",RTLD_LAZY);
// 获取libc.so.6 库中strcmp 函数的地址
old_strcmp = (STRCMP)dlsym(handle,"strcmp");
}
printf("hack function invoked. s1=<%s> s2=<%s>\n",s1,s2);
return old_strcmp(s1,s2);
}

编译函数源程序时选用-shared 选项即可创建动态链接库,注意应以 .so 后缀命名,最好放到公用库目录(如/lib,/usr/lib 等)下面,并要写好用户接口文件,以便其他用户共享。

使用动态链接库,源程序中要包含 dlfcn.h 头文件,写程序时注意 dlopen 等函数的正确调用,编译时要采用 -rdynamic 选项与 -ldl 选项,以产生可调用动态链接库的执行代码。

1
gcc -fPIC -shared -o hook_strncmp_1.so hook_strncmp_1.c -ldl

我们hook 的函数可以将正确的登录口令打印出来。

image-20211201220448721

0x08 总结

使用LD_PREOAD 进行hook 可以很好的控制程序的流程,以达到自己想要的结果,另外在物联网设备固件模拟或者某些特殊服务模拟的时候,能起到非常至关重要的效果,比如模拟路由器中的http服务时,会缺少nvram的相关配置的设置,这个时候就可以使用LD_PRELOAD对相关配置进行hook 。

0x08 hook 总结

  1. 定义与目标函数完全一样的函数,包括名称、变量及类型、返回值及类型等
  2. 将包含替换函数的源码编译为动态链接库
  3. 通过命令 export LD_PRELOAD="库文件路径",设置要优先替换动态链接库
  4. 如果找不替换库,可以通过 export LD_LIBRARY_PATH=库文件所在目录路径,设置系统查找库的目录
  5. 替换结束,要还原函数调用关系,用命令unset LD_PRELOAD 解除

参考

expoit LD_PRELOAD 的逆向工程讲解,很受用

https://www.exploit-db.com/papers/13233

LD_PRELOAD 后门 | Linux 后门系列(这个作者的一系列后门文章都值得看)

https://cloud.tencent.com/developer/article/1683272

有趣的 LD_PRELOAD

https://www.anquanke.com/post/id/254388#h2-11

C 语言定义函数指针(typedef)

https://blog.csdn.net/xiezhi123456/article/details/80630705

动态链接库dlopen 函数的使用

https://blog.csdn.net/A493203176/article/details/78792274

LD_PRELOAD 程序编译的路程图和试验。

https://blog.csdn.net/chen_jianjian/article/details/80627693


Linux 使用LD_PRELOAD 进行hook
https://tig3rhu.github.io/2023/12/18/22_使用LD_PROLOAD_进行hook/
Author
Tig3rHu
Posted on
December 18, 2023
Licensed under