hook io异常注入

news2024/9/30 1:30:40

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0开始学习JavaScript--JavaScript中的对象

JavaScript中的对象是一种重要的数据结构&#xff0c;它不仅是语言的基石&#xff0c;还提供了丰富的功能和灵活性。本文将深入研究JavaScript对象的创建、属性访问、方法定义&#xff0c;以及实际应用中的技巧&#xff0c;通过丰富的示例代码&#xff0c;帮助读者更全面地了解…

pycharm统计代码运行时间

方法1&#xff1a;写代码实现 import……&#xff08;自己会用到的包&#xff09; import time start time.perf_counter() #开始计时#代码开始了 …… …… …… end time.perf_counter() #结束计时 runtime end - start print(f"输出代码运行时间{runtime}")…

七天.NET 8操作SQLite入门到实战 - 第二天 在 Windows 上配置 SQLite环境

前言 SQLite的一个重要的特性是零配置的、无需服务器&#xff0c;这意味着不需要复杂的安装或管理。它跟微软的Access差不多&#xff0c;只是一个.db格式的文件。但是与Access不同的是&#xff0c;它不需要安装任何软件&#xff0c;非常轻巧。 七天.NET 8操作SQLite入门到实战…

RabbitMQ消息队列快速入门

RabbitMQ消息队列快速入门 初始MQ MQ全称为Message Queue&#xff0c;即消息队列&#xff0c;是在消息的传输过程中保存消息的容器。它是典型的生产者-消费者模型。 生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。消息的生产和消费都是异步的&am…

多项式求和

题目描述 给定程序中 fun 函数的功能是&#xff1a;求出以下分数序列的前 n 项之和&#xff0c;并通过函数值返回 main 函数。 输入格式 输入参数。 输出格式 计算公式返回的结果。 输入输出样例 输入1 5 输出1 8.391667 python解&#xff1a; def fun(n):a1b2s0for…

PyTorch中并行训练的几种方式

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Linux下使用宏定义判断系统架构和系统类型

文章目录 查看编译器当前支持的宏定义查找指定的宏不同架构不同系统 附录-编译器内部常用的一些宏定义宏定义实际应用使用宏定义判断系统架构使用宏定义判断系统类型 一般情况下在linux下做C/C方面的开发不需要太关注系统架构&#xff0c;当然如果涉及到不同架构下的适配问题&a…

『亚马逊云科技产品测评』活动征文|基于Lightsail 使用 html + css 实现圣诞树

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前言 又快要到今年的圣诞节了&#xff0c;去年看好多小伙伴分享自己的圣…

完美解决:yum -y install nginx 报出 没有可用软件包 nginx。错误:无须任何处理

目录 一、问题&#xff1a; 二、原因&#xff1a; 三、解决方法&#xff1a; 一、问题&#xff1a; [rootlocalhost ~]# yum -y install nginx 已加载插件&#xff1a;fastestmirror Loading mirror speeds from cached hostfile * base: mirrors.bfsu.edu.cn * extras: m…

windows电脑连接Android和iPhone真机调试

windows电脑连接Android和iPhone真机调试 目前用的是Hbuilder X编辑器&#xff0c;在正常情况下&#xff0c;Android手机需要在 "设置 ----> 更多设置 ----->关于手机 ------> 版本号&#xff08;手指点击5-7下即可打开开发者模式&#xff09;"(我的是vivo的…

环境配置|GitHub——如何在github上搭建自己写的网站

下面简单地总结了从本地的网页文件到在github服务器上展示出来即可以通过网络端打开的过程&#xff1a; &#xff08;以下可能会出现一些难点&#xff0c;照着做就可以了&#xff0c;由于笔者是小白&#xff0c;也不清楚具体原理是什么&#xff0c;希望有一天成为大神的时候能轻…

【漏洞复现】IP-guard WebServer 存在远程命令执行漏洞

漏洞描述 IP-guard是由溢信科技股份有限公司开发的一款终端安全管理软件,旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危…

MySQL InnoDB 引擎底层解析(三)

6.3.3. InnoDB 的内存结构总结 InnoDB 的内存结构和磁盘存储结构图总结如下&#xff1a; 其中的 Insert/Change Buffer 主要是用于对二级索引的写入优化&#xff0c;Undo 空间则是 undo 日志一般放在系统表空间&#xff0c;但是通过参数配置后&#xff0c;也可以用独立表空 间…

linux 系统调用流程分析

x86 1.系统调用 系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同&#xff0c;因为它调用的是内核里的代码。使用系统调用时&#xff0c;需要特殊指令以使处理器权限转换到内核态。另外&#xff0c;被调用的内核代码由系统调用号来标识&#xff0c;而…

从android.graphics.Path中取出Point点,Kotlin

从android.graphics.Path中取出Point点&#xff0c;Kotlin /*** 从一条Path中获取多少个Point点*/private fun getPoints(path: Path, pointCount: Int): Array<FloatPoint?> {val points arrayOfNulls<FloatPoint>(pointCount)val pm PathMeasure(path, false)…

[Linux] 进程入门

&#x1f4bb;文章目录 &#x1f4c4;前言计算机的结构体系与概念冯诺依曼体系结构操作系统概念目的与定位 进程概念描述进程-PCBtask_struct检查进程利用fork创建子进程 进程状态进程状态查看僵尸进程孤儿进程 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员&#xff0c…

HarmonyOS云开发基础认证【最新题库 满分答案】

系列文章 HarmonyOS应用开发者基础认证【闯关习题 满分答案】 HarmonyOS应用开发者基础认证【满分答案】 HarmonyOS云开发基础认证【最新题库 满分答案】 目录 系列文章一、判断题二、单选题三、多选题 一、判断题 1.应用架构的演进依次经历了微服务架构、单体架构、Serverle…

Python (十二) 文件

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

【深度优先搜索遍历算法的实现,广度优先遍历(BFS-Breadth_First Search),构造最小生成树】

文章目录 深度优先搜索遍历算法的实现邻接矩阵表示的无向图深度遍历实现&#xff1a;DFS算法分析 广度优先遍历&#xff08;BFS-Breadth_First Search&#xff09;构造最小生成树 深度优先搜索遍历算法的实现 邻接矩阵表示的无向图深度遍历实现&#xff1a; 实现深度优先遍历的…

MATLAB | 绘图复刻(十三) | 带NaN图例的地图绘制

有粉丝问我地图绘制如何添加NaN&#xff0c;大概像这样&#xff1a; 或者这样&#xff1a; 直接上干货&#xff1a; 原始绘图 假设我们有这样的一张图地图&#xff0c;注意运行本文代码需要去matlab官网下载Mapping Toolbox工具箱&#xff0c;但是其实原理都是相似的&…