了解linux 内核模块 、强制卸载

关于一种调用设备驱动程序,出现异常时,驱动设备无法正常退出(lsmod 显示驱动设备被占用、无法rmmod 退出),也无法继续使用的问题。

1 linux 模块

内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。模块具有以下特点:

模块本身不被编译入内核映像,从而控制了内核的大小 。

模块一旦被加载,它就和内核中的其他部分一样 。

模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

【文章福利】小编在群文件上传了一些个人觉得比较好得学习书籍、视频资料,有需要的可以进群【977878001】领取!!!额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

2 编译内核模块

hello 模块

#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h>   // init&exit相关宏
#include <linux/kernel.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");      
MODULE_DESCRIPTION("hello world module");
 
static int __init hello_init(void)
{
      printk(KERN_WARNING "hello world.\n");
      return 0;
}
static void __exit hello_exit(void)
{
      printk(KERN_WARNING "hello exit!\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

Makefile

ifneq ($(KERNELRELEASE),)
obj-m :=hello.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
all:
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
endif

使用

make

sudo insmod hello.ko

sudo rmmod hello

dmesg

[10611.395806] hello world.

[10644.832158] hello exit!

KERNELRELEASE

这个 makefile 在一次典型的建立中要被读 2 次。

KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次(刚开始)读取执行此Makefile时,KERNELRELEASE没有被定义(为空),所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C(KDIR)指明跳转到内核源码目录下读取那里的Makefile,M=(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容(指 obj-m :=hello.o)。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。

以上介绍了内核模块的概念以及内核模块的编译。

3 强制卸载内核模块

发现一篇非常好的文章,对照无法强制卸载内核模块的各个情况,相应进行了介绍。

原文链接:Linux强制卸载内核模块(由于驱动异常导致rmmod不能卸载)

对照文章,本人遇到的驱动被占用问题属于以下这种类型。

(1)现象

rmmod: ERROR: Module XXX is in use

(2)其本质就是模块的模块的引用计数不为 0, 要解决此类问题, 只需要将模块的引用计数强制置为 0 即可.

查找到 none_exit 模块的内核模块结构 struct moudle, 可以通过 find_module 函数查找到, 也可以参照 find_module 函数实现.

重置模块的引用计数

//  清除驱动的引用计数
for_each_possible_cpu(cpu)
{
    local_set((local_t*)per_cpu_ptr(&(mod->refcnt), cpu), 0);
    //local_set(__module_ref_addr(mod, cpu), 0);
    //per_cpu_ptr(mod->refptr, cpu)->decs;
    //module_put(mod);
}
atomic_set(&mod->refcnt, 1);

4 强制卸载驱动源码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cpumask.h>
#include <linux/list.h>
#include <asm-generic/local.h>
#include <linux/platform_device.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>
 
 
/*
 *  加载模块的时候, 传递字符串到模块的一个全局字符数组里面
 *
 *  module_param_string(name, string, len, perm)
 *
 *  @name   在加载模块时,参数的名字
 *  @string 模块内部的字符数组的名字
 *  @len    模块内部的字符数组的大小
 *  #perm   访问权限
 *
 * */
static char *modname = NULL;
module_param(modname, charp, 0644);
MODULE_PARM_DESC(modname, "The name of module you want do clean or delete...\n");
 
 
//#define CONFIG_REPLACE_EXIT_FUNCTION
 
#ifdef CONFIG_REPLACE_EXIT_FUNCTION
//  此处为外部注册的待卸载模块的exit函数
//  用于替代模块原来的exit函数
//  注意--此函数由于需要被待删除模块引用, 因此不能声明为static
/* static */ void force_replace_exit_module_function(void)
{
    /
    //  此处完善待卸载驱动的 exit/cleanup 函数
    /
 
    printk("module %s exit SUCCESS...\n", modname);
//    platform_device_unregister((struct platform_device*)(*(int*)symbol_addr));
}
#endif  //  CONFIG_REPLACE_EXIT_FUNCTION
 
 
static int force_cleanup_module(char *del_mod_name)
{
    struct module   *mod = NULL, *relate = NULL;
    int              cpu;
#ifdef CONFIG_REPLACE_EXIT_FUNCTION
    void            *origin_exit_addr = NULL;
#endif
 
    /
    //  找到待删除模块的内核module信息
    /
#if 0
    //  方法一, 遍历内核模块树list_mod查询
    struct module *list_mod = NULL;
    /*  遍历模块列表, 查找 del_mod_name 模块  */
    list_for_each_entry(list_mod, THIS_MODULE->list.prev, list)
    {
        if (strcmp(list_mod->name, del_mod_name) == 0)
        {
            mod = list_mod;
        }
    }
    /*  如果未找到 del_mod_name 则直接退出  */
    if(mod == NULL)
    {
        printk("[%s] module %s not found\n", THIS_MODULE->name, modname);
        return -1;
    }
#endif
 
    //  方法二, 通过find_mod函数查找
    if((mod = find_module(del_mod_name)) == NULL)
    {
        printk("[%s] module %s not found\n", THIS_MODULE->name, del_mod_name);
        return -1;
    }
    else
    {
        printk("[before] name:%s, state:%d, refcnt:%u\n",
                mod->name ,mod->state, module_refcount(mod));
    }
 
    /
    //  如果有其他驱动依赖于当前驱动, 则不能强制卸载, 立刻退出
    /
    /*  如果有其他模块依赖于 del_mod  */
    if (!list_empty(&mod->source_list))
    {
        /*  打印出所有依赖target的模块名  */
        list_for_each_entry(relate, &mod->source_list, source_list)
        {
            printk("[relate]:%s\n", relate->name);
        }
    }
    else
    {
        printk("No modules depond on %s...\n", del_mod_name);
    }
 
    /
    //  清除驱动的状态和引用计数
    /
    //  修正驱动的状态为LIVE
    mod->state = MODULE_STATE_LIVE;
 
    //  清除驱动的引用计数
    for_each_possible_cpu(cpu)
    {
        local_set((local_t*)per_cpu_ptr(&(mod->refcnt), cpu), 0);
        //local_set(__module_ref_addr(mod, cpu), 0);
        //per_cpu_ptr(mod->refptr, cpu)->decs;
        //module_put(mod);
    }
    atomic_set(&mod->refcnt, 1);
 
#ifdef CONFIG_REPLACE_EXIT_FUNCTION
    /
    //  重新注册驱动的exit函数
    /
    origin_exit_addr = mod->exit;
    if (origin_exit_addr == NULL)
    {
        printk("module %s don't have exit function...\n", mod->name);
    }
    else
    {
        printk("module %s exit function address %p\n", mod->name, origin_exit_addr);
    }
 
    mod->exit = force_replace_exit_module_function;
    printk("replace module %s exit function address (%p -=> %p)\n", mod->name, origin_exit_addr, mod->exit);
#endif
 
    printk("[after] name:%s, state:%d, refcnt:%u\n",
            mod->name, mod->state, module_refcount(mod));
 
    return 0;
}
 
 
static int __init force_rmmod_init(void)
{
    return force_cleanup_module(modname);
}
 
 
static void __exit force_rmmod_exit(void)
{
    printk("=======name : %s, state : %d EXIT=======\n", THIS_MODULE->name, THIS_MODULE->state);
}
 
module_init(force_rmmod_init);
module_exit(force_rmmod_exit);
 
MODULE_LICENSE("GPL");

5 Makefile

MODULE_NAME := force_rmmod
#MODCFLAGS:=-O2 -Wall -DMODULE -D__KERNEL__ -DLINUX -std=c99
#EXTRA_CFLAGS  += $(MODULE_FLAGS) $(CFG_INC) $(CFG_INC)
EXTRA_CFLAGS  += -g -std=gnu99  -Wfatal-errors 
 
 
 
ifneq ($(KERNELRELEASE),)  # kernelspace
 
obj-m := $(MODULE_NAME).o
 
else                        # userspace
 
 
CURRENT_PATH ?= $(shell pwd)
LINUX_KERNEL ?= $(shell uname -r)
LINUX_KERNEL_PATH ?= /lib/modules/$(LINUX_KERNEL)/build
 
CURRENT_PATH := $(shell pwd)
 
modules:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
 
modules_install:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules_install
 
 
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
    rm -f modules.order Module.symvers Module.markers
 
.PHNOY:
    modules modules_install clean
 
 
 
endif

6 使用方法

# 通过 `modname` 制定待卸载驱动的信息

sudo insmod force_rmmod.ko modname=卸载驱动名称

sudo rmmod 卸载驱动名称

 

第一部分 基础知识 <br>1.1 什么是LKMs <br>1.2 什么是系统调用 <br>1.3 什么是内核符号表(Kernel-Symbol-Table) <br>1.4 如何实现从用户空间到内核空间的转换 <br>1.5 使用用户空间函数的方法 <br>1.6 常用内核空间函数列表 <br>1.7 什么是内核守护进程 <br>1.8 创建你自己的设备 <br><br>第二部分 渐入佳境 <br>2.1 如何截获系统调用 <br>2.2 一些有趣的系统调用 <br>2.2.1 发现有趣的系统调用(strace方法) <br>2.3 迷惑内核的系统表 <br>2.4 和文件系统有关的攻击 <br>2.4.1 如何隐藏文件 <br>2.4.2 如何隐藏文件的内容(完全的) <br>2.4.3 如何隐藏文件的某一部分(一个实现原型) <br>2.4.4 如何重新定向或者监视文件操作 <br>2.4.5 如何避免任何文件权限问题 <br>2.4.6 如何使的一个有入侵工具的目录不可存取 <br>2.4.7 如何改变CHROOT环境 <br>2.5 和进程有关的入侵 <br>2.5.1 如何隐藏任何进程 <br>2.5.2 如果改变文件的执行结果 <br>2.6 和网络(Socket)有关的入侵 <br>2.6.1 如果控制Socket操作 <br>2.7 TTY纪录的方法 <br>2.8 用LKMs写病毒 <br>2.8.1 如何让LKM病毒感染任何文件(不仅仅是模块) <br>2.8.2 如何让LKM病毒帮助我们进入系统 <br>2.9 使我们的LKM不可见,而且不可卸载 <br>2.10 其他的入侵kerneld进程的方法 <br>2.11 如何检查当前的我们的LKM <br><br>第三部分 解决方案(给系统管理员) <br>3.1 LKM检测的理论和想法 <br>3.1.1 一个使用的检测器的原形 <br>3.1.2 一个密码保护的create_module(...)的例子 <br>3.2 防止LKM传染者的方法 <br>3.3 使你的程序不可以被跟踪(理论) <br>3.3.1 一个反跟踪的实用例子 <br>3.4 使用LKMs来防护你的linux内核 <br>3.4.1 为什么我们必须允许任何一个程序都拥有可执行的权限 <br>3.4.2 链接的补丁 <br>3.4.3 /proc权限的补丁 <br>3.4.4 安全级别的补丁 <br>3.4.5 底层磁盘补丁 <br><br>第四部分 一些更好的想法(给hacker的) <br>4.1 击败系统管理员的LKM的方法 <br>4.2 修补整个内核-或者创建Hacker-OS <br>4.2.1 如何在/dev/kmem中找到内核符号表 <br>4.2.2 新的不需要内核支持的'insmod' <br>4.3 最后的话 <br><br>第五部分 最近的一些东西:2.2.x版本的内核 <br>5.1 对于LKM作者来说,一些主要的不同点 <br><br>第六部分 最后的话 <br>6.1 LKM传奇以及如何使得一个系统即好用又安全 <br>6.2 一些资源链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是阿发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值