hook io异常注入

news/2023/11/30 8:45:37

文中code https://gitee.com/bbjg001/darcy_common/tree/master/io_hook

需求引入

最近工作需要,需要验证一下我们的服务在硬盘故障下的鲁棒性。

从同事大佬哪里了解到hook技术,可以通过LD_PRELOAD这个环境变量拦截依赖库的调用链,将对标准库函数的调用转移到自己自定义的函数,然后返回自定义的错误代码。

使用方式

export LD_PRELOAD=hook_lib.so
./main
# hook_lib.so是自行编译的用来拦截的so文件
# ./main是要运行的二进制文件

一个简单的例子

有这样一个简单的main函数

// main.cpp
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
#include <string.h>int main()
{int rfd = open("test.txt", O_RDONLY);std::cout << "open(), with ret: " << rfd << ", err(" << errno << "): " << strerror(errno) << std::endl;close(rfd);
}

如果test.txt不存在,输出是这样的

在这里插入图片描述

如果test.txt是一个存在的正常文件,输出

在这里插入图片描述

下面自定义open函数,通过hook拦截调用链调用自己的open函数,找到open函数的定义

在这里插入图片描述

自定义open函数,注意函数传参要与原open函数一致

// hook_lib.cpp
#include <unistd.h>  
#include <iostream> 
#include <dlfcn.h>
#include <fcntl.h>typedef int(*OPEN)(const char *__path, int __oflag, ...);int open(const char *__path, int __oflag, ...){std::cout << "!!! open函数被拦截了" << std::endl;errno = 2;return -1;
}

正常编译并运行main.cpp

g++ main.cpp -o main && ./main

输出是正常的

在这里插入图片描述

下面拦截注入自己的open函数

# 把自己的函数文件编译成.so文件
g++ --shared -fPIC  hook_lib.cpp -o hook_lib.so -ldl
# 通过LD_PRELOAD拦截调用链启动main函数
LD_PRELOAD=./hook_lib.so ./main

将hook函数的文件编译成.so文件

g++ --shared -fPIC  hook_lib.cpp -o hook_lib.so -ldl

在启动时通过LD_PRELOAD指定hook的库文件

g++ main.cpp -o main
LD_PRELOAD=./hook_lib.so ./main

在这里插入图片描述

进一步的做更多自定义的逻辑

这次以write函数为例

返回正常的write函数

可以定义在某些情况下返回错误码,某些情况下返回正常的write函数。这里通过随机概率返回两者。

hook逻辑

// hook_lib
extern ssize_t std_write (int __fd, __const void *__buf, size_t __n) {static void *handle = NULL;static WRITE old_write = NULL;if (!handle) {handle = dlopen("libc.so.6", RTLD_LAZY);old_write = (WRITE)dlsym(handle, "write");}return old_write(__fd, __buf, __n);
}
// 模拟的write函数
extern ssize_t write (int __fd, __const void *__buf, size_t __n) {if (rand() % 100 / 100.0 > 0.5) {errno = 2;return -1;}return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){srand(time(NULL));const char *f_path = "test.txt";int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);for (int i = 0; i < 10; i++){int ret = write(fd, "HelloWorld", 10);if (ret < 0){std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;}else{std::cout << "write(), with ret: " << ret << std::endl;}}close(fd);return 0;
}

执行结果

$ LD_PRELOAD=./hook_lib.so ./main
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10

控制注入异常的path

hook逻辑

