PageRenderTime 89ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/sagator-1.2.3/avir/basic.py

#
Python | 346 lines | 311 code | 12 blank | 23 comment | 10 complexity | 22ba06f093f8c5a06cdb4fe06818be42 MD5 | raw file
Possible License(s): GPL-2.0
  1. '''
  2. Basic realscanners for sagator
  3. (c) 2003-2007 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. '''
  9. from avlib import *
  10. import re,os,time,socket,filetype
  11. __all__=['regexp_scan', 'string_scan', 'smtp_comm',
  12. 'const', 'max_file_size',
  13. 'file_type', 'file_magic',
  14. 'sender_regexp']
  15. def to_list(args,fx=None):
  16. ''' Convert dictionary to list if type is dict or return original. '''
  17. if type(args)==type({}):
  18. ret=[]
  19. for vname,vals in args.items():
  20. if fx!=None:
  21. ret.append([vname]+[fx(x) for x in vals])
  22. else:
  23. ret.append([vname]+vals)
  24. return ret
  25. elif fx!=None:
  26. ret=[]
  27. for arg in args:
  28. ret.append([arg[0]]+[fx(x) for x in arg[1:]])
  29. return ret
  30. else:
  31. return args
  32. class regexp_scan(ascanner):
  33. '''
  34. Primitive regexp pattern scanner.
  35. There can be more patterns for one virus. All patterns in []
  36. must match to assign an buffer as virus (AND opeator).
  37. There can also be more virnames in one dictionary.
  38. Usage: regexp_scan([['VirName','RegExp_Pattern...'],...],size=0,flags=0)
  39. Where: 'VirName' is a string, which identifies defined virus
  40. 'RegExp_Pattern...' is a regexp pattern
  41. size is a number, which defines, how many bytes may be checked.
  42. If it is 0 or not defined, whole buffer is scanned.
  43. If it is -1, email header is scanned.
  44. flags is an integer, which defines regular expression flags.
  45. By default no flags are used.
  46. Example: regexp_scan([
  47. # Scan for a part of EICAR virus test file pattern
  48. ['EICAR','^X5O!P%@AP[4.*EICAR-STANDARD-ANTIVIRUS-TEST-FILE'],
  49. # Scan for a an EXE file pattern endoded as base64.
  50. ['UnknownEXE','^TVqQ']
  51. ])
  52. '''
  53. name='RegExpScanner()'
  54. def __init__(self,regexps,size=0,flags=0):
  55. self.size=size
  56. self.flags=re.M|flags
  57. # compile regexps
  58. self.regexps=to_list(regexps,self.reg_compile)
  59. def reg_compile(self,arg):
  60. return re.compile(arg,self.flags)
  61. def scanbuffer(self,buffer,args={}):
  62. for regs in self.regexps:
  63. for reg in regs[1:]:
  64. if self.size>0:
  65. reg1=reg.search(buffer[:self.size])
  66. elif self.size<0:
  67. reg1=reg.search(buffer[:mail.bodypos])
  68. else:
  69. reg1=reg.search(buffer)
  70. if reg1:
  71. if reg==regs[-1]:
  72. mail.addheader("X-Sagator-RegExp",regs[0])
  73. return iret(1.0,regs[0],['regexp: '+regs[0]+' FOUND!\n'])
  74. else:
  75. break
  76. return 0.0,'',[]
  77. class string_scan(ascanner):
  78. '''
  79. Primitive string pattern scanner.
  80. There can be more patterns for one virus. All patterns
  81. must match to assign an buffer as virus.
  82. There can also be more virnames in one dictionary.
  83. Usage: string_scan([['VirName','Pattern1...',...],...],size=0)
  84. Where: 'VirName' is a string, which identifies defined virus
  85. 'Pattern...' is a string pattern
  86. size is a number, which defines, how many bytes may be checked.
  87. If it is 0 or not defined, whole buffer is scanned.
  88. If it is -1, email header is scanned.
  89. Example: string_scan([
  90. # Scan for a part of EICAR virus test file pattern
  91. ['EICAR','X5O!P%@AP[4','EICAR-STANDARD-ANTIVIRUS-TEST-FILE'],
  92. # Scan for a an EXE file pattern endoded as base64.
  93. ['UnknownEXE','TVqQ']
  94. ])
  95. '''
  96. name='StringScanner()'
  97. def __init__(self,strings,size=0):
  98. self.strings=to_list(strings)
  99. self.size=size
  100. def scanbuffer(self,buffer,args={}):
  101. for regs in self.strings:
  102. for reg in regs[1:]:
  103. if self.size>0:
  104. str1=buffer[:self.size].find(reg)
  105. elif self.size<0:
  106. str1=buffer[:mail.bodypos].find(reg)
  107. else:
  108. str1=buffer.find(reg)
  109. if str1>=0:
  110. if reg==regs[-1]:
  111. mail.addheader("X-Sagator-StringScan",regs[0])
  112. return iret(1.0,regs[0],['string: '+regs[0]+' FOUND!\n'])
  113. else:
  114. break
  115. return 0.0,'',[]
  116. class smtp_comm(ascanner):
  117. '''
  118. Primitive regexp pattern scanner for SMTP communication.
  119. For more information about this scanner see documentation for
  120. regexp_scan realscanner.
  121. This scanner also can be used to apply some scnanner to defined
  122. email adresses. For example:
  123. smtp_comm([['D','^RCPT TO:.*anydomain.dom']]])
  124. & buffer2mbox(libclam())
  125. In this example, libclam() scanner is used only if mail is
  126. adressed to an user on anydomain.dom.
  127. Usage: smtp_comm([['VirName','RegExp_Pattern...',...],...],flags=0)
  128. Where: 'VirName' is a string, which identifies defined virus
  129. 'RegExp_Pattern...' is a regexp pattern
  130. flags is an integer, which defines regular expression flags.
  131. By default re.IGNORECASE|re.MULTILINE is used.
  132. '''
  133. name='smtp_comm()'
  134. def __init__(self,regexps,flags=re.I|re.M):
  135. self.flags=flags
  136. # compile regexps
  137. self.regexps=to_list(regexps,self.reg_compile)
  138. def reg_compile(self,arg):
  139. return re.compile(arg,self.flags)
  140. def scanbuffer(self,buffer,args={}):
  141. if globals.scan_only:
  142. return 0.0,'',[]
  143. for regs in self.regexps:
  144. for reg in regs[1:]:
  145. if reg.search(mail.comm):
  146. if reg==regs[-1]:
  147. mail.addheader("X-Sagator-SMTP-comm",regs[0])
  148. return iret(1.0,regs[0],['smtp_comm: '+regs[0]+' FOUND!\n'])
  149. else:
  150. break
  151. return 0.0,'',[]
  152. def scanfile(self,files,dir='',args={}):
  153. return self.scanbuffer('',args)
  154. #####################################################################
  155. ### primitive class
  156. class const(ascanner):
  157. '''
  158. Realscanner to return a constant value (virus or clean).
  159. This scanner has no special functionality. It returns always
  160. a defined virus, or clean (if virus is not defined).
  161. This scanner has no error codes.
  162. Usage: const(level,VirName,return_string=[])
  163. or const()
  164. Where: level is an float which defines returned infection level
  165. If level is not defined, after scanning an error is raised.
  166. VirName is a returned virus.
  167. return_string is a array of strings returned
  168. Examples: const(0.0) # Return clean
  169. const(1.0,'Virus') # Return virus name "Virus"
  170. const() # Raise an error
  171. '''
  172. name='const()'
  173. ignore_name=True
  174. def __init__(self,level=None,vir='',ret=[]):
  175. self.level=level
  176. self.vir=vir
  177. self.ret=ret
  178. self.filename=''
  179. def scanbuffer(self,buffer,args={}):
  180. if self.level==None:
  181. raise ScannerError,'Forced error by const() scanner'
  182. if self.filename:
  183. debug.echo(6,self.name,": Scanning file: ",self.filename)
  184. return self.level,self.vir,self.ret
  185. def scanfile(self,files,dirname='',args={}):
  186. if self.level==None:
  187. raise ScannerError,'Forced error by const() scanner'
  188. for fn in files:
  189. debug.echo(6,self.name,": Scanning file: ",fn)
  190. return self.level,self.vir,self.ret
  191. #####################################################################
  192. ### max_file_size class
  193. class max_file_size(ascanner):
  194. '''
  195. Realscanner to test email's size.
  196. Usage: max_file_size(size,name='FileSizeOverrun')
  197. Where: size is a number of bytes, which if exceeded, virus is returned
  198. name is a string, which identifies the virus name.
  199. If this parameter is not present, 'FileSizeOverrun' is returned.
  200. Example: max_file_size(1024*1024*10)
  201. # all files at least 10MB will be reported as virus
  202. '''
  203. name='max_file_size()'
  204. def __init__(self,size,vname='FileSizeOverrun'):
  205. self.size=size
  206. self.vname=vname
  207. def scanbuffer(self,buffer,args={}):
  208. if len(buffer)<=self.size:
  209. return 0.0,'',[]
  210. else:
  211. return 1.0,self.vname,[self.filename+": "+self.vname]
  212. def scanfile(self,files,dir='',args={}):
  213. for fn in files:
  214. if os.lstat(safe.fn(fn)).st_size>self.size:
  215. return 1.0,self.vname,[fn+': '+self.vname]
  216. return 0.0,'',[]
  217. #####################################################################
  218. ### other scanners
  219. class file_type(ascanner):
  220. '''
  221. Realscanner, which scans type of a file.
  222. A scanner to chcek content of a file for a special type (like zip, ...).
  223. Usage: file_type({'type':'vir',...})
  224. Where 'type' is a name of type returned by filetype.* function
  225. 'vir' is a vir name returned, if file type is matched
  226. Example: # scan for executables (COM,EXE,PIF and similiar types)
  227. parsemail(file_type({'exe': 'MS Executable',
  228. 'elf': 'Linux Executable'}))
  229. Obsolete since 1.1.1, use file_magic() instead.
  230. '''
  231. name='file_type()'
  232. def __init__(self,types):
  233. self.types=types
  234. def scanbuffer(self,buffer,args={}):
  235. fta=filetype.what(buffer)
  236. for ft in fta:
  237. if ft in self.types:
  238. return 1.0,self.types[ft],[self.filename+": "+str(fta)]
  239. return 0.0,'',[]
  240. def scanfile(self,files,dirname='',args={}):
  241. for fname in files:
  242. fta=filetype.file(fname)
  243. for ft in fta:
  244. if ft in self.types:
  245. return 1.0,self.types[ft],[fname+": "+str(fta)]
  246. return 0.0,'',[]
  247. class file_magic(ascanner):
  248. '''
  249. File magic test (like "file -i command").
  250. This scanner can be used to test file content for a special type.
  251. You need a python module "magic", which is by default in file's
  252. source, but in most distributions it is not compiled in package.
  253. Usage: file_magic({'VirName':'regexp_pattern',..}, flags=0)
  254. Where: 'VirName' is a string, which identifies defined virus
  255. 'regexp_pattern' is a regular expression pattern
  256. flags is an int, which defines regular expression flags,
  257. like re.I to ignore it's case.
  258. '''
  259. name='file_magic()'
  260. def __init__(self,magics,flags=re.I):
  261. import magic
  262. self.magic=magic.open(magic.MAGIC_MIME)
  263. self.magic.load()
  264. # compile regexps
  265. self.magics={}
  266. for vname,reg in magics.items():
  267. self.magics[vname]=re.compile(reg,flags)
  268. def __del__(self):
  269. self.magic.close()
  270. def scanbuffer(self,buffer,args={}):
  271. for vname,reg in self.magics.items():
  272. if reg.search(self.magic.buffer(buffer)):
  273. mail.addheader("X-Sagator-FileMagic",vname)
  274. return iret(1.0,vname,['file_magic: '+vname+' found in '+self.filename+'!\n'])
  275. return 0.0,'',[]
  276. def scanfile(self,files,dirname='',args={}):
  277. for fname in files:
  278. for vname,reg in self.magics.items():
  279. if reg.search(self.magic.file(fname)):
  280. return iret(1.0,vname,[self.name+": "+vname+' found in '+self.filename+'!\n'])
  281. return 0.0,'',[]
  282. class sender_regexp(regexp_scan):
  283. '''
  284. Sender IP address regexp scanner.
  285. This scanner can be used to scan for sender's IP address. You can use
  286. it to perform some actions (for example send report to admin) if
  287. a virus is comming from your local addresses.
  288. Usage: sender_regexp([['VirName','RegExp_Pattern...'],...])
  289. Where: 'VirName' is a string, which identifies defined virus
  290. 'RegExp_Pattern...' is a regexp pattern
  291. Example: sender_regexp([
  292. ['LOCAL_IP','(192\.168|172\.(1[6789]|2[0-9]|3[01])|10)\.']
  293. ])
  294. '''
  295. def scanbuffer(self, buffer, args={}):
  296. ip = mail.getsender()['ADDR']
  297. debug.echo(7, self.name+': IP=', ip)
  298. return regexp_scan.scanbuffer(self, ip, args)