OpenBSD可加載內(nèi)核模塊編程完全向?qū)?/h1>
發(fā)表時(shí)間:2024-01-18 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]緒論 這篇文章我說(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_longlkm_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_longlkm_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 rootwheel 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_longlkm_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_longlkm_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 intlkmexists __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ā)揮想象力,留給大家了,呵呵