一些Kernel 知识

  • 内核的各个模块

  • LKM
    LKM(loadable kernel module),内核作为一个那么复杂,庞大的系统,如果每次产生新的设备,更新新的驱动都要把源码加到内核再重新编译,明显是不太现实。所以呢,LKM也就千呼万唤始出来了。

  • 设备分类
    按照LDD3的说法,分为三种设备Character devices block device network device。这只是一些比较抽象的分类,各个设备在编写驱动时肯定在逻辑设计上有交叉。

  • Character devices

    A character (char) device is one that can be accessed as a stream of bytes (like a file); a char driver is in charge of implementing this behavior. Such a driver usually implements at least the open, close, read, and write system calls. The text console (/dev/console) and the serial ports (/dev/ttyS0 and friends) are examples of char devices, as they are well represented by the stream abstraction.

本着一切皆文件的思想,字符设备被映射到文件系统的文件上。可以像操作文件一样操作一个设备。open,read,write之类的方法也可被用于字符设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ls -alh /dev
brw-rw---- 1 root disk 8, 0 12月 23 17:29 sda
brw-rw---- 1 root disk 8, 1 12月 23 17:29 sda1
brw-rw---- 1 root disk 8, 2 12月 23 17:29 sda2
brw-rw---- 1 root disk 8, 5 12月 23 17:29 sda5
drwxr-xr-x 3 root root 200 12月 23 17:29 snd
brw-rw----+ 1 root cdrom 11, 0 12月 23 17:29 sr0
crw-rw-rw- 1 root tty 5, 0 1月 12 16:51 tty
crw--w---- 1 root tty 4, 0 12月 23 17:29 tty0
crw--w---- 1 root tty 4, 1 1月 12 11:32 tty1
crw--w---- 1 root tty 4, 2 12月 23 17:29 tty2
crw--w---- 1 root tty 4, 3 12月 23 17:29 tty3
crw--w---- 1 root tty 4, 4 12月 23 17:29 tty4
crw-rw-rw- 1 root root 1, 5 12月 23 17:29 zero

关注第一列,c表示字符设备(char device),b表示块设备(block device),-表示文件(file),l表示链接(link),d表示文件夹(directory)。

块设备与字符设备很相似,不做提及。

  • Network interfaces

    Network interface is in charge of sending and receiving data packets, driven by the network subsystem of the kernel, without knowing how individual transactions map to the actual packets being transmitted. Many network connections (especially those using TCP) are stream-oriented, but network devices are,usually, designed around the transmission and receipt of packets.A network driver knows nothing about individual connections; it only handles packets.

比较特殊的是网络设备作为stream-oriented device,并不会像前边字符设备,块设备一样以文件的形式存在于文件系统中。

Hello World

首先先写一个hello world试试看

  • hello.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cat03");


static int __init cat_module_init(void)
{
printk(KERN_DEBUG "Hello ,cat 's coming");
return 0;
}
static void __exit cat_module_exit(void)
{
printk(KERN_DEBUG "Bye ,cat 's leaving");
}


module_init(cat_module_init);
module_exit(cat_module_exit);
  • Makefile
1
2
3
4
5
6
7
8
9
10
11
obj-m += hello.o

CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

  • 编译出的hello.ko也是elf,没有main函数,入口在哪?

module_init(init_function);
当insmod时,init_function会被执行

module_exit(exit_function);
当rmmod时,exit_function会被执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// include/linux/module.h
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);

/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
  • printk?,它的参数是怎么回事?

user_mode时可以动态链接到libc的printf来打印信息,显然LKM工作在kernel_mode,不可能再使用libc的函数。printk时内核中的打印函数,采用类似于什么符号定位的方式来链接。

int printk(const char *fmt, ...);这应该是它的函数声明,在使用时会加入一个loglevels,printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__);。内核作为一个庞大的系统,日志分优先级确实是应该的。另外它当然是支持格式化。

1
2
3
4
5
6
7
8
9
10
11
12
//include/linux/kern_levels.h

#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */

#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */
  • static int ?为什么要如此声明成静态的?

