明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

OpenBSD可加載內(nèi)核模塊編程完全向?qū)?/h1>

[摘要]緒論 這篇文章我說(shuō)明在openbsd上如何進(jìn)行內(nèi)核編程,以下句子來(lái)自lkm手冊(cè)頁(yè): "可加載內(nèi)核模塊可以允許系統(tǒng)管理員在一臺(tái)運(yùn)行著的系統(tǒng)上動(dòng) 態(tài)的增加或刪除功能模塊,它同時(shí)可以幫助軟件工程師們?yōu)閮?nèi)核增加新的功能而根本就不需要重起計(jì)算機(jī)就可以測(cè)試他們開(kāi)發(fā)的程序." 當(dāng)然,像眾多系...

緒論

這篇文章我說(shuō)明在openbsd上如何進(jìn)行內(nèi)核編程,以下句子來(lái)自lkm手冊(cè)頁(yè): "可加載內(nèi)核模塊可以允許系統(tǒng)管理員在一臺(tái)運(yùn)行著的系統(tǒng)上動(dòng)

態(tài)的增加或刪除功能模塊,它同時(shí)可以幫助軟件工程師們?yōu)閮?nèi)核增加新的功能而根本就不需要重起計(jì)算機(jī)就可以測(cè)試他們開(kāi)發(fā)的程序."

當(dāng)然,像眾多系統(tǒng)的lkm一樣,它存在一定的安全隱患,哈哈,其實(shí)這也是我寫(xiě)這篇文章給大家的原因:)它提供了更廣泛的空間給惡意的

superroot,其實(shí)也就是已經(jīng)得到系統(tǒng)管理員權(quán)限的我們。我們利用lkm可以駕馭整個(gè)系統(tǒng)而不會(huì)輕易被發(fā)現(xiàn). 同樣的, 如果你系統(tǒng)的
securelevel在0級(jí)一行的話就不能加載或卸載模塊了,如果你要使系統(tǒng)在進(jìn)入securemode之前可以加載模塊,可以編輯
/etc/rc.securelevel文件,添加相應(yīng)的入口.

總覽

/dev/lkm設(shè)備與用戶(hù)的交互通過(guò)ioctl(2)系列系統(tǒng)調(diào)用來(lái)進(jìn)行. 主要是一些工具如modload,modunload和modstat等來(lái)控制模塊的加載

和卸載以及模塊的狀態(tài).
lkm接口定義了五種不同的模塊類(lèi)型:

系統(tǒng)調(diào)用模塊
虛擬文件系統(tǒng)模塊
設(shè)備驅(qū)動(dòng)模塊
可執(zhí)行程序解釋器模塊
其它模塊
一個(gè)普通的模塊包括三個(gè)主要部分:
1) 內(nèi)核入口和出口的處理(也就是當(dāng)模塊被加載,被卸載時(shí)的動(dòng)作).

2) 一個(gè)外部入口點(diǎn), 當(dāng)模塊用modload程序被加載的時(shí)候需要用到

3) 模塊的主體, 包含函數(shù)代碼等.

對(duì)于其他類(lèi)型的模塊來(lái)說(shuō),它需要開(kāi)發(fā)人員提供嚴(yán)格的控制和當(dāng)內(nèi)核模塊卸載的時(shí)候?qū)?nèi)核原有的狀態(tài)的保存.

對(duì)于模塊的支持必須用'option LKM'編譯進(jìn)內(nèi)核的配置文件.模塊需要支持默認(rèn)的openBSD 2.9的內(nèi)核.通常,內(nèi)核空間的數(shù)據(jù)接口都被提供

給了模塊來(lái)操作.后面
就有一個(gè)lkm設(shè)備的例子.

每個(gè)類(lèi)型的模塊的內(nèi)部數(shù)據(jù)結(jié)構(gòu)里面都存在一個(gè)宏用來(lái)加載自己.也就類(lèi)似模塊本身模塊名的東東,它被指定在內(nèi)核數(shù)據(jù)結(jié)構(gòu)中,和模塊的一些

特殊數(shù)據(jù)如sysent這樣
的針對(duì)系統(tǒng)調(diào)用模塊的結(jié)構(gòu)在一起.

讓我們看看一些例子吧.

★系統(tǒng)調(diào)用模塊.

這里我們將增加一個(gè)新的系統(tǒng)調(diào)用printf()的整型和字符串參數(shù).它的原型如下:

int syscall(int, char *)

內(nèi)核內(nèi)部定義的一個(gè)lkm的syscall結(jié)構(gòu)如下:
struct lkm_syscall {
  MODTYPE lkm_type;
  int lkm_ver;
  char*lkm_name;
  u_longlkm_offset; /* 保存/分配 內(nèi)存空間 */
  struct sysent *lkm_sysent;
  struct sysent lkm_oldent; /*保存原調(diào)用,用于lkm的卸載 */
};

現(xiàn)在我們已經(jīng)有了一個(gè)簡(jiǎn)單的模塊框架了(應(yīng)該叫LM_SYSCALL),lkm的版本,模塊名,都在系統(tǒng)調(diào)用表里存在一個(gè)相應(yīng)的入口.這樣我們有

