緒論
這篇文章我說明在openbsd上如何進行內核編程,以下句子來自lkm手冊頁: "可加載內核模塊可以答應系統治理員在一台運行著的系統上動態的增加或刪除功能模塊,它同時可以幫助軟件工程師們為內核增加新的功能而根本就不需要重起計算機就可以測試他們開發的程序."
當然,像眾多系統的lkm一樣,它存在一定的安全隱患,哈哈,其實這也是我寫這篇文章給大家的原因:)它提供了更廣泛的空間給惡意的superroot,其實也就是已經得到系統治理員權限的我們。我們利用lkm可以駕馭整個系統而不會輕易被發現. 同樣的, 假如你系統的securelevel在0級一行的話就不能加載或卸載模塊了,假如你要使系統在進入securemode之前可以加載模塊,可以編輯/etc/rc.securelevel文件,添加相應的入口.
總覽
/dev/lkm設備與用戶的交互通過ioctl(2)系列系統調用來進行. 主要是一些工具如modload,modunload和modstat等來控制模塊的加載
和卸載以及模塊的狀態.
lkm接口定義了五種不同的模塊類型:
系統調用模塊
虛擬文件系統模塊
設備驅動模塊
可執行程序解釋器模塊
其它模塊
一個普通的模塊包括三個主要部分:
1) 內核入口和出口的處理(也就是當模塊被加載,被卸載時的動作).
2) 一個外部入口點, 當模塊用modload程序被加載的時候需要用到
3) 模塊的主體, 包含函數代碼等.
對於其他類型的模塊來說,它需要開發人員提供嚴格的控制和當內核模塊卸載的時候對內核原有的狀態的保存.
對於模塊的支持必須用'option LKM'編譯進內核的配置文件.模塊需要支持默認的openBSD 2.9的內核.通常,內核空間的數據接口都被提供
給了模塊來操作.後面
就有一個lkm設備的例子.
每個類型的模塊的內部數據結構裡面都存在一個宏用來加載自己.也就類似模塊本身模塊名的東東,它被指定在內核數據結構中,和模塊的一些
非凡數據如sysent這樣
的針對系統調用模塊的結構在一起.
讓我們看看一些例子吧.
★系統調用模塊.
這裡我們將增加一個新的系統調用printf()的整型和字符串參數.它的原型如下:
int syscall(int, char *)
內核內部定義的一個lkm的syscall結構如下:
strUCt lkm_syscall {
MODTYPE lkm_type;
int lkm_ver;
char *lkm_name;
u_long lkm_offset; /* 保存/分配 內存空間 */
struct sysent *lkm_sysent;
struct sysent lkm_oldent; /*保存原調用,用於lkm的卸載 */
};
現在我們已經有了一個簡單的模塊框架了(應該叫LM_SYSCALL),lkm的版本,模塊名,都在系統調用表裡存在一個相應的入口.這樣我們有
了一個指向結構sysent的模塊框架
我們將用MOD_SYSCALL宏來安裝它:
MOD_SYSCALL("ourcall", -1, &newcallent)
我們來分析一下上面的宏,很明顯,模塊名為"ourcall",用來標示模塊,還有一個作用就是我們利用modstat命令時會顯示出來.-1代表我們
的syscall該插入的位置,在這個
宏當中的-1的意思是我們不用關心位置具體在什麼地方,它會被分配到一個空的位置.最後一個字段newcallent是一個指向sysent的結構,
它包含了我們系統調用的相應的數
據.
除此之外我們還需要一個句柄用來加載和卸載內核模塊,好,在這個例子中我用'hi'來加載,用'bye'來卸載.這對我們調試程序很有幫助.句柄可
以是相同的函數或者單個函數,
假如沒有定義句柄,那麼lkm_nofunc()會簡單的返回0,這個模塊是沒有加載卸載的,也就失去了作用.
我們模塊的外部入口點是ourcall():
int
ourcall(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc)
}
這個句柄可以用來加載,卸載模塊.第四個參數我們用作加載操作,第五個參數用作卸載操作,第六個參數是狀態函數(在此例中沒有用到).
ok!完整的系統調用模塊代碼如下(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>
/* 定義我們自己的系統調用原型 */
int newcall __P((struct proc *p, void *uap, int *retval));
/*
* 所有的系統調用都有三個參數: 一個指向proc結構的結構指針,一個空指針指向參
* 數本身和一個返回指針.下面,我們定義這些參數的結構.假如你只有一個參數,則
* 只需要一個入口就可以了.
*/
struct newcall_args{
syscallarg(int) value;
syscallarg(char *) msg;
};
/*
* 下面這個結構定義了我們的系統調用.第一個參數是系統調用的參數數目,第二個參數
* 是參數的大小,第三個參數是我們的系統調用的代碼了,呵呵:)
*/
static struct sysent newcallent = {
2, sizeof(struct newcall_args), newcall
};
/*
* 好了,到了我們的syscall的核心結構了,呵呵:)
* 第一個參數是syscall的名稱,ioctl()調用用它來查詢syscall.第二個參數告訴我們
* syscall的位置.這裡你可以輸入數字,或者-1來讓系統自動分配.第三個參數指向一個
* sysent結構的指針.
*/
MOD_SYSCALL("ourcall", -1, &newcallent);
/*
* 要使我們的模塊正常運行我們還要用到以下函數.此函數類似Linux的lkm裡面的init_module
* 和cleanup_module.
* 它通過一個指向lkm_table結構的指針來完成我們給定的動作.檢查cmd的值來判定該加載
* 什麼樣的句柄.當我們利用模塊來增加一個系統調用的時候,這兒沒有專門的句柄來操作.
* 當然,我們hacking kernel的時候是不會用例如"hi"和"bye"這樣的簡單的句柄的,我們
* 需要改變系統調用.我們現在是說明原理,其實大同小異:)
*/
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);
}
/*
* 下面就是我們模塊的外部入口點,也就是我們的系統調用的主體.
* 象上面那樣我們通過判定一個cmd所匹配的句柄來描述動作的執行.我們也可以通過一個版本號
* 答應一個模塊兼容以後版本內核的源碼,以保證向下的兼容性.
* DISPATCH宏通過三個參數來表示動作的加載,卸載和狀態.我們看下面例子,對於加載和卸載
* 我們用共享函數ourcall_handler().對於狀態(當增加系統調用的時候就用不到它了)我們
* 用lkm_nofunc(),該函數僅僅簡單的返回0.
*/
int
ourcall(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc)
}
/*
* 最後對於我們的系統調用應該有主體代碼,該調用干了什麼之類.
*/
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參數指定輸出文件名,這和gcc的-o選項是一樣的.-e參數指定我們的外部標示,最後一個參數就是輸入文件.好,我們用modstat看看我們的
模塊有沒有被成功加載:
# modstat
Type Id Off Loadaddr Size Info Rev Module Name
SYSCALL 0 210 e0b92000 0002 e0b93008 2 ourcall
#
以上顯示需要注重一下'off'字段,它標示了該模塊在system call表裡面的位置.這在創建系統調用的時候需要用到.我們可以通過dmesg命令
的輸出'hi'來驗證我們
的模塊正確的加載運行了:
# dmesg tail -2
hi!
DDB symbols added: 150060 bytes
#
好,現在讓我們來看一個測試我們剛才新的系統調用的簡單程序(calltest.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#