首页 > 其他分享 >系统调用捕获和分析—使用LKM方法添加系统调用

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

时间:2022-11-08 20:10:35浏览次数:50  
标签:cr0 调用 系统 LKM call 内核 模块 hello

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

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

文章目录

  • LKM相关概念
  • 什么是LKM
  • 使用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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

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.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

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

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

编译

make

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

查看编译生成的模块信息

modinfo hello.ko

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

装载,查看装载结果

insmod hello.ko
lsmod | grep hello

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

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

dmesg

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

报错解决方案

在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 <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>

//许可证
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<stdio.h>
#include<stdlib.h>
#include<linux/kernel.h>
#include<sys/syscall.h>
#include<unistd.h>

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

编译运行,查看dmesg的信息

gcc lab.c -o lab
./lab
dmesg


标签:cr0,调用,系统,LKM,call,内核,模块,hello
From: https://blog.51cto.com/u_15457669/5834737

相关文章

  • 系统调用捕获和分析—Ring0层kprobe劫持系统调用
    本文为毕业设计过程中学习相关知识、动手实践记录下来的完整笔记,通过阅读本系列文章,您可以从零基础了解系统调用的底层原理并对系统调用进行拦截。由于本人能力有限,文章中可......
  • 系统调用捕获和分析—修改内核方法添加系统调用
    本文为毕业设计过程中学习相关知识、动手实践记录下来的完整笔记,通过阅读本系列文章,您可以从零基础了解系统调用的底层原理并对系统调用进行拦截。由于本人能力有限,文章中可......
  • Linux系统编程——进程控制
    在学习Linux系统编程总结了笔记,并分享出来。09-linux-day05(进程控制)目录:一、学习目标二、进程1、进程和程序2、单道和多道程序设计3、进程的状态转化4、MMU的作用5、PCB......
  • Linux系统编程——信号
    在学习Linux系统编程总结了笔记,并分享出来。09-linux-day07(信号)目录:一、学习目标二、进程通信——信号1、信号的概念回顾2、阻塞信号集、未决信号集、信号产生3、raise和a......
  • Linux系统编程——进程间通信
    在学习Linux系统编程总结了笔记,并分享出来。09-linux-day06(进程间通信)目录:一、学习目标二、进程通信——管道1、管道的概念2、管道通信举例3、父子进程实现ps、grep命令4......
  • 【操作系统】典型内核架构对比
    目录一、架构分类1、宏内核2、微内核3、混合内核:二、Linux三、iOSDarwin四、WindowsNT参考:(1)操作系统实战45讲(2)Linux系统——架构浅析一、架构分类内核是操作系......
  • 关于如何在Windows下通过Golang调用cmd指令
    太nm操蛋了,我tm弄了两小时。起因目前我的项目中,当并发量提高的时候会出现UDP的bufferqueuefull的情况,我怀疑是因为UDP端口释放太慢导致堆积。于是就打算用golang写一......
  • Centos修改系统时间
    Centos系统,必须同时修改系统时间和硬件时间,才可以保证修改有效,单纯的使用date命令修改系统时间,是立即生效,重启后系统还原。具体操作如下:1.date{查看目前本地的时间}2.hwc......
  • 20220920 14. 磁盘配额(Quota)与进阶文件系统管理
    14.1磁盘配额(Quota)的应用与实作14.1.1什么是Quota在Linux系统中,由于是多用户多任务的环境,所以会有多人共同使用一个硬盘空间的情况发生,因此管理员应该适当的限制......
  • 20220923 17. 认识系统服务 (daemons)
    17.1什么是daemon与服务(service)常驻在记体体中的程序,且可以提供一些系统或网络功能,那就是服务系统为了某些功能必须要提供一些服务(不论是系统本身还是网络方面),这个......