了一個(gè)指向結(jié)構(gòu)sysent的模塊框架
我們將用MOD_SYSCALL宏來(lái)安裝它:

MOD_SYSCALL("ourcall", -1, &newcallent)

我們來(lái)分析一下上面的宏,很明顯,模塊名為"ourcall",用來(lái)標(biāo)示模塊,還有一個(gè)作用就是我們利用modstat命令時(shí)會(huì)顯示出來(lái).-1代表我們

的syscall該插入的位置,在這個(gè)
宏當(dāng)中的-1的意思是我們不用關(guān)心位置具體在什么地方,它會(huì)被分配到一個(gè)空的位置.最后一個(gè)字段newcallent是一個(gè)指向sysent的結(jié)構(gòu),

它包含了我們系統(tǒng)調(diào)用的相應(yīng)的數(shù)
據(jù).
除此之外我們還需要一個(gè)句柄用來(lái)加載和卸載內(nèi)核模塊,好,在這個(gè)例子中我用'hi'來(lái)加載,用'bye'來(lái)卸載.這對(duì)我們調(diào)試程序很有幫助.句柄可

以是相同的函數(shù)或者單個(gè)函數(shù),
如果沒(méi)有定義句柄,那么lkm_nofunc()會(huì)簡(jiǎn)單的返回0,這個(gè)模塊是沒(méi)有加載卸載的,也就失去了作用.

我們模塊的外部入口點(diǎn)是ourcall():

int
ourcall(lkmtp, cmd, ver)
  struct lkm_table *lkmtp;
  int cmd;
  int ver;
{
  DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc)
}

這個(gè)句柄可以用來(lái)加載,卸載模塊.第四個(gè)參數(shù)我們用作加載操作,第五個(gè)參數(shù)用作卸載操作,第六個(gè)參數(shù)是狀態(tài)函數(shù)(在此例中沒(méi)有用到).
ok!完整的系統(tǒng)調(diào)用模塊代碼如下(syscall.c):

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/cdefs.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/proc.h>
#include <sys/syscallargs.h>


/* 定義我們自己的系統(tǒng)調(diào)用原型 */
int newcall __P((struct proc *p, void *uap, int *retval));

/*
* 所有的系統(tǒng)調(diào)用都有三個(gè)參數(shù): 一個(gè)指向proc結(jié)構(gòu)的結(jié)構(gòu)指針,一個(gè)空指針指向參
* 數(shù)本身和一個(gè)返回指針.下面,我們定義這些參數(shù)的結(jié)構(gòu).如果你只有一個(gè)參數(shù),則
* 只需要一個(gè)入口就可以了.
*/

struct newcall_args{
  syscallarg(int) value;
  syscallarg(char *) msg;
};

/*
* 下面這個(gè)結(jié)構(gòu)定義了我們的系統(tǒng)調(diào)用.第一個(gè)參數(shù)是系統(tǒng)調(diào)用的參數(shù)數(shù)目,第二個(gè)參數(shù)
* 是參數(shù)的大小,第三個(gè)參數(shù)是我們的系統(tǒng)調(diào)用的代碼了,呵呵:)
*/

static struct sysent newcallent = {
  2, sizeof(struct newcall_args), newcall
};

/*
* 好了,到了我們的syscall的核心結(jié)構(gòu)了,呵呵:)
* 第一個(gè)參數(shù)是syscall的名稱(chēng),ioctl()調(diào)用用它來(lái)查詢(xún)syscall.第二個(gè)參數(shù)告訴我們
* syscall的位置.這里你可以輸入數(shù)字,或者-1來(lái)讓系統(tǒng)自動(dòng)分配.第三個(gè)參數(shù)指向一個(gè)
* sysent結(jié)構(gòu)的指針.
*/

MOD_SYSCALL("ourcall", -1, &newcallent);

/*
* 要使我們的模塊正常運(yùn)行我們還要用到以下函數(shù).此函數(shù)類(lèi)似linux的lkm里面的init_module
* 和cleanup_module.
* 它通過(guò)一個(gè)指向lkm_table結(jié)構(gòu)的指針來(lái)完成我們給定的動(dòng)作.檢查cmd的值來(lái)判斷該加載
* 什么樣的句柄.當(dāng)我們利用模塊來(lái)增加一個(gè)系統(tǒng)調(diào)用的時(shí)候,這兒沒(méi)有專(zhuān)門(mén)的句柄來(lái)操作.
* 當(dāng)然,我們hacking kernel的時(shí)候是不會(huì)用例如"hi"和"bye"這樣的簡(jiǎn)單的句柄的,我們
* 需要改變系統(tǒng)調(diào)用.我們現(xiàn)在是說(shuō)明原理,其實(shí)大同小異:)
*/

static int
ourcall_handler(lkmtp, cmd)
  struct lkm_table *lkmtp;
  int cmd;
{
  if (cmd == LKM_E_LOAD)
  printf("hi!n");
  else if (cmd == LKM_E_UNLOAD)
  printf("bye!n");

  return(0);
}

