刺蝟@http://blog.csdn.net/littlehedgehog
[數據流病毒掃描]
數據流病毒掃描,聽上去貌似很牛逼的稱呼,其實就是一個傳送數據流,接收數據流,掃描病毒而已. 貌似掃描病毒代碼亦是很牛逼的代碼,其實也不然,不過調用掃描病毒的引擎API函數爾. 調API調久了,自然想去看看這個API究竟怎麼回事,於是乎就去看kernel代碼了,想剛哥早年做遊戲開發,光是調用庫函數,索然無味,便一門心思要開發3D引擎. 唉,看久了API自然想去實際操作,封封API,這也就是國家要禁止黃片的原因吧...
文章開篇我就決定暫時不要研究引擎API,一來裏面難度比較大,二則我目前還沒準備要從事病毒工作. 這裏我們也就限於調用clamlib庫API而已.
先貼代碼,湊齊字數
- /* 基於流的病毒掃描,很高級的稱呼麼? 其實就是客戶端把數據傳到服務端,服務器掃描. 需要注意這裏服務端是動態分配端口,也就是說我們需要把新看上的動態端口告訴客戶端,這樣那小子纔好傳麻煩過來 :*/
- int scanstream(int odesc, unsigned long int *scanned, const struct cl_node *root, const struct cl_limits *limits, int options, const struct cfgstruct *copt)
- {
- int ret, portscan = CL_DEFAULT_MAXPORTSCAN, sockfd, port = 0, acceptd;
- int tmpd, bread, retval, timeout, btread, min_port, max_port;
- long int size = 0, maxsize = 0;
- short bound = 0, rnd_port_first = 1;
- const char *virname;
- char buff[FILEBUFF];
- struct sockaddr_in server;
- struct hostent *he;
- struct cfgstruct *cpt;
- FILE *tmp = NULL;
- /* get min port 獲取最小端口號*/
- if ((cpt = cfgopt(copt, "StreamMinPort")))
- {
- if (cpt->numarg < 1024 || cpt->numarg > 65535)
- min_port = 1024;
- else
- min_port = cpt->numarg;
- }
- else
- min_port = 1024;
- /* get max port 獲取最大端口號*/
- if ((cpt = cfgopt(copt, "StreamMaxPort")))
- {
- if (cpt->numarg < min_port || cpt->numarg > 65535)
- max_port = 65535;
- else
- max_port = cpt->numarg;
- }
- else
- max_port = 2048;
- /* bind to a free port */
- while (!bound && --portscan) //這裏portscan默認是1000,也就是這裏最多嘗試1000個端口
- {
- if (rnd_port_first) //第一次分配端口 是最小端口+隨機產生的數字(當然保證在正確的範圍之類)
- {
- /* try a random port first */
- port = min_port + cli_rndnum(max_port - min_port + 1);
- rnd_port_first = 0;
- }
- else //第一次分配失敗後後面就是依次嘗試了
- {
- /* try the neighbor ports */
- if (--port < min_port)
- port=max_port;
- }
- memset((char *) &server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = htons(port);
- if ((cpt = cfgopt(copt, "TCPAddr")))
- {
- pthread_mutex_lock(&gh_mutex);
- if ((he = gethostbyname(cpt->strarg)) == 0) //這裏通常我們設置的是127.0.0.1
- {
- logg("!gethostbyname(%s) error: %s/n", cpt->strarg);
- mdprintf(odesc, "gethostbyname(%s) ERROR/n", cpt->strarg);
- pthread_mutex_unlock(&gh_mutex);
- return -1;
- }
- server.sin_addr = *(struct in_addr *) he->h_addr_list[0];
- pthread_mutex_unlock(&gh_mutex);
- }
- else
- server.sin_addr.s_addr = INADDR_ANY;
- if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
- continue;
- if (bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1)
- close(sockfd);
- else
- bound = 1;
- }
- if ((cpt = cfgopt(copt, "ReadTimeout")))
- timeout = cpt->numarg;
- else
- timeout = CL_DEFAULT_SCANTIMEOUT;
- if (timeout == 0)
- timeout = -1;
- if (!bound && !portscan)
- {
- logg("!ScanStream: Can't find any free port./n");
- mdprintf(odesc, "Can't find any free port. ERROR/n");
- close(sockfd);
- return -1;
- }
- else
- {
- listen(sockfd, 1); //這裏隊列長度只爲1
- mdprintf(odesc, "PORT %d/n", port); //這裏把新看上的端口告訴客戶端
- }
- //retval如果是正數表示有多少文件描述符準備好了 --我老是忘字 -_-!
- switch (retval = poll_fd(sockfd, timeout))
- {
- case 0: /* timeout */
- mdprintf(odesc, "Accept timeout. ERROR/n");
- logg("!ScanStream: accept timeout./n");
- close(sockfd);
- return -1;
- case -1:
- mdprintf(odesc, "Accept poll. ERROR/n");
- logg("!ScanStream: accept poll failed./n");
- close(sockfd);
- return -1;
- }
- if ((acceptd = accept(sockfd, NULL, NULL)) == -1)
- {
- close(sockfd);
- mdprintf(odesc, "accept() ERROR/n");
- logg("!ScanStream: accept() failed./n");
- return -1;
- }
- logg("*Accepted connection on port %d, fd %d/n", port, acceptd);
- /* 下面是建立臨時文件,tmpfile其實是一個庫函數,真沒想到*/
- if ((tmp = tmpfile()) == NULL)
- {
- shutdown(sockfd, 2);
- close(sockfd);
- close(acceptd);
- mdprintf(odesc, "tempfile() failed. ERROR/n");
- logg("!ScanStream: Can't create temporary file./n");
- return -1;
- }
- tmpd = fileno(tmp);
- if ((cpt = cfgopt(copt, "StreamMaxLength")))
- maxsize = cpt->numarg;
- else
- maxsize = CL_DEFAULT_STREAMMAXLEN;
- btread = sizeof(buff);
- /* 下面主要是通過才綁定的socket獲取客戶端傳來的文件,然後我們掃描,前面我們綁定端口忙活了一大陣子就是爲了打開通道獲取文件*/
- while ((retval = poll_fd(acceptd, timeout)) == 1) //大於0說明有文件描述符就緒了,好,我們開始行動
- {
- bread = read(acceptd, buff, btread);
- if (bread <= 0)
- break;
- size += bread;
- if (writen(tmpd, buff, bread) != bread) //writen是作者自己編寫的函數 因爲這裏我們需要一個函數很賣命的給我們寫數據,可以說了往死裏寫了
- {
- shutdown(sockfd, 2);
- close(sockfd);
- close(acceptd);
- mdprintf(odesc, "Temporary file -> write ERROR/n");
- logg("!ScanStream: Can't write to temporary file./n");
- if (tmp)
- fclose(tmp);
- return -1;
- }
- if (maxsize && (size + btread >= maxsize)) //tmp文件我們默認10M
- {
- btread = (maxsize - size); /* only read up to max 要保證不能超過文件最大值*/
- if (btread <= 0)
- {
- logg("^ScanStream: Size limit reached ( max: %d)/n", maxsize);
- break; /* Scan what we have */
- }
- }
- }
- switch (retval)
- {
- case 0: /* timeout */
- mdprintf(odesc, "read timeout ERROR/n");
- logg("!ScanStream: read timeout./n");
- case -1:
- mdprintf(odesc, "read poll ERROR/n");
- logg("!ScanStream: read poll failed./n");
- }
- lseek(tmpd, 0, SEEK_SET);
- ret = cl_scandesc(tmpd, &virname, scanned, root, limits, options);
- if (tmp)
- fclose(tmp);
- close(acceptd);
- close(sockfd);
- if (ret == CL_VIRUS) //有毒 注意這裏odesc是我們和客戶端通信的唯一通道
- {
- mdprintf(odesc, "stream: %s FOUND/n", virname);
- logg("stream: %s FOUND/n", virname);
- virusaction("stream", virname, copt);
- }
- else if (ret != CL_CLEAN) //無毒又不是乾淨的,表明出錯了
- {
- mdprintf(odesc, "stream: %s ERROR/n", cl_strerror(ret));
- logg("stream: %s ERROR/n", cl_strerror(ret));
- }
- else
- {
- mdprintf(odesc, "stream: OK/n");
- if (logok)
- logg("stream: OK/n");
- }
- return ret;
- }
寫這篇文章的時候,想起一個問題,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的這系列代碼最好緊貼着客戶端代碼進行,看了服務端接收信息,我們打破書上限制,直接來看客戶端.