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的这系列代码最好紧贴着客户端代码进行,看了服务端接收信息,我们打破书上限制,直接来看客户端.



你可能感兴趣的:(api,socket,Stream,server,服务器,杀毒软件)