本文为毕业设计过程中学习相关知识、动手实践记录下来的完整笔记,通过阅读本系列文章,您可以从零基础了解系统调用的底层原理并对系统调用进行拦截。由于本人能力有限,文章中可能会出现部分错误信息,如有错误欢迎指正。另外,本系列所有内容仅作为个人学习研究的笔记,转载请标明出处。感谢您的关注!这也是作者极力推荐的一个系列文章!
完整系列文章列表
系统调用捕获和分析—通过ptrace获取系统调用信息
系统调用捕获和分析—通过strace获取系统调用信息
系统调用捕获和分析—必备的系统安全的知识点
文章目录
- LKM相关概念
- 用户态和内核态代码区别
- 管理内核模块
- 制作第一个LKM—helloworld
- 通过内核加载模块的方法添加一个系统调用
LKM相关概念
什么是LKM
LKM(Load Kernel Modules)是Linux内核为了扩展其功能所使用的可加载内核模块。
LKM的优点:动态加载,无须重新实现整个内核。
如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间。
使用LKM可以干什么
如果想要自己向内核添加一个系统调用,有两种方法。
一是修改内核源码,然后重新编译整个内核来实现。少说一个小时!!!
二是使用LKM,可以编写并编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,可省去编译新内核并用新内核重新启动的麻烦。
用户态和内核态代码区别
| 应用程序 | 内核模块 |
---|
使用函数 | libc库 | 内核函数 |
运行空间 | 用户空间 | 内核空间 |
运行权限 | 普通用户 | 超级用户 |
入口函数 | main() | module_init |
出口函数 | exit() | module_exit |
编译 | gcc | make |
链接 | gcc | insmod |
运行 | 直接运行 | insmod |
调试 | gdb | kdbug、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
查看编译生成的模块信息
modinfo hello.ko
装载,查看装载结果
insmod hello.ko
lsmod | grep hello
查看系统开机和内核模块装载信息,发现报错
dmesg
报错解决方案
在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");
#define SYS_CALL_TABLE_ADDRESS 0xffffffffbc200280
#define NUM 334
unsigned long orig_cr0;
unsigned long *sys_call_table_my=0;static unsigned long(*anything_saved)(void);
static unsigned long clear_cr0(void)
{unsigned long cr0=0;unsigned long ret;asm volatile("movq %%cr0,%%rax":"=a"(cr0));ret=cr0;cr0&=0xfffffffffffeffff;asm volatile("movq %%rax,%%cr0"::"a"(cr0));return ret;
}
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]);orig_cr0=clear_cr0();sys_call_table_my[NUM]=(unsigned long) &sys_mycall;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
:input operands
:list of clobbered registers
);
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); 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