// hook_lib
// 检查当前操作的文件是否是要注入异常的文件
bool is_current_path(int fd, std::string path){    if(path==""){return false;}// get current pathchar buf[256] = {'\0'};char _file_path[256] = {'\0'};std::string file_path;snprintf(buf, sizeof (buf), "/proc/self/fd/%d", fd);if (readlink(buf, _file_path, sizeof(_file_path) - 1) != -1) {file_path = _file_path;}if(file_path.find(path) != std::string::npos){  // 路径中包含${path}即被命中return true;}return false;
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {if (is_current_path(__fd, "test")) {errno = 2;return -1;}return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){const char *f_path = argv[1];int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret = write(fd, "HelloWorld", 10);if (ret < 0){std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;}else {std::cout << "write(), with ret: " << ret << std::endl;}close(fd);return 0;
}

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt   
write(), with ret: -1, err_info: No such file or directory$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

延时返回

这里比较简单不再做代码示例

sleep(time_s);		// 秒
usleep(time_ms);	// 微秒

动态控制异常注入

希望能从第三方位置读取配置,通过变更配置动态的对指定path注入指定的错误(码)类型。

从文件获得配置

hook逻辑

// hook_lib
void get_ctrl_var_file(std::string *path, int *eno, int *sleep_time){std::ifstream ifs("conf.txt");ifs >> *path;ifs >> *eno;ifs >> *sleep_time;ifs.close();
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {std::string epath;int eno, ehang_time;get_ctrl_var_file(&epath, &eno, &ehang_time);if (is_current_path(__fd, epath)) {errno = eno;hang_sleep(ehang_time);return -1;}return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){const char *f_path = argv[1];int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret = write(fd, "HelloWorld", 10);if (ret < 0){std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;}else {std::cout << "write(), with ret: " << ret << std::endl;}close(fd);return 0;
}

conf.txt

test.txt 2 1000000

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt   
write(), with ret: -1, err_info: No such file or directory$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

从redis获得配置

hook逻辑

#include <hiredis/hiredis.h>
// hook_lib
void get_ctrl_var_redis(std::string *path, int *eno, int *sleep_time){redisContext *conn  = redisConnect("127.0.0.1", 6379);if(conn != NULL && conn->err){printf("connection error: %s\n",conn->errstr);return;}redisReply *reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/epath");*path = reply->str;reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/eno");*eno = std::atoi(reply->str);reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/ehang");*sleep_time = std::atoi(reply->str);freeReplyObject(reply);redisFree(conn);
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {std::string epath;int eno, ehang_time;get_ctrl_var_redis(&epath, &eno, &ehang_time);if (is_current_path(__fd, epath)) {errno = eno;hang_sleep(ehang_time);return -1;}return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){const char *f_path = argv[1];int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret = write(fd, "HelloWorld", 10);if (ret < 0){std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;}else {std::cout << "write(), with ret: " << ret << std::endl;}close(fd);return 0;
}

在redis中添加如下变量

set /hook/write/epath test.txt
set /hook/write/eno 5
set /hook/write/ehang 1000000

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt 
write(), with ret: -1, err_info: Input/output error$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

in mac os

在mac os中需要使用其他的环境变量进行注入,简单试了下没能成功,抛砖引玉

https://stackoverflow.com/questions/34114587/dyld-library-path-dyld-insert-libraries-not-working

参考

https://blog.51cto.com/u_15703183/5464438

https://sq.sf.163.com/blog/article/173506648836333568

https://xz.aliyun.com/t/6883

https://www.cnblogs.com/wainiwann/p/3340277.html


https://www.xjx100.cn/news/3092711.html

相关文章

OSG文字-各种文字效果(边框、阴影及颜色倾斜)示例(2)

各种文字效果(边框、阴影及颜色倾斜)示例 各种文字效果(边框、阴影及颜色倾斜)示例的代码如程序清单9-2所示&#xff1a; 1. /* 各种文字效果(边框、阴影及颜色倾斜)示例 */ 2. osg::ref_ptr<osg::Camera> createAllKindText(const string &strDataFolder) 3. {…

maven打包可执行jar含依赖lib

修改pom.xml <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- jdk8可用&#xff0c;其他jdk版本可能需改插件版本 --><version>2.3.7.RE…

黑盒子测试

黑盒子测试的步骤包括&#xff1a; 测试计划&#xff1a;根据用户需求报告中关于功能要求和性能指标的规格说明书&#xff0c;定义相应的测试需求报告&#xff0c;制订黑盒测试的最高标准。同时适当选择测试内容&#xff0c;合理安排测试人员、测试时间及测试资源等。测试设计…

Linux 安全 - 扩展属性xattr

文章目录 前言一、简介二、扩展属性命名空间2.1 简介2.2 security扩展属性2.3 System扩展属性2.4 Trusted扩展属性2.5 User扩展属性 三、用户空间使用3.1 setfattr/getfattr3.2 setxattr/getxattr/listxattr 参考资料 前言 一、简介 xattr - Extended attributes扩展属性是与…

数字化转型导师坚鹏:数字化时代银行网点厅堂营销5大特点分析

数字化时代银行网点厅堂营销存在以下5大特点&#xff1a; 1、产品多样化&#xff1a;在数字化时代&#xff0c;银行的产品和服务变得更加多样化。除了传统的存款、贷款、理财等金融服务外&#xff0c;还新增了各种创新产品&#xff0c;如网上银行、移动支付、投资咨询、保险、…

Docker 安装 Oracle Database 23c

目录 访问 Oracle 官方网站 使用 Docker 运行 Oracle Database 23c 免费容器映像 创建并运行 Oracle Database 23c 容器 查看已下载的镜像 列出正在运行的容器 进入容器 sqlplus 命令 访问 Oracle 官方网站 Database Software Downloads | Oracle 中国 使用 Docker 运行…

nvm切换node后,没有npm

当我们想要在不同的 Node.js 版本之间切换的时候&#xff0c;通常会使用 nvm&#xff08;Node Version Manager&#xff09; 来完成。但是&#xff0c;当我们在使用 nvm 切换 Node.js 版本的时候&#xff0c;可能会遇到没有 npm 的情况。这种情况通常发生在我们在新环境或者重新…

(C)一些题2

1.在 C 语言中&#xff08;以 16位 PC 机为例&#xff09;,5种基本数据类型的存储空间长度的顺序为&#xff08;&#xff09; A . char < int < long int <float < double B . char int < long int<float <double C . char < int < long int …