/*
* 下面就是我們模塊的外部入口點(diǎn),也就是我們的系統(tǒng)調(diào)用的主體.
* 象上面那樣我們通過(guò)判斷一個(gè)cmd所匹配的句柄來(lái)描述動(dòng)作的執(zhí)行.我們也可以通過(guò)一個(gè)版本號(hào)
* 允許一個(gè)模塊兼容以后版本內(nèi)核的源碼,以保證向下的兼容性.
* DISPATCH宏通過(guò)三個(gè)參數(shù)來(lái)表示動(dòng)作的加載,卸載和狀態(tài).我們看下面例子,對(duì)于加載和卸載
* 我們用共享函數(shù)ourcall_handler().對(duì)于狀態(tài)(當(dāng)增加系統(tǒng)調(diào)用的時(shí)候就用不到它了)我們
* 用lkm_nofunc(),該函數(shù)僅僅簡(jiǎn)單的返回0.
*/

int
ourcall(lkmtp, cmd, ver)
  struct lkm_table *lkmtp;
  int cmd;
  int ver;
{
  DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc)
}

/*
* 最后對(duì)于我們的系統(tǒng)調(diào)用應(yīng)該有主體代碼,該調(diào)用干了什么之類(lèi).
*/

int
newcall(p, v, retval)
  struct proc *p;
  void *v;
  int *retval;
{
  struct newcall_args *uap = v;

  printf("%d %sn", SCARG(uap, value), SCARG(uap, msg));
  return(0);
}

ok!我們編譯安裝它:
# cc -D_KERNEL -I/sys -c syscall.c
# modload -o ourcall.o -e ourcall syscall.o
Module loaded as ID 0
#

-o參數(shù)指定輸出文件名,這和gcc的-o選項(xiàng)是一樣的.-e參數(shù)指定我們的外部標(biāo)示,最后一個(gè)參數(shù)就是輸入文件.好,我們用modstat看看我們的

模塊有沒(méi)有被成功加載:
# modstat
Type Id Off Loadaddr Size Info Rev Module Name
SYSCALL 0 210 e0b92000 0002 e0b93008 2 ourcall
#

以上顯示需要注意一下'off'字段,它標(biāo)示了該模塊在system call表里面的位置.這在創(chuàng)建系統(tǒng)調(diào)用的時(shí)候需要用到.我們可以通過(guò)dmesg命令

的輸出'hi'來(lái)驗(yàn)證我們
的模塊正確的加載運(yùn)行了:
# dmesg tail -2
hi!
DDB symbols added: 150060 bytes
#

好,現(xiàn)在讓我們來(lái)看一個(gè)測(cè)試我們剛才新的系統(tǒng)調(diào)用的簡(jiǎn)單程序(calltest.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <err.h>
#include <sys/lkm.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>

int
main(argc, argv)
  int argc;
  char **argv;
{
  int error, fd;
  struct lmc_stat modstat;

  if (argc != 3)
  erro(1, "%s", argv[0]);

  modstat.name = "ourcall";
 
  fd = open("/dev/lkm", O_RDONLY);
  if (fd == -1)
  err(1, "open");

  error = ioctl(fd, LMSTAT, &modstat);
  if (error == -1)
  err(1, "ioctl");

  printf("syscall no: %lun", modstat.offset);

  error = syscall(modstat.offset, atoi(argv[1]), argv[2]);
  if (error == -1)
  err(1, "syscall");
 
  exit(0);
}

注意我們是怎么從module的modstat結(jié)構(gòu)來(lái)利用ioctl調(diào)用獲得syscall的偏移量的.一般的用戶(hù)權(quán)限是不允許訪問(wèn)/dev/lkm設(shè)備的,同樣,

我們也可以從modstat來(lái)獲得象以上那
樣的信息.
所以我們的程序需要一個(gè)整數(shù)和字符串參數(shù)提交給新系統(tǒng)調(diào)用,好,編譯運(yùn)行我們的程序:

# cc -o calltest calltest.c
# ./calltest 4 beers
syscall no: 210
# dmesg tail -1
4 beers
#

我們用unloadmod來(lái)卸載內(nèi)核模塊:
# modunload -n ourcall
#

再用dmesg命令可以看出我們的模塊被成功卸載了:
# dmesg tail -1
bye!
#

好,現(xiàn)在讓我們來(lái)看看設(shè)備驅(qū)動(dòng)的編寫(xiě).
★設(shè)備驅(qū)動(dòng)程序模塊

設(shè)備驅(qū)動(dòng)模塊和系統(tǒng)調(diào)用的模塊的編寫(xiě)方法有很大的相同之處.他們有一個(gè)外部入口點(diǎn),且句柄關(guān)聯(lián)著特殊的模塊代碼.在下面的這段特殊的模

塊代碼會(huì)直接操作我們的設(shè)備.在這個(gè)
例子中我們簡(jiǎn)單的演示了一個(gè)字符設(shè)備的例子,只能支持open,close,read和ioctl操作.在我們剖析它的內(nèi)部機(jī)理之前,讓我們先來(lái)看看lkm

是如何來(lái)解釋設(shè)備的.

下面這段代碼定義了一個(gè)可加載的設(shè)備驅(qū)動(dòng):

struct lkm_dev {
  MODTYPE lkm_type;
  int lkm_ver;
  char*lkm_name;
  u_longlkm_offset;
  DEVTYPE lkm_devtype;
  union {
  void*anon;
  struct bdevsw *bdev;
  struct cdevsw *cdev;
  } lkm_dev;
  union
  {
  struct bdevsw bdev;
  struct cdevsw cdev;
  } lkm_olddev;
};

首先我們需要一個(gè)模塊的類(lèi)型(這里是LM_DEV),然后是lkm的版本號(hào),再就是它的名稱(chēng)和它在cdevsw[]或者bdevsw[]表中的位置

(lkm_offset).然后我們到了DEVTYPE定義的
lkm_devtype成員,它定義了我們?cè)O(shè)備的類(lèi)型,或者是一個(gè)字符型設(shè)備或者是一個(gè)塊設(shè)備,分別被LM_DT_CHAR或者LM_DT_BLOCK宏指

