diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/__init__.py | 0 | ||||
| -rw-r--r-- | modules/entropy.py | 56 | ||||
| -rw-r--r-- | modules/grep_count.py | 234 | ||||
| -rw-r--r-- | modules/levenshtein.py | 73 | ||||
| -rw-r--r-- | modules/libfuzzy.py | 98 | ||||
| -rw-r--r-- | modules/scanmodule.py | 56 | ||||
| -rw-r--r-- | modules/whitelist.py | 46 |
7 files changed, 563 insertions, 0 deletions
diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/modules/__init__.py | |||
diff --git a/modules/entropy.py b/modules/entropy.py new file mode 100644 index 0000000..48b2924 --- /dev/null +++ b/modules/entropy.py | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | ''' This module uses shannon's Entropy to detect packed malwares | ||
| 2 | ''' | ||
| 3 | import os | ||
| 4 | import math | ||
| 5 | import logging | ||
| 6 | logging.basicConfig(level=logging.DEBUG) | ||
| 7 | |||
| 8 | import scanmodule | ||
| 9 | |||
| 10 | def main(): | ||
| 11 | return Entropy() | ||
| 12 | |||
| 13 | class Entropy(scanmodule.ScanModule): | ||
| 14 | name = 'entropy' | ||
| 15 | def populate(self, path): | ||
| 16 | pass | ||
| 17 | def load(self, path): | ||
| 18 | pass | ||
| 19 | def save(self, path): | ||
| 20 | pass | ||
| 21 | |||
| 22 | def __compute_score(self, path): | ||
| 23 | return (self.__entropy(path) - 5) * 100 | ||
| 24 | |||
| 25 | def is_malware(self, path): | ||
| 26 | score = self.__compute_score(path) | ||
| 27 | logging.info('Entropy score for ' + path + ' : ' + str(score)) | ||
| 28 | return score > 75 | ||
| 29 | |||
| 30 | def evaluate(self, path): | ||
| 31 | ''' Computes an arbitraty score for the given path | ||
| 32 | @ret A sorted list of the form [name, match_in_percent_superior_to_zero] | ||
| 33 | ''' | ||
| 34 | score = self.__compute_score(path) | ||
| 35 | if score > 0: | ||
| 36 | return [['MALWARE', score],] | ||
| 37 | return None | ||
| 38 | |||
| 39 | def __entropy(self, path): | ||
| 40 | ''' Computes shannon's entropy for the given file | ||
| 41 | @param path Path to the file | ||
| 42 | ''' | ||
| 43 | # Computes the frequency of each byte in the file | ||
| 44 | fsize = max(float(os.path.getsize(path)), 1.0) | ||
| 45 | |||
| 46 | freq = [0] * 256 | ||
| 47 | with open(path, 'rb') as f: | ||
| 48 | for c in f.read(): | ||
| 49 | freq[ord(c)] += 1 | ||
| 50 | |||
| 51 | entropy = 0.0 | ||
| 52 | for f in freq: | ||
| 53 | if f: | ||
| 54 | f /= fsize | ||
| 55 | entropy += f * math.log(f, 2) | ||
| 56 | return -entropy | ||
diff --git a/modules/grep_count.py b/modules/grep_count.py new file mode 100644 index 0000000..2431960 --- /dev/null +++ b/modules/grep_count.py | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | ''' This module count the occurences of dodgy terms present in a file | ||
| 2 | ''' | ||
| 3 | import os | ||
| 4 | import logging | ||
| 5 | logging.basicConfig(level=logging.DEBUG) | ||
| 6 | |||
| 7 | import scanmodule | ||
| 8 | |||
| 9 | def main(): | ||
| 10 | return GrepCount() | ||
| 11 | |||
| 12 | class GrepCount(scanmodule.ScanModule): | ||
| 13 | name = 'grep count' | ||
| 14 | |||
| 15 | # ranked from 1 to 10, 10 being EVIL | ||
| 16 | # Also, 100 is awarded to MEGA-DUH-OBVIOUS things. | ||
| 17 | dodgy_terms = { | ||
| 18 | '$GLOBALS': 6, | ||
| 19 | 'WWW-Authenticate': 7, | ||
| 20 | 'ZipArchive': 6, | ||
| 21 | 'apache_get_modules': 8, | ||
| 22 | 'assert': 5, | ||
| 23 | 'base64_decode': 7, | ||
| 24 | 'bzdecompress': 7, | ||
| 25 | 'chmod': 8, | ||
| 26 | 'curl_init("file://': 100, # safe mode bypass exploit | ||
| 27 | #'dl': 10, | ||
| 28 | #'exec': 10, | ||
| 29 | 'eval(': 10, | ||
| 30 | 'eval(base64_decode': 100, | ||
| 31 | 'eval($_GET': 100, | ||
| 32 | 'eval($_POST': 100, | ||
| 33 | 'eval($_REQUEST': 100, | ||
| 34 | 'eval(base64_decode': 100, | ||
| 35 | 'eval(gzinflate': 100, | ||
| 36 | 'file_get_contents': 6, | ||
| 37 | 'fpassthru': 100, | ||
| 38 | 'fsockopen': 7, | ||
| 39 | 'ftp_connect': 7, | ||
| 40 | 'ftp_exec': 7, | ||
| 41 | 'ftp_login': 7, | ||
| 42 | 'function_exists': 4, | ||
| 43 | 'get_current_user': 10, | ||
| 44 | 'getcwd': 8, | ||
| 45 | 'getenv': 9, | ||
| 46 | 'getmxrr': 5, | ||
| 47 | 'getmygid': 10, | ||
| 48 | 'getmygid': 10, | ||
| 49 | 'getmyinode': 10, | ||
| 50 | 'getmypid': 10, | ||
| 51 | 'getmyuid': 10, | ||
| 52 | 'gzinflate': 7, | ||
| 53 | 'gzinflate(base64_decode(': 100, | ||
| 54 | 'gzuncompress': 7, | ||
| 55 | 'ini_get': 6, | ||
| 56 | 'ini_set': 6, | ||
| 57 | 'is_readable': 10, | ||
| 58 | 'mysql_get_client_info': 7, | ||
| 59 | 'open_basedir': 9, | ||
| 60 | 'passthru': 10, | ||
| 61 | 'passthru($_GET': 100, | ||
| 62 | 'passthru($_POST': 100, | ||
| 63 | 'passthru($_REQUEST': 100, | ||
| 64 | 'pclose': 9, | ||
| 65 | 'pcntl_fork': 10, | ||
| 66 | 'php_logo_guid': 10, | ||
| 67 | 'php_uname': 8, | ||
| 68 | 'phpcredits': 10, | ||
| 69 | 'phpinfo': 10, | ||
| 70 | 'phpversion': 5, | ||
| 71 | 'pnctl_exec': 10, | ||
| 72 | 'pnctl_fork': 10, | ||
| 73 | 'popen': 10, | ||
| 74 | 'posix_getegid': 10, | ||
| 75 | 'posix_geteuid': 10, | ||
| 76 | 'posix_getgetgruid': 10, | ||
| 77 | 'posix_getpwuid': 10, | ||
| 78 | 'posix_kill': 10, | ||
| 79 | 'posix_mkfifo': 10, | ||
| 80 | 'posix_setgid': 10, | ||
| 81 | 'posix_setpgid': 10, | ||
| 82 | 'posix_setsid': 10, | ||
| 83 | 'posix_setuid': 10, | ||
| 84 | 'posix_uname': 10, | ||
| 85 | 'php://input': 7, | ||
| 86 | 'proc_close': 10, | ||
| 87 | 'proc_get_status': 10, | ||
| 88 | 'proc_nice': 10, | ||
| 89 | 'proc_open': 10, | ||
| 90 | 'proc_terminate': 10, | ||
| 91 | 'putenv': 10, | ||
| 92 | 'putenv("PHP': 100, # Shellshock exploit | ||
| 93 | 'putenv(\'PHP': 100, # Shellshock exploit | ||
| 94 | 'safe_mode': 10, | ||
| 95 | 'shell_exec': 10, | ||
| 96 | 'show_source': 10, | ||
| 97 | 'socket_create(AF_INET, SOCK_STREAM, SOL_TCP)': 10, # Used for SYN flood | ||
| 98 | 'symlink': 8, | ||
| 99 | 'system(': 9, | ||
| 100 | 'system($_GET': 100, | ||
| 101 | 'system($_POST': 100, | ||
| 102 | 'system($_REQUEST': 100, | ||
| 103 | 'win_shell_execute': 10, | ||
| 104 | 'win_create_service': 100, | ||
| 105 | 'wscript': 8, | ||
| 106 | 'zend_logo_guid': 10, | ||
| 107 | 'zend_thread_id': 9, | ||
| 108 | 'zend_version': 9, | ||
| 109 | } | ||
| 110 | |||
| 111 | dodgy_terms.update({ | ||
| 112 | '/bin/bash ': 100, | ||
| 113 | '/bin/sh ': 100, | ||
| 114 | '/etc/hosts': 100, | ||
| 115 | '/etc/passwd': 100, | ||
| 116 | '/etc/resolv.conf ': 100, | ||
| 117 | '/etc/shadow': 100, | ||
| 118 | '/etc/syslog.conf': 100, | ||
| 119 | '/proc/cpuinfo': 10, | ||
| 120 | '/tmp': 8, | ||
| 121 | '/var/cpanel/accounting.log': 100, | ||
| 122 | 'IRC server': 100, | ||
| 123 | 'LD_PRELOAD': 100, | ||
| 124 | 'PRIVMSG': 100, | ||
| 125 | 'Safe Mod Bypass': 100, | ||
| 126 | 'Shell ': 9, | ||
| 127 | '\\x': 3, # Shellcodes | ||
| 128 | '\x00/../': 100, # safe mode bypass | ||
| 129 | 'backdoor': 10, | ||
| 130 | 'bypass': 8, | ||
| 131 | 'chkrootkit': 100, | ||
| 132 | 'chmod 777': 7, | ||
| 133 | 'cmd.exe': 100, | ||
| 134 | 'dir /OG /X': 100, | ||
| 135 | 'find . -type f': 100, | ||
| 136 | 'gcc ': 8, | ||
| 137 | 'id_rsa': 100, | ||
| 138 | 'ipconfig /all': 100, | ||
| 139 | 'jschl_vc': 100, # Cloudflare bypass | ||
| 140 | 'jschl_answer': 100, # Cloudflare bypass | ||
| 141 | 'kernel32.dll': 100, | ||
| 142 | 'ls -la': 100, | ||
| 143 | 'milw0rm': 100, | ||
| 144 | 'my.cnf': 100, | ||
| 145 | 'my.conf': 100, | ||
| 146 | 'nc -l': 100, | ||
| 147 | 'netstat ': 100, | ||
| 148 | 'file:file://': 100, # basedir bypass | ||
| 149 | 'portsentry': 100, | ||
| 150 | 'proftpd.conf': 100, | ||
| 151 | 'ps -aux': 100, | ||
| 152 | 'rkhunter': 100, | ||
| 153 | 'shellcode': 100, | ||
| 154 | 'slowloris': 100, | ||
| 155 | 'snort': 100, | ||
| 156 | 'system32': 9, | ||
| 157 | 'tripwire': 100, | ||
| 158 | 'uname -a': 100, | ||
| 159 | 'wget': 8, | ||
| 160 | 'WinExec': 10, | ||
| 161 | }) | ||
| 162 | |||
| 163 | dodgy_terms.update({ | ||
| 164 | '/cdn-cgi/l/chk_jschl': 100, # Cloudflare bypass for DDoS'ing | ||
| 165 | 'Antichat Shell': 100, | ||
| 166 | 'Cr@zy_King': 100, | ||
| 167 | 'KAdot@ngs.ru': 100, | ||
| 168 | 'Kacak': 100, | ||
| 169 | 'KingDefacer': 100, | ||
| 170 | 'SimAttacker': 100, | ||
| 171 | 'SoldiersOfAllah': 100, | ||
| 172 | 'ak74-team.net': 100, | ||
| 173 | 'alturks.com': 100, | ||
| 174 | 'egy_spider' : 100, | ||
| 175 | 'egyspider.eu' : 100, | ||
| 176 | 'exploit-db.com': 100, | ||
| 177 | 'forever5pi': 100, | ||
| 178 | 'grayhatz.org': 100, | ||
| 179 | 'kacaq.blogspot.com': 100, | ||
| 180 | 'locus7s.com': 100, | ||
| 181 | 'michaeldaw.org': 100, | ||
| 182 | 'milw0rm.com': 100, | ||
| 183 | 'pentestmonkey': 100, | ||
| 184 | 'r57.biz': 100, | ||
| 185 | 'r57shell.net': 100, | ||
| 186 | 'rootshell-team.info': 100, | ||
| 187 | 'simorgh': 100, | ||
| 188 | 'thecrowsrew.org': 100, | ||
| 189 | 'vnhacker.org': 100, | ||
| 190 | 'xdevil.org': 100, | ||
| 191 | 'zehirhacker': 100, | ||
| 192 | '~z0mbie': 100, | ||
| 193 | }) | ||
| 194 | |||
| 195 | def populate(self, path): | ||
| 196 | ''' Does nothing :< | ||
| 197 | ''' | ||
| 198 | pass | ||
| 199 | |||
| 200 | def evaluate(self, path): | ||
| 201 | ''' Check the given file against a list of know dodgy strings. | ||
| 202 | The calculation formulae is empirical. | ||
| 203 | @ret A sorted list of the form [name, match_in_percent_superior_to_zero] | ||
| 204 | ''' | ||
| 205 | fsize = os.path.getsize(path) | ||
| 206 | if not fsize: | ||
| 207 | return None | ||
| 208 | |||
| 209 | content = '' | ||
| 210 | with open(path, 'r') as f: | ||
| 211 | content = f.read() | ||
| 212 | |||
| 213 | score = 0 | ||
| 214 | for key,data in self.dodgy_terms.iteritems(): | ||
| 215 | nb = content.find(key) * data | ||
| 216 | if nb > 0: | ||
| 217 | score += nb | ||
| 218 | score /= fsize | ||
| 219 | |||
| 220 | logging.info('Grep score for ' + path + ' : ' + str(score)) | ||
| 221 | |||
| 222 | if score > 75: | ||
| 223 | return [['MALWARE', min(score, 100)],] | ||
| 224 | return None | ||
| 225 | |||
| 226 | def load(self, path): | ||
| 227 | pass | ||
| 228 | |||
| 229 | def save(self, path): | ||
| 230 | pass | ||
| 231 | |||
| 232 | def is_malware(self, path): | ||
| 233 | return self.evaluate(path) is not None | ||
| 234 | |||
diff --git a/modules/levenshtein.py b/modules/levenshtein.py new file mode 100644 index 0000000..2e854e2 --- /dev/null +++ b/modules/levenshtein.py | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | ''' | ||
| 2 | This modules has a super-awful complexity (something along n^4), | ||
| 3 | so I'm quite sure that you don't want to run it by default ;) | ||
| 4 | |||
| 5 | Anyway, this modules computes the Levenshtein distance between samples of malwares | ||
| 6 | and files to check, to find similarities. | ||
| 7 | ''' | ||
| 8 | import os | ||
| 9 | |||
| 10 | import scanmodule | ||
| 11 | |||
| 12 | def main(): | ||
| 13 | return Levenshtein() | ||
| 14 | |||
| 15 | class Levenshtein(scanmodule.ScanModule): | ||
| 16 | name = 'levenshtein' | ||
| 17 | def populate(self, path): | ||
| 18 | ''' We can't really populate the database with Levenshtein scores, | ||
| 19 | but we can speedup the calculation by storing files lenghts | ||
| 20 | ''' | ||
| 21 | for root, _, filenames in os.walk(path): | ||
| 22 | for filename in filenames: | ||
| 23 | full_path = os.path.join(root, filename) | ||
| 24 | with open(full_path, 'r') as f: | ||
| 25 | self.samples[full_path] = [os.path.getsize(full_path), f.read().lower()] | ||
| 26 | |||
| 27 | def evaluate(self, path): | ||
| 28 | ''' Compare the hash of the given path to every samples one. | ||
| 29 | @ret A sorted list of the form [name, match_in_percent_superior_to_zero] | ||
| 30 | ''' | ||
| 31 | file_to_test = path | ||
| 32 | file_size = os.path.getsize(file_to_test) | ||
| 33 | |||
| 34 | lst = list() | ||
| 35 | for sample_name, sample_intel in self.samples.iteritems(): | ||
| 36 | if sample_name != file_to_test: | ||
| 37 | score = self.__levenshtein(file_to_test, sample_intel[1]) | ||
| 38 | score = score / ((file_size + sample_intel[0]) / 2.0) # mean value | ||
| 39 | if score > 25: # if the match is under 10%, we don't care | ||
| 40 | lst.append([sample_name, score * 10]) | ||
| 41 | return sorted(lst, key=lambda lst: lst[1], reverse=True) | ||
| 42 | |||
| 43 | def __levenshtein_file(self, f, b): | ||
| 44 | ''' Computes the Levenshtein's distance between a file and a buffer | ||
| 45 | @param f1 File | ||
| 46 | @param fs2 Buffer | ||
| 47 | @return The levenshtein distance | ||
| 48 | ''' | ||
| 49 | with open(f, 'r') as of: | ||
| 50 | return self.__levenshtein(of.read().lower(), b) | ||
| 51 | |||
| 52 | def __levenshtein(self, s1, s2): | ||
| 53 | ''' Computes the Levenshtein's distance between two strings | ||
| 54 | @param s1 First string | ||
| 55 | @param s2 Second string | ||
| 56 | @return The levenshtein distance | ||
| 57 | ''' | ||
| 58 | |||
| 59 | if len(s1) < len(s2): # Minimize computation | ||
| 60 | s1, s2 = s2, s1 | ||
| 61 | |||
| 62 | previous_row = range(len(s2) + 1) | ||
| 63 | for i, c1 in enumerate(s1): | ||
| 64 | current_row = [i + 1] | ||
| 65 | for j, c2 in enumerate(s2): | ||
| 66 | insertions = previous_row[j + 1] + 1 | ||
| 67 | deletions = current_row[j] + 1 | ||
| 68 | substitutions = previous_row[j] + (c1 != c2) | ||
| 69 | current_row.append(min(insertions, deletions, substitutions)) | ||
| 70 | previous_row = current_row | ||
| 71 | |||
| 72 | return previous_row[-1] | ||
| 73 | |||
diff --git a/modules/libfuzzy.py b/modules/libfuzzy.py new file mode 100644 index 0000000..a0d3f15 --- /dev/null +++ b/modules/libfuzzy.py | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | ''' Ugly-pseudo-bindings to libfuzzy (used by ssdeep) to check | ||
| 2 | if a file is similar to a given list of samples | ||
| 3 | ''' | ||
| 4 | |||
| 5 | import os | ||
| 6 | import ctypes | ||
| 7 | import pickle | ||
| 8 | import sys | ||
| 9 | import logging | ||
| 10 | logging.basicConfig(level=logging.DEBUG) | ||
| 11 | |||
| 12 | import scanmodule | ||
| 13 | |||
| 14 | |||
| 15 | SPAMSUM_LENGTH = 64 | ||
| 16 | FUZZY_MAX_RESULT = SPAMSUM_LENGTH + SPAMSUM_LENGTH // 2 + 20 | ||
| 17 | |||
| 18 | def main(): | ||
| 19 | return FuzzyMatcher() | ||
| 20 | |||
| 21 | class FuzzyMatcher(scanmodule.ScanModule): | ||
| 22 | name = 'libfuzzy' | ||
| 23 | def __init__(self, samples_path=None, persistence_path=None): | ||
| 24 | self.__initialize_libfuzzy() | ||
| 25 | super(FuzzyMatcher, self).__init__() | ||
| 26 | |||
| 27 | def __initialize_libfuzzy(self): | ||
| 28 | ''' Bind to libfuzzy thanks to ctypes. | ||
| 29 | This will create the "fuzzy_hash_buf" and | ||
| 30 | the "fuzzy_compare" methods | ||
| 31 | ''' | ||
| 32 | try: | ||
| 33 | fuzzy = ctypes.CDLL('libfuzzy.so') | ||
| 34 | except OSError: | ||
| 35 | print('[-] Please check that you installed libfuzzy') | ||
| 36 | sys.exit(1) | ||
| 37 | |||
| 38 | self.__fuzzy_hash_buf = fuzzy.fuzzy_hash_buf | ||
| 39 | self.__fuzzy_hash_buf.restype = ctypes.c_int | ||
| 40 | self.__fuzzy_hash_buf.argtypes = [ | ||
| 41 | ctypes.c_char_p, #buf | ||
| 42 | ctypes.c_uint32, #buf_len | ||
| 43 | ctypes.c_char_p, #result | ||
| 44 | ] | ||
| 45 | self.__fuzzy_compare = fuzzy.fuzzy_compare | ||
| 46 | self.__fuzzy_compare.restype = ctypes.c_int | ||
| 47 | self.__fuzzy_compare.argtypes = [ | ||
| 48 | ctypes.c_char_p, #sig1 | ||
| 49 | ctypes.c_char_p, #sig2 | ||
| 50 | ] | ||
| 51 | |||
| 52 | def populate(self, path): | ||
| 53 | ''' Computes fuzzy hashes of files under the given path, | ||
| 54 | and store them in the dict self.samples with the form dict {name: fuzzy_hash} | ||
| 55 | @param path Path containing the samples | ||
| 56 | ''' | ||
| 57 | for root, _, filenames in os.walk(path): | ||
| 58 | for filename in filenames: | ||
| 59 | full_path = os.path.join(root, filename) | ||
| 60 | self.samples[full_path] = self.__hash_from_file(full_path) | ||
| 61 | |||
| 62 | def __hash_from_file(self, path): | ||
| 63 | ''' Return the hash of the given file | ||
| 64 | @param path Path to the file to hash | ||
| 65 | @ret Fuzzy hash of the given file | ||
| 66 | ''' | ||
| 67 | with open(path, 'r') as f: | ||
| 68 | out = ctypes.create_string_buffer('\x00' * FUZZY_MAX_RESULT) | ||
| 69 | content = f.read() | ||
| 70 | self.__fuzzy_hash_buf(content, len(content), out) | ||
| 71 | return out.value | ||
| 72 | |||
| 73 | def evaluate(self, path): | ||
| 74 | ''' Compare the hash of the given path to every samples one. | ||
| 75 | @ret A sorted list of the form [name, match_in_percent_superior_to_zero] | ||
| 76 | ''' | ||
| 77 | fuzzy_hash = self.__hash_from_file(path) | ||
| 78 | |||
| 79 | lst = list() | ||
| 80 | for f in self.samples: | ||
| 81 | score = self.__fuzzy_compare(fuzzy_hash, self.samples[f]) | ||
| 82 | if score: | ||
| 83 | lst.append([f, score]) | ||
| 84 | return sorted(lst, key=lambda lst: lst[1], reverse=True) | ||
| 85 | |||
| 86 | def is_malware(self, path): | ||
| 87 | max_score = 0 | ||
| 88 | fuzzy_hash = self.__hash_from_file(path) | ||
| 89 | |||
| 90 | for f in self.samples: | ||
| 91 | score = self.__fuzzy_compare(fuzzy_hash, self.samples[f]) | ||
| 92 | if score > max_score: | ||
| 93 | score = max_score | ||
| 94 | logging.info('fuzzy score for ' + path + ' matches ' + f + ' at ' + str(score) + '%%') | ||
| 95 | |||
| 96 | return max_score > 90 | ||
| 97 | |||
| 98 | |||
diff --git a/modules/scanmodule.py b/modules/scanmodule.py new file mode 100644 index 0000000..6ace387 --- /dev/null +++ b/modules/scanmodule.py | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | import ConfigParser | ||
| 2 | import pickle | ||
| 3 | |||
| 4 | |||
| 5 | class ScanModule(object): | ||
| 6 | def __init__(self): | ||
| 7 | self.config = ConfigParser.ConfigParser() | ||
| 8 | self.config.read('modules.conf') | ||
| 9 | |||
| 10 | self.samples = dict() | ||
| 11 | |||
| 12 | try: | ||
| 13 | self.populate(self.config.get(self.name, 'samples')) | ||
| 14 | except ConfigParser.NoOptionError: | ||
| 15 | pass | ||
| 16 | |||
| 17 | try: | ||
| 18 | self.load(self.config.get(self.name, 'persistence')) | ||
| 19 | except ConfigParser.NoOptionError: | ||
| 20 | pass | ||
| 21 | |||
| 22 | def is_disable(self): | ||
| 23 | try: | ||
| 24 | return self.config.getboolean(self.name, 'disable') | ||
| 25 | except ConfigParser.NoOptionError: | ||
| 26 | return False | ||
| 27 | |||
| 28 | def evaluate(self, path): | ||
| 29 | ''' Return in percent, the probability that | ||
| 30 | the file is a malware | ||
| 31 | @param path File to evaluate | ||
| 32 | ''' | ||
| 33 | raise NotImplemented | ||
| 34 | |||
| 35 | def populate(self, path): | ||
| 36 | ''' Populate the module's internal database | ||
| 37 | with data from the given path | ||
| 38 | @param path Path to the data | ||
| 39 | ''' | ||
| 40 | raise NotImplemented | ||
| 41 | |||
| 42 | |||
| 43 | def load(self, path): | ||
| 44 | ''' Unpickle the given path, and updates the samples dict with it. | ||
| 45 | @param path Path to the dict to unpickle | ||
| 46 | ''' | ||
| 47 | with open(path, 'r') as f: | ||
| 48 | self.samples.update(pickle.load(f)) | ||
| 49 | |||
| 50 | def save(self, path): | ||
| 51 | ''' Save the database to the given file | ||
| 52 | @param path Path where to save the database | ||
| 53 | ''' | ||
| 54 | with open(path, 'w') as f: | ||
| 55 | pickle.dump(self.samples, f) | ||
| 56 | |||
diff --git a/modules/whitelist.py b/modules/whitelist.py new file mode 100644 index 0000000..587b392 --- /dev/null +++ b/modules/whitelist.py | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | import os | ||
| 2 | import hashlib | ||
| 3 | import scanmodule | ||
| 4 | |||
| 5 | def main(): | ||
| 6 | return HashWhitelist() | ||
| 7 | |||
| 8 | class HashWhitelist(scanmodule.ScanModule): | ||
| 9 | name = 'hashwhitelist' | ||
| 10 | def evaluate(self, path): | ||
| 11 | ''' Return in percent, the probability that | ||
| 12 | the file is a malware | ||
| 13 | @param path File to evaluate | ||
| 14 | ''' | ||
| 15 | sha1 = '' | ||
| 16 | with open(path, 'r') as f: | ||
| 17 | sha1 = hashlib.sha1(f.read()).hexdigest() | ||
| 18 | |||
| 19 | lst = list() | ||
| 20 | for f in self.samples: | ||
| 21 | if sha1 == self.samples[f]: | ||
| 22 | lst.append([f, 100]) | ||
| 23 | return sorted(lst, key=lambda lst: lst[1], reverse=True) | ||
| 24 | |||
| 25 | def is_malware(self, path): | ||
| 26 | ''' Return False if the file is whitelisted | ||
| 27 | ''' | ||
| 28 | sha1 = '' | ||
| 29 | with open(path, 'r') as f: | ||
| 30 | sha1 = hashlib.sha1(f.read()).hexdigest() | ||
| 31 | |||
| 32 | for f in self.samples: | ||
| 33 | if sha1 == self.samples[f]: | ||
| 34 | return False | ||
| 35 | return True | ||
| 36 | |||
| 37 | def populate(self, path): | ||
| 38 | ''' Populate the module's internal database | ||
| 39 | with data from the given path | ||
| 40 | @param path Path to the data | ||
| 41 | ''' | ||
| 42 | for root, _, filenames in os.walk(path): | ||
| 43 | for filename in filenames: | ||
| 44 | full_path = os.path.join(root, filename) | ||
| 45 | with open(full_path, 'r') as f: | ||
| 46 | self.samples[full_path] = hashlib.sha1(f.read()).hexdigest() | ||
