hook io异常注入

文中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 path
    char 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/173092.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PowerShell无人参与安装最新版本SQL Server Management Studio (SSMS)

文章目录 下载SQL Server Management Studio (SSMS)Power Shell实现无人安装推荐阅读 下载SQL Server Management Studio (SSMS) SSMS 19.2 是最新的正式发布 (GA) 版本。 如果已经安装了 SSMS 19 预览版&#xff0c;需要在安装 SSMS 19.2 之前将其卸载。 如果安装了 SSMS 19.…

【Java】多线程-单例模式/volatile-指令重排序

单例模式即代码中只有一个实例的模式 适用场景&#xff1a;有些场景下&#xff0c;有的类只能有一个对象&#xff0c;不能有多个 要注意&#xff1a;在单例模式下&#xff0c;要保证不能产生多个实例 1、饿汉模式 class Singleton{private static Singleton instance new …

人员定位系统人员聚集风险监测预警功能为企业安全生产保驾护航!

危化企业生产区域由于装置设施开停工、设备检维修或其它原因&#xff0c;会导致在局部作业区域出现作业操作人员、指挥管理人员不定时、不定点的聚集情况&#xff0c;一旦发生泄漏中毒、火灾爆炸、高温高热物质喷溅等事故&#xff0c;人员聚集将会在一定程度上加重事故后果的严…

【Echart】Echart设置label太长隐藏:

文章目录 第一种&#xff1a;竖排显示第二种&#xff1a;显示部分第三种&#xff1a;强制显示所有标签并旋转 第一种&#xff1a;竖排显示 xAxis: {type: category,data: res.data.data.sz.xAxis,axisLabel:{fontSize:12,formatter: function(value) {return value.split().joi…

nginx基础组件的使用

文章目录 一、Nginx 的相关组件介绍1.1、ngx_palloc相关源码1.2、ngx_array组件的相关源码1.3、ngx_array的数据结构1.4、ngx_cycle简介和相关源码1.5、ngx_list相关源码1.6、ngx_list 的数据结构 二、Nginx 组件的使用2.1、makefile的编写2.2、ngx_pallocngx_array的使用2.3、…

【代码随想录】刷题笔记Day33

前言 Day33虽说是一个月&#xff0c;但是从第一篇开始实际上已经过了8个月了&#xff0c;得抓紧啊 46. 全排列 - 力扣&#xff08;LeetCode&#xff09; 前面组合就强调过差别了&#xff0c;这道题是排序&#xff0c;因此每次要从头到尾扫&#xff0c;结合used数组 class So…

ky10 server aarch64 离线安装openssl3.1.4

离线程序 https://gitcode.net/zengliguang/ky10_aarch64_openssl_install.git 输入下面命令执行离线安装脚本 source openssl_offline_install.sh 安装完成

对比学习15篇顶会论文及代码合集,2023最新

对比学习&#xff08;contrastive learning&#xff09;是现在无监督学习中一种常用的学习机制&#xff0c;它可以在没有标签的数据上进行学习&#xff0c;避免依赖大量标签数据&#xff0c;从而帮助我们更好地理解和利用数据集中的信息&#xff0c;提高模型的性能和表现。 作…

做医疗影像心脏方面的看过来:医学图像重建的心脏 MRI 数据集

本文发布了CMRxRecon数据集&#xff0c;包括来自 300 名受试者的多对比度、多视图、多切片和多通道 CMR 成像数据&#xff0c;还由经验丰富的放射科医生提供了所有受试者的心肌和心室的手动分割。单位&#xff1a;复旦, 香港理工大学, 厦大等 心脏磁共振成像&#xff08;CMR&a…

线程池有几种创建方式?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

【开源】基于微信小程序的音乐平台

项目编号&#xff1a; S 055 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S055&#xff0c;文末获取源码。} 项目编号&#xff1a;S055&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首…

(02)vite环境变量配置

文章目录 将开发环境和生产环境区分开环境变量vite处理环境变量loadEnv 业务代码需要使用环境变量.env.env.development.env.test修改VITE_前缀 将开发环境和生产环境区分开 分别创建三个vite 的配置文件&#xff0c;并将它们引入vite.config.js vite.base.config.js import…

【OpenGauss源码学习 —— 列存储(ColumnTableSample)】

执行算子&#xff08;ColumnTableSample&#xff09; 概述ColumnTableSample 类ColumnTableSample::ColumnTableSample 构造函数ColumnTableSample::~ColumnTableSample 析构函数ExecCStoreScan 函数ColumnTableSample::scanVecSample 函数ColumnTableSample::getMaxOffset 函数…

「Qt Widget中文示例指南」如何创建一个计算器?(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 本文将展示如何使用…

快速幂极简写法快速幂求逆元

快速幂原理介绍 快速幂模板 int qmi(int a, int k, int p) {int res 1;while (k) {//后面的a其实是底数与其指数的运算结果了&#xff0c;是不断迭代的//第一个a其实就是a的2的0次方if (k & 1) res (res * a) % p;a (a * a) % p;//注意&#xff0c;a是一个不断变化的过…

聊一聊Linux动态链接和GOT、PLT

共享动态库是现代系统的一个重要组成部分&#xff0c;大家肯定都不陌生&#xff0c;但是通常对背后的一些细节上的实现机制了解得不够深入。当然&#xff0c;网上有很多关于这方面的文章。希望这篇文章能够以一种与其他文章不同的角度呈现&#xff0c;可以对你产生一点启发。 …

【开源】基于Vue和SpringBoot的服装店库存管理系统

项目编号&#xff1a; S 052 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S052&#xff0c;文末获取源码。} 项目编号&#xff1a;S052&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服…

什么是Jmeter ?Jmeter使用的原理步骤是什么?

1.1 什么是 JMeter Apache JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于 Web 应用测试&#xff0c;但后来扩展到其他测试领域。 它可以用于测试静态和动态资源&#xff0c;例如静态文件、Java 小服务程序、CGI 脚本…