Clamav殺毒軟件源碼分析筆記[九]

Clamav殺毒軟件源碼分析筆記[九]


刺蝟@http://blog.csdn.net/littlehedgehog





[數據流病毒掃描]


數據流病毒掃描,聽上去貌似很牛逼的稱呼,其實就是一個傳送數據流,接收數據流,掃描病毒而已. 貌似掃描病毒代碼亦是很牛逼的代碼,其實也不然,不過調用掃描病毒的引擎API函數爾. 調API調久了,自然想去看看這個API究竟怎麼回事,於是乎就去看kernel代碼了,想剛哥早年做遊戲開發,光是調用庫函數,索然無味,便一門心思要開發3D引擎. 唉,看久了API自然想去實際操作,封封API,這也就是國家要禁止黃片的原因吧...


文章開篇我就決定暫時不要研究引擎API,一來裏面難度比較大,二則我目前還沒準備要從事病毒工作. 這裏我們也就限於調用clamlib庫API而已.


先貼代碼,湊齊字數

  1. /* 基於流的病毒掃描,很高級的稱呼麼? 其實就是客戶端把數據傳到服務端,服務器掃描. 需要注意這裏服務端是動態分配端口,也就是說我們需要把新看上的動態端口告訴客戶端,這樣那小子纔好傳麻煩過來 :*/
  2. int scanstream(int odesc, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt)
  3. {
  4.     int ret, portscan = CL_DEFAULT_MAXPORTSCAN, sockfd, port = 0, acceptd;
  5.     int tmpd, bread, retval, timeout, btread, min_port, max_port;
  6.     long int size = 0, maxsize = 0;
  7.     short bound = 0, rnd_port_first = 1;
  8.     const char *virname;
  9.     char buff[FILEBUFF];
  10.     struct sockaddr_in server;
  11.     struct hostent *he;
  12.     struct cfgstruct *cpt;
  13.     FILE *tmp = NULL;


  14.     /* get min port 獲取最小端口號*/
  15.     if ((cpt = cfgopt(copt, "StreamMinPort")))
  16.     {
  17.         if (cpt->numarg < 1024 || cpt->numarg > 65535)
  18.             min_port = 1024;
  19.         else
  20.             min_port = cpt->numarg;
  21.     }
  22.     else
  23.         min_port = 1024;

  24.     /* get max port 獲取最大端口號*/
  25.     if ((cpt = cfgopt(copt, "StreamMaxPort")))
  26.     {
  27.         if (cpt->numarg < min_port || cpt->numarg > 65535)
  28.             max_port = 65535;
  29.         else
  30.             max_port = cpt->numarg;
  31.     }
  32.     else
  33.         max_port = 2048;

  34.     /* bind to a free port */
  35.     while (!bound && --portscan)    //這裏portscan默認是1000,也就是這裏最多嘗試1000個端口
  36.     { 
  37.         if (rnd_port_first)         //第一次分配端口   是最小端口+隨機產生的數字(當然保證在正確的範圍之類)
  38.         {
  39.             /* try a random port first */
  40.             port = min_port + cli_rndnum(max_port - min_port + 1);
  41.             rnd_port_first = 0;
  42.         }
  43.         else                        //第一次分配失敗後後面就是依次嘗試了
  44.         {
  45.             /* try the neighbor ports */
  46.             if (--port < min_port)
  47.                 port=max_port;
  48.         }

  49.         memset((char *) &server, 0, sizeof(server));
  50.         server.sin_family = AF_INET;
  51.         server.sin_port = htons(port);

  52.         if ((cpt = cfgopt(copt, "TCPAddr")))
  53.         {
  54.             pthread_mutex_lock(&gh_mutex);
  55.             if ((he = gethostbyname(cpt->strarg)) == 0)     //這裏通常我們設置的是127.0.0.1 
  56.             {
  57.                 logg("!gethostbyname(%s) error: %s/n", cpt->strarg);
  58.                 mdprintf(odesc, "gethostbyname(%s) ERROR/n", cpt->strarg);
  59.                 pthread_mutex_unlock(&gh_mutex);
  60.                 return -1;
  61.             }
  62.             server.sin_addr = *(struct in_addr *) he->h_addr_list[0];
  63.             pthread_mutex_unlock(&gh_mutex);
  64.         }
  65.         else
  66.             server.sin_addr.s_addr = INADDR_ANY;

  67.         if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  68.             continue;

  69.         if (bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1)
  70.             close(sockfd);
  71.         else
  72.             bound = 1;
  73.     }

  74.     if ((cpt = cfgopt(copt, "ReadTimeout")))
  75.         timeout = cpt->numarg;
  76.     else
  77.         timeout = CL_DEFAULT_SCANTIMEOUT;

  78.     if (timeout == 0)
  79.         timeout = -1;
  80.  
  81.     if (!bound && !portscan)
  82.     {
  83.         logg("!ScanStream: Can't find any free port./n");
  84.         mdprintf(odesc, "Can't find any free port. ERROR/n");
  85.         close(sockfd);
  86.         return -1;
  87.     }
  88.     else
  89.     {
  90.         listen(sockfd, 1);      //這裏隊列長度只爲1
  91.         mdprintf(odesc, "PORT %d/n", port);     //這裏把新看上的端口告訴客戶端
  92.     }

  93.     //retval如果是正數表示有多少文件描述符準備好了  --我老是忘字 -_-!
  94.     switch (retval = poll_fd(sockfd, timeout))
  95.     {
  96.     case 0: /* timeout */
  97.         mdprintf(odesc, "Accept timeout. ERROR/n");
  98.         logg("!ScanStream: accept timeout./n");
  99.         close(sockfd);
  100.         return -1;
  101.     case -1:
  102.         mdprintf(odesc, "Accept poll. ERROR/n");
  103.         logg("!ScanStream: accept poll failed./n");
  104.         close(sockfd);
  105.         return -1;
  106.     }

  107.     if ((acceptd = accept(sockfd, NULL, NULL)) == -1)
  108.     {
  109.         close(sockfd);
  110.         mdprintf(odesc, "accept() ERROR/n");
  111.         logg("!ScanStream: accept() failed./n");
  112.         return -1;
  113.     }

  114.     logg("*Accepted connection on port %d, fd %d/n", port, acceptd);

  115.     /* 下面是建立臨時文件,tmpfile其實是一個庫函數,真沒想到*/
  116.     if ((tmp = tmpfile()) == NULL)
  117.     {
  118.         shutdown(sockfd, 2);
  119.         close(sockfd);
  120.         close(acceptd);
  121.         mdprintf(odesc, "tempfile() failed. ERROR/n");              
  122.         logg("!ScanStream: Can't create temporary file./n");
  123.         return -1;
  124.     }
  125.     tmpd = fileno(tmp);

  126.     if ((cpt = cfgopt(copt, "StreamMaxLength")))
  127.         maxsize = cpt->numarg;
  128.     else
  129.         maxsize = CL_DEFAULT_STREAMMAXLEN;


  130.     btread = sizeof(buff);
  131.     
  132.     /* 下面主要是通過才綁定的socket獲取客戶端傳來的文件,然後我們掃描,前面我們綁定端口忙活了一大陣子就是爲了打開通道獲取文件*/
  133.     while ((retval = poll_fd(acceptd, timeout)) == 1)       //大於0說明有文件描述符就緒了,好,我們開始行動
  134.     {
  135.         bread = read(acceptd, buff, btread);                
  136.         if (bread <= 0)
  137.             break;
  138.         size += bread;

  139.         if (writen(tmpd, buff, bread) != bread)             //writen是作者自己編寫的函數 因爲這裏我們需要一個函數很賣命的給我們寫數據,可以說了往死裏寫了
  140.         {
  141.             shutdown(sockfd, 2);
  142.             close(sockfd);
  143.             close(acceptd);
  144.             mdprintf(odesc, "Temporary file -> write ERROR/n");
  145.             logg("!ScanStream: Can't write to temporary file./n");
  146.             if (tmp)
  147.                 fclose(tmp);
  148.             return -1;
  149.         }

  150.         if (maxsize && (size + btread >= maxsize))      //tmp文件我們默認10M
  151.         {
  152.             btread = (maxsize - size); /* only read up to max 要保證不能超過文件最大值*/

  153.             if (btread <= 0)
  154.             {
  155.                 logg("^ScanStream: Size limit reached ( max: %d)/n", maxsize);
  156.                 break/* Scan what we have */
  157.             }
  158.         }
  159.     }

  160.     switch (retval)
  161.     {
  162.     case 0: /* timeout */
  163.         mdprintf(odesc, "read timeout ERROR/n");
  164.         logg("!ScanStream: read timeout./n");
  165.     case -1:
  166.         mdprintf(odesc, "read poll ERROR/n");
  167.         logg("!ScanStream: read poll failed./n");
  168.     }

  169.     lseek(tmpd, 0, SEEK_SET);
  170.     ret = cl_scandesc(tmpd, &virname, scanned, root, limits, options);
  171.     if (tmp)
  172.         fclose(tmp);

  173.     close(acceptd);
  174.     close(sockfd);

  175.     if (ret == CL_VIRUS)         //有毒 注意這裏odesc是我們和客戶端通信的唯一通道
  176.     {
  177.         mdprintf(odesc, "stream: %s FOUND/n", virname);
  178.         logg("stream: %s FOUND/n", virname);
  179.         virusaction("stream", virname, copt);
  180.     }
  181.     else if (ret != CL_CLEAN)       //無毒又不是乾淨的,表明出錯了
  182.     {
  183.         mdprintf(odesc, "stream: %s ERROR/n", cl_strerror(ret));
  184.         logg("stream: %s ERROR/n", cl_strerror(ret));
  185.     }
  186.     else
  187.     {
  188.         mdprintf(odesc, "stream: OK/n");
  189.         if (logok)
  190.             logg("stream: OK/n");
  191.     }

  192.     return ret;
  193. }