定.再下面定義了兩個(gè)枚舉類(lèi)型的結(jié)構(gòu),在模
快被加載的時(shí)候分別定義了新的設(shè)備的操作空間以及保留了老的設(shè)備結(jié)構(gòu),此結(jié)構(gòu)通過(guò)MOD_DEV宏來(lái)初始化:
MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev)

首先我們通過(guò)我們的模塊名以及設(shè)備類(lèi)型,在此例中我們得知我們創(chuàng)建的是一個(gè)字符型的設(shè)備.接下來(lái)需要在cdevsw[]中有個(gè)入口,就象上面

的系統(tǒng)調(diào)用的例子那樣,-1代表我們可以
不去關(guān)心放置的確切位置,讓系統(tǒng)自己去尋找可用的入口.如果沒(méi)有空閑的入口,函數(shù)ENFILE ("Too many open files in system")將會(huì)被

返回.最后我們通過(guò)初始化cdevsw
結(jié)構(gòu)來(lái)對(duì)我們的設(shè)備進(jìn)行操作.
我們的字符設(shè)備將會(huì)支持四種操作:open,close,read和ioctl.不能干再多的事情了,它將存儲(chǔ)一個(gè)字符串和一個(gè)數(shù)字,該數(shù)字可以被ioctl調(diào)用

設(shè)置和返回,字符串也可以用read
調(diào)用返回.
我們定義的內(nèi)部結(jié)構(gòu)如下:

#define MAXMSGLEN 100

struct ourdev_io {
  int value;
  char msg[MAXMSGLEN];
};

當(dāng)模塊第一次被加載的時(shí)候,我們?cè)O(shè)置value為13并且為我們的字符串賦值"hello world!".我們定義了兩個(gè)簡(jiǎn)單的ioctl調(diào)用來(lái)設(shè)置或獲取內(nèi)

部結(jié)構(gòu)的當(dāng)前的value的值.這些
都利用ourdev_io結(jié)構(gòu)作為一個(gè)參數(shù),然后利用ioctl執(zhí)行一個(gè)相應(yīng)的動(dòng)作.
在模塊的入口指針中,我這里再次用了IDSPATH宏.

以下是我們自定義的設(shè)備程序的完整代碼(chardev.c):

#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>

#include "common.h"

/*
* 導(dǎo)入我們支持的操作:open,read,close,ioctl等
*/

int ourdevopen __P((dev_t dev, int oflags, int devtype, struct proc *p));
int ourdevclose __P((dev_t dev, int fflag, int devtype, struct proc *p));
int ourdevread __P((dev_t dev, struct uio *uio, int ioflag));
int ourdevioctl __P((dev_t dev, u_long cmd, caddr_t data, int fflag,
  struct proc *p));
int ourdev_handler __P((struct lkm_table *lkmtp, int cmd));

/*
* outdev_io結(jié)構(gòu)定義在頭文件common.h中,我們的設(shè)備會(huì)通過(guò)ioctl調(diào)用來(lái)獲取和設(shè)置它的值.
*/

static struct ourdev_io dio;

/*
* 這里我們初始化我們的設(shè)備的操作向量
*/

cdev_decl(ourdev);
static struct cdevsw cdev_ourdev = cdev_ourdev_init(1, ourdev);


/*
* 初始化lkm接口的內(nèi)部結(jié)構(gòu).第一個(gè)參數(shù)是模塊名,第二個(gè)參數(shù)是設(shè)備的類(lèi)型,在我的例子里標(biāo)記為
* LM_DT_CHAR表示是一個(gè)字符設(shè)備.第三個(gè)參數(shù)是我們存儲(chǔ)在cdevsw[]表中的操作結(jié)構(gòu).就象系統(tǒng)
* 調(diào)用的例子中一樣,值為-1的話系統(tǒng)自動(dòng)找尋空閑的位置存儲(chǔ).最后我們初始化cdevsw結(jié)構(gòu)
*/

MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev)

/*
* 以下的動(dòng)作在設(shè)備被打開(kāi)的時(shí)候執(zhí)行,這里打印"hello",哈,僅做測(cè)試之用:)
*/

