系统调用拦截被广泛用于各种目的,例如安全性,调试等。 自AIX 6.1 TL06和AIX 7.1起,AIX提供了一个名为kmod_util
的标准公共接口来支持系统调用拦截。 本教程说明了如何在不同情况下使用kmod_util
接口进行系统调用拦截。
了解系统调用指令的运行
系统调用是允许用户应用程序请求需要特殊特权的操作的例程。 系统调用是通过运行系统调用指令的用户代码进行的,这导致输入系统调用处理程序。
所有对进程的系统调用都将解析为伪函数描述符,从而导致svc_instr被调用。 链接用户级程序时,活页夹为每个未解决的外部调用插入粘合代码(glink.s)。 加载程序后,加载程序通过将用户TOC中的条目设置为指向系统调用(svc)表的用户空间部分中的条目(是系统调用)来解决这些调用。 该svc表条目包含一个伪函数描述符,该描述符以svc_instr作为其IAR,以svc号作为其TOC。 因此,当用户进行系统调用时,代码将分支到粘合代码,该粘合代码加载描述符的地址(svc表条目),获取iar(svc_instr)和toc值(svc编号),并将svc编号加载到寄存器中2并分支到svc_instr。 我们必须先获取系统调用的svc编号,然后才能对其进行拦截。
基本系统调用
为了更好地理解系统调用的运行,我们编写了一个示例程序testchmod (请参见“可下载资源”部分中的sc_intercept.zip )来调用基本系统调用chmod
。
清单1.示例程序:testchmod.c
int main(void)
{
printf("0x%llx\n", chmod);
chmod("./testchmod.c", S_IRUSR|S_IWUSR|S_IXUSR);
return 0;
}
该示例程序将chmod
的函数指针显示为输出:
# xlc -q64 -o testchmod testchmod.c
# ./testchmod
0x90000000048f3e0
现在,让我们使用dbx
检查调用chmod
时发生了什么。
首先,我们可以获得如图1所示的粘连代码。
图1.显示胶水代码
然后,让我们检查一下寄存器r0,r2和r12中存储了什么值,如图2所示。
图2.获取svc号
很明显,函数指针chmod
(0x090000000048f3e0)指向伪函数描述符。 伪函数描述符的第一个双字包含系统调用入口点的地址(0x3700),第二个双字包含chmod
的svc号(0x13e)。
我们还可以使用kd
b
来检查0x90000000048f3e0的内容,如图3所示。
图3.显示svc号
存储在入口点0x03700的下一条指令是系统调用指令sc
。
内核扩展添加的系统调用
添加系统调用是扩展内核提供的功能的几种方法之一。 出于演示目的,我们编写了一个内核扩展,该扩展将名为demo_syscall
的系统调用(请参见“可下载资源”部分中的demo_syscall.zip )添加到内核。 调用demo_syscall
的进程将进入睡眠状态,直到内核扩展终止。
清单2.示例内核扩展:demo_syscall.c
int demo_syscall_init(int cmd, struct uio *uio)
{
if (cmd == CFG_INIT) {
scwait = EVENT_NULL;
} else if (cmd == CFG_TERM) {
e_wakeup(&scwait);
} else {
return -1;
}
return 0;
}
int demo_syscall(int arg)
{
int rc;
bsdlog(LOG_DEBUG | LOG_KERN, "demo_syscall %llx\n", arg);
rc = e_sleep_thread(&scwait, NULL, INTERRUPTIBLE);
return(rc);
}
我们还编写了一个示例程序,调用demo_syscall。
清单3.示例程序:invoke_syscall.c
int main()
{
if (demo_syscall(99) < 0){
perror("demo_syscall error");
exit(1);
}
printf("demo_syscall 0x%llx\n", demo_syscall);
return 0;
}
以下示例程序将demo_syscall
的函数指针显示为输出:
nimtb157:/home/ext>./invoke_syscall
demo_syscall 0x90000000086fe90
使用dbx
,我们可以获得粘合代码并检查伪函数描述符,如图4所示。
图4.显示胶水代码和svc号
我们可以发现,内核扩展添加的系统调用的执行与基本系统调用相同。 伪函数描述符的第一个双字包含系统调用入口点的地址(0x3700),第二个双字包含demo_syscall
的svc号(0x3e9)。
kmod_util内核服务
从AIX 6.1 TL06和AIX 7.1开始,提供了kmod_util
内核服务,该服务允许截取系统调用。 实现了系统调用的侦听,以便即使对于现有进程也侦听对系统调用的所有调用。 kmod_util
的声明在<sys/sysconfig.h>
:
int kmod_util(
int flags, /* cmd : KU_INTERCEPT, KU_INTERCEPT_STOP, KU_INTERCEPT_CANCEL */
void *buffer, /* cmd buffer */
int length /* buffer length */
);
前置和后置功能
称为pre-sc函数的例程被指定为在拦截的系统调用之前被调用。 称为post-sc函数的例程被指定为在截获的系统调用之后被调用。 另外,允许pre-sc函数中止系统调用,提供其自己的返回值,并防止后续的pre-sc函数和系统调用本身被调用。 同样,每个后sc函数可以检查和更改返回值。 如果系统调用没有返回到调用者(例如thread_terminate),则不会调用post-sc函数。
对于每个截获的系统调用,必须指定pre-sc函数或post-sc函数,或两者都指定。 如果在同一kmod_util
调用中为同一系统调用注册了pre-sc函数和post-sc函数,则它们将被视为配对。 kmod_util
调用中指定的所有pre-sc和post-sc函数必须在与kmod_util
内核服务的调用者相同的内核扩展中定义。 但是,其他内核扩展可以拦截相同的系统调用。 最近注册的pre-sc函数被称为first,其成对的post-sc函数被称为last。
pre-sc函数的原型为:
int pre_sc(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer);
其中parms
是指向系统调用参数的指针, cookie
是kmod_util
的调用者指定的不透明值, buffer
是草稿的128字节缓冲区,供pre-sc函数及其成对的post-sc函数使用。
如果一个pre-sc函数返回非零值,则所有后续的pre-sc函数和系统调用都将中止。 rc参数是可以指定备用返回值的地址。 对于已经调用的pre-sc函数,将调用其成对的post-sc函数。
例如,假设内核扩展A请求截获系统调用foo()
,从而提供pre-sc和post-sc函数A-pre和A-post。 然后,内核扩展B和C要求拦截系统调用foo()
,从而提供pre-sc和post-sc函数B-pre,C-pre,B-post和C-post。
调用系统调用foo()
,假定所有pre-sc函数都返回0,则执行以下调用序列:
- C-pre
- B-pre
- 预
- 富
- 一个帖子
- B柱
- C柱
现在假设B-pre返回一个非零值。 在这种情况下,调用顺序为:
- C-pre
- B-pre
- C柱
post-sc函数的原型为:
void post_sc(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer);
post-sc函数的参数与pre-sc函数的参数相同。 特别是,buffer参数是传递给配对的pre-sc函数的同一缓冲区。 返回值可以通过post-sc函数修改。
拦截系统调用
使用KU_INTERCEPT
标志调用kmod_util()
可以启动系统调用拦截。
停止系统呼叫拦截
使用KU_INTERCEPT_STOP
标志调用kmod_util()
暂停对指定系统调用的拦截。 如果已经为指定的系统调用调用了pre-sc函数,则仍将调用其配对的post-sc函数,但是以后对系统调用的调用将不会调用pre-sc或post-sc函数。 停止拦截最初未被调用内核扩展拦截的系统调用是无效的。
取消系统呼叫拦截
通过指定KU_INTERCEPT_CANCEL
标志可以取消系统调用拦截。 取消拦截后,即使调用了成对的pre-sc函数,也不会调用post-sc函数。 取消对最初未被调用内核扩展拦截的系统调用的拦截是无效的,但是可以在不先停止拦截的情况下取消拦截。
缓冲区布局
对于对kmod_util
内核服务的调用,缓冲区包含标头和有关要拦截的系统调用的元素数组。 这些结构的布局在<sys/sysconfig.h>
定义:
struct ku_element {
void *ku_svc; /* function descriptor of system call */
int (*ku_pre)(); /* pre-sc function */
void (*ku_post)(); /* post-sc function */
void *ku_cookie; /* cookie passed to pre-sc and post-sc function */
int ku_bsize; /* intercept_stack size */
unsigned short ku_iflags;
unsigned short ku_oflags;
};
struct ku_intercept {
int ku_version; /* KU_VERSION */
int ku_hdr_len; /* sizeof(struct ku_intercept) */
int ku_element_len; /* sizeof(struct ku_element) */
int ku_num_elements; /* how many system calls to be
intercepted */
};
样本内核扩展
为了演示kmod_util
的用法,我们编写了一个示例内核扩展,其中添加了名为sc_intercept
的系统调用(请参见“可下载资源”部分中的sc_intercept.zip )。 sc_intercept
系统调用具有一个cmd
参数和一个svc
参数,该参数选择调用者想要执行的操作(拦截,停止或取消),该参数指定已拦截系统调用的函数描述符。
清单4.示例内核扩展:sc_intercept.c
int myprec(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer)
{
bsdlog(LOG_DEBUG | LOG_KERN, "pre_sc: parms %llx, %s, %llx, %llx, %s, buffer
%llx\n",
parms, *(long *)parms, ((char *)parms+8), *(long *)((char *)parms+8), cookie,
buffer);
return 0;
}
void mypost(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer)
{
bsdlog(LOG_DEBUG | LOG_KERN, "post_sc: parms %llx, %s, %llx, %s, buffer %llx\n",
parms, *(long *)parms, (long *)(parms+8), cookie, buffer);
}
int sc_intercept(unsigned long long svc, int cmd)
{
struct ku_intercept *buffer;
struct ku_element *kue;
int buflen, rc;
/* valid cmd: KU_INTERCEPT, KU_INTERCEPT_STOP, KU_INTERCEPT_CANCEL */
if(cmd<1 || cmd>3)
return -1;
buflen = sizeof(struct ku_intercept) + sizeof(struct ku_element);
buffer = xmalloc(buflen, 0, kernel_heap);
if(buffer==NULL)
return -1;
buffer->ku_version = KU_VERSION;
buffer->ku_hdr_len = sizeof(struct ku_intercept);
buffer->ku_element_len = sizeof(struct ku_element);
buffer->ku_num_elements = 1;
kue = (struct ku_element *)((char *)buffer + buffer->ku_hdr_len);
kue->ku_svc = (void *)svc;
kue->ku_pre = myprec;
kue->ku_post = mypost;
kue->ku_cookie = "This is cookie!";
kue->ku_bsize = 0;
kue->ku_iflags = 0;
kue->ku_oflags = 0;
rc = kmod_util(cmd, buffer, buflen);
bsdlog(LOG_DEBUG | LOG_KERN, "rc: %d\n", rc);
xmfree(buffer, kernel_heap);
return rc;
}
获取系统调用的函数描述符
要使用kmod_util
拦截系统调用,我们必须知道其函数描述符。 本节演示如何在不同情况下获取系统调用的函数描述符。
内核存储保护键状态
内核存储保护密钥为内核和内核扩展提供了一种存储保护机制。 当内核存储保护键状态不同时,获取系统调用函数描述符的方法也不同。 我们可以使用skeyctl
来检查和更改内核存储保护键的状态,如图5所示。
图5.显示和更改内核键状态
启用内核密钥时的基本系统调用
我们使用相同的示例程序testchmod
来演示如何获取chmod
的函数描述符。 使用kdb
,我们可以获得svc表的基地址。 在“ 了解系统调用指令的运行”部分中,我们知道chmod
的svc号为0x13e。 因此,我们可以计算chmod
的svc表条目并获得函数描述符,如图6所示。
图6.获取函数描述符
在此示例中,svc表的基本地址为le_svc64
(0x04CB0000),而chmod
的svc表条目位于0x04CB09F0。 但是,由于启用了内核密钥,因此存储在0x04CB09F0中的数据不是函数描述符。 相反,它指向函数描述符地址之前的两个双字。 因此,功能描述符为0x2B8F930。
为了测试拦截,我们编写了一个名为样本程序demo_intercept
调用sc_intercept
。
清单5.示例程序:demo_intercept.c
x = strtoull(argv[optind], 0, 16);
rc = sc_intercept(x, cmd);
图7.测试系统调用拦截
要查看拦截结果,我们将syslogd
的输出发送到系统控制台:
nimtb157:/home/ext/sc> more /etc/syslog.conf
*.debug /dev/console
图8.拦截测试结果
禁用内核密钥时的基本系统调用
禁用内核密钥时,系统调用的函数描述符存储在其svc表条目中,如图9所示。
图9.获取函数描述符
我们使用相同的方法来测试拦截,如图10所示。
图10.测试系统调用拦截
我们可以在系统控制台中查看拦截结果,如图11所示。
图11.拦截测试结果
启用内核密钥时,由内核扩展添加的系统调用
我们可以使用与基本系统调用相同的方法来获取由内核扩展添加的系统调用的函数描述符,如图12所示。有趣的是,我们可以发现svc表条目不仅包含函数描述符,而且还包含到函数描述符地址之前的两个双字。
图12.获取函数描述符
我们使用相同的方法来测试拦截,如图13所示。
图13.测试系统调用拦截
我们可以在系统控制台中查看拦截结果,如图14所示。
图14.拦截测试结果
禁用内核密钥时,由内核扩展添加的系统调用
与基本系统调用类似,功能描述符存储在svc表条目中,如图15所示。
图15.获取函数描述符
我们使用相同的方法来测试拦截,如图16所示。
图16.测试系统调用拦截
我们可以在系统控制台中查看拦截结果,如图17所示。
图17.拦截测试结果
翻译自: https://www.ibm.com/developerworks/aix/tutorials/au-interceptcall/index.html