热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

系统调用捕获和分析—使用LKM方法添加系统调用

本文为毕业设计过程中学习相关知识、动手实践记录下来的完整笔记,通过阅读本系列文章,您可以从零基础了解系统调用的底层原理并对系统调用进行拦截。由于本人能力

本文为毕业设计过程中学习相关知识、动手实践记录下来的完整笔记,通过阅读本系列文章,您可以从零基础了解系统调用的底层原理并对系统调用进行拦截。由于本人能力有限,文章中可能会出现部分错误信息,如有错误欢迎指正。另外,本系列所有内容仅作为个人学习研究的笔记,转载请标明出处。感谢您的关注!这也是作者极力推荐的一个系列文章!

完整系列文章列表
系统调用捕获和分析—通过ptrace获取系统调用信息
系统调用捕获和分析—通过strace获取系统调用信息
系统调用捕获和分析—必备的系统安全的知识点

文章目录

  • LKM相关概念
    • 什么是LKM
    • 使用LKM可以干什么
  • 用户态和内核态代码区别
  • 管理内核模块
  • 制作第一个LKM—helloworld
  • 通过内核加载模块的方法添加一个系统调用


LKM相关概念

什么是LKM

LKM(Load Kernel Modules)是Linux内核为了扩展其功能所使用的可加载内核模块。
LKM的优点:动态加载,无须重新实现整个内核。
如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间。

使用LKM可以干什么

如果想要自己向内核添加一个系统调用,有两种方法。
一是修改内核源码,然后重新编译整个内核来实现。少说一个小时!!!
二是使用LKM,可以编写并编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,可省去编译新内核并用新内核重新启动的麻烦。

用户态和内核态代码区别
应用程序内核模块
使用函数libc库内核函数
运行空间用户空间内核空间
运行权限普通用户超级用户
入口函数main()module_init
出口函数exit()module_exit
编译gccmake
链接gccinsmod
运行直接运行insmod
调试gdbkdbug、kdb、kgdb

管理内核模块

使用insmod套件
列出所有内核模块lsmod
加载或插入模块insmod
删除模块rmmod

使用modprobe命令
获取模块信息 modinfo module_name
添加模块 modprobe -a module_name
删除模块 modporbe -r module_name

模块间的依赖关系
如果模块a引用了模块b,那么在加载的过程中如果先加载模块a再加载模块b,则会发生错误。
使用insmod就可能会发生这个错误,而modprobe由于自己的机制不同会智能地先加载模块b。

制作第一个LKM—helloworld

编写hello.c文件

