说明:该方法是在linux内核版本为4.4.4下进行操作(高版本可能会出现无法修改系统调用表的问题)
- 内核版本的查看:打开终端,输入命令
uname -r进行查看
在确定linux内核版本是4.4.4之后进行以下一系列操作:
- 在
/usr/include/x86_64-linux-gnu/asm/uniste_64.h中查找一个未使用的系统调用编号。- 如果在系统调用编号列表中间有未使用的系统调用编号,可以直接使用它,例如,225和227号被使用,但是226没有被使用,则可以使用226该编号。
- 如果在系统调用编号列表中间没有未使用的系统调用编号,可以查看列表最后一个编号,例如,最后一个编号是325,则可以使用326该编号,或者更大的数字,如327,328等。
在此次测试中,使用的是326编号(根据自己机器情况进行确定)
- 获取本机的sys_call_table的地址,命令为:
sudo cat /boot/System map-'uname -r'|grep sys_call_table(注意命令中的将uname -r括起的符号为键盘Tab按键上方的按键在英文输入法下的符号``) 在此次测试中,本机的sys_call_table的地址是ffffffff81a001c0
说明:以上两步获得的编号和地址要在插入模块的代码中使用
- 编写代码user_define_mode.c 使用touch user_define_mode.c命令进行user_define_mode.c文件的新建,gedit user_deifne_mode.c命令进行对user_define_mode.c的编辑(当然也可以直接在图形界面进行新建与编辑),user_define_mode.c的代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
//从usr/include/x86_64-linux-gnu/asm/unistd_64.h最后查找到的一个未使用的系统调用编号
#define my_syscall_num 326
//如下的这个就是上一步获得的sys_call_table的地址值
#define sys_call_table_adress 0xffffffff81a001c0
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
asmlinkage long sys_mycall(long);
int orig_cr0;
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void);
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm("movq %%cr0, %%rax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffffffffffeffff;
asm("movq %%rax, %%cr0"::"a"(cr0));
return ret;
}
void setback_cr0(unsigned int val) //读取val的值到rax寄存器,再将rax寄存器的值放入cr0中
{
asm volatile("movq %%rax, %%cr0"::"a"(val));
}
static int __init init_addsyscall(void)
{
printk("hello, kernel\n");
sys_call_table = (unsigned long *)sys_call_table_adress;//获取系统调用服务首地址
anything_saved = (int(*)(void))(sys_call_table[my_syscall_num]);//保存原始系统调用的地址
orig_cr0 = clear_and_return_cr0();//设置cr0可更改
sys_call_table[my_syscall_num] = (unsigned long)&sys_mycall;//更改原始的系统调用服务地址
setback_cr0(orig_cr0);//设置为原始的只读cr0
return 0;
}
asmlinkage long sys_mycall(long num)
{
printk("This is my system call!\n");
return num;
}
static void __exit exit_addsyscall(void)
{
//设置cr0中对sys_call_table的更改权限。
orig_cr0 = clear_and_return_cr0();//设置cr0可更改
//恢复原有的中断向量表中的函数指针的值。
sys_call_table[my_syscall_num] = (unsigned long)anything_saved;
//恢复原有的cr0的值
setback_cr0(orig_cr0);
printk("call exit \n");
}
module_init(init_addsyscall);
module_exit(exit_addsyscall);
MODULE_LICENSE("GPL");在编写完代码user_define_mode.c之后,要先新建一个Makefile文件,Makefile文件要和user_define_mode.c文件放在同一个文件夹中
- 编写Makefile脚本(Makefile中内容的格式一定要正确,obj-m :=后的内容一定要与前边插入模块的文件名相同,例如,前边使用的是user_define_mode.c,那么此处就应该是user_define_mode.o)
obj-m := user_define_mode.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions- make user_define_mode模块,并将模块插入(以下几步需要在上边user_define_mode.c和Makefile文件所在的文件夹下进行)
make #执行该命令之后会生成一个user_define_mode.ko文件
sudo insmod user_define_mode.ko # 插入模块
lsmod #查看模块是否插入成功插入成功的显示如下图(注意Used by的信息为0则插入成功):
- 进行测试 此次的测试代码1:
#include<stdio.h>
#include<unistd.h>
int main(){
long pid = 0;
long abc = 500;
pid = syscall(326,abc); //这里的326要与在user_define_mode.c中#define my_syscall_num的编号一致
printf("num:%ld\n",pid);
return 0;
}在模块插入成功的情况下,使用该测试代码可看出代码执行结果:num:500,当给abc赋值为负数的时候,由于linux机制,返回的总是-1,并不是表示错误。
测试代码2:
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <stdlib.h>
int main(void)
{
long in;
printf("-----------------------------------------\n");
in = syscall(326,malloc(5));
printf("use syscall(326) : in = %ld\n",in);
return 0;
}在模块插入成功的情况下,使用该测试代码可看出代码执行结果:返回开辟空间的首地址,当给malloc(负数)时,返回0,表示开辟空间失败。 使用命令dmesg可以看到所添加的系统函数的打印结果:This is my system call!
多次测试,可以使用命令sudo rmmod mode 删除之前加上的模块(如本次测试的删除模块的命令为sudo rmmod user_define_mode)
