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

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

原理:内核模块可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为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进程:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容