经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Linux/Shell » 查看文章
Linux Kernel CFI机制简介及测试禁用
来源:cnblogs  作者:SkyOnSky  时间:2024/7/29 9:26:24  对本文有异议

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

前言


当我们为android移植linux的驱动程序的时候,总会遇到一些错误,这些错误有一部分就是android 内核开启的安全的机制导致的。本文就会介绍一种内核的安全机制:Kernel Control Flow Integrity(kCFI)。

此外,这里还要说明一下,Control Flow Integrity(CFI)与 Kernel Control Flow Integrity(kCFI)是不一样的,kCFI只检查函数指针,CFI还具备其他很多的检查,详情请参考:https://clang.llvm.org/docs/ControlFlowIntegrity.html





Kernel Control Flow Integrity(kCFI)原理简单介绍


Control Flow Integrity的翻译是控制流完整性,从直译来看,其实就是用一些方法保证来保证我们的指令执行到正确的位置。我们从clang官方文档知道,kCFI只检查函数指针,那么其实kCFI就是保证函数指针跳转到正确的位置,并且返回到正确的位置。

从这里来看,其实我们可以看到对于函数指针来说,我们需要保护两个地方:跳转到正确的位置、返回到正确的位置。这两个地方有两个专有名词:

  • forward-edge
  • backward-edge

此外,我们还应该知道,在编写代码的时候,分为直接函数调用(direct function call),间接函数调用(indirect function call)。他们的示例如下:

  1. void target(void)
  2. {
  3. //... ...
  4. }
  5. typedef void(*fn)(void);
  6. int main(int argc, char * argv[])
  7. {
  8. // direct function call
  9. target();
  10. //indirect function call
  11. fn _id_fn = target;
  12. _id_fn();
  13. }

从示例可以知道,indirect function call其实就是函数指针这种调用形式。

此外,我们还要知道,如果我们想破坏代码的执行流,那么我们必须在可写、可读、可执行的内存里面写入shellcode,并跳转到这个shellcode,否则我们的代码是无法工作的。那么显而易见的事情是,通过函数指针来调用函数,我们的目标是明确的,因此我们可以校验这些目标的原型、地址等等信息。

因为我们需要验证目标的原型、地址等等信息,所以,当我们在生成可执行文件的时候,需要知道所有的函数目标的信息,这个时候,就需要一个叫做Link Time Optimization(LTO)的功能,因为只有最终可执行文件链接时,才知道所有的函数目标信息。





kCFI演示示例


首先在qemu中运行一个arm64的linux模拟器,然后为linux内核配置如下内核选项:

  1. # General architecture-dependent options -> LTO
  2. CONFIG_CFI_CLANG=y
  3. CONFIG_CFI_PERMISSIVE=y

我们的测试驱动例子:

  1. #include <linux/module.h> // 必须的头文件,定义了MODULE_*宏
  2. #include <linux/kernel.h> // 包含内核信息头文件
  3. #include <linux/init.h> // 包含 __init 和 __exit 宏
  4. static int param_int = 0;
  5. module_param(param_int, int, 0644);
  6. static void hello_cfi_i(int i){
  7. printk(KERN_INFO "hello_cfi_i\n");
  8. }
  9. static void hello_cfi_f(float i){
  10. printk(KERN_INFO "hello_cfi_f\n");
  11. }
  12. typedef void (*hello_cfi_func_i)(int);
  13. typedef void (*hello_cfi_func_f)(float);
  14. struct node {
  15. hello_cfi_func_i i0[1];
  16. hello_cfi_func_f f0[1];
  17. hello_cfi_func_i i1[1];
  18. hello_cfi_func_f f1[1];
  19. hello_cfi_func_i i2[1];
  20. hello_cfi_func_f f2[1];
  21. };
  22. struct node fn_arr = {
  23. .i0 = {hello_cfi_i},
  24. .f0 = {hello_cfi_f},
  25. .i1 = {hello_cfi_i},
  26. .f1 = {hello_cfi_f},
  27. .i2 = {hello_cfi_i},
  28. .f2 = {hello_cfi_f},
  29. };
  30. // 模块初始化函数
  31. static int __init hello_init(void)
  32. {
  33. fn_arr.i0[param_int](param_int);
  34. printk(KERN_INFO "Hello, World!\n");
  35. return 0; // 返回0表示加载成功
  36. }
  37. // 模块清理函数
  38. static void __exit hello_exit(void)
  39. {
  40. printk(KERN_INFO "Goodbye, World!\n");
  41. }
  42. // 注册模块初始化和清理函数
  43. module_init(hello_init);
  44. module_exit(hello_exit);
  45. MODULE_LICENSE("GPL"); // 模块许可证
  46. MODULE_AUTHOR("Your Name"); // 模块作者
  47. MODULE_DESCRIPTION("A simple Hello World Module"); // 模块描述

我们传入参数0,执行fn_arr.i0[0],测试正常跳转

rep_img

我们传入参数1,执行fn_arr.i0[1],测试传入参数原型不匹配(本来应该调用hello_cfi_i,实际调用hello_cfi_f)

rep_img

测试数组越界访问

rep_img




后记


从上面来看,kCFI一般会对调用类型、调用的目标地址进行判断,更多细节,去看CFI的具体原理。

参考文献




打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

原文链接:https://www.cnblogs.com/Iflyinsky/p/18328698

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号