盾怪网教程:是一个免费提供流行杀毒软件教程、在线学习分享的学习平台!

OpenBSD可加载内核模块编程完全向导

时间:2024/10/30作者:未知来源:盾怪网教程人气:

[摘要]tail -2 loading module ourdev DDB symbols added: 140232 bytes [e4gle@openbsd29]# 好,我们测试一下我们新创建的设备,用...
tail -2
loading module ourdev
DDB symbols added: 140232 bytes
[e4gle@openbsd29]#

好,我们测试一下我们新创建的设备,用dd命令来测试:
[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]#

现在我来通过一个测试程序来测试一下我们的ioctl调用是否工作.测试程序必须包括模块代码和头文件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);
}

首先它读取存在的值,然后自己替换掉.最后它读取这个新的值并且打印出来,用来确定它们替换成功.

编译测试程序:

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

运行:
[e4gle@openbsd29]# ./chardevtest
13 hello world!
42 cows
[e4gle@openbsd29]#

再用dd命令看看现在的内部字符应该是'cows'了.

★虚拟文件系统模块

增加一个虚拟文件系统是非常简单的.假如你要开发一个新的文件系统或者支持现存的文件系统,就需要写一个模块作为接口.同样的,假如需

要调试已经存在的文件系统,也需要那样
一个接口.必须确定你的内核不支持目标文件系统.

一个虚拟文件系统的模块的结构应该象如下定义:

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

和前面的例子差不多,我们也有个模块类型(LM_VFS),一个版本号,一个模块名和一个偏移值.在这个vfs模块的例子中,offset值是用不到的.

最后我们需要一个指向vfsconf结构
的指针,它包括了虚拟文件系统的操作向量以及一些其他信息(vfsconf结构在头文件/usr/include/sys/mount.h中定义).
此结构通过MOD_VFS宏来初始化:

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

我们看看上面的代码,第一个参数是我们的模块名,第二个参数offset,这个参数在我们的vfs模块中无关紧要(前面说过,可以不用).最后一个参

数是我们的文件系统的结构.
在你的模块的外部接口中,你必须调用vfs_opv_init_explicit和vfs_opv_init_default来分配和初始化默认操作向量.因为文件系统被编译

进内核,所以通过定义在
/usr/src/sys/kern/vfs_conf.c里的vfs_opv_desc[]来在系统启动的时候装载.

一个需要注意的是当用需要用ld程序来链接多个源代码文件来为modload提供目标文件时,你必须用-r标记来创建一个可重定位的目标文件.

因为modload在把你的模块链接进
内核的同时需要用到ld程序.可以用modload的-d标记来察看ld运行的内部参数.
这儿是一个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>

/*
* 文件系统的操作结构
* 参考:/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
};

/*
* 声明我们的模块结构,通过我们文件系统的模块名,offset和初始的vfsconf结构
*/

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

/*
* 我们的外部接口.我们初始化文件系统并且用到了DISPATCH宏,在此例中没有用到句柄
*/

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,虚拟文件系统模块就说到这.

★其他类型的模块

这些模块被用来执行一些预定的模块类型所没有定义的操作.在我这个例子中我们将为网络协议栈里加入控制代码,然后打印出我们接收到的

tcp包的一些信息.

当我们在书写其他类型的模块时,我们必须要完整的检查一遍,确定没有预定的操作.例如,同样的操作模块不能被加载两次.这等于我们在往内

核中去写入模块.当然,
我们都会在模块加载和卸载的控制函数里去控制.

一个其他类型的模块结构象下面这样定义:

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

同样,我们首先有一个模块的类型(在这个例子中试LM_MISC),然后是lkm的版本,再接着是模块名和offset的值.在我的这个例子中offset值

没有用到,但在/usr/share/lkm/misc
提供的例子中(增加一个系统调用)offset被用来在系统调用表里面标记一个新的系统调用的位置.
用MOD_MISC宏来初始化该结构:

MOD_MISC("tcpinfo")

这里只有一个参数,指定了模块名.
当我们的模块被加载后,该模块把tcp_input函数的指针改为我们制定的new_input函数.新的函数会打印出mbuf里的包头的一些信息,然后

再调用原来的tcp_input函数.在做这些
之前,我们一定要确定同类的模块没有被加载.

对于这个模块一些值得注意的地方:首先运行这个模块时不适合传输大量的tcp包,printf()会变的很慢.大家试一下就知道.这个例子只是做测

试之用,其实大家可以想想我们既然
可以改变tcp协议栈里的函数指针,我们用模块来做一个tcp的内核后门也应该很容易,就留给大家思考吧,呵呵.第二,此代码原来是运行在

freebsd之上的,稍微修改了一下而已,
bsd系列的内核真是很相像.

以下是该模块的完整代码(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结构中的TCP入口.
*/

extern struct protosw inetsw[];

/*
* 我们自定义的函数
*/

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 *, ...));

/*
* 声明我们的模块结构
*/

MOD_MISC("tcpinfo")

/*
* 我们的句柄函数,用来加载和卸载模块.
*/

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

  switch(cmd) {

  case LKM_E_LOAD:
 
  /*
 * 确定此模块是第一次加载使用
 */

  if (lkmexists(lkmtp))
  return(EEXIST);
 
  /*
 * 阻赛网络协议进程,我们把tcp_input函数指针改成我们自己的包装函数.
 */

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

  break;

  case LKM_E_UNLOAD:

  /*
 * 当模块退出时返回原来的结构
 */

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

  return(0);
}

/*
* 我们的外部接口,没有做什么,用到了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函数.假如mbuf里有包头,则打印出网络接口接收到的包
* 的总长度以及包的源地址.然后使原来的tcp_input函数正常运行.
*/

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

产生一些tcp连接,用dmesg来看看是否正常工作:
[e4gle@openbsd29]# dmesg

关键词:OpenBSD可加载内核模块编程完全向导




Copyright © 2012-2018 盾怪网教程(http://www.dunguai.com) .All Rights Reserved 网站地图 友情链接

免责声明:本站资源均来自互联网收集 如有侵犯到您利益的地方请及时联系管理删除,敬请见谅!

QQ:1006262270   邮箱:kfyvi376850063@126.com   手机版