#include
#include
#include static int __init hello_init(void){printk(KERN_INFO "Hello world\n");return 0;
}static void __exit hello_exit(void){printk(KERN_INFO "Goodbye world\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL"); //许可证

编写Makefile文件

obj-m := hello.oall:make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

-C linux内核源代码的目录
M= hello.c和Makefile所在的目录

注意all和clean以两个空格开头,make以tab键开头

编译

make

a

查看编译生成的模块信息

modinfo hello.ko

a

装载,查看装载结果

insmod hello.ko
lsmod | grep hello

b

查看系统开机和内核模块装载信息,发现报错

dmesg

d

报错解决方案

在Makefile中第一行加入CONFIG_MODULE_SIG=n关闭签名,insmode hello.ko时再次报错File exists,原因是刚才加载了同名文件没有卸载掉,卸载重装后成功。

sudo rmmod hello.ko
sudo insmod hello.ko
dmesg


查看日志信息
日志信息位置/var/log/messages,但是没有找到。
编辑vim /etc/rsyslog.d/50-default.conf,取消messages注释(好几行都取消注释)
重启服务sudo service rsyslog restart
重装模块sudo rmmod hello.ko, sudo insmod hello.ko
cat /var/log/messages找到信息


内核入门链接https://tldp.org/LDP/lkmpg/2.6/html/index.html


通过内核加载模块的方法添加一个系统调用

先查看自己本次开机后的sys_call_table的地址,待会放在代码里。需要注意地址0x不能省略,且每次重启地址都会变化。

sudo cat /proc/kallsyms | grep sys_call_table

编写hello.c文件

#include
#include
#include
#include
#include //许可证
MODULE_LICENSE("Dual BSD/GPL");//获取当前sys_call_table地址 -> sudo cat /proc/kallsyms | grep sys_call_table
#define SYS_CALL_TABLE_ADDRESS 0xffffffffbc200280 //sys_call_table对应的地址
#define NUM 334 //添加的系统调用号为334
unsigned long orig_cr0; //用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;static unsigned long(*anything_saved)(void); //定义一个函数指针,用来保存一个系统调用//使cr0寄存器的第17位设置为0,让内核空间可写
static unsigned long clear_cr0(void)
{unsigned long cr0=0;unsigned long ret;asm volatile("movq %%cr0,%%rax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中ret=cr0;cr0&=0xfffffffffffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器asm volatile("movq %%rax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中return ret;
}//恢复cr0寄存器的值,设置为内核不可写
static void setback_cr0(unsigned long val)
{asm volatile("movq %%rax,%%cr0"::"a"(val));
}//定义自己的系统调用
asmlinkage int sys_mycall(void)
{ printk("模块系统调用-当前pid:%d,当前comm:%s\n",current->pid,current->comm);printk("hello,world!\n");return current->pid;
}//添加模块时执行的代码
static int __init call_init(void)
{sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);printk("call_init......\n");anything_saved=(unsigned long(*)(void))(sys_call_table_my[NUM]);//保存系统调用表中的NUM位置上的系统调用orig_cr0=clear_cr0();//使内核地址空间可写sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系统调用替换NUM位置上的系统调用setback_cr0(orig_cr0);//使内核地址空间不可写return 0;
}//卸载模块时执行的代码
static void __exit call_exit(void)
{printk("call_exit......\n");orig_cr0=clear_cr0();sys_call_table_my[NUM]=(unsigned long)anything_saved;//将系统调用恢复setback_cr0(orig_cr0);
}module_init(call_init);
module_exit(call_exit);

代码相关解释
1、module_init在insmod时调用,module_exit在rmmod时调用

2、C语言中内嵌汇编语法格式

asm (assembler template
:output operands /* optional */
:input operands /* optional */
:list of clobbered registers /* optional */
);

3、movl 操作32位数据,movq 操作64位数据

4、CR寄存器

CR0~CR3是控制寄存器,用于控制和确定处理器的操作模式以及当前执行任务的特性。
CR0中含有控制处理器操作模式和状态的系统控制标志;
CR1保留不用;
CR2含有导致页错误的线性地址;
CR3中含有页目录表物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base addressRegister)。

5、系统调用号的选择
由于需要查看添加的系统调用号是多少,网上说/usr/include/asm/unistd_32.h文件中有,
但是比较高的版本内核里的这个文件中的内容和网上说的不太一样,所以这里使用上一次编译好的4.13.10版本内核。
添加的系统调用号为334,即原本系统调用号到333,这里使用334新添加一个系统调用号。
linux-source-4.13.0/arch/x86/entry/syscalls下的syscall_64.tbl文件中查看。
当然也可以选择原有的内核版本,只不过没有源码看不到系统调用号信息,但是可以猜一个。

编写Makefile文件

CONFIG_MODULE_SIG=n
obj-m:=hello.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/lib/modules/$(shell uname -r)/build
all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

编译并加载模块

sudo make
sudo insmod hello.ko
lsmod | grep hello 查看添加模块的信息
dmesg 获取内核信息

测试系统调用,编写lab.c

#include
#include
#include
#include
#includeint main(){int x = 0;x = syscall(334); //测试334号系统调用printf("res = %d\n", x);return 0;
}

编译运行,查看dmesg的信息

gcc lab.c -o lab
./lab
dmesg

参考 https://blog.csdn.net/Egqawkq/article/details/88970390


附动态获取sys_call_table地址方法https://www.cnblogs.com/LittleHann/p/3854977.html#_lab2_3_0


推荐阅读
author-avatar
手机用户2502924641
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有