seteuid0's blog
Themed by Diary.
简要ClamAV安装、使用与实现分析

ClamAV使用广泛且开源的杀毒软件,支持windows、linux、Unix等主流操作系统,除自身具有病毒查杀功能外,还支持邮件、http代理扫描等插件方式。

安装

由于ClamAV的广泛使用,很多发行版的源里都有clamav,只需要使用相应的包管理工具进行安装即可。如fedora或ubuntu下只需执行:

yum install -y clamav-* sudo apt-get install clamav-*

但然,你也可以源码包安装,网上有很多教程,这里就不在多说。

使用

杀毒软件最终要的当然是病毒库,使用前当然是更新最新的病毒库。

freshclam

ClamAv有2种使用模式,单个程序查杀和前、后台模式查杀。 clamscan就是单个程序查杀的程序,它调用libclamav库完成对指定目录、文件的扫描检查。 clamd、clamdscan是前、后台模式的查杀工具。从名字就可以看出clamd是提供查杀服务的daemon程序,clamdscan是调用查杀服务的客户端。 最简单的使用方式就是命令后加你要扫描的文件或目录,如:

  clamdscan  ~/

ClamAv当然也支持很多参数进行扫描定制,这里略过。 除了命令行的工具外还有很多第三方的图形化工具,如clamtk,更多的见:http://www.clamav.net/lang/en/download/third-party-tools/3rdparty-gui/

实现分析

ClamAv实现的是最原始的特征码扫描,而加载特征码库与实现扫描功能的代码都在libclamav库中实现,两种使用模式都是调用该库完成对应的扫描功能。 libclamav库API提供了病毒扫描的各种函数接口。从病毒中提取的特征字符串被用一定的格式组织在一起并加上签名保护就形成了病毒库,clamav使用的病毒库一般后缀为.cvd文件。 libclamav库的病毒扫描法使用Aho-Corasick精确字符串匹配算法,将病毒库中的特征码与文件中的字符串进行比较,以确定文件中是否有字符串精确匹配上病毒库中的特征码,从而确定是否感染病毒。 Aho-Corasick在Boyer-Moore算法基础上进行了的多种改进,Boyer Moore算法对要搜索的字符串进行逆序字符比较,当被搜索的字符串中的字符在特征字符串中不存在时,将跳过搜索字符串中一个子段。Boyer Moore算法还利用上一次比较的结果来确定下一次的比较位置,Boyer Moore算法与线性搜索比起来每次移动的步长比较多,线性搜索每次移动一个字符,因此,Boyer Moore算法比线性搜索快得多。Aho-Corasick通过创建一种状态图并采用由软件实现的有限状态机来确定字符串在文本中的位置,消除了搜索性能与字符串数量的相关性。 当然ClamAV必须实现相应的两个算法,看如下数据结构:

struct cli_matcher { unsigned int type; /* Extended Boyer-Moore */ uint8_t *bm_shift; struct cli_bm_patt **bm_suffix, **bm_pattab; uint32_t *soff, soff_len; /* for PE section sigs */ uint32_t bm_offmode, bm_patterns, bm_reloff_num, bm_absoff_num; /* HASH */ struct cli_hash_patt hm; /* Extended Aho-Corasick */ uint32_t ac_partsigs, ac_nodes, ac_patterns, ac_lsigs; struct cli_ac_lsig **ac_lsigtable; struct cli_ac_node *ac_root, **ac_nodetable; struct cli_ac_patt **ac_pattable; struct cli_ac_patt **ac_reloff; uint32_t ac_reloff_num, ac_absoff_num; uint8_t ac_mindepth, ac_maxdepth; struct filter *filter; uint16_t maxpatlen; uint8_t ac_only; #ifdef USE_MPOOL mpool_t *mempool; #endif };

ClamAV的病毒库也支持多种多样,看如下一段病毒库加载过程的代码片段,clamav依据不同的病毒库格式进行不同的加载方式:

