linux常见的hook技术
内核模块、Inline Hook、Got Hook、Preload Hook、系统文件修改
需求需要将所有的进程的系统调用都截断
可选的有内核模块、Preload Hook、系统文件修改
内核模块Hook
通常是从内核源码特殊位置,修改回调、修改中断表;或修改重编译内核,导出内部函数,从而跳转到自定义函数,开发内核模块实现hook。
- 可以拦截到所有应用层系统调用,应用层无法绕过
- 开发调试复杂,测试周期长,升级和卸载内核模块带来稳定性问题
应用层Preload Hook
对于Preload的解释,详见[Preload Hook原理](#Preload Hook原理)。Preload Hook是指利用系统支持的preload能力,将模块自动注入进程实现hook。可以通过以下手段使用Preload技术:一种是环境变量配置(LD_PRELOAD);另一种是文件配置:(/etc/ld.so.preload)。
- 若使用命令行指定LD_PRELOAD则只影响该新进程及子进程;若写入全局环境变量则LD_PRELOAD对所有新进程生效;父进程可以控制子进程的环境变量从而取消preload
- 文件preload方式影响所有新进程且无法被取消
- 可以拦截到系统调用和普通库函数
- 实现和操作最为简单,只需要编写同名系统调用函数即可实现hook
- 可以使用动态调用方式或自定义实现方式绕过
需要快速的实现选择开发难度比较低的Preload Hook
Preload技术有自身的作用范围,如果将Preload环境变量到全局环境(/etc/profile)或使用Preload文件时,Preload便在所有在此(配置)时间节点之后运行的进程生效,而无法影响已经在运行的进程;若将Preload环境变量作为启动进程的环境变量(如shell中执行LD_PREOAD=/lib64/inject.so ./myprocess
),则Preload只对当前进程生效。默认情况下进程创建的子进程也会继承Preload环境变量,而父进程可以在子进程初始化前修改子进程的环境变量,从而避免往子代传播。Preload具体使用方式如下:
- LD_PRELOAD环境变量 指定动态库全路径,触发时机:启动时
- /etc/ld.so.preload 指定动态库全路径,触发时机:启动时
进程在启动后,会按照一定顺序加载动态库:
- 加载环境变量LD_PRELOAD指定的动态库
- 加载文件/etc/ld.so.preload指定的动态库
- 搜索环境变量LD_LIBRARY_PATH指定的动态库搜索路径
- 搜索路径/lib64下的动态库文件
如前述,模块通过Preload技术添加到模块链后,进程加载器在解析导入表时,会按照链表顺序依次查找符号,因此如果在Preload动态库中实现并导出了同名(而不是C++修饰名)库函数,即可实现Hook。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
//直接看open函数
//如果试图打开这些文件将要验证 账号和密码(用户自定义)
#define PROCESS_FILE_PATH "自己的文件路径"
#define FILE_PATH "自己的文件路径"
#define LOG "自己的文件路径"
#define PATHLENGTH 128
#define MAX_PATH_LEN 256
#define ERR_ERROR -1
#define ERR_SUCCESS 0
bool m_start = false; //标志位 防止一直hook形成递归
//暂时全部没有close
//获取进程的路径 写入process_path中
int get_process_path(char *process_path)
{
char *p = NULL;
int n = 0;
memset(process_path,0x00,MAX_PATH_LEN);
n = readlink("/proc/self/exe",process_path,MAX_PATH_LEN);// /proc/self/exe代表当前程序
if (NULL != (p = strrchr(process_path,'/'))){
*p = '\0';
}else{
printf("wrong process path");
return ERR_ERROR;
}
return ERR_SUCCESS;
}
//返回true表示不是保护文件
bool no_protect(const char* pathname) //pathname试图打开的文件
{
//1.打开 会递归
//int fd = open(FILE_PATH,O_RDONLY);//打开指定存放保护文件路径的文件
FILE* fp = fopen(FILE_PATH,"r");
//2.解析
//分配存储空间
char buf[PATHLENGTH];//先用定长 之后优化 用指针 先判断文件的大小 然后malloc分配合适内存
memset(buf,0,sizeof(buf));
//读文件
fread(buf,sizeof(char),sizeof(buf),fp);
//size_t fsize = read(fd,buf,PATHLENGTH);
//这里需要判段 暂时没判断
//3.判断 返回
fclose(fp);
if(!strstr(buf,pathname))//没有查到
{
printf("不是保护文件\n");
return true;
}else
{
printf("是保护文件\n");
return false;
}
}
//如果是允许进程返回true
bool primit_process()
{
//将进程的启动路径记录在path中
char path[MAX_PATH_LEN];
if (get_process_path(path)){
printf("get process path error!\n");
return ERR_ERROR;
}
//1.打开 不能用open会递归
//int fd = open(PROCESS_FILE_PATH,O_RDONLY);//打开指定存放保护文件路径的文件
FILE* fp = fopen(PROCESS_FILE_PATH,"r");
//2.解析
//分配存储空间
char buf[PATHLENGTH];//先用定长 之后优化 用指针 先判断文件的大小 然后malloc分配合适内存
memset(buf,0,sizeof(buf));
//读文件
fread(buf,sizeof(char),sizeof(buf),fp);
//size_t fsize = read(fd,buf,PATHLENGTH);
fclose(fp);
//这里需要判段 暂时没判断
//3.判断 返回
if(!strstr(buf,path))//没有查到
{
printf("不是允许进程\n");
return false;
}else
{
printf("是允许进程\n");
return true;
}
}
// 定义一个函数指针,用于保存原始的 open 函数地址
int (*original_open)(const char *pathname, int flags, ...);
void encode(char** point,const char* str)
{
strncpy(*point,str,strlen(str));
*point = *point+strlen(str);
**point=' ';
*point+=1;
}
//记录时间 调用函数 被访问文件pathname 访问的状态 此处false 访问的进程 get_process_path
int file_open_log(const char* pathname,bool state)
{
time_t current_time;
char* c_time_string;
// 获取当前时间
current_time = time(NULL);
c_time_string = ctime(¤t_time);
//获取执行文件的路径
char path[MAX_PATH_LEN];
if (get_process_path(path)){
printf("get process path error!\n");
return ERR_ERROR;
}
char* point;//记录位置
char log_buf[1024];//strncpy到这个路径下
point = log_buf;
memset(log_buf,0,sizeof(log_buf));
//时间 此步骤可以写一个函数 代码复用性高
encode(&point,c_time_string);
//路径
encode(&point,path);
//被访问的文件 写入内存
encode(&point,pathname);
if(state)
{
encode(&point,"true");
}
else
{
encode(&point,"false");
}
encode(&point,"\n");
FILE* fd = fopen(LOG,"a");
//path只是执行文件的路径 在之前要统一
fputs(log_buf,fd);//介绍符为\0
fclose(fd);
}
int open(const char *pathname, int flags, ...) {
va_list args;
va_start(args, flags);
mode_t mode = va_arg(args, mode_t);
va_end(args);
bool noProtect;//判断是否在保护文件中
noProtect = no_protect(pathname);
//获取原来的open函数 如果之前没有hook 这次hook
if(!m_start)
{
original_open = dlsym(RTLD_NEXT, "open");
m_start = true;
printf("Intercepted open call for file: %s\n", pathname);
}
//不是保护文件
if(noProtect)
{
//此处open已经被hook 所以里面用的fopen
file_open_log(pathname,true);//成功打开
return original_open(pathname, flags, mode);
}else//是保护文件
{
//判断是否是允许的进程
if(primit_process())//open改掉
{
file_open_log(pathname,true);
return original_open(pathname, flags, mode);
}else{
//记录时间 调用函数 被访问文件pathname 访问的状态 此处false 访问的进程 get_process_path
file_open_log(pathname,false);
return -1;
}
}
}