int
ourdevopen(dev, oflags, devtype, p)
  dev_t dev;
  int oflags, devtype;
  struct proc *p;
{
  printf("device opened, hi!n");
  return(0);
}

/*
* 以下動(dòng)作在設(shè)備被關(guān)閉的時(shí)候執(zhí)行,這里打印一段信息
*/

int
ourdevclose(dev, fflag, devtype, p)
  dev_t dev;
  int fflag, devtype;
  struct proc *p;
{
  printf("device closed! bye!n");
  return(0);
}

/*
* 定義我們?cè)O(shè)備執(zhí)行的read動(dòng)作,這里它把存儲(chǔ)在內(nèi)部結(jié)構(gòu)ourdev_io里的string的當(dāng)前值讀出來(lái)
*/

int
ourdevread(dev, uio, ioflag)
  dev_t dev;
  struct uio *uio;
  int ioflag;
{
  int resid = MAXMSGLEN;
  int error = 0;

  do {
  if (uio->uio_resid < resid)
  resid = uio->uio_resid;

  error = uiomove(dio.msg, resid, uio);

  } while (resid > 0 && error == 0);

  return(error);
}

/*
* ioctl操作的代碼.這里定義了兩個(gè)操作,一個(gè)負(fù)責(zé)從ourdev_io中讀取當(dāng)前值,一個(gè)負(fù)責(zé)設(shè)置當(dāng)前值.
*/

int
ourdevioctl(dev, cmd, data, fflag, p)
  dev_t dev;
  u_long cmd;
  caddr_t data;
  int fflag;
  struct proc *p;
{
  struct ourdev_io *d;
  int error = 0;

  switch(cmd) {
  case ODREAD:

  d = (struct ourdev_io *)data;
  d->value = dio.value;
  error = copyoutstr(&dio.msg, d->msg, MAXMSGLEN - 1, NULL);

  break;

  case ODWRITE:

  if ((fflag & FWRITE) == 0)
  return(EPERM);

  d = (struct ourdev_io *)data;
  dio.value = d->value;
  bzero(&dio.msg, MAXMSGLEN);
  error = copyinstr(d->msg, &dio.msg, MAXMSGLEN - 1, NULL);

  break;

  default:
  error = ENOTTY;
  break;
  }
 
  return(error);
}

/*
* 我們的外部入口點(diǎn).非常象前面介紹的系統(tǒng)調(diào)用的例子,用來(lái)控制模塊的加載,這里和系統(tǒng)調(diào)用模塊不
* 同的是我們?cè)谀K卸載的時(shí)候沒(méi)有制定特殊的動(dòng)作
*/

int
ourdev(lkmtp, cmd, ver)
  struct lkm_table *lkmtp;
  int cmd;
  int ver;
{
  DISPATCH(lkmtp, cmd, ver, ourdev_handler, lkm_nofunc, lkm_nofunc)
}

/*
* 控制加載模塊的代碼.我們?yōu)槲覀兊膬?nèi)部結(jié)構(gòu)設(shè)置一些初始值,這些值以后會(huì)被ioctl改變.它僅僅
* 在模塊被加載的時(shí)候用到.
*/

int
ourdev_handler(lkmtp, cmd)
  struct lkm_table *lkmtp;
  int cmd;
{
  struct lkm_dev *args = lkmtp->private.lkm_dev;
 
  if (cmd == LKM_E_LOAD) {
  dio.value = 13;
  strncpy(dio.msg,"hello world!n", MAXMSGLEN - 1);
  printf("loading module %sn", args->lkm_name);
  }

  return 0;
}

好了,最后我們可以用modload的-p參數(shù)來(lái)安裝我們的設(shè)備模塊,我可以寫(xiě)一個(gè)腳本來(lái)完成編譯安裝我們的設(shè)備的任務(wù).腳本利用mknod在

/dev目錄里面創(chuàng)建了一個(gè)設(shè)備,就叫
'/dev/ourdev'.在此安裝腳本中,我們用模塊號(hào)作為第一個(gè)參數(shù),模塊的類(lèi)型作為第二個(gè)參數(shù).如果模塊是一個(gè)系統(tǒng)調(diào)用,我們還需要指定系統(tǒng)

調(diào)用號(hào)作為第三個(gè)參數(shù)這里,我
們的第三個(gè)參數(shù)是主設(shè)備號(hào).
以下就是該安裝腳本(dev-install.sh):

#!/bin/sh
MAJOR=`modstat -n ourdev tail -1 awk '{print $3}'`
mknod -m 644 /dev/ourdev c $MAJOR 0
echo "created device /dev/ourdev, major number $MAJOR"
ls -l /dev/ourdev

好,開(kāi)始安裝.
首先編譯源碼:
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c chardev.c
[e4gle@openbsd29]#

安裝模塊:
[e4gle@openbsd29]# modload -o ourdev.o -eourdev -p ./dev-install.sh chardev.o
Module loaded as ID 0
created device /dev/ourdev, major number 29
crw-r--r--1 rootwheel 29, 0 Jul 10 05:16 /dev/ourdev
[e4gle@openbsd29]#