   if(cli_strbcasestr(dbname, “.db”)) { ret = cli_loaddb(fs, engine, signo, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.cvd”)) { ret = cli_cvdload(fs, engine, signo, options, 0, filename, 0);   } else if(cli_strbcasestr(dbname, “.cld”)) { ret = cli_cvdload(fs, engine, signo, options, 1, filename, 0);   } else if(cli_strbcasestr(dbname, “.hdb”) || cli_strbcasestr(dbname, “.hsb”)) { ret = cli_loadhash(fs, engine, signo, MD5_HDB, options, dbio, dbname); } else if(cli_strbcasestr(dbname, “.hdu”) || cli_strbcasestr(dbname, “.hsu”)) { if(options & CL_DB_PUA) ret = cli_loadhash(fs, engine, signo, MD5_HDB, options | CL_DB_PUA_MODE, dbio, dbname); else skipped = 1;   } else if(cli_strbcasestr(dbname, “.fp”) || cli_strbcasestr(dbname, “.sfp”)) { ret = cli_loadhash(fs, engine, signo, MD5_FP, options, dbio, dbname); } else if(cli_strbcasestr(dbname, “.mdb”) || cli_strbcasestr(dbname, “.msb”)) { ret = cli_loadhash(fs, engine, signo, MD5_MDB, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.mdu”) || cli_strbcasestr(dbname, “.msu”)) { if(options & CL_DB_PUA) ret = cli_loadhash(fs, engine, signo, MD5_MDB, options | CL_DB_PUA_MODE, dbio, dbname); else skipped = 1;   } else if(cli_strbcasestr(dbname, “.ndb”)) { ret = cli_loadndb(fs, engine, signo, 0, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.ndu”)) { if(!(options & CL_DB_PUA)) skipped = 1; else ret = cli_loadndb(fs, engine, signo, 0, options | CL_DB_PUA_MODE, dbio, dbname);   } else if(cli_strbcasestr(filename, “.ldb”)) { ret = cli_loadldb(fs, engine, signo, options, dbio, dbname);   } else if(cli_strbcasestr(filename, “.ldu”)) { if(options & CL_DB_PUA) ret = cli_loadldb(fs, engine, signo, options | CL_DB_PUA_MODE, dbio, dbname); else skipped = 1; } else if(cli_strbcasestr(filename, “.cbc”)) { if(options & CL_DB_BYTECODE) ret = cli_loadcbc(fs, engine, signo, options, dbio, dbname); else skipped = 1; } else if(cli_strbcasestr(dbname, “.sdb”)) { ret = cli_loadndb(fs, engine, signo, 1, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.zmd”)) { ret = cli_loadmd(fs, engine, signo, 1, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.rmd”)) { ret = cli_loadmd(fs, engine, signo, 2, options, dbio, dbname);   } else if(cli_strbcasestr(dbname, “.cfg”)) { ret = cli_dconf_load(fs, engine, options, dbio);   } else if(cli_strbcasestr(dbname, “.info”)) { ret = cli_loadinfo(fs, engine, options, dbio);   } else if(cli_strbcasestr(dbname, “.wdb”)) { if(options & CL_DB_PHISHING_URLS) { ret = cli_loadwdb(fs, engine, options, dbio); } else skipped = 1; } else if(cli_strbcasestr(dbname, “.pdb”) || cli_strbcasestr(dbname, “.gdb”)) { if(options & CL_DB_PHISHING_URLS) { ret = cli_loadpdb(fs, engine, signo, options, dbio); } else skipped = 1; } else if(cli_strbcasestr(dbname, “.ftm”)) { ret = cli_loadftm(fs, engine, options, 0, dbio);   } else if(cli_strbcasestr(dbname, “.ign”) || cli_strbcasestr(dbname, “.ign2”)) { ret = cli_loadign(fs, engine, options, dbio);   } else if(cli_strbcasestr(dbname, “.idb”)) { ret = cli_loadidb(fs, engine, signo, options, dbio);   } else if(cli_strbcasestr(dbname, “.cdb”)) { ret = cli_loadcdb(fs, engine, signo, options, dbio); } else { cli_dbgmsg(“cli_load: unknown extension - assuming old database format\n”); ret = cli_loaddb(fs, engine, signo, options, dbio, dbname); }

调用函数cl_loaddb装载病毒库时,函数cl_loaddb将病毒库文件clamav/database/main.cvd经数字签名验证和解压缩后,存放在在/tmp目录下生成临时目录中。病毒库解码成main.db、main.ndb、main.zmd和main.fp几个库,然后依次将这些库解析出病毒数据存放在病毒数据链表中,解析完后,会删除这些临时病毒库文件。 解码的病毒库经装载后形成病毒数据链表,所有的数据存放在结构cl_node实例root中,当扫描病毒时,使用root中的数据与文件中数据进行匹配,以确定是否染病毒。 理解了临时病毒库的结构,就很容易理解函数cl_loaddb解析病毒库的过程,因此,这里不分析函数cl_loaddb了。临时病毒库的结构分别说明如下: (1) main.db库 main.db库描述了一般文件病毒的特征码,使用Boyer Moore算法进行扫描。main.db库以‘=’符号为分隔符,前面为病毒名,后面为病毒特征码,对于"(Clam)“字符串必须从病毒名中剔除。main.db库的内容经函数cli_loaddb解析后,存于结构cli_bm_patt的virname和pattern成员中。每一个病毒对应一个cli_bm_patt结构实例,这些结构实例组成链表存入在结构cl_node中。 main.db库部分内容列出如下:

_0017_0001_000=21b8004233c999cd218bd6b90300b440cd218b4c198b541bb80157cd21b43ecd2132ed8a4c18 _0017_0001_001=b3005a8b4e27b440cd21e8c2045a59b440cd21e81902b440cd21b8004233c999cd218bd6b90300

(2) main.fp 库main.fp数据用于填充结构cli_md5_node,函数cli_loadhdb解析库中的每一行,每行以”:“分隔字段,每个字段填入结构cli_md5_node中各成员,每行填充一个结构实例,多个实例组成链表。库main.fp数据部分数据如下:

d1ef8a0e477570ad39f4667129400b05:1598056 :Submission 21770

(3) main.hdb 库main.hdb的解析与库main.fp类似,都填充结构cli_md5_node,解析函数都是函数cli_loadhdb,区别仅在于结构cli_md5_node填充0。库main.fp数据部分数据如下:

0060c80aedf81b1e4b58358afe1bf299:761344 :Trojan.Beastdoor.207.B-cli 192bd7afb1479aad2b64a6c176773a01:761344:Trojan.Beastdoor.207.C-cli aaab755d9baf21baf05de8f32af2c996:57856:Trojan.BO.A-Cli d3edf9b7d99205afda64b3a7c1a63264:307200:Trojan.Boid.20-cli-1

在magic_scandesc的函数里面,会依据不同的文件类型调用不同的处理程序。如ELF的为cli_scanelf。cli_scanelf完成对elf文件的分析,而在magic_scandesc里面会调用cli_scanraw,这个函数就调用以上的模式匹配算法进行病毒库检查。在cli_lsig_eval完成判断。 举例: 现在clamav版本可以检查出cve-2013-2094的exploit程序fsheep,在daily.cld里可以找到该exploit的特征码:

Unix.Exploit.Fsheep;Engine:51-255,Target:6;0&1&2&3&4;0F01F8E8050000000F01F848CF;2f62696e2f6261736800;2d736800;21736574756964283029;7364406675636b73686565702e6f72672032303130

而通过clamav的工具sigtool生成hex格式数据查找上面用“;”分割的各个特征码可以发现,特征码都在生成的hex格式的文件中。生成hex格式文件的方式为:

cat semtex|sigtool –hex-dump > hex.log

至于cli_lsig_eval等函数的处理就涉及到具体的算法时间,等有时间了再详细研究,就先简要的介绍到这里。

参考

http://www.clamav.net/lang/en/ http://blog.csdn.net/wdbfz/article/details/6047161