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

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

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

相关文章

微信小程序记住密码,让登录解放双手

密码是用户最重要的数据&#xff0c;也是系统最需要保护的数据&#xff0c;我们在登录的时候需要用账号密码请求登录接口&#xff0c;如果用户勾选记住密码&#xff0c;那么下一次登录时&#xff0c;我们需要将账号密码回填到输入框&#xff0c;用户可以直接登录系统。我们分别…

Thinkphp5报错:htmlentities() expects parameter 1 to be string, array given

注意注意&#xff1a; 本文对应 ThinkPHP5.1 版本。前言 - 出现问题的原因 为避免出现 XSS 安全问题&#xff0c;Thinkphp5.1 默认变量输出都会使用 htmlentities 方法进行转义输出。 如果不想被转义输出&#xff0c;模板渲染时&#xff0c;需要在变量后面加上 raw方法&#xf…

综述--知识蒸馏

知识蒸馏&#xff08;Knowledge Distillation&#xff09;作为一种类似迁移学习的方法&#xff0c;最早是由Hinton等人在2015年提出[1]&#xff0c;在近年来得到了广泛的关注和研究。它通过将一个复杂的模型的知识转移到一个简化的模型中&#xff0c;以实现在模型大小和计算资源…

每天一道算法题(九)——寻找字符串中所有字母异位词的子串

文章目录 前言1、问题2、示例3、解决方法&#xff08;1&#xff09;方法1——双指针 总结 前言 提示&#xff1a; 1、问题 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字…

推荐一个windows上传linux服务器/linux服务器的docker镜像的工具,摆脱docker cp,以及解决常见问题。

Lrzsz&#xff0c;又称为lrz和lsz&#xff0c;是一个用于在Unix、Linux、macOS等操作系统上进行串行文件传输的工具。它支持基于X/Y/ZModem协议的文件传输&#xff0c;能够通过串口或者Telnet/SSH等网络连接进行文件传输。Lrzsz具有传输速度快、可靠性高、易于使用等特点&#…

详解ES6的Promise

ES6&#xff08;ECMAScript 6&#xff09;是JavaScript的一种标准&#xff0c;也被称为ES2015。它是在2015年发布的第六个ECMAScript标准版本&#xff0c;引入了许多新的语法和特性来增强JavaScript的功能和可读性。 文章目录 一、创建promise 二、处理Promise 三.Promise链…

OpenCV入门9——目标识别(车辆统计)

文章目录 图像轮廓查找轮廓绘制轮廓轮廓的面积与周长多边形逼近与凸包外接矩形项目总览【车辆统计】视频加载【车辆统计】去背景【车辆统计】形态学处理【车辆统计】逻辑处理【车辆统计】显示信息【车辆统计】 图像轮廓 查找轮廓 # -*- coding: utf-8 -*- import cv2 import n…

return关键字

return 是编程语言中的一个关键字&#xff0c;用于结束当前函数或方法的执行&#xff0c;并返回一个值&#xff08;如果函数有返回值的话&#xff09;。return 的作用是将程序的控制权从当前的函数或方法返回给调用方&#xff0c;并且返回一个指定的值。 在函数中的作用&#…

vue3的api使用

vue官网链接 API 全览图 createApp() // 内敛根组建import { createApp } from vueconst app createApp({})// 导入根组建import App from ./App.vueconst app createApp(App)app.mount() import { createApp } from vue const app createApp() const vm app.mount(#app…

Threejs_09 gltf模型的引入(效果初现)

本节使用到的图片、素材、gltf文件&#xff0c;都是老陈打码的原素材 支持&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#x…

口罩问题

描述 两年前的今天&#xff0c;疫情肆虐武汉&#xff0c;曾经几毛钱的一个一次性医用口罩供不应求&#xff0c;价格也是水涨船高&#xff0c;更有甚者一只口罩卖到了10元。在利益的驱使下也让一些不良商家道德涣散&#xff0c;据相关报道&#xff0c;有商家回收废弃口罩并重新…

C++:重载

运算符重载本质上就是函数重载。这里举例子是定义三维数组。下面代码是进行简单的定义&#xff0c;输出分别是000和123。 #include<iostream> using namespace std; class Vec3 { public:Vec3() :r(0), g(0), b(0){}Vec3(int _r, int _g, int _b):r(_r),g(_g),b(_b){} p…

SQL DELETE 语句:删除表中记录的语法和示例,以及 SQL SELECT TOP、LIMIT、FETCH FIRST 或 ROWNUM 子句的使用

SQL DELETE 语句 SQL DELETE 语句用于删除表中的现有记录。 DELETE 语法 DELETE FROM 表名 WHERE 条件;注意&#xff1a;在删除表中的记录时要小心&#xff01;请注意DELETE语句中的WHERE子句。WHERE子句指定应删除哪些记录。如果省略WHERE子句&#xff0c;将会删除表中的所…

ANSYS中如何手动为装配体添加接触约束教程

接触的类型&#xff1a; 在接触类型&#xff08;Type&#xff09;选项中&#xff0c;软件共提供了绑定接触&#xff08;Bonded&#xff09;、不分离接触&#xff08;No Separation&#xff09;、无摩擦接触&#xff08;Frictionless&#xff09;、粗糙接触&#xff08;Rough&a…

深入Ansible

1.什么是ansible ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 ansible是基于 paramiko 开发的…

如何使用GitHub托管代码(简易版)

打开IDEA并打开你的项目。 确保你已经安装了Git&#xff0c;并且在你的计算机上设置了Git的全局用户信息&#xff08;用户名和电子邮箱地址&#xff09;。这里设置用户签名和将来登录 GitHub&#xff08;或其他代码托管中心&#xff09;的账号没有任何关系。Git 首次安装必须设…

Linux操作系统使用及C高级编程-D11-D13结构体

由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员”&#xff0c;其描述了一块内存空间的大小及解释意义。 语法&#xff1a; struct 结构体名 { 结构体成员列表 }; 下图是struct的定义和使用方法&#xff0c;其中20行这种赋值方式错误&#xf…

向pycdc项目提的一个pr

向pycdc项目提的一个pr 前言 pycdc这个项目&#xff0c;我之前一直有在关注&#xff0c;之前使用他反编译python3.10项目&#xff0c;之前使用的 uncompyle6无法反编译pyhton3.10生成的pyc文件&#xff0c;但是pycdc可以&#xff0c;但是反编译效果感觉不如uncompyle6。但是版…

BGP笔记

自治系统----AS AS划分的原因&#xff1a;如果网络太大&#xff0c;路由数量进一步增加&#xff0c;路由表规模变得太大&#xff0c;会导致路由收敛速度变慢&#xff0c;设备性能消耗加大&#xff0c;且全网设备对于路由信息的认知不同&#xff0c;造成流量通讯障碍 AS号&…