看看日志確定模塊是否被正常加載:
[e4gle@openbsd29]# dmesg tail -2
loading module ourdev
DDB symbols added: 140232 bytes
[e4gle@openbsd29]#

好,我們測(cè)試一下我們新創(chuàng)建的設(shè)備,用dd命令來(lái)測(cè)試:
[e4gle@openbsd29]# dd if=/dev/ourdev of=/dev/fd/1 count=1 bs=100
hello world!
1+0 records in
1+0 records out
100 bytes transferred in 1 secs (100 bytes/sec)
[e4gle@openbsd29]#

現(xiàn)在我來(lái)通過(guò)一個(gè)測(cè)試程序來(lái)測(cè)試一下我們的ioctl調(diào)用是否工作.測(cè)試程序必須包括模塊代碼和頭文件common.h:

#define MAXMSGLEN 100

struct ourdev_io {
  int value;
  char msg[MAXMSGLEN];
};

#define ODREAD_IOR('O', 0, struct ourdev_io)
#define ODWRITE _IOW('O', 1, struct ourdev_io)

#ifdef _KERNEL

/* open, close, read, ioctl */
#define cdev_ourdev_init(c,n) {
  dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read),
  (dev_type_write((*))) lkmenodev, dev_init(c,n,ioctl),
  (dev_type_stop((*))) lkmenodev, 0, (dev_type_select((*))) lkmenodev,
  (dev_type_mmap((*))) lkmenodev }

#endif /* _KERNEL */

Now this is the program we'll use to test (chardevtest.c):
#include <sys/types.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#include "common.h"

int
main(void)
{
  struct ourdev_io a;
  int error, fd;

  fd = open("/dev/ourdev", O_WRONLY);
  if (fd == -1)
  err(1, "open");

  error = ioctl(fd, ODREAD, &a);
  if (error == -1)
  err(1, "ioctl");

  printf("%d %s", a.value, a.msg);
 
  bzero(a.msg, MAXMSGLEN);

  strlcpy(a.msg, "cowsn", sizeof(a.msg));
  a.value = 42;

  error = ioctl(fd, ODWRITE, &a);
  if (error == -1)
  err(1, "ioctl");

  bzero(&a, sizeof(struct ourdev_io));

  error = ioctl(fd, ODREAD, &a);
  if (error == -1)
  err(1, "ioctl");

  printf("%d %s", a.value, a.msg);
 
  close(fd);
 
  exit(0);
}

首先它讀取存在的值,然后自己替換掉.最后它讀取這個(gè)新的值并且打印出來(lái),用來(lái)確定它們替換成功.

編譯測(cè)試程序:

[e4gle@openbsd29]# gcc -o chardevtest chardevtest.c
[e4gle@openbsd29]#

運(yùn)行:
[e4gle@openbsd29]# ./chardevtest
13 hello world!
42 cows
[e4gle@openbsd29]#

再用dd命令看看現(xiàn)在的內(nèi)部字符應(yīng)該是'cows'了.

★虛擬文件系統(tǒng)模塊

增加一個(gè)虛擬文件系統(tǒng)是非常簡(jiǎn)單的.假如你要開(kāi)發(fā)一個(gè)新的文件系統(tǒng)或者支持現(xiàn)存的文件系統(tǒng),就需要寫(xiě)一個(gè)模塊作為接口.同樣的,假如需

要調(diào)試已經(jīng)存在的文件系統(tǒng),也需要那樣
一個(gè)接口.必須確定你的內(nèi)核不支持目標(biāo)文件系統(tǒng).

一個(gè)虛擬文件系統(tǒng)的模塊的結(jié)構(gòu)應(yīng)該象如下定義:

struct lkm_vfs {
  MODTYPE lkm_type;
  int lkm_ver;
  char*lkm_name;
  u_longlkm_offset;
  struct vfsconf*lkm_vfsconf;
};

和前面的例子差不多,我們也有個(gè)模塊類(lèi)型(LM_VFS),一個(gè)版本號(hào),一個(gè)模塊名和一個(gè)偏移值.在這個(gè)vfs模塊的例子中,offset值是用不到的.

最后我們需要一個(gè)指向vfsconf結(jié)構(gòu)
的指針,它包括了虛擬文件系統(tǒng)的操作向量以及一些其他信息(vfsconf結(jié)構(gòu)在頭文件/usr/include/sys/mount.h中定義).
此結(jié)構(gòu)通過(guò)MOD_VFS宏來(lái)初始化:

MOD_VFS("nullfs", -1, &nullfs_vfsconf)

我們看看上面的代碼,第一個(gè)參數(shù)是我們的模塊名,第二個(gè)參數(shù)offset,這個(gè)參數(shù)在我們的vfs模塊中無(wú)關(guān)緊要(前面說(shuō)過(guò),可以不用).最后一個(gè)參

數(shù)是我們的文件系統(tǒng)的結(jié)構(gòu).
在你的模塊的外部接口中,你必須調(diào)用vfs_opv_init_explicit和vfs_opv_init_default來(lái)分配和初始化默認(rèn)操作向量.因?yàn)槲募到y(tǒng)被編譯