Initialization functions should be declared static, since they are not meant to be visible outside the specific file; there is no hard rule about this, though, as no function is exported to the rest of the kernel unless explicitly requested.

init_function和exit_function,就像构造和析构,不应该为外部所引用,是由系统所调用。声明称静态更好,当然并不是硬性要求的。

  • __init,__exit是什么?

The __init token in the definition may look a little strange; it is a hint to the kernel that the given function is used only at initialization time. The module loader drops the initialization function after the module is loaded, making its memory available for other uses.

据说 具有 __exit __init标识的函数会被放入elf的特殊段,执行之后会释放

1
2
3
4
5
6
7
8
9
10
// include/linux/init.h
#define __exit __section(.exit.text) __exitused __cold notrace
#define __init __section(.init.text) __cold __latent_entropy __noinitretpoline
/* These macros are used to mark some functions or
* initialized data (doesn't apply to uninitialized data)
* as `initialization' functions. The kernel can take this
* as hint that the function is used only during the initialization
* phase and free up used memory resources after
*/

  • 那些标明证书和作者之类的宏是什么?

有些宏并非必须的,他们最终会出现elf的某一个数据段内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 一些说明性,标识性的,非必需的宏
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

// 必要的宏
MODULE_LICENSE("GPL");
/*
The specific licenses recognized by the kernel are “GPL” (for any version of the GNU
General Public License), “GPL v2” (for GPL version two only), “GPL and additional
rights,” “Dual BSD/GPL,” “Dual MPL/GPL,” and “Proprietary.” Unless your module is explicitly marked as being under a free license recognized by the kernel, it is
assumed to be proprietary, and the kernel is “tainted” when the module is loaded. Kernel developers tend to
be unenthusiastic about helping users who experience problems after loading proprietary modules.
*/


$ modinfo hello.ko
filename: /home/giles/Desktop/LKM_guide/hello.ko
description: It's for testing the macro
author: Cat03
license: GPL
srcversion: 1BD7CD505852F21364A939F
depends:
retpoline: Y
name: hello
vermagic: 4.15.0-142-generic SMP mod_unload

走向字符设备

我参考了几个kernel pwn的题目源码,简化成了下边的模板,以方便实验,调试使用。

  • Makefile
1
2
3
4
5
6
7
8
9
10
obj-m += template.o # <= module.o id from module.c

CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
  • template.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/kern_levels.h>

#define NAME ("cat03")
#define MAGIC (0x20010827)


static dev_t devn;
static struct class* dev_class;
static struct cdev cdev;

static int cat_open(struct inode *, struct file *);
static int cat_release(struct inode *, struct file *);

static ssize_t cat_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t cat_write(struct file *, const char __user *, size_t, loff_t *);

static long cat_ioctl(struct file *, unsigned int, unsigned long);

static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = cat_open,
.release = cat_release,
.read = cat_read,
.write = cat_write,
.unlocked_ioctl = cat_ioctl
};


static int cat_open(struct inode *inode, struct file *flip)
{
printk(KERN_ALERT "[ %s ]: open() is called",NAME);
return 0;
}

static int cat_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "[ %s ]: close() is called",NAME);
return 0;
}

static ssize_t cat_read(struct file *filp, char __user *buf, size_t len, loff_t *f_pos)
{
char *kernel_buf = kmalloc(0x20,GFP_KERNEL);
strcpy(kernel_buf,"It's from kernel!!");

printk(KERN_ALERT "[ %s ]: read() is called",NAME);
copy_to_user(buf,kernel_buf,len>0x20?0x20:len);

kfree(kernel_buf);
return len;
}

static ssize_t cat_write(struct file *filp, const char __user *buf, size_t len, loff_t *f_pos)
{
char *kernel_buf = kmalloc(0x20,GFP_KERNEL);

printk(KERN_ALERT "[ %s ]: write() is called",NAME);

copy_from_user(kernel_buf,buf,len>0x20?0x20:len);
printk(KERN_ALERT "[ %s ]: kernel_buf => %s",NAME,kernel_buf);

kfree(kernel_buf);
return len;
}

