• 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是要运行的二进制文件
    
    • 1
    • 2
    • 3
    • 4

    一个简单的例子

    有这样一个简单的main函数

    // main.cpp
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        int rfd = open("test.txt", O_RDONLY);
        std::cout << "open(), with ret: " << rfd << ", err(" << errno << "): " << strerror(errno) << std::endl;
    
        close(rfd);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    // hook_lib.cpp
    #include   
    #include  
    #include 
    #include 
    
    typedef int(*OPEN)(const char *__path, int __oflag, ...);
    
    int open(const char *__path, int __oflag, ...){
        std::cout << "!!! open函数被拦截了" << std::endl;
        errno = 2;
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    正常编译并运行main.cpp

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

    输出是正常的

    在这里插入图片描述

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

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

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

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

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

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

    在这里插入图片描述

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

    这次以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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行结果

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    控制注入异常的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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行结果

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    延时返回

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

    sleep(time_s);		// 秒
    usleep(time_ms);	// 微秒
    
    • 1
    • 2

    动态控制异常注入

    希望能从第三方位置读取配置,通过变更配置动态的对指定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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    conf.txt

    test.txt 2 1000000
    
    • 1

    执行结果

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从redis获得配置

    hook逻辑

    #include 
    // 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在redis中添加如下变量

    set /hook/write/epath test.txt
    set /hook/write/eno 5
    set /hook/write/ehang 1000000
    
    • 1
    • 2
    • 3

    执行结果

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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

  • 相关阅读:
    【Java编程进阶之路--面向对象】
    Go语言学习笔记——错误处理
    linux上安装tomcat
    【无标题】markdow 模板
    实现自动扫描工作区npm包并同步cnpm
    Java 进阶File类、IO流
    学习C++第二十五课--using定义模板别名与显式指定模板参数笔记
    【力扣白嫖日记】SQL
    解锁云计算的未来:AI、容器和数据隐私的挑战
    android自定义View: 九宫格解锁
  • 原文地址:https://blog.csdn.net/BBJG_001/article/details/134541491