Linux编译模块及通过模块修改系统调用【转】

转自:https://www.jianshu.com/p/64def4ed0849

理解内核模块原理及正确编写源代码

原理:内核模块可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为Linux本身是一个单内核。单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制可以弥补这一缺陷。

Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。

一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间通信。

编写模块代码

  • 模块构造函数:
  • 执行insmod或modprobe指令加载内核模块时会调用的初始化函数。函数原型必须是module_init(),内是函数指针。
  • 模块析构函数(释放模块对象所占用的内存空间前所必须执行的一个函数):
  • 执行rmmod指令卸载模块时调用的函数。函数原型是module_exit();
  • 模块模块许可声明:
  • 函数原型是MODULE_LICENSE(),告诉内核程序使用的许可证,不然在加载时它会提示该模块污染内核。一般会写GPL。

向内核输出简单信息的代码:

test.c

#include<linux/init.h>
#include<linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static char *name="tomorrow";
static void __init name_init(void)
{
        printk("Hello World~\n");
        printk("Hello %s\n",name);
}

static void __exit name_exit(void)
{
        printk(KERN_INFO"Name module exit\n");
}

module_init(name_init);
module_exit(name_exit);

module_param(name,charp,S_IRUGO);

Makefile文件

obj-m:=test.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

make运行成功后会在原目录下生成以下文件:

 
 

将模块加载进内核:

sudo insmod test.ko

查看模块向内核输入的信息:

dmesg

 
 

卸载模块:

sudo rmmod test

 
 

实现输出当前进程信息的功能

module2.c

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>

static struct task_struct *pcurrent;

int print_current_task_info(void);
static int __init print_init(void)
{
        printk(KERN_INFO "print current task info\n");
        print_current_task_info();
        return 0;
}

static void __exit print_exit(void)
{
        printk(KERN_INFO "Finished\n");
}

int print_current_task_info(void)
{
        pcurrent=get_current();
        printk(KERN_INFO "Task state: %ld\n",current->state);
        printk(KERN_INFO "pid: %d\n",current->pid);
        printk(KERN_INFO "tgid: %d\n",current->tgid);
        printk(KERN_INFO "prio: %d\n",current->prio);
        return 0;
}
module_init(print_init);
module_exit(print_exit);

Makefile

obj-m:=module2.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

运行后在内核中将输出以下信息:

 
 

卸载后效果如下:

 
 

module3.c

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/sched.h>

static struct task_struct *pcurrent;

static int __init print_init(void)
{
        printk(KERN_INFO "print current task info\n");
        printk("pid\ttgid\tprio\tstate\n");
        for_each_process(pcurrent){
                printk("%d\t",pcurrent->pid);
                printk("%d\t",pcurrent->tgid);
                printk("%d\t",pcurrent->prio);
                printk("%ld\n",pcurrent->state);
        }
        return 0;
}
static void __exit print_exit(void)
{
        printk(KERN_INFO "Finished\n");
}

module_init(print_init);
module_exit(print_exit);

Makefile

obj-m:=module3.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

加载后效果如下:

 
 

卸载后效果如下:

 
 

修改系统调用的模块

在/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系统调用序号

 
 

发现222号和223号系统调用是空的,因此选取223作为新的系统调用号。

在/boot/System.map-3.16.0-30-generic查看系统调用表的内存地址:

 
 

为0xc1697140

编写模块文件:

module5.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");

#define SYS_CALL_TABLE_ADDRESS 0xc1697140  //sys_call_table对应的地址
#define NUM 223  //系统调用号为223
int orig_cr0;  //用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;

static int(*anything_saved)(void);  //定义一个函数指针,用来保存一个系统调用

static int clear_cr0(void) //使cr0寄存器的第17位设置为0(内核空间可写)
{
    unsigned int cr0=0;
    unsigned int ret;
    asm volatile("movl %%cr0,%%eax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中
    ret=cr0;
    cr0&=0xfffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器
    asm volatile("movl %%eax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中
    return ret;
}

static void setback_cr0(int val) //使cr0寄存器设置为内核不可写
{
    asm volatile("movl %%eax,%%cr0"::"a"(val));
}

asmlinkage long sys_mycall(void) //定义自己的系统调用
{   
    printk("模块系统调用-当前pid:%d,当前comm:%s\n",current->pid,current->comm);
    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=(int(*)(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);

MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0");
MODULE_DESCRIPTION("a module for replace a syscall");

Makefile

obj-m:=module5.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-3.16.0-30-generic
all:
        make -C  $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C  $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
~                                                               

测试程序:
test.c

#include<stdio.h>
#include<stdlib.h>
int main()
{
        unsigned long x = 0;
        x = syscall(223);        //测试223号系统调用
        printf("Hello, %ld\n", x);
        return 0;
}

测试结果:

 
 

将2号系统调用fork替换为获取当前进程的pid:

编写模块文件module.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");

#define SYS_CALL_TABLE_ADDRESS 0xc1697140  //sys_call_table对应的地址
#define NUM 2  //系统调用号为2
int orig_cr0;  //用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;

static int(*anything_saved)(void);  //定义一个函数指针,用来保存一个系统调用

static int clear_cr0(void) //使cr0寄存器的第17位设置为0(内核空间可写)
{
    unsigned int cr0=0;
    unsigned int ret;
    asm volatile("movl %%cr0,%%eax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中
    ret=cr0;
    cr0&=0xfffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器
    asm volatile("movl %%eax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中
    return ret;
}

static void setback_cr0(int val) //使cr0寄存器设置为内核不可写
{
    asm volatile("movl %%eax,%%cr0"::"a"(val));
}

asmlinkage long sys_mycall(void) //定义自己的系统调用
{   
    printk("模块系统调用-当前pid:%d,当前comm:%s\n",current->pid,current->comm);
    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=(int(*)(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);

MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0");
MODULE_DESCRIPTION("a module for replace a syscall");

测试程序:

test.c

#include<stdio.h>
#include<stdlib.h>
int main()
{
        unsigned long x = 0;
        x = syscall(2);        //测试2号系统调用
        printf("Hello, %ld\n", x);
        return 0;
}

测试结果:

插入模块前输出子进程的pid和子进程中的返回值0,为fork调用:

 
 

插入模块后,将2号fork进程替换为getpid,输出当前进程的pid:

 
 

卸载模块后,将2号进程还原为fork进程:

 
 
 
 
2人点赞
 
随笔
 
 


作者:Sawoom
链接:https://www.jianshu.com/p/64def4ed0849
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2022-05-20 14:39  Sky&Zhang  阅读(362)  评论(0编辑  收藏  举报