static long cat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk(KERN_ALERT "[ %s ]: ioctl() is called",NAME);
if(cmd == MAGIC)
{
printk(KERN_ALERT "[ %s ]: cmd => 0x%x arg => 0x%lx",NAME,cmd,arg);
return 827;
}
/*
* if(cmd == YOUR_COMMAND)
* {
* ...
* }
*/
return 0;
}

static int __init cat_init(void)
{
printk(KERN_ALERT "[ %s ]: init function is called !! Hello",NAME);

if (alloc_chrdev_region(&devn, 0, 1, NAME) < 0)
{
return -1;
}
if ((dev_class = class_create(THIS_MODULE, "chrdev")) == NULL)
{
unregister_chrdev_region(devn, 1);
return -1;
}
if (device_create(dev_class, NULL, devn, NULL, NAME) == NULL)
{
class_destroy(dev_class);
unregister_chrdev_region(devn, 1);
return -1;
}
cdev_init(&cdev, &fops);
if (cdev_add(&cdev, devn, 1) == -1)
{
device_destroy(dev_class, devn);
class_destroy(dev_class);
unregister_chrdev_region(devn, 1);
return -1;
}
return 0;
}

static void __exit cat_exit(void)
{
printk(KERN_ALERT "[ %s ]: exit function is called !! Bye",NAME);
cdev_del(&cdev);
device_destroy(dev_class, devn);
class_destroy(dev_class);
unregister_chrdev_region(devn, 1);
}


module_init(cat_init);
module_exit(cat_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cat03");
  • test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// gcc test.c -g
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main()
{
int fd;
char userbuf[0x40];

getchar();
fd = open("/dev/cat03",2);

read(fd,userbuf,23);
puts(userbuf);

write(fd,"nihao kernel",0x20);

close(fd);
return 0;
}
  • 测试驱动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls
Makefile template.c test.c
$ make
$ gcc test.c -g
$ sudo insmod template.ko
$ sudo chmod 777 /dev/cat03
$ ./a.out

It's from kernel!!
$ tail /var/log/syslog
Jan 12 19:13:12 ubuntu kernel: [20034.550377] [ cat03 ]: init function is called !! Hello
Jan 12 19:13:12 ubuntu kernel: [20078.383024] [ cat03 ]: open() is called
Jan 12 19:13:12 ubuntu kernel: [20078.383036] [ cat03 ]: read() is called
Jan 12 19:13:12 ubuntu kernel: [20078.383129] [ cat03 ]: write() is called
Jan 12 19:13:12 ubuntu kernel: [20078.383132] [ cat03 ]: kernel_buf => nihao kernel

一些说明

  • 为什么是那些头文件?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/fs.h>         
/*
* struct file_operations 定义其中。
* open,release,read,write,ioctl之类的操作字符文件的各种方法以函数指针形式定义其中。
* 有点像c++对象的vft,是不是有点面向对象那味了。
*/
#include <linux/cdev.h> // => cdev
/*
* struct cdev 结构体定义其中。
* 实际仔细看看,init里字符设备的注册,能表明他是字符设备的就是cdev。
* The kernel uses structures of type struct cdev to represent char
* devices internally. Before the kernel invokes your device’s operations
* you must allocate and register one or more of these structures.* To do
* so, your code should include <linux/cdev.h>,
*/
#include <linux/slab.h>
/*
* Kernel 使用伙伴系统来管理小块内存。研究内核堆漏洞更多是建立在slab内存管理机制上的
* kmalloc和kfree也定义在其中
*/
#include <linux/types.h> // => dev_t
#include <linux/string.h> // => strcpy
#include <linux/kernel.h> // => printk
#include <linux/device.h>
/*
* struct class定义在其中,init中关于这一部分是在filesystem中创立文件节点。
* 从而就没必要 mknod 手动创建文件节点。
*** cat /proc/devices
*** mknod /dev/cat03 c 248 0
*/
#include <linux/uaccess.h> // => cpoy*
#include <linux/kern_levels.h>// log_level
  • 其他注册设备的方式
  1. 可以使用misc_register来注册misc设备。关于misc设备,他是绑定在10号(major num)驱动上的字符设备。它的minor num既可以指定,但更应该动态获取。在进行注册时,应该向misc_register函数传入已经赋值minor,name,fops成员的struct miscdevice结构体指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h> // => fops
// #include <linux/cdev.h> // => cdev
#include<linux/miscdevice.h>
#include <linux/slab.h> // => kmalloc kfree
#include <linux/types.h> // => dev_t
#include <linux/string.h> // => strcpy
#include <linux/kernel.h> // => printk
#include <linux/device.h> // => struct class
#include <linux/uaccess.h> // => cpoy*
#include <linux/kern_levels.h>// log_level

#define NAME ("cat03")
#define MAGIC (0x20010827)


static int cat_open(struct inode *, struct file *);
static int cat_release(struct inode *, struct file *);

static ssize_t cat_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t cat_write(struct file *, const char __user *, size_t, loff_t *);

static long cat_ioctl(struct file *, unsigned int, unsigned long);

static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = cat_open,
.release = cat_release,
.read = cat_read,
.write = cat_write,
.unlocked_ioctl = cat_ioctl
};
static struct miscdevice cat_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = NAME,
.fops = &fops
};