進(jìn)內(nèi)核,所以通過(guò)定義在
/usr/src/sys/kern/vfs_conf.c里的vfs_opv_desc[]來(lái)在系統(tǒng)啟動(dòng)的時(shí)候裝載.

一個(gè)需要注意的是當(dāng)用需要用ld程序來(lái)鏈接多個(gè)源代碼文件來(lái)為modload提供目標(biāo)文件時(shí),你必須用-r標(biāo)記來(lái)創(chuàng)建一個(gè)可重定位的目標(biāo)文件.

因?yàn)閙odload在把你的模塊鏈接進(jìn)
內(nèi)核的同時(shí)需要用到ld程序.可以用modload的-d標(biāo)記來(lái)察看ld運(yùn)行的內(nèi)部參數(shù).
這兒是一個(gè)fs模塊的完整代碼 (nullmod.c):


#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/file.h>
#include <sys/errno.h>

/*
* 文件系統(tǒng)的操作結(jié)構(gòu)
* 參考:/usr/src/sys/miscfs/nullfs/
*/

extern struct vfsops null_vfsops;
extern struct vnodeopv_desc null_vnodeop_opv_desc;

struct vfsconf nullfs_vfsconf = {
  &null_vfsops, MOUNT_NULL, 9, 0, 0, NULL, NULL
};

/*
* 聲明我們的模塊結(jié)構(gòu),通過(guò)我們文件系統(tǒng)的模塊名,offset和初始的vfsconf結(jié)構(gòu)
*/

MOD_VFS("nullfs", -1, &nullfs_vfsconf)

/*
* 我們的外部接口.我們初始化文件系統(tǒng)并且用到了DISPATCH宏,在此例中沒(méi)有用到句柄
*/

int
nullfsmod(lkmtp, cmd, ver)
  struct lkm_table *lkmtp;
  int cmd;
  int ver;
{
  vfs_opv_init_explicit(&null_vnodeop_opv_desc);
  vfs_opv_init_default(&null_vnodeop_opv_desc);

  DISPATCH(lkmtp, cmd, ver, lkm_nofunc, lkm_nofunc, lkm_nofunc)
}

好,編譯安裝它:
(一些其他的附加代碼在/usr/src/sys/miscfs/nullfs里)

[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_subr.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_vfsops.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_vnops.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c nullmod.c
[e4gle@openbsd29]# ld -r -o nullfs.o null_vfsops.o null_vnops.o null_subr.o nullmod.o
[e4gle@openbsd29]# modload -o nullfsmod -enullfsmod nullfs.o
[e4gle@openbsd29]# modstat
Type Id Off Loadaddr Size Info Rev Module Name
VFS 0-1 e0b84000 0003 e0b860d0 2 nullfs
[e4gle@openbsd29]#

ok,虛擬文件系統(tǒng)模塊就說(shuō)到這.

★其他類(lèi)型的模塊

這些模塊被用來(lái)執(zhí)行一些預(yù)定的模塊類(lèi)型所沒(méi)有定義的操作.在我這個(gè)例子中我們將為網(wǎng)絡(luò)協(xié)議棧里加入控制代碼,然后打印出我們接收到的

tcp包的一些信息.

當(dāng)我們?cè)跁?shū)寫(xiě)其他類(lèi)型的模塊時(shí),我們必須要完整的檢查一遍,確定沒(méi)有預(yù)定的操作.例如,同樣的操作模塊不能被加載兩次.這等于我們?cè)谕鶅?nèi)

核中去寫(xiě)入模塊.當(dāng)然,
我們都會(huì)在模塊加載和卸載的控制函數(shù)里去控制.

一個(gè)其他類(lèi)型的模塊結(jié)構(gòu)象下面這樣定義:

struct lkm_misc {
  MODTYPE lkm_type;
  int lkm_ver;
  char*lkm_name;
  u_longlkm_offset;
};

同樣,我們首先有一個(gè)模塊的類(lèi)型(在這個(gè)例子中試LM_MISC),然后是lkm的版本,再接著是模塊名和offset的值.在我的這個(gè)例子中offset值

沒(méi)有用到,但在/usr/share/lkm/misc
提供的例子中(增加一個(gè)系統(tǒng)調(diào)用)offset被用來(lái)在系統(tǒng)調(diào)用表里面標(biāo)記一個(gè)新的系統(tǒng)調(diào)用的位置.
用MOD_MISC宏來(lái)初始化該結(jié)構(gòu):

MOD_MISC("tcpinfo")

這里只有一個(gè)參數(shù),指定了模塊名.
當(dāng)我們的模塊被加載后,該模塊把tcp_input函數(shù)的指針改為我們制定的new_input函數(shù).新的函數(shù)會(huì)打印出mbuf里的包頭的一些信息,然后

再調(diào)用原來(lái)的tcp_input函數(shù).在做這些
之前,我們一定要確定同類(lèi)的模塊沒(méi)有被加載.