scanstream的功能就是: 分配臨時端口─────從臨時端口上讀取客戶端的數據流(Y的,就是病毒嫌疑文件)──────掃描病毒──────結果反饋給客戶端。 很清晰很明瞭的過程,不過要明白, 我們分配臨時端口,只是爲了獲取數據流,傳完文件就關閉,其它的一切與客戶端的通信都建立在我們的老端口上.比如後面我們通知客戶端小心,有毒,我們就用的是 mdprintf(odesc, "stream: %s FOUND/n", virname);   

寫這篇文章的時候,想起一個問題,INADDR_ANY,用這個代表什麼? 網上斷章取義的拿來一段博文:


INADDR_ANY就是指定地址爲0.0.0.0的地址,這個地址事實上表示不確定地址,或“所有地址”、“任意地址”。 一般來說,在各個系統中均定義成爲0值。

例如MontiVista Linux中在/usr/include/netinet/in.h定義爲:

/* Address to accept any incoming messages.  */
#define INADDR_ANY              ((in_addr_t) 0x00000000)


一般情況下,如果你要建立網絡服務器應用程序,則你要通知服務器操作系統:請在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上進行偵聽,並且把偵聽到的數據包發送給我。這個過程,你是通過bind()系統調用完成的。——也就是說,你的程序要綁定服務器的某地址,或者 說:把服務器的某地址上的某端口占爲已用。服務器操作系統可以給你這個指定的地址,也可以不給你。
如果你的服務器有多個網卡(每個網卡上有不同的 IP地址),而你的服務(不管是在udp端口上偵聽,還是在tcp端口上偵聽),出於某種原因:可能是你的服務器操作系統可能隨時增減IP地址,也有可能 是爲了省去確定服務器上有什麼網絡端口(網卡)的麻煩 —— 可以要在調用bind()的時候,告訴操作系統:“我需要在 yyyy 端口上偵聽,所以發送到服務器的這個端口,不管是哪個網卡/哪個IP地址接收到的數據,都是我處理的。”



其實關於病毒command的這系列代碼最好緊貼着客戶端代碼進行,看了服務端接收信息,我們打破書上限制,直接來看客戶端.



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章