吼吼
全称Loadable Kernel Modules
可加载核心模块 也称为内核模块 其实就是内核态的可执行程序 主要包括驱动程序
设备驱动
文件系统驱动
内核扩展驱动模块
等 其实只是名称上的区别和用户态的可执行文件没有区别
内核模块可以单独编译 但是不能单独运行 只能链接到内核中作为内核的一部分在内核态运行
单内核系统简单理解就是一个大进程 其内部包含许多模块 在运行时 模块之间的通信是通过调用其他模块中的函数实现的 而不是消息传递 相对应的 单一内核在运行效率上有很大的优势 但是编写难 修改难 内核代码高度集成 以上所有模块均在内核态执行
虽然微内核和单内核名字很相似 但是实现方式是截然不同的 有点类似RISC和CISC的区别(个人理解) 微内核系统只提供必要服务的操作系统内核 包括任务
线程
交互进程通信
内存管理
等 所有的服务都在用户态下进行
Linux是一个典型的单内核系统 所以Linux本身也提供模块机制 一般来说Pwn中的漏洞就是存在这些模块之中
比较常见的命令有
insmod: 将指定的模块加载到内核中
rmmod: 将指定模块从内核中写挨着
lsmod: 列出所有已经加载的模块
modprobe: 添加或者删除模块 在加载模块时会自动寻找依赖
dmesg: 输出内核态缓冲区
ioctl
是一个C语言的函数 是一个系统调用 可以用于与设备通信
C语言程序 保存为helloworld.c
c#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init kernel_module_init(void) // 初始化函数 和构造函数一样 在载入内核后自动调用
{
// printk() 内核态输出函数 只不过所有内核态输出不能输出到屏幕 只能输出到缓冲区
printk("<1>Hello the Linux kernel world!\n");
return 0;
}
static void __exit kernel_module_exit(void) // 析构函数 卸载内核时自动调用
{
printk("<1>Good bye the Linux kernel world! See you again!\n");
}
module_init(kernel_module_init); // 传入函数指针 声明构造函数
module_exit(kernel_module_exit); // 传入函数指针 声明析构函数
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Du4t");
Makefile
makefiletarget=helloworld
kernel_dir=/usr/src/linux-headers-$(shell uname -r)
pwd=$(shell pwd)
obj-m+=$(target).o
all:
make -C $(kernel_dir) M=$(pwd) modules
测试模块
bashsudo insmod helloworld.ko sudo rmmod helloworld sudo dmesg
上面的LKM实例 我们只实现了LKM能自己对缓冲区进行输出 显然作为一个内核模块光这些功能是不够的..最起码需要能够实现一定的IO操作 并且对相应的IO操作进行相应的反应 而LKM是不能直接与用户态的用户进行通信的 LKM规定的通信是通过设备
进行的 所以我们要先构建一个我们想要的设备
然后编写对应的LKM从我们创造的设备中取出数据 并且输出
在Linux中设备主要分为两类 分别是字符设备
和块设备
其实区别在名字上就有所体现了
字符设备只允许以字符流的形式进行IO操作 例如键盘 串口等 不能进行随机存取
而块设备是以数据块的方式进行交互 例如硬盘等 可以实现随机存取
Linux中使用设备号来标记一个设备 这个设备号在内核中是以dev_t(unsigned long)
类型来进行存储 其中高字节是主设备号 低字节是次设备号
主设备号主要负责标识设备类型 使用宏MAJOR(dev_t dev)可以获取主设备号
次设备号主要负责区分同类型设备 使用宏MINOR(dev_t dev)可以获取次设备号
LInux创建一个设备号使用宏MKDEV(int major, int minor) 通过传入主设备号和次设备号自动生成设备号
其实简单来说就是 主设备号标记类型 次设备号标记具体ID
这个其实就是Linux的基础知识了 所有Linux中的设备都是以文件的形式存在的 这些文件都放在/dev
下 每个设备都是一个设备节点
所有设备的都是结构体device
的实例化 device结构体
定义在include/linux/device.h
中
在设备树中使用device_node
表示一个设备
file_operations结构体
定义在include/linux/fs.h
中 包含许多关于文件操作的函数指针 例如read
write
等函数指针
cstruct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
...
} __randomize_layout;
结构体class定义于include/linux/device/class.h
中 用以表示高层次抽象的设备
每个设备节点实例中都应当包含着一个指向其自身class结构体
的指针
注册一个设备的流程很直球 我们先来创造一个字符型设备
register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
来创造主设备号函数定义在
include/linux/fs.h
中
cstatic inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
major : 主设备号 若为0则由内核分配主设备号
name: 设备名 用户自己指定
fops: file_operations结构体指针
class_create(owner, name)
创建设备对应的class结构体宏定义在
include/linux/device/class.h
中
c#define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
owner: 指向拥有此
class结构体
的模块的指针 通常直接传入THIS_MODULE
name:
class结构体
的名字这里这个
THIS_MODULE
定义在include/linux/export.h
中
cextern struct module __this_module; #define THIS_MODULE (&__this_module)
即是保存了
__this_module
这个对象的地址 而这个__this_module
定义在..我们刚刚编译的内核模块时多出来的helloworld.mod.c
中
c__visible struct module __this_module __section(".gnu.linkonce.this_module") = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, };
device_create(struct class *cls, struct device *parent, dev_t devt,void *drvdata, const char *fmt, ...)
来创建设备节点文件cls:
class结构体
的指针parent: 父设备的设备节点 一般为某种总线或者主机控制器 若为顶级设备则为
NULL
devt: 设备号
drvdata: 驱动相关信息 若无则为
NULL
fmt: 设备名称
设备注销的流程就是反着来
device_destroy(struct class *cls, dev_t devt)
-> class_destroy(struct class *cls)
->unregister_chrdev(unsigned int major, const char *name)
内核模块运行在内核空间 所创建的设备节点只能在root用户使用 这显然不是我们想要的 所以我们需要想办法使所有用户都有权限使用该设备和我们的内核模块进行交互
在内核中使用iNode结构体
来表示一个文件(没错 就是组成原理中那个iNode节点) INode结构体
定于include/linux/fs.h
中
cstruct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
...
}
其中文件的权限管理就是由这个i_mode
成员管理的 而在内核中打开一个文件是通过flip_open()
函数进行的 在内核态中 文件以file结构体
进行描述 该结构体同样定义于include/linux/fs.h
cstruct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
...
}
可以注意到 结构体之中是包含针对inode结构体
的指针的 且内核提供file_inode()
函数来获得file结构体中的inode结构体指针
那么其实怎么修改权限也很简单了.. 首先我们在内核态使用flip_open()
打开我们的设备节点 然后使用file_inode()
来取得对应设备节点的inode结构体指针
然后修改对应结构体中的i_mode
成员即可
c#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#define DEVICE_NAME "Du4t"
#define DEVICE_PATH "/dev/Du4t"
#define CLASS_NAME "C_Du4t"
static int major_num; //主设备号
static struct class *module_class=NULL;
static struct device *module_device=NULL;
static struct file *__file=NULL;
struct inode *__inode=NULL;
static struct file_operations PW_module_fo=
{
.owner=THIS_MODULE
};
static int __init kernel_module_init(void)
{
printk(KERN_INFO "[du4t_TestModule]: Module loaded. Start to register device...\n");
major_num=register_chrdev(0,DEVICE_NAME,&PW_module_fo); // 创造主设备号
if (major_num<0)
{
printk(KERN_INFO "[du4t_TestModule]: Failed to register a major number...\n");
return major_num;
}
printk(KERN_INFO "[du4t_TestModule]: Major number is %d...\n",major_num);
module_class=class_create(THIS_MODULE,CLASS_NAME); // 创造class结构体
if(IS_ERR(module_class))
{
unregister_chrdev(major_num,DEVICE_NAME); // 谁创造谁注销 设备号作为操作系统的一种资源 在退出时应当注销
printk(KERN_INFO "[du4t_TestModule]: Failed to register class device...\n");
return PTR_ERR(module_class);
}
printk(KERN_INFO "[du4t_TestModule]: Have created class...\n");
module_device=device_create(module_class,NULL,MKDEV(major_num,0),NULL,DEVICE_NAME); // 创造设备节点
if(IS_ERR(module_class))
{
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: Failed to create device...\n");
return PTR_ERR(module_device);
}
__file=filp_open(DEVICE_PATH,O_RDONLY,0); // 打开设备节点
if (IS_ERR(__file))
{
device_destroy(module_class,MKDEV(major_num,0));
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: Failed to open device...\n");
}
__inode=file_inode(__file);
__inode->i_mode=0666;
filp_close(__file,NULL);
printk(KERN_INFO "[du4t_TestModule]: Have changed the device's privilege...\n");
return 0;
}
static void __exit kernel_module_exit(void)
{
printk(KERN_INFO "[du4t_TestModule]: Start to clean up the module...\n");
device_destroy(module_class,MKDEV(major_num,0));
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: See you next time...\n");
}
module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Du4t");
显然上面只能在内核内部自己进行输出的操作是不符合我们要求的 我们的想法是构建一个可以响应用户输入的驱动程序 所以我们要实现相应的IO操作 这个时候就要用到上面我们提到的file_operations结构体
了 这里再展示下其结构
cstruct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
...
} __randomize_layout;
其中结构体之中的函数指针就是给我们自定义实现read
write
ioctl
来用的 那么就是来写一套实现函数了
首先是P_Device.h
c#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#define DEVICE_NAME "Du4t"
#define DEVICE_PATH "/dev/Du4t"
#define CLASS_NAME "C_Du4t"
#define NOT_INIT 0xffffffff
#define READ_ONLY 0x1000
#define ALLOW_WRITE 0x1001
#define BUFFER_RESET 0x1002
static int major_num; //主设备号
static int module_mode=READ_ONLY;
static struct class *module_class=NULL;
static struct device *module_device=NULL;
static struct file *__file=NULL;
struct inode *__inode=NULL;
static void *buffer=NULL;
static spinlock_t spin;
// 自定义以下这些函数以实现使用read write ioctl来对LKM进行通信
static int __init kernel_module_init(void);
static void __exit kernel_module_exit(void);
static int module_open(struct inode *,struct file *);
static ssize_t module_read(struct file *,char __user *,size_t,loff_t *);
static ssize_t module_write(struct file *,const char __user * ,size_t,loff_t *);
static int module_release(struct inode *, struct file *);
static long module_ioctl(struct file *,unsigned int,unsigned long);
static long __internal_module_ioctl(struct file * __file,unsigned int cmd,unsigned long param);
static struct file_operations PW_module_fo = { //descripe the device
.owner = THIS_MODULE,
.unlocked_ioctl = module_ioctl,
.open = module_open,
.read = module_read,
.write = module_write,
.release = module_release,
};
然后是IO_Device.c
c#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "P_Device.h"
static int __init kernel_module_init(void)
{
spin_lock_init(&spin); //增加自旋锁以支持多线程
printk(KERN_INFO "[du4t_TestModule]: Module loaded. Start to register device...\n");
major_num=register_chrdev(0,DEVICE_NAME,&PW_module_fo); // 创造主设备号
if (major_num<0)
{
printk(KERN_INFO "[du4t_TestModule]: Failed to register a major number...\n");
return major_num;
}
printk(KERN_INFO "[du4t_TestModule]: Major number is %d...\n",major_num);
module_class=class_create(THIS_MODULE,CLASS_NAME); // 创造class结构体
if(IS_ERR(module_class))
{
unregister_chrdev(major_num,DEVICE_NAME); // 谁创造谁注销 设备号作为操作系统的一种资源 在退出时应当注销
printk(KERN_INFO "[du4t_TestModule]: Failed to register class device...\n");
return PTR_ERR(module_class);
}
printk(KERN_INFO "[du4t_TestModule]: Have created class...\n");
module_device=device_create(module_class,NULL,MKDEV(major_num,0),NULL,DEVICE_NAME); // 创造设备节点
if(IS_ERR(module_class))
{
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: Failed to create device...\n");
return PTR_ERR(module_device);
}
__file=filp_open(DEVICE_PATH,O_RDONLY,0); // 打开设备节点
if (IS_ERR(__file))
{
device_destroy(module_class,MKDEV(major_num,0));
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: Failed to open device...\n");
}
__inode=file_inode(__file);
__inode->i_mode=0666;
filp_close(__file,NULL);
printk(KERN_INFO "[du4t_TestModule]: Have changed the device's privilege...\n");
return 0;
}
static void __exit kernel_module_exit(void)
{
printk(KERN_INFO "[du4t_TestModule]: Start to clean up the module...\n");
device_destroy(module_class,MKDEV(major_num,0));
class_destroy(module_class);
unregister_chrdev(major_num,DEVICE_NAME);
printk(KERN_INFO "[du4t_TestModule]: See you next time...\n");
}
static long module_ioctl(struct file *__file,unsigned int cmd,unsigned long param)
{
long ret;
spin_lock(&spin);
ret=__internal_module_ioctl(__file,cmd,param);
spin_unlock(&spin);
return ret;
}
static long __internal_module_ioctl(struct file * __file,unsigned int cmd,unsigned long param)
{
printk(KERN_INFO "[du4t_TestModule]: Received operation code %d...\n",cmd);
switch (cmd)
{
case READ_ONLY:
if(!buffer)
{
printk(KERN_INFO "[du4t_TestModule]: Please reset buffer firstly...\n");
return -1;
}
printk(KERN_INFO "[du4t_TestModule]: Module operation mode set to READ_ONLY...\n");
module_mode=READ_ONLY;
break;
case ALLOW_WRITE:
if(!buffer)
{
printk(KERN_INFO "[du4t_TestModule]: Please reset buffer firstly...\n");
return -1;
}
printk(KERN_INFO "[du4t_TestModule]: Module operation mode set to ALLOW_WRITE...\n");
module_mode=ALLOW_WRITE;
break;
case BUFFER_RESET:
if(!buffer)
{
buffer=kmalloc(0x500,GFP_ATOMIC); // 内核态malloc GFP_ATOMIC不可睡眠 GFP_KERNEL可睡眠
if(buffer==NULL)
{
printk(KERN_INFO "[du4t_TestModule]: Malloc Error...\n");
module_mode=NOT_INIT;
return -1;
}
}
printk(KERN_INFO "[du4t_TestModule]: Buffer reset, Module operation mode set to READ_ONLY...\n");
memset(buffer,0,0x500);
module_mode=READ_ONLY;
break;
case NOT_INIT:
printk(KERN_INFO "[du4t_TestModule]: Module operation mode set to NOT_INIT...\n");
module_mode=NOT_INIT;
kfree(buffer);
return 0;
default:
printk(KERN_INFO "[du4t_TestModule]: Module operation mode error...\n");
return -1;
}
return 0;
}
static int module_open(struct inode *__inode,struct file *__file)
{
spin_lock(&spin);
if (buffer==NULL)
{
buffer=kmalloc(0x500,GFP_ATOMIC);
if(buffer==NULL)
{
printk(KERN_INFO "[du4t_TestModule]: Malloc Error...\n");
module_mode=NOT_INIT;
return -1;
}
memset(buffer,0,0x500);
module_mode=READ_ONLY;
printk(KERN_INFO "[du4t_TestModule]: Device open, buffer initialized successfully...\n");
}
else
{
printk(KERN_INFO "[du4t_TestModule]: Device has opened...\n");
}
spin_unlock(&spin);
return 0;
}
static int module_release(struct inode *__inode, struct file *__file)
{
spin_lock(&spin);
if (buffer)
{
kfree(buffer);
buffer=NULL;
}
printk(KERN_INFO "[du4t_TestModule]: Device has closed...\n");
spin_unlock(&spin);
return 0;
}
static ssize_t module_read(struct file *__file,char __user *user_buffer,size_t size,loff_t *__loff)
{
const char * const buf=(char *)buffer;
int count;
spin_lock(&spin);
if(module_mode==NOT_INIT)
{
printk(KERN_INFO "[du4t_TestModule]: Module mode is NOT_INIT...\n");
return -1;
}
count=copy_to_user(user_buffer,buf,size>0x500?0x500:size); // copy_to_user(to,from,size) 具体功能是将数据从内核空间复制到用户空间 from是内核空间的指针 to是用户空间的指针
spin_unlock(&spin);
return count;
}
static ssize_t module_write(struct file *__file,const char __user *user_buf,size_t size,loff_t *__loff)
{
const char * const buf=(char *)buffer;
int count;
spin_lock(&spin);
if(module_mode==NOT_INIT)
{
printk(KERN_INFO "[du4t_TestModule]: Module mode is NOT_INIT...\n");
return -1;
}
else if(module_mode==READ_ONLY)
{
printk(KERN_INFO "[du4t_TestModule]: Module mode is READ_ONLY...\n");
return -1;
}
else
{
count=copy_from_user(buf,user_buf,size>0x500?0x500:size); // copy_to_user(to,from,size) 具体功能是将数据从内核空间复制到用户空间 from是内核空间的指针 to是用户空间的指针
}
spin_unlock(&spin);
return count;
}
module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Du4t");
整体逻辑还是很简单的 下面就是写一个可以跟我们驱动交互的程序了
c#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/ioctl.h>
char * buf = "Test Module..";
int main(void)
{
char output[100];
int fd=open("/dev/Du4t",2);
int len=strlen(buf);
ioctl(fd,0x1000,NULL); // READ_ONLY
write(fd,buf,len);
ioctl(fd,0x1001,NULL); // ALLOW_WRITE
write(fd,buf,len);
read(fd,output,len);
write(0,output,len);
ioctl(fd,0x1002,NULL); // BUFFER_RESET
read(fd,output,len);
write(0,output,len);
close(fd);
return 0;
}
本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!