對(duì)于這個(gè)模塊一些值得注意的地方:首先運(yùn)行這個(gè)模塊時(shí)不適合傳輸大量的tcp包,printf()會(huì)變的很慢.大家試一下就知道.這個(gè)例子只是做測(cè)

試之用,其實(shí)大家可以想想我們既然
可以改變tcp協(xié)議棧里的函數(shù)指針,我們用模塊來(lái)做一個(gè)tcp的內(nèi)核后門(mén)也應(yīng)該很容易,就留給大家思考吧,呵呵.第二,此代碼原來(lái)是運(yùn)行在

freebsd之上的,稍微修改了一下而已,
bsd系列的內(nèi)核真是很相像.

以下是該模塊的完整代碼(tcpmod.c):

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>

/*
* 我們將改變protosw結(jié)構(gòu)中的TCP入口.
*/

extern struct protosw inetsw[];

/*
* 我們自定義的函數(shù)
*/

extern intlkmexists __P((struct lkm_table *));
extern char*inet_ntoa __P((struct in_addr));

static void new_input __P((struct mbuf *, ...));
static void (*old_tcp_input) __P((struct mbuf *, ...));

/*
* 聲明我們的模塊結(jié)構(gòu)
*/

MOD_MISC("tcpinfo")

/*
* 我們的句柄函數(shù),用來(lái)加載和卸載模塊.
*/

int
tcpmod_handler(lkmtp, cmd)
  struct lkm_table *lkmtp;
  int cmd;
{
  int s;

  switch(cmd) {

  case LKM_E_LOAD:
 
  /*
 * 確定此模塊是第一次加載使用
 */

  if (lkmexists(lkmtp))
  return(EEXIST);
 
  /*
 * 阻賽網(wǎng)絡(luò)協(xié)議進(jìn)程,我們把tcp_input函數(shù)指針改成我們自己的包裝函數(shù).
 */

  s = splnet();
  old_tcp_input = inetsw[2].pr_input;
  inetsw[2].pr_input = new_input;
  splx(s);

  break;

  case LKM_E_UNLOAD:

  /*
 * 當(dāng)模塊退出時(shí)返回原來(lái)的結(jié)構(gòu)
 */

  s = splnet();
  inetsw[2].pr_input = old_tcp_input;
  splx(s);
 
  break;
  }

  return(0);
}

/*
* 我們的外部接口,沒(méi)有做什么,用到了DISPATCH宏
*/

int
tcpinfo(lkmtp, cmd, ver)
  struct lkm_table *lkmtp;
  int cmd;
  int ver;
{
  DISPATCH(lkmtp, cmd, ver, tcpmod_handler, tcpmod_handler, lkm_nofunc)
}

/*
* 定義我們自己的包裝的tcp_input函數(shù).假如mbuf里有包頭,則打印出網(wǎng)絡(luò)接口接收到的包
* 的總長(zhǎng)度以及包的源地址.然后使原來(lái)的tcp_input函數(shù)正常運(yùn)行.
*/

static void
new_input(struct mbuf *m, ...)
{
  va_list ap;
  int iphlen;
  struct ifnet *ifnp;
  struct ip *ip;

  va_start(ap, m);
  iphlen = va_arg(ap, int);
  va_end(ap);
 
  if (m->m_flags & M_PKTHDR) {
  ifnp = m->m_pkthdr.rcvif;
  ip = mtod(m, struct ip *);
  printf("incoming packet: %d bytes ", m->m_pkthdr.len);
  printf("on %s from %sn", ifnp->if_xname, inet_ntoa(ip->ip_src));
 
  }
 
  (*old_tcp_input)(m, iphlen);

  return;
}

好,我們編譯安裝它:
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c tcpmod.c
[e4gle@openbsd29]# modload -o tcpinfo.o -etcpinfo tcpmod.o

產(chǎn)生一些tcp連接,用dmesg來(lái)看看是否正常工作:
[e4gle@openbsd29]# dmesg tail -3
incoming packet: 1500 bytes on ne3 from 129.128.5.191
incoming packet: 1205 bytes on ne3 from 129.128.5.191
incoming packet: 52 bytes on ne3 from 129.128.5.191
[e4gle@openbsd29]#

ok,到這里結(jié)束,足以說(shuō)明問(wèn)題了.

★結(jié)束語(yǔ)

寫(xiě)這篇文章的目的還是為了讓大家如們bsd系列的內(nèi)核編程,驅(qū)動(dòng)程序編程的入門(mén),當(dāng)然,作為一個(gè)網(wǎng)絡(luò)安全的專(zhuān)業(yè)人員應(yīng)該可以從這篇文章里

面看到一些東西,就是一些內(nèi)核級(jí)別
的后門(mén)和截獲技術(shù),例如,我們可以通過(guò)增加和重定向系統(tǒng)調(diào)用的模塊來(lái)截獲系統(tǒng)調(diào)用,我們可以用剛才的最后一種模塊來(lái)做一個(gè)內(nèi)核級(jí)別的

tcp后門(mén)等等.當(dāng)然我們還可以利用
模塊來(lái)制作一些內(nèi)核級(jí)的安全工具.發(fā)揮想象力,留給大家了,呵呵