static int cat_open(struct inode *inode, struct file *flip)
{
printk(KERN_ALERT "[ %s ]: open() is called",NAME);
return 0;
}

static int cat_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "[ %s ]: close() is called",NAME);
return 0;
}

static ssize_t cat_read(struct file *filp, char __user *buf, size_t len, loff_t *f_pos)
{
char *kernel_buf = kmalloc(0x20,GFP_KERNEL);
strcpy(kernel_buf,"It's from kernel!!");

printk(KERN_ALERT "[ %s ]: read() is called",NAME);
copy_to_user(buf,kernel_buf,len>0x20?0x20:len);

kfree(kernel_buf);
return len;
}

static ssize_t cat_write(struct file *filp, const char __user *buf, size_t len, loff_t *f_pos)
{
char *kernel_buf = kmalloc(0x20,GFP_KERNEL);

printk(KERN_ALERT "[ %s ]: write() is called",NAME);

copy_from_user(kernel_buf,buf,len>0x20?0x20:len);
printk(KERN_ALERT "[ %s ]: kernel_buf => %s",NAME,kernel_buf);

kfree(kernel_buf);
return len;
}

static long cat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk(KERN_ALERT "[ %s ]: ioctl() is called",NAME);
if(cmd == MAGIC)
{
printk(KERN_ALERT "[ %s ]: cmd => 0x%x arg => 0x%lx",NAME,cmd,arg);
return 827;
}
/*
* if(cmd == YOUR_COMMAND)
* {
* ...
* }
*/
return 0;
}


static int __init cat_init(void)
{
printk(KERN_ALERT "[ %s ]: init function is called !! Hello",NAME);
return misc_register(&cat_dev);
}

static void __exit cat_exit(void)
{
printk(KERN_ALERT "[ %s ]: exit function is called !! Bye",NAME);
misc_deregister(&cat_dev);
}


module_init(cat_init);
module_exit(cat_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cat03");

如此修改便可以。

  1. 也可以注册/proc目录下的设备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static struct proc_dir_entry *the_dev;
static int __init cat_init(void)
{
printk(KERN_ALERT "[ %s ]: init function is called !! Hello",NAME);
if((the_dev = proc_create(NAME,0666,NULL,&fops)) == NULL)
{
return -1;
}
return 0;
}

static void __exit cat_exit(void)
{
printk(KERN_ALERT "[ %s ]: exit function is called !! Bye",NAME);
proc_remove(the_dev);
}

  • 抱怨

愚花了一个星期左右的时间在研究LDD3的前几章(英文版(找不到中文版)看的很煎熬),我的目的是为了自己编写驱动从而模拟kernel pwn的些许题目,方便带符号调试,但是大多是在学习整个驱动开发的框架,关注太多旁系的知识。实际不如直接去研究代码,看书的效率确实有限。。。。