/src/clamav/libclamav/matcher.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2013-2023 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
3 | | * Copyright (C) 2007-2013 Sourcefire, Inc. |
4 | | * |
5 | | * Authors: Tomasz Kojm |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License version 2 as |
9 | | * published by the Free Software Foundation. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
19 | | * MA 02110-1301, USA. |
20 | | */ |
21 | | |
22 | | #if HAVE_CONFIG_H |
23 | | #include "clamav-config.h" |
24 | | #endif |
25 | | |
26 | | #include <string.h> |
27 | | #include <ctype.h> |
28 | | #include <sys/types.h> |
29 | | #include <sys/stat.h> |
30 | | #ifdef HAVE_UNISTD_H |
31 | | #include <unistd.h> |
32 | | #endif |
33 | | #include <stdbool.h> |
34 | | |
35 | | #include "clamav.h" |
36 | | #include "clamav_rust.h" |
37 | | #include "others.h" |
38 | | #include "matcher-ac.h" |
39 | | #include "matcher-bm.h" |
40 | | #include "matcher-pcre.h" |
41 | | #include "filetypes.h" |
42 | | #include "matcher.h" |
43 | | #include "pe.h" |
44 | | #include "elf.h" |
45 | | #include "execs.h" |
46 | | #include "special.h" |
47 | | #include "scanners.h" |
48 | | #include "str.h" |
49 | | #include "default.h" |
50 | | #include "macho.h" |
51 | | #include "fmap.h" |
52 | | #include "pe_icons.h" |
53 | | #include "regex/regex.h" |
54 | | #include "filtering.h" |
55 | | #include "perflogging.h" |
56 | | #include "bytecode_priv.h" |
57 | | #include "bytecode_api_impl.h" |
58 | | #ifdef HAVE_YARA |
59 | | #include "yara_clam.h" |
60 | | #include "yara_exec.h" |
61 | | #endif |
62 | | |
63 | | #ifdef CLI_PERF_LOGGING |
64 | | |
65 | | static inline void perf_log_filter(int32_t pos, int32_t length, int8_t trie) |
66 | | { |
67 | | cli_perf_log_add(RAW_BYTES_SCANNED, length); |
68 | | cli_perf_log_add(FILTER_BYTES_SCANNED, length - pos); |
69 | | cli_perf_log_count2(TRIE_SCANNED, trie, length - pos); |
70 | | } |
71 | | |
72 | | static inline int perf_log_tries(int8_t acmode, int8_t bm_called, int32_t length) |
73 | | { |
74 | | if (bm_called) |
75 | | cli_perf_log_add(BM_SCANNED, length); |
76 | | if (acmode) |
77 | | cli_perf_log_add(AC_SCANNED, length); |
78 | | return 0; |
79 | | } |
80 | | |
81 | | #else |
82 | | static inline void perf_log_filter(int32_t pos, uint32_t length, int8_t trie) |
83 | 26.4M | { |
84 | 26.4M | UNUSEDPARAM(pos); |
85 | 26.4M | UNUSEDPARAM(length); |
86 | 26.4M | UNUSEDPARAM(trie); |
87 | 26.4M | } |
88 | | |
89 | | static inline int perf_log_tries(int8_t acmode, int8_t bm_called, int32_t length) |
90 | 42.9M | { |
91 | 42.9M | UNUSEDPARAM(acmode); |
92 | 42.9M | UNUSEDPARAM(bm_called); |
93 | 42.9M | UNUSEDPARAM(length); |
94 | | |
95 | 42.9M | return 0; |
96 | 42.9M | } |
97 | | #endif |
98 | | |
99 | | static inline cl_error_t matcher_run(const struct cli_matcher *root, |
100 | | const unsigned char *buffer, uint32_t length, |
101 | | const char **virname, struct cli_ac_data *mdata, |
102 | | uint32_t offset, |
103 | | const struct cli_target_info *tinfo, |
104 | | cli_file_t ftype, |
105 | | struct cli_matched_type **ftoffset, |
106 | | unsigned int acmode, |
107 | | unsigned int pcremode, |
108 | | struct cli_ac_result **acres, |
109 | | fmap_t *map, |
110 | | struct cli_bm_off *offdata, |
111 | | struct cli_pcre_off *poffdata, |
112 | | cli_ctx *ctx) |
113 | 26.4M | { |
114 | 26.4M | cl_error_t ret, saved_ret = CL_CLEAN; |
115 | 26.4M | int32_t pos = 0; |
116 | 26.4M | struct filter_match_info info; |
117 | 26.4M | uint32_t orig_length, orig_offset; |
118 | 26.4M | const unsigned char *orig_buffer; |
119 | | |
120 | 26.4M | if (root->filter) { |
121 | 18.8M | if (filter_search_ext(root->filter, buffer, length, &info) == -1) { |
122 | | /* for safety always scan last maxpatlen bytes */ |
123 | 6.72M | pos = length - root->maxpatlen - 1; |
124 | 6.72M | if (pos < 0) pos = 0; |
125 | 6.72M | perf_log_filter(pos, length, root->type); |
126 | 12.1M | } else { |
127 | | /* must not cut buffer for 64[4-4]6161, because we must be able to check |
128 | | * 64! */ |
129 | 12.1M | pos = info.first_match - root->maxpatlen - 1; |
130 | 12.1M | if (pos < 0) pos = 0; |
131 | 12.1M | perf_log_filter(pos, length, root->type); |
132 | 12.1M | } |
133 | 18.8M | } else { |
134 | 7.62M | perf_log_filter(0, length, root->type); |
135 | 7.62M | } |
136 | | |
137 | 26.4M | orig_length = length; |
138 | 26.4M | orig_buffer = buffer; |
139 | 26.4M | orig_offset = offset; |
140 | 26.4M | length -= pos; |
141 | 26.4M | buffer += pos; |
142 | 26.4M | offset += pos; |
143 | 26.4M | if (!root->ac_only) { |
144 | 16.5M | perf_log_tries(0, 1, length); |
145 | 16.5M | if (root->bm_offmode) { |
146 | | /* Don't use prefiltering for BM offset mode, since BM keeps tracks |
147 | | * of offsets itself, and doesn't work if we skip chunks of input |
148 | | * data */ |
149 | 569k | ret = cli_bm_scanbuff(orig_buffer, orig_length, virname, NULL, root, orig_offset, tinfo, offdata, ctx); |
150 | 15.9M | } else { |
151 | 15.9M | ret = cli_bm_scanbuff(buffer, length, virname, NULL, root, offset, tinfo, offdata, ctx); |
152 | 15.9M | } |
153 | 16.5M | if (ret != CL_SUCCESS) { |
154 | 0 | if (ret != CL_VIRUS) |
155 | 0 | return ret; |
156 | | /* else (ret == CL_VIRUS) */ |
157 | | |
158 | 0 | ret = cli_append_virus(ctx, *virname); |
159 | 0 | if (ret != CL_SUCCESS) |
160 | 0 | return ret; |
161 | 0 | } |
162 | 16.5M | } |
163 | 26.4M | perf_log_tries(acmode, 0, length); |
164 | 26.4M | ret = cli_ac_scanbuff(buffer, length, virname, NULL, acres, root, mdata, offset, ftype, ftoffset, acmode, ctx); |
165 | 26.4M | if (ret != CL_SUCCESS) { |
166 | 6.58M | if (ret == CL_VIRUS) { |
167 | 0 | ret = cli_append_virus(ctx, *virname); |
168 | 0 | if (ret != CL_SUCCESS) |
169 | 0 | return ret; |
170 | 6.58M | } else if (ret > CL_TYPENO && acmode & AC_SCAN_VIR) { |
171 | 6.58M | saved_ret = ret; |
172 | 6.58M | } else { |
173 | 0 | return ret; |
174 | 0 | } |
175 | 6.58M | } |
176 | | |
177 | 26.4M | if (root->bcomp_metas) { |
178 | 0 | ret = cli_bcomp_scanbuf(orig_buffer, orig_length, acres, root, mdata, ctx); |
179 | 0 | if (ret != CL_CLEAN) { |
180 | 0 | if (ret > CL_TYPENO && acmode & AC_SCAN_VIR) { |
181 | 0 | saved_ret = ret; |
182 | 0 | } else { |
183 | 0 | return ret; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | } |
187 | | |
188 | 26.4M | switch (ftype) { |
189 | 739k | case CL_TYPE_GIF: |
190 | 980k | case CL_TYPE_TIFF: |
191 | 3.05M | case CL_TYPE_JPEG: |
192 | 3.37M | case CL_TYPE_PNG: |
193 | 3.38M | case CL_TYPE_GRAPHICS: { |
194 | 3.38M | if (ctx->recursion_stack[ctx->recursion_level].calculated_image_fuzzy_hash && |
195 | 3.38M | !fuzzy_hash_check(root->fuzzy_hashmap, mdata, ctx->recursion_stack[ctx->recursion_level].image_fuzzy_hash)) { |
196 | 0 | cli_errmsg("Unexpected error when checking for fuzzy hash matches.\n"); |
197 | 0 | return CL_ERROR; |
198 | 0 | } |
199 | 3.38M | } |
200 | 26.4M | default: |
201 | 26.4M | break; |
202 | 26.4M | } |
203 | | |
204 | 26.4M | #if HAVE_PCRE |
205 | | /* due to logical triggered, pcres cannot be evaluated until after full subsig matching */ |
206 | | /* cannot save pcre execution state without possible evasion; must scan entire buffer */ |
207 | | /* however, scanning the whole buffer may require the whole buffer being loaded into memory */ |
208 | 26.4M | if (root->pcre_metas) { |
209 | 0 | int rc; |
210 | 0 | uint64_t maxfilesize; |
211 | |
|
212 | 0 | if (map && (pcremode == PCRE_SCAN_FMAP)) { |
213 | 0 | if (offset + length >= map->len) { |
214 | | /* check that scanned map does not exceed pcre maxfilesize limit */ |
215 | 0 | maxfilesize = (uint64_t)cl_engine_get_num(ctx->engine, CL_ENGINE_PCRE_MAX_FILESIZE, &rc); |
216 | 0 | if (rc != CL_SUCCESS) |
217 | 0 | return rc; |
218 | 0 | if (maxfilesize && (map->len > maxfilesize)) { |
219 | 0 | cli_dbgmsg("matcher_run: pcre max filesize (map) exceeded (limit: %llu, needed: %llu)\n", |
220 | 0 | (long long unsigned)maxfilesize, (long long unsigned)map->len); |
221 | 0 | return CL_EMAXSIZE; |
222 | 0 | } |
223 | | |
224 | 0 | cli_dbgmsg("matcher_run: performing regex matching on full map: %u+%u(%u) >= %zu\n", offset, length, offset + length, map->len); |
225 | |
|
226 | 0 | buffer = fmap_need_off_once(map, 0, map->len); |
227 | 0 | if (!buffer) |
228 | 0 | return CL_EMEM; |
229 | | |
230 | | /* scan the full buffer */ |
231 | 0 | ret = cli_pcre_scanbuf(buffer, map->len, virname, acres, root, mdata, poffdata, ctx); |
232 | 0 | } |
233 | 0 | } else if (pcremode == PCRE_SCAN_BUFF) { |
234 | | /* check that scanned buffer does not exceed pcre maxfilesize limit */ |
235 | 0 | maxfilesize = (uint64_t)cl_engine_get_num(ctx->engine, CL_ENGINE_PCRE_MAX_FILESIZE, &rc); |
236 | 0 | if (rc != CL_SUCCESS) |
237 | 0 | return rc; |
238 | 0 | if (maxfilesize && (length > maxfilesize)) { |
239 | 0 | cli_dbgmsg("matcher_run: pcre max filesize (buf) exceeded (limit: %llu, needed: %u)\n", (long long unsigned)maxfilesize, length); |
240 | 0 | return CL_EMAXSIZE; |
241 | 0 | } |
242 | | |
243 | 0 | cli_dbgmsg("matcher_run: performing regex matching on buffer with no map: %u+%u(%u)\n", offset, length, offset + length); |
244 | | /* scan the specified buffer */ |
245 | 0 | ret = cli_pcre_scanbuf(buffer, length, virname, acres, root, mdata, poffdata, ctx); |
246 | 0 | } |
247 | 0 | } |
248 | 26.4M | #endif /* HAVE_PCRE */ |
249 | | /* end experimental fragment */ |
250 | | |
251 | 26.4M | if (ctx && ret == CL_VIRUS) { |
252 | 0 | ret = cli_append_virus(ctx, *virname); |
253 | 0 | if (ret != CL_SUCCESS) |
254 | 0 | return ret; |
255 | 0 | } |
256 | | |
257 | 26.4M | if (saved_ret && ret == CL_CLEAN) { |
258 | 0 | return saved_ret; |
259 | 0 | } |
260 | | |
261 | 26.4M | return ret; |
262 | 26.4M | } |
263 | | |
264 | | cl_error_t cli_scan_buff(const unsigned char *buffer, uint32_t length, uint32_t offset, cli_ctx *ctx, cli_file_t ftype, struct cli_ac_data **acdata) |
265 | 284k | { |
266 | 284k | cl_error_t ret = CL_CLEAN; |
267 | 284k | unsigned int i = 0, j = 0; |
268 | 284k | struct cli_ac_data matcher_data; |
269 | 284k | struct cli_matcher *generic_ac_root, *target_ac_root = NULL; |
270 | 284k | const char *virname = NULL; |
271 | 284k | const struct cl_engine *engine = ctx->engine; |
272 | | |
273 | 284k | if (!engine) { |
274 | 0 | cli_errmsg("cli_scan_buff: engine == NULL\n"); |
275 | 0 | return CL_ENULLARG; |
276 | 0 | } |
277 | | |
278 | 284k | generic_ac_root = engine->root[0]; /* generic signatures */ |
279 | | |
280 | 284k | if (ftype != CL_TYPE_ANY) { |
281 | | // Identify the target type, to find the matcher root for that target. |
282 | | |
283 | 1.97M | for (i = 1; i < CLI_MTARGETS; i++) { |
284 | 4.79M | for (j = 0; j < cli_mtargets[i].target_count; ++j) { |
285 | 3.09M | if (cli_mtargets[i].target[j] == ftype) { |
286 | | // Identified the target type, now get the matcher root for that target. |
287 | 284k | target_ac_root = ctx->engine->root[i]; |
288 | 284k | break; // Break out of inner loop |
289 | 284k | } |
290 | 3.09M | } |
291 | 1.97M | if (target_ac_root) break; |
292 | 1.97M | } |
293 | 284k | } |
294 | | |
295 | 284k | if (target_ac_root) { |
296 | | /* If a target-specific specific signature root was found for the given file type, match with it. */ |
297 | | |
298 | 284k | if (!acdata) { |
299 | | // no ac matcher data was provided, so we need to initialize our own. |
300 | 0 | ret = cli_ac_initdata(&matcher_data, target_ac_root->ac_partsigs, target_ac_root->ac_lsigs, target_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); |
301 | 0 | if (CL_SUCCESS != ret) { |
302 | 0 | return ret; |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | 284k | ret = matcher_run(target_ac_root, buffer, length, &virname, |
307 | 284k | acdata ? (acdata[0]) : (&matcher_data), |
308 | 284k | offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); |
309 | | |
310 | 284k | if (!acdata) { |
311 | | // no longer need our AC local matcher data (if using) |
312 | 0 | cli_ac_freedata(&matcher_data); |
313 | 0 | } |
314 | | |
315 | 284k | if (ret == CL_EMEM || ret == CL_VIRUS) { |
316 | 0 | return ret; |
317 | 0 | } |
318 | | |
319 | | // reset virname back to NULL for matching with the generic AC root. |
320 | 284k | virname = NULL; |
321 | 284k | } |
322 | | |
323 | 284k | if (!acdata) { |
324 | | // no ac matcher data was provided, so we need to initialize our own. |
325 | 0 | ret = cli_ac_initdata(&matcher_data, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); |
326 | 0 | if (CL_SUCCESS != ret) { |
327 | 0 | return ret; |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | 284k | ret = matcher_run(generic_ac_root, buffer, length, &virname, |
332 | 284k | acdata ? (acdata[1]) : (&matcher_data), |
333 | 284k | offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx); |
334 | | |
335 | 284k | if (!acdata) { |
336 | | // no longer need our AC local matcher data (if using) |
337 | 0 | cli_ac_freedata(&matcher_data); |
338 | 0 | } |
339 | | |
340 | 284k | return ret; |
341 | 284k | } |
342 | | |
343 | | /* |
344 | | * offdata[0]: type |
345 | | * offdata[1]: offset value |
346 | | * offdata[2]: max shift |
347 | | * offdata[3]: section number |
348 | | */ |
349 | | cl_error_t cli_caloff(const char *offstr, const struct cli_target_info *info, cli_target_t target, uint32_t *offdata, uint32_t *offset_min, uint32_t *offset_max) |
350 | 18.1M | { |
351 | 18.1M | char offcpy[65] = {0}; |
352 | 18.1M | unsigned int n = 0, val = 0; |
353 | 18.1M | char *pt = NULL; |
354 | | |
355 | 18.1M | if (!info) { /* decode offset string */ |
356 | 2.91M | if (!offstr) { |
357 | 0 | cli_errmsg("cli_caloff: offstr == NULL\n"); |
358 | 0 | return CL_ENULLARG; |
359 | 0 | } |
360 | | |
361 | 2.91M | if (!strcmp(offstr, "*")) { |
362 | 1.17M | offdata[0] = *offset_max = *offset_min = CLI_OFF_ANY; |
363 | 1.17M | return CL_SUCCESS; |
364 | 1.17M | } |
365 | | |
366 | 1.73M | if (strlen(offstr) > 64) { |
367 | 14 | cli_errmsg("cli_caloff: Offset string too long\n"); |
368 | 14 | return CL_EMALFDB; |
369 | 14 | } |
370 | 1.73M | strcpy(offcpy, offstr); |
371 | | |
372 | 1.73M | if ((pt = strchr(offcpy, ','))) { |
373 | 646k | if (!cli_isnumber(pt + 1)) { |
374 | 3 | cli_errmsg("cli_caloff: Invalid offset shift value\n"); |
375 | 3 | return CL_EMALFDB; |
376 | 3 | } |
377 | 646k | offdata[2] = atoi(pt + 1); |
378 | 646k | *pt = 0; |
379 | 1.09M | } else { |
380 | 1.09M | offdata[2] = 0; |
381 | 1.09M | } |
382 | | |
383 | 1.73M | *offset_max = *offset_min = CLI_OFF_NONE; |
384 | | |
385 | 1.73M | if (!strncmp(offcpy, "EP+", 3) || !strncmp(offcpy, "EP-", 3)) { |
386 | 21.6k | if (offcpy[2] == '+') |
387 | 2.87k | offdata[0] = CLI_OFF_EP_PLUS; |
388 | 18.7k | else |
389 | 18.7k | offdata[0] = CLI_OFF_EP_MINUS; |
390 | | |
391 | 21.6k | if (!cli_isnumber(&offcpy[3])) { |
392 | 5 | cli_errmsg("cli_caloff: Invalid offset value\n"); |
393 | 5 | return CL_EMALFDB; |
394 | 5 | } |
395 | 21.6k | offdata[1] = atoi(&offcpy[3]); |
396 | | |
397 | 1.71M | } else if (offcpy[0] == 'S') { |
398 | 6.20k | if (offcpy[1] == 'E') { |
399 | 1.67k | if (!cli_isnumber(&offcpy[2])) { |
400 | 4 | cli_errmsg("cli_caloff: Invalid section number\n"); |
401 | 4 | return CL_EMALFDB; |
402 | 4 | } |
403 | 1.67k | offdata[0] = CLI_OFF_SE; |
404 | 1.67k | offdata[3] = atoi(&offcpy[2]); |
405 | | |
406 | 4.52k | } else if (!strncmp(offstr, "SL+", 3)) { |
407 | 1.52k | offdata[0] = CLI_OFF_SL_PLUS; |
408 | 1.52k | if (!cli_isnumber(&offcpy[3])) { |
409 | 3 | cli_errmsg("cli_caloff: Invalid offset value\n"); |
410 | 3 | return CL_EMALFDB; |
411 | 3 | } |
412 | 1.52k | offdata[1] = atoi(&offcpy[3]); |
413 | | |
414 | 3.00k | } else if (sscanf(offcpy, "S%u+%u", &n, &val) == 2) { |
415 | 2.96k | offdata[0] = CLI_OFF_SX_PLUS; |
416 | 2.96k | offdata[1] = val; |
417 | 2.96k | offdata[3] = n; |
418 | 2.96k | } else { |
419 | 38 | cli_errmsg("cli_caloff: Invalid offset string\n"); |
420 | 38 | return CL_EMALFDB; |
421 | 38 | } |
422 | | |
423 | 1.71M | } else if (!strncmp(offcpy, "EOF-", 4)) { |
424 | 167k | offdata[0] = CLI_OFF_EOF_MINUS; |
425 | 167k | if (!cli_isnumber(&offcpy[4])) { |
426 | 3 | cli_errmsg("cli_caloff: Invalid offset value\n"); |
427 | 3 | return CL_EMALFDB; |
428 | 3 | } |
429 | 167k | offdata[1] = atoi(&offcpy[4]); |
430 | 1.54M | } else if (!strncmp(offcpy, "VI", 2)) { |
431 | | /* versioninfo */ |
432 | 30.4k | offdata[0] = CLI_OFF_VERSION; |
433 | 1.51M | } else if (strchr(offcpy, '$')) { |
434 | 1.15k | if (sscanf(offcpy, "$%u$", &n) != 1) { |
435 | 24 | cli_errmsg("cli_caloff: Invalid macro($) in offset: %s\n", offcpy); |
436 | 24 | return CL_EMALFDB; |
437 | 24 | } |
438 | 1.13k | if (n >= 32) { |
439 | 95 | cli_errmsg("cli_caloff: at most 32 macro groups supported\n"); |
440 | 95 | return CL_EMALFDB; |
441 | 95 | } |
442 | 1.03k | offdata[0] = CLI_OFF_MACRO; |
443 | 1.03k | offdata[1] = n; |
444 | 1.51M | } else { |
445 | 1.51M | offdata[0] = CLI_OFF_ABSOLUTE; |
446 | 1.51M | if (!cli_isnumber(offcpy)) { |
447 | 162 | cli_errmsg("cli_caloff: Invalid offset value\n"); |
448 | 162 | return CL_EMALFDB; |
449 | 162 | } |
450 | 1.51M | *offset_min = offdata[1] = atoi(offcpy); |
451 | 1.51M | *offset_max = *offset_min + offdata[2]; |
452 | 1.51M | } |
453 | | |
454 | 1.73M | if (offdata[0] != CLI_OFF_ANY && offdata[0] != CLI_OFF_ABSOLUTE && |
455 | 1.73M | offdata[0] != CLI_OFF_EOF_MINUS && offdata[0] != CLI_OFF_MACRO) { |
456 | 58.2k | if (target != TARGET_PE && target != TARGET_ELF && target != TARGET_MACHO) { |
457 | 17 | cli_errmsg("cli_caloff: Invalid offset type for target %u\n", target); |
458 | 17 | return CL_EMALFDB; |
459 | 17 | } |
460 | 58.2k | } |
461 | | |
462 | 15.2M | } else { |
463 | | /* calculate relative offsets */ |
464 | 15.2M | *offset_min = CLI_OFF_NONE; |
465 | 15.2M | if (offset_max) |
466 | 15.2M | *offset_max = CLI_OFF_NONE; |
467 | 15.2M | if (info->status == -1) { |
468 | | // If the executable headers weren't parsed successfully then we |
469 | | // can't process any ndb/ldb EOF-n/EP+n/EP-n/Sx+n/SEx/SL+n subsigs |
470 | 2.52M | return CL_SUCCESS; |
471 | 2.52M | } |
472 | | |
473 | 12.6M | switch (offdata[0]) { |
474 | 12.6M | case CLI_OFF_EOF_MINUS: |
475 | 12.6M | *offset_min = info->fsize - offdata[1]; |
476 | 12.6M | break; |
477 | | |
478 | 0 | case CLI_OFF_EP_PLUS: |
479 | 0 | *offset_min = info->exeinfo.ep + offdata[1]; |
480 | 0 | break; |
481 | | |
482 | 0 | case CLI_OFF_EP_MINUS: |
483 | 0 | *offset_min = info->exeinfo.ep - offdata[1]; |
484 | 0 | break; |
485 | | |
486 | 0 | case CLI_OFF_SL_PLUS: |
487 | 0 | *offset_min = info->exeinfo.sections[info->exeinfo.nsections - 1].raw + offdata[1]; |
488 | 0 | break; |
489 | | |
490 | 0 | case CLI_OFF_SX_PLUS: |
491 | 0 | if (offdata[3] >= info->exeinfo.nsections) |
492 | 0 | *offset_min = CLI_OFF_NONE; |
493 | 0 | else |
494 | 0 | *offset_min = info->exeinfo.sections[offdata[3]].raw + offdata[1]; |
495 | 0 | break; |
496 | | |
497 | 0 | case CLI_OFF_SE: |
498 | 0 | if (offdata[3] >= info->exeinfo.nsections) { |
499 | 0 | *offset_min = CLI_OFF_NONE; |
500 | 0 | } else { |
501 | 0 | *offset_min = info->exeinfo.sections[offdata[3]].raw; |
502 | 0 | if (offset_max) |
503 | 0 | *offset_max = *offset_min + info->exeinfo.sections[offdata[3]].rsz + offdata[2]; |
504 | | // TODO offdata[2] == MaxShift. Won't this make offset_max |
505 | | // extend beyond the end of the section? This doesn't seem like |
506 | | // what we want... |
507 | 0 | } |
508 | 0 | break; |
509 | | |
510 | 0 | case CLI_OFF_VERSION: |
511 | 0 | if (offset_max) |
512 | 0 | *offset_min = *offset_max = CLI_OFF_ANY; |
513 | 0 | break; |
514 | 0 | default: |
515 | 0 | cli_errmsg("cli_caloff: Not a relative offset (type: %u)\n", offdata[0]); |
516 | 0 | return CL_EARG; |
517 | 12.6M | } |
518 | | |
519 | 12.6M | if (offset_max && *offset_max == CLI_OFF_NONE && *offset_min != CLI_OFF_NONE) |
520 | 12.6M | *offset_max = *offset_min + offdata[2]; |
521 | 12.6M | } |
522 | | |
523 | 14.4M | return CL_SUCCESS; |
524 | 18.1M | } |
525 | | |
526 | | void cli_targetinfo_init(struct cli_target_info *info) |
527 | 15.4M | { |
528 | | |
529 | 15.4M | if (NULL == info) { |
530 | 0 | return; |
531 | 0 | } |
532 | 15.4M | info->status = 0; |
533 | 15.4M | cli_exe_info_init(&(info->exeinfo), 0); |
534 | 15.4M | } |
535 | | |
536 | | void cli_targetinfo(struct cli_target_info *info, cli_target_t target, cli_ctx *ctx) |
537 | 15.4M | { |
538 | 15.4M | cl_error_t (*einfo)(cli_ctx *, struct cli_exe_info *) = NULL; |
539 | | |
540 | 15.4M | info->fsize = ctx->fmap->len; |
541 | | |
542 | 15.4M | switch (target) { |
543 | 406k | case TARGET_PE: |
544 | 406k | einfo = cli_pe_targetinfo; |
545 | 406k | break; |
546 | 119k | case TARGET_ELF: |
547 | 119k | einfo = cli_elfheader; |
548 | 119k | break; |
549 | 2.14M | case TARGET_MACHO: |
550 | 2.14M | einfo = cli_machoheader; |
551 | 2.14M | break; |
552 | 12.8M | default: |
553 | 12.8M | return; |
554 | 15.4M | } |
555 | | |
556 | 2.66M | if (CL_SUCCESS != einfo(ctx, &info->exeinfo)) |
557 | 2.52M | info->status = -1; |
558 | 148k | else |
559 | 148k | info->status = 1; |
560 | 2.66M | } |
561 | | |
562 | | void cli_targetinfo_destroy(struct cli_target_info *info) |
563 | 15.4M | { |
564 | | |
565 | 15.4M | if (NULL == info) { |
566 | 0 | return; |
567 | 0 | } |
568 | | |
569 | 15.4M | cli_exe_info_destroy(&(info->exeinfo)); |
570 | 15.4M | info->status = 0; |
571 | 15.4M | } |
572 | | |
573 | | cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) |
574 | 3.29M | { |
575 | 3.29M | cl_error_t status = CL_VIRUS; |
576 | 3.29M | char md5[33]; |
577 | 3.29M | unsigned int i; |
578 | 3.29M | const char *virname = NULL; |
579 | 3.29M | fmap_t *map; |
580 | 3.29M | int32_t stack_index; |
581 | 3.29M | const char *ptr; |
582 | 3.29M | uint8_t shash1[SHA1_HASH_SIZE * 2 + 1]; |
583 | 3.29M | uint8_t shash256[SHA256_HASH_SIZE * 2 + 1]; |
584 | 3.29M | int have_sha1, have_sha256; |
585 | 3.29M | unsigned char *digest; |
586 | 3.29M | size_t size; |
587 | | |
588 | 3.29M | stack_index = (int32_t)ctx->recursion_level; |
589 | | |
590 | 35.0M | while (stack_index >= 0) { |
591 | 31.7M | map = ctx->recursion_stack[stack_index].fmap; |
592 | | |
593 | 31.7M | if (CL_SUCCESS != fmap_get_hash(map, &digest, CLI_HASH_MD5)) { |
594 | 0 | cli_dbgmsg("cli_check_fp: Failed to get a hash for the map at stack index # %u\n", stack_index); |
595 | 0 | stack_index--; |
596 | 0 | continue; |
597 | 0 | } |
598 | 31.7M | size = map->len; |
599 | | |
600 | | /* |
601 | | * First, check the MD5 digest. |
602 | | * MD5 is default, so it always exists. |
603 | | */ |
604 | 31.7M | if (cli_hm_scan(digest, size, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) { |
605 | 0 | cli_dbgmsg("cli_check_fp(md5): Found false positive detection (fp sig: %s), size: %d\n", virname, (int)size); |
606 | 0 | return CL_CLEAN; |
607 | 31.7M | } else if (cli_hm_scan_wild(digest, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) { |
608 | 0 | cli_dbgmsg("cli_check_fp(md5): Found false positive detection (fp sig: %s), size: *\n", virname); |
609 | 0 | return CL_CLEAN; |
610 | 0 | } |
611 | | |
612 | 31.7M | if (cli_debug_flag || ctx->engine->cb_hash) { |
613 | 0 | const char *name = ctx->recursion_stack[stack_index].fmap->name; |
614 | 0 | const char *type = cli_ftname(ctx->recursion_stack[stack_index].type); |
615 | |
|
616 | 0 | for (i = 0; i < 16; i++) |
617 | 0 | sprintf(md5 + i * 2, "%02x", digest[i]); |
618 | 0 | md5[32] = 0; |
619 | |
|
620 | 0 | cli_dbgmsg("FP SIGNATURE: %s:%u:%s # Name: %s, Type: %s\n", |
621 | 0 | md5, (unsigned int)size, vname ? vname : "Name", name ? name : "n/a", type); |
622 | 0 | } |
623 | | |
624 | 31.7M | have_sha1 = cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, size) || cli_hm_have_wild(ctx->engine->hm_fp, CLI_HASH_SHA1) || cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 1); |
625 | 31.7M | have_sha256 = cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA256, size) || cli_hm_have_wild(ctx->engine->hm_fp, CLI_HASH_SHA256); |
626 | 31.7M | if (have_sha1 || have_sha256) { |
627 | 0 | if ((ptr = fmap_need_off_once(map, 0, size))) { |
628 | 0 | if (have_sha1) { |
629 | 0 | cl_sha1(ptr, size, &shash1[SHA1_HASH_SIZE], NULL); |
630 | |
|
631 | 0 | if (cli_hm_scan(&shash1[SHA1_HASH_SIZE], size, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) { |
632 | 0 | cli_dbgmsg("cli_check_fp(sha1): Found false positive detection (fp sig: %s)\n", virname); |
633 | 0 | return CL_CLEAN; |
634 | 0 | } |
635 | 0 | if (cli_hm_scan_wild(&shash1[SHA1_HASH_SIZE], &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) { |
636 | 0 | cli_dbgmsg("cli_check_fp(sha1): Found false positive detection (fp sig: %s)\n", virname); |
637 | 0 | return CL_CLEAN; |
638 | 0 | } |
639 | | /* See whether the hash matches those loaded in from .cat files |
640 | | * (associated with the .CAB file type) */ |
641 | 0 | if (cli_hm_scan(&shash1[SHA1_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) { |
642 | 0 | cli_dbgmsg("cli_check_fp(sha1): Found .CAB false positive detection via catalog file\n"); |
643 | 0 | return CL_CLEAN; |
644 | 0 | } |
645 | 0 | } |
646 | | |
647 | 0 | if (have_sha256) { |
648 | 0 | cl_sha256(ptr, size, &shash256[SHA256_HASH_SIZE], NULL); |
649 | |
|
650 | 0 | if (cli_hm_scan(&shash256[SHA256_HASH_SIZE], size, &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) { |
651 | 0 | cli_dbgmsg("cli_check_fp(sha256): Found false positive detection (fp sig: %s)\n", virname); |
652 | 0 | return CL_CLEAN; |
653 | 0 | } |
654 | 0 | if (cli_hm_scan_wild(&shash256[SHA256_HASH_SIZE], &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) { |
655 | 0 | cli_dbgmsg("cli_check_fp(sha256): Found false positive detection (fp sig: %s)\n", virname); |
656 | 0 | return CL_CLEAN; |
657 | 0 | } |
658 | | /* See whether the hash matches those loaded in from .cat files |
659 | | * (associated with the .CAB file type) */ |
660 | 0 | if (cli_hm_scan(&shash256[SHA256_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) { |
661 | 0 | cli_dbgmsg("cli_check_fp(sha256): Found .CAB false positive detection via catalog file\n"); |
662 | 0 | return CL_CLEAN; |
663 | 0 | } |
664 | 0 | } |
665 | 0 | } |
666 | 0 | } |
667 | | |
668 | | #ifdef HAVE__INTERNAL__SHA_COLLECT |
669 | | if (SCAN_DEV_COLLECT_SHA && (ctx->sha_collect > 0)) { |
670 | | if ((ptr = fmap_need_off_once(map, 0, size))) { |
671 | | if (!have_sha256) |
672 | | cl_sha256(ptr, size, shash256 + SHA256_HASH_SIZE, NULL); |
673 | | |
674 | | for (i = 0; i < SHA256_HASH_SIZE; i++) |
675 | | sprintf((char *)shash256 + i * 2, "%02x", shash256[SHA256_HASH_SIZE + i]); |
676 | | |
677 | | if (!have_sha1) |
678 | | cl_sha1(ptr, size, shash1 + SHA1_HASH_SIZE); |
679 | | |
680 | | for (i = 0; i < SHA1_HASH_SIZE; i++) |
681 | | sprintf((char *)shash1 + i * 2, "%02x", shash1[SHA1_HASH_SIZE + i]); |
682 | | |
683 | | if (NULL == ctx->target_filepath) { |
684 | | cli_errmsg("COLLECT:%s:%s:%u:%s:%s\n", shash256, shash1, size, vname ? vname : "noname", "NO_IDEA"); |
685 | | } else { |
686 | | cli_errmsg("COLLECT:%s:%s:%u:%s:%s\n", shash256, shash1, size, vname ? vname : "noname", ctx->target_filepath); |
687 | | } |
688 | | } else |
689 | | cli_errmsg("can't compute sha\n!"); |
690 | | |
691 | | ctx->sha_collect = -1; |
692 | | } |
693 | | #endif |
694 | | |
695 | 31.7M | if (ctx->engine->cb_hash) |
696 | 0 | ctx->engine->cb_hash(fmap_fd(ctx->fmap), size, (const unsigned char *)md5, vname ? vname : "noname", ctx->cb_ctx); |
697 | | |
698 | 31.7M | if (ctx->engine->cb_stats_add_sample) { |
699 | 0 | stats_section_t sections; |
700 | 0 | memset(§ions, 0x00, sizeof(stats_section_t)); |
701 | |
|
702 | 0 | if (!(ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_STATS) && |
703 | 0 | !(ctx->engine->dconf->stats & (DCONF_STATS_DISABLED | DCONF_STATS_PE_SECTION_DISABLED))) |
704 | 0 | cli_genhash_pe(ctx, CL_GENHASH_PE_CLASS_SECTION, 1, §ions); |
705 | | |
706 | | // TODO We probably only want to call cb_stats_add_sample when |
707 | | // sections.section != NULL... leaving as is for now |
708 | 0 | ctx->engine->cb_stats_add_sample(vname ? vname : "noname", digest, size, §ions, ctx->engine->stats_data); |
709 | |
|
710 | 0 | if (sections.sections) { |
711 | 0 | free(sections.sections); |
712 | 0 | } |
713 | 0 | } |
714 | | |
715 | 31.7M | stack_index -= 1; |
716 | 31.7M | } |
717 | | |
718 | 3.29M | return status; |
719 | 3.29M | } |
720 | | |
721 | | static cl_error_t matchicon(cli_ctx *ctx, struct cli_exe_info *exeinfo, const char *grp1, const char *grp2) |
722 | 0 | { |
723 | 0 | icon_groupset iconset; |
724 | |
|
725 | 0 | if (!ctx || |
726 | 0 | !ctx->engine || |
727 | 0 | !ctx->engine->iconcheck || |
728 | 0 | !ctx->engine->iconcheck->group_counts[0] || |
729 | 0 | !ctx->engine->iconcheck->group_counts[1] || |
730 | 0 | !exeinfo->res_addr) return CL_CLEAN; |
731 | | |
732 | 0 | if (!(ctx->dconf->pe & PE_CONF_MATCHICON)) |
733 | 0 | return CL_CLEAN; |
734 | | |
735 | 0 | cli_icongroupset_init(&iconset); |
736 | 0 | cli_icongroupset_add(grp1 ? grp1 : "*", &iconset, 0, ctx); |
737 | 0 | cli_icongroupset_add(grp2 ? grp2 : "*", &iconset, 1, ctx); |
738 | 0 | return cli_scanicon(&iconset, ctx, exeinfo); |
739 | 0 | } |
740 | | |
741 | | int32_t cli_bcapi_matchicon(struct cli_bc_ctx *ctx, const uint8_t *grp1, int32_t grp1len, |
742 | | const uint8_t *grp2, int32_t grp2len) |
743 | 0 | { |
744 | 0 | cl_error_t ret; |
745 | 0 | char group1[128], group2[128]; |
746 | 0 | struct cli_exe_info info; |
747 | | |
748 | | // TODO This isn't a good check, since EP will be zero for DLLs and |
749 | | // (assuming pedata->ep is populated from exeinfo->pe) non-zero for |
750 | | // some MachO and ELF executables |
751 | 0 | if (!ctx->hooks.pedata->ep) { |
752 | 0 | cli_dbgmsg("bytecode: matchicon only works with PE files\n"); |
753 | 0 | return -1; |
754 | 0 | } |
755 | 0 | if ((size_t)grp1len > sizeof(group1) - 1 || |
756 | 0 | (size_t)grp2len > sizeof(group2) - 1) |
757 | 0 | return -1; |
758 | | |
759 | 0 | memcpy(group1, grp1, grp1len); |
760 | 0 | memcpy(group2, grp2, grp2len); |
761 | 0 | group1[grp1len] = 0; |
762 | 0 | group2[grp2len] = 0; |
763 | 0 | memset(&info, 0, sizeof(info)); |
764 | 0 | if (ctx->bc->kind == BC_PE_UNPACKER || ctx->bc->kind == BC_PE_ALL) { |
765 | 0 | if (le16_to_host(ctx->hooks.pedata->file_hdr.Characteristics) & 0x2000 || |
766 | 0 | !ctx->hooks.pedata->dirs[2].Size) |
767 | 0 | info.res_addr = 0; |
768 | 0 | else |
769 | 0 | info.res_addr = ctx->hooks.pedata->dirs[2].VirtualAddress; |
770 | 0 | } else |
771 | 0 | info.res_addr = ctx->resaddr; /* from target_info */ |
772 | 0 | info.sections = (struct cli_exe_section *)ctx->sections; |
773 | 0 | info.nsections = ctx->hooks.pedata->nsections; |
774 | 0 | info.hdr_size = ctx->hooks.pedata->hdr_size; |
775 | 0 | cli_dbgmsg("bytecode matchicon %s %s\n", group1, group2); |
776 | 0 | ret = matchicon(ctx->ctx, &info, group1[0] ? group1 : NULL, |
777 | 0 | group2[0] ? group2 : NULL); |
778 | |
|
779 | 0 | return (int32_t)ret; |
780 | 0 | } |
781 | | |
782 | | cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, const char *name, uint32_t attributes) |
783 | 790k | { |
784 | 790k | cl_error_t status = CL_CLEAN; |
785 | 790k | int empty; |
786 | 790k | fmap_t *new_map = NULL; |
787 | | |
788 | 790k | new_map = fmap_check_empty(desc, 0, 0, &empty, name); |
789 | 790k | if (NULL == new_map) { |
790 | 2 | if (!empty) { |
791 | 0 | cli_dbgmsg("cli_scan_desc: Failed to allocate new map for file descriptor scan.\n"); |
792 | 0 | status = CL_EMEM; |
793 | 0 | } |
794 | 2 | goto done; |
795 | 2 | } |
796 | | |
797 | 790k | status = cli_recursion_stack_push(ctx, new_map, ftype, true, attributes); /* Perform scan with child fmap */ |
798 | 790k | if (CL_SUCCESS != status) { |
799 | 8.28k | cli_dbgmsg("cli_scan_desc: Failed to scan fmap.\n"); |
800 | 8.28k | goto done; |
801 | 8.28k | } |
802 | | |
803 | 781k | status = cli_scan_fmap(ctx, ftype, filetype_only, ftoffset, acmode, acres, NULL); |
804 | | |
805 | 781k | (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ |
806 | | |
807 | 790k | done: |
808 | 790k | if (NULL != new_map) { |
809 | 790k | funmap(new_map); |
810 | 790k | } |
811 | | |
812 | 790k | return status; |
813 | 781k | } |
814 | | |
815 | | static int intermediates_eval(cli_ctx *ctx, struct cli_ac_lsig *ac_lsig) |
816 | 0 | { |
817 | 0 | uint32_t i, icnt = ac_lsig->tdb.intermediates[0]; |
818 | | |
819 | | // -1 is the deepest layer (the current layer), so we start at -2, which is the first ancestor |
820 | 0 | int32_t j = -2; |
821 | |
|
822 | 0 | if (ctx->recursion_level < icnt) |
823 | 0 | return 0; |
824 | | |
825 | 0 | for (i = icnt; i > 0; i--) { |
826 | 0 | if (ac_lsig->tdb.intermediates[i] == CL_TYPE_ANY) |
827 | 0 | continue; |
828 | 0 | if (ac_lsig->tdb.intermediates[i] != cli_recursion_stack_get_type(ctx, j--)) |
829 | 0 | return 0; |
830 | 0 | } |
831 | 0 | return 1; |
832 | 0 | } |
833 | | |
834 | | static cl_error_t lsig_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_ac_data *acdata, struct cli_target_info *target_info, const char *hash, uint32_t lsid) |
835 | 0 | { |
836 | 0 | cl_error_t status = CL_CLEAN; |
837 | 0 | unsigned evalcnt = 0; |
838 | 0 | uint64_t evalids = 0; |
839 | 0 | fmap_t *new_map = NULL; |
840 | 0 | struct cli_ac_lsig *ac_lsig = root->ac_lsigtable[lsid]; |
841 | 0 | char *exp = ac_lsig->u.logic; |
842 | 0 | char *exp_end = exp + strlen(exp); |
843 | |
|
844 | 0 | status = cli_ac_chkmacro(root, acdata, lsid); |
845 | 0 | if (status != CL_SUCCESS) |
846 | 0 | return status; |
847 | | |
848 | 0 | if (cli_ac_chklsig(exp, exp_end, acdata->lsigcnt[lsid], &evalcnt, &evalids, 0) != 1) { |
849 | | // Logical expression did not match. |
850 | 0 | goto done; |
851 | 0 | } |
852 | | |
853 | | // Logical expression matched. |
854 | | // Need to check the other conditions, like target description block, icon group, bytecode, etc. |
855 | | |
856 | | // If the lsig requires a specific container type, check if check that it matches |
857 | 0 | if (ac_lsig->tdb.container && |
858 | 0 | ac_lsig->tdb.container[0] != cli_recursion_stack_get_type(ctx, -2)) { |
859 | | // So far the match is good, but the container type doesn't match. |
860 | | // Because this may need to match in a different scenario where the |
861 | | // container does match, we do not want to cache this result. |
862 | 0 | ctx->fmap->dont_cache_flag = 1; |
863 | |
|
864 | 0 | goto done; |
865 | 0 | } |
866 | | |
867 | | // If the lsig has intermediates, check if they match the current recursion stack |
868 | 0 | if (ac_lsig->tdb.intermediates && |
869 | 0 | !intermediates_eval(ctx, ac_lsig)) { |
870 | | // So far the match is good, but the intermediates type(s) do not match. |
871 | | // Because this may need to match in a different scenario where the |
872 | | // intermediates do match, we do not want to cache this result. |
873 | 0 | ctx->fmap->dont_cache_flag = 1; |
874 | |
|
875 | 0 | goto done; |
876 | 0 | } |
877 | | |
878 | | // If the lsig has filesize requirements, check if they match |
879 | 0 | if (ac_lsig->tdb.filesize && (ac_lsig->tdb.filesize[0] > ctx->fmap->len || ac_lsig->tdb.filesize[1] < ctx->fmap->len)) { |
880 | 0 | goto done; |
881 | 0 | } |
882 | | |
883 | 0 | if (ac_lsig->tdb.ep || ac_lsig->tdb.nos) { |
884 | 0 | if (!target_info || target_info->status != 1) |
885 | 0 | goto done; |
886 | 0 | if (ac_lsig->tdb.ep && (ac_lsig->tdb.ep[0] > target_info->exeinfo.ep || ac_lsig->tdb.ep[1] < target_info->exeinfo.ep)) |
887 | 0 | goto done; |
888 | 0 | if (ac_lsig->tdb.nos && (ac_lsig->tdb.nos[0] > target_info->exeinfo.nsections || ac_lsig->tdb.nos[1] < target_info->exeinfo.nsections)) |
889 | 0 | goto done; |
890 | 0 | } |
891 | | |
892 | 0 | if (ac_lsig->tdb.handlertype) { |
893 | | // This logical signature has a handler type, which means it's effectively a complex file type signature. |
894 | | // Instead of alerting, we'll make a duplicate fmap (add recursion depth, to prevent infinite loops) and |
895 | | // scan the file with the handler type. |
896 | |
|
897 | 0 | if (hash && 0 != memcmp(ctx->handlertype_hash, hash, 16)) { |
898 | | /* |
899 | | * Create an fmap window into our current fmap using the original offset & length, and rescan as the new type |
900 | | * |
901 | | * TODO: Unsure if creating an fmap is the right move, or if we should rescan with the current fmap as-is, |
902 | | * since it's not really a container so much as it is type reassignment. This new fmap layer protect against |
903 | | * a possible infinite loop by applying the scan recursion limit, but maybe there's a better way? |
904 | | * Testing with both HandlerType type reassignment sigs + Container/Intermediates sigs should indicate if |
905 | | * a change is needed. |
906 | | */ |
907 | 0 | new_map = fmap_duplicate(ctx->fmap, 0, ctx->fmap->len, ctx->fmap->name); |
908 | 0 | if (NULL == new_map) { |
909 | 0 | status = CL_EMEM; |
910 | 0 | cli_dbgmsg("Failed to duplicate the current fmap for a re-scan as a different type.\n"); |
911 | 0 | goto done; |
912 | 0 | } |
913 | | |
914 | 0 | memcpy(ctx->handlertype_hash, hash, 16); |
915 | |
|
916 | 0 | status = cli_recursion_stack_push(ctx, new_map, ac_lsig->tdb.handlertype[0], true, LAYER_ATTRIBUTES_NONE); /* Perform scan with child fmap */ |
917 | 0 | if (CL_SUCCESS != status) { |
918 | 0 | cli_dbgmsg("Failed to re-scan fmap as a new type.\n"); |
919 | 0 | goto done; |
920 | 0 | } |
921 | | |
922 | 0 | status = cli_magic_scan(ctx, ac_lsig->tdb.handlertype[0]); |
923 | |
|
924 | 0 | (void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */ |
925 | |
|
926 | 0 | goto done; |
927 | 0 | } |
928 | 0 | } |
929 | | |
930 | 0 | if (ac_lsig->tdb.icongrp1 || ac_lsig->tdb.icongrp2) { |
931 | | // Logical sig depends on icon match. Check for the icon match. |
932 | |
|
933 | 0 | if (!target_info || target_info->status != TARGET_PE) { |
934 | | // Icon group feature only applies to PE files, so target description must match a PE file. |
935 | | // This is a signature issue and should have been caught at load time, but just in case, we're checking again here. |
936 | 0 | goto done; |
937 | 0 | } |
938 | | |
939 | 0 | if (CL_VIRUS != matchicon(ctx, &target_info->exeinfo, ac_lsig->tdb.icongrp1, ac_lsig->tdb.icongrp2)) { |
940 | | // No icon match! |
941 | 0 | goto done; |
942 | 0 | } |
943 | 0 | } |
944 | | |
945 | 0 | if (!ac_lsig->bc_idx) { |
946 | | // Logical sig does not depend on bytecode match. Report the virus. |
947 | 0 | status = cli_append_virus(ctx, ac_lsig->virname); |
948 | 0 | if (status != CL_SUCCESS) { |
949 | 0 | goto done; |
950 | 0 | } |
951 | 0 | } else { |
952 | | // Logical sig depends on bytecode match. Check for the bytecode match. |
953 | 0 | status = cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap); |
954 | 0 | if (CL_SUCCESS != status) { |
955 | 0 | goto done; |
956 | 0 | } |
957 | | |
958 | | // Check time limit here, because bytecode functions may take a while. |
959 | 0 | status = cli_checktimelimit(ctx); |
960 | 0 | if (CL_SUCCESS != status) { |
961 | 0 | goto done; |
962 | 0 | } |
963 | 0 | } |
964 | | |
965 | 0 | done: |
966 | 0 | if (NULL != new_map) { |
967 | 0 | free_duplicate_fmap(new_map); |
968 | 0 | } |
969 | |
|
970 | 0 | return status; |
971 | 0 | } |
972 | | |
973 | | #ifdef HAVE_YARA |
974 | | static cl_error_t yara_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_ac_data *acdata, struct cli_target_info *target_info, const char *hash, uint32_t lsid) |
975 | 0 | { |
976 | 0 | struct cli_ac_lsig *ac_lsig = root->ac_lsigtable[lsid]; |
977 | 0 | cl_error_t rc; |
978 | 0 | YR_SCAN_CONTEXT context; |
979 | |
|
980 | 0 | (void)hash; |
981 | |
|
982 | 0 | memset(&context, 0, sizeof(YR_SCAN_CONTEXT)); |
983 | 0 | context.fmap = ctx->fmap; |
984 | 0 | context.file_size = ctx->fmap->len; |
985 | 0 | if (target_info != NULL) { |
986 | 0 | if (target_info->status == 1) |
987 | 0 | context.entry_point = target_info->exeinfo.ep; |
988 | 0 | } |
989 | |
|
990 | 0 | rc = yr_execute_code(ac_lsig, acdata, &context, 0, 0); |
991 | |
|
992 | 0 | if (rc == CL_VIRUS) { |
993 | 0 | if (ac_lsig->flag & CLI_LSIG_FLAG_PRIVATE) { |
994 | 0 | rc = CL_CLEAN; |
995 | 0 | } else { |
996 | 0 | rc = cli_append_virus(ctx, ac_lsig->virname); |
997 | 0 | } |
998 | 0 | } |
999 | 0 | return rc; |
1000 | 0 | } |
1001 | | #endif |
1002 | | |
1003 | | cl_error_t cli_exp_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_ac_data *acdata, struct cli_target_info *target_info, const char *hash) |
1004 | 25.6M | { |
1005 | 25.6M | uint32_t i; |
1006 | 25.6M | cl_error_t status = CL_SUCCESS; |
1007 | | |
1008 | 25.6M | for (i = 0; i < root->ac_lsigs; i++) { |
1009 | 0 | if (root->ac_lsigtable[i]->type == CLI_LSIG_NORMAL) { |
1010 | 0 | status = lsig_eval(ctx, root, acdata, target_info, hash, i); |
1011 | 0 | } |
1012 | 0 | #ifdef HAVE_YARA |
1013 | 0 | else if (root->ac_lsigtable[i]->type == CLI_YARA_NORMAL || root->ac_lsigtable[i]->type == CLI_YARA_OFFSET) { |
1014 | 0 | status = yara_eval(ctx, root, acdata, target_info, hash, i); |
1015 | 0 | } |
1016 | 0 | #endif |
1017 | |
|
1018 | 0 | if (CL_SUCCESS != status) { |
1019 | 0 | break; |
1020 | 0 | } |
1021 | | |
1022 | 0 | if (i % 10 == 0) { |
1023 | | // Check the time limit every n'th lsig. |
1024 | | // In testing with a large signature set, we found n = 10 to be just as fast as 100 or |
1025 | | // 1000 and has a significant performance improvement over checking with every lsig. |
1026 | 0 | status = cli_checktimelimit(ctx); |
1027 | 0 | if (CL_SUCCESS != status) { |
1028 | 0 | cli_dbgmsg("Exceeded scan time limit while evaluating logical and yara signatures (max: %u)\n", ctx->engine->maxscantime); |
1029 | 0 | break; |
1030 | 0 | } |
1031 | 0 | } |
1032 | 0 | } |
1033 | | |
1034 | 25.6M | return status; |
1035 | 25.6M | } |
1036 | | |
1037 | | cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, bool filetype_only, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash) |
1038 | 15.2M | { |
1039 | 15.2M | const unsigned char *buff; |
1040 | 15.2M | cl_error_t ret = CL_CLEAN, type = CL_CLEAN; |
1041 | 15.2M | bool compute_hash[CLI_HASH_AVAIL_TYPES]; |
1042 | 15.2M | unsigned int i = 0, j = 0; |
1043 | 15.2M | uint32_t maxpatlen, bytes, offset = 0; |
1044 | | |
1045 | 15.2M | struct cli_ac_data generic_ac_data; |
1046 | 15.2M | bool gdata_initialized = false; |
1047 | | |
1048 | 15.2M | struct cli_ac_data target_ac_data; |
1049 | 15.2M | bool tdata_initialized = false; |
1050 | | |
1051 | 15.2M | struct cli_bm_off bm_offsets_table; |
1052 | 15.2M | bool bm_offsets_table_initialized = false; |
1053 | | |
1054 | 15.2M | struct cli_pcre_off generic_pcre_offsets_table; |
1055 | 15.2M | bool generic_pcre_offsets_table_initialized = false; |
1056 | | |
1057 | 15.2M | struct cli_pcre_off target_pcre_offsets_table; |
1058 | 15.2M | bool target_pcre_offsets_table_initialized = false; |
1059 | | |
1060 | 15.2M | unsigned char digest[CLI_HASH_AVAIL_TYPES][CLI_HASHLEN_MAX]; |
1061 | | |
1062 | 15.2M | struct cli_matcher *generic_ac_root = NULL, *target_ac_root = NULL; |
1063 | | |
1064 | 15.2M | struct cli_target_info info; |
1065 | 15.2M | bool info_initialized = false; |
1066 | | |
1067 | 15.2M | struct cli_matcher *hdb, *fp; |
1068 | | |
1069 | 15.2M | void *md5ctx = NULL; |
1070 | 15.2M | void *sha1ctx = NULL; |
1071 | 15.2M | void *sha256ctx = NULL; |
1072 | | |
1073 | 15.2M | if (!ctx->engine) { |
1074 | 0 | cli_errmsg("cli_scan_fmap: engine == NULL\n"); |
1075 | 0 | ret = CL_ENULLARG; |
1076 | 0 | goto done; |
1077 | 0 | } |
1078 | | |
1079 | 15.2M | md5ctx = cl_hash_init("md5"); |
1080 | 15.2M | if (!(md5ctx)) { |
1081 | 0 | ret = CL_EMEM; |
1082 | 0 | goto done; |
1083 | 0 | } |
1084 | | |
1085 | 15.2M | sha1ctx = cl_hash_init("sha1"); |
1086 | 15.2M | if (!(sha1ctx)) { |
1087 | 0 | ret = CL_EMEM; |
1088 | 0 | goto done; |
1089 | 0 | } |
1090 | | |
1091 | 15.2M | sha256ctx = cl_hash_init("sha256"); |
1092 | 15.2M | if (!(sha256ctx)) { |
1093 | 0 | ret = CL_EMEM; |
1094 | 0 | goto done; |
1095 | 0 | } |
1096 | | |
1097 | 15.2M | if (!filetype_only) { |
1098 | 15.2M | generic_ac_root = ctx->engine->root[0]; /* generic signatures */ |
1099 | 15.2M | } |
1100 | | |
1101 | 15.2M | if (ftype != CL_TYPE_ANY) { |
1102 | | // Identify the target type, to find the matcher root for that target. |
1103 | | |
1104 | 149M | for (i = 1; i < CLI_MTARGETS; i++) { |
1105 | 334M | for (j = 0; j < cli_mtargets[i].target_count; ++j) { |
1106 | 199M | if (cli_mtargets[i].target[j] == ftype) { |
1107 | | // Identified the target type, now get the matcher root for that target. |
1108 | 9.87M | target_ac_root = ctx->engine->root[i]; |
1109 | 9.87M | break; // Break out of inner loop |
1110 | 9.87M | } |
1111 | 199M | } |
1112 | 144M | if (target_ac_root) break; |
1113 | 144M | } |
1114 | 14.7M | } |
1115 | | |
1116 | 15.2M | if (!generic_ac_root) { |
1117 | 0 | if (!target_ac_root) { |
1118 | | // Don't have a matcher root for either generic signatures or target-specific signatures. |
1119 | | // Nothing to do! |
1120 | 0 | ret = CL_CLEAN; |
1121 | 0 | goto done; |
1122 | 0 | } |
1123 | | |
1124 | | // Only have a matcher root for target-specific signatures. |
1125 | 0 | maxpatlen = target_ac_root->maxpatlen; |
1126 | 15.2M | } else { |
1127 | 15.2M | if (target_ac_root) { |
1128 | | // Have both generic and target-specific signatures. |
1129 | 9.87M | maxpatlen = MAX(target_ac_root->maxpatlen, generic_ac_root->maxpatlen); |
1130 | 9.87M | } else { |
1131 | | // Only have generic signatures. |
1132 | 5.34M | maxpatlen = generic_ac_root->maxpatlen; |
1133 | 5.34M | } |
1134 | 15.2M | } |
1135 | | |
1136 | 15.2M | cli_targetinfo_init(&info); |
1137 | 15.2M | cli_targetinfo(&info, i, ctx); |
1138 | 15.2M | info_initialized = true; |
1139 | | |
1140 | 15.2M | if (-1 == info.status) { |
1141 | 2.52M | cli_dbgmsg("cli_scan_fmap: Failed to successfully parse the executable header. " |
1142 | 2.52M | "Scan features will be disabled, such as " |
1143 | 2.52M | "NDB/LDB subsigs using EOF-n/EP+n/EP-n/Sx+n/SEx/SL+n, " |
1144 | 2.52M | "fuzzy icon matching, " |
1145 | 2.52M | "MDB/IMP sigs, " |
1146 | 2.52M | "and bytecode sigs that require exe metadata\n"); |
1147 | 2.52M | } |
1148 | | |
1149 | | /* If it's a PE, check the Authenticode header. This would be more |
1150 | | * appropriate in cli_scanpe, but scanraw->cli_scan_fmap gets |
1151 | | * called first for PEs, and we want to determine the trust/block |
1152 | | * status early on so we can skip things like embedded PE extraction |
1153 | | * (which is broken for signed binaries within signed binaries). |
1154 | | * |
1155 | | * If we want to add support for more signature parsing in the future |
1156 | | * (Ex: MachO sigs), do that here too. |
1157 | | * |
1158 | | * One benefit of not continuing on to scan files with trusted signatures |
1159 | | * is that the bytes associated with the exe won't get counted against the |
1160 | | * scansize limits, which means we have an increased chance of catching |
1161 | | * malware in container types (NSIS, iShield, etc.) where the file size is |
1162 | | * large. A common case where this occurs is installers that embed one |
1163 | | * or more of the various Microsoft Redistributable Setup packages. These |
1164 | | * can easily be 5 MB or more in size, and might appear before malware |
1165 | | * does in a given sample. |
1166 | | */ |
1167 | | |
1168 | 15.2M | if (1 == info.status && i == 1) { |
1169 | 130k | ret = cli_check_auth_header(ctx, &(info.exeinfo)); |
1170 | 130k | if (ret == CL_VIRUS || ret == CL_VERIFIED) { |
1171 | 0 | goto done; |
1172 | 0 | } |
1173 | | |
1174 | 130k | ret = CL_CLEAN; |
1175 | 130k | } |
1176 | | |
1177 | 15.2M | if (!filetype_only) { |
1178 | | /* If we're not doing a filetype-only scan, so we definitely need to include generic signatures. |
1179 | | So initialize the ac data for the generic signatures root. */ |
1180 | | |
1181 | 15.2M | ret = cli_ac_initdata(&generic_ac_data, generic_ac_root->ac_partsigs, generic_ac_root->ac_lsigs, generic_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); |
1182 | 15.2M | if (CL_SUCCESS != ret) { |
1183 | 0 | goto done; |
1184 | 0 | } |
1185 | 15.2M | gdata_initialized = true; |
1186 | | |
1187 | | /* Recalculate the relative offsets in ac sigs (e.g. those that are based on pe/elf/macho section start/end). */ |
1188 | 15.2M | ret = cli_ac_caloff(generic_ac_root, &generic_ac_data, &info); |
1189 | 15.2M | if (CL_SUCCESS != ret) { |
1190 | 0 | goto done; |
1191 | 0 | } |
1192 | | |
1193 | | /* Recalculate the pcre offsets. |
1194 | | This does an allocation, that we will need to free later. */ |
1195 | 15.2M | ret = cli_pcre_recaloff(generic_ac_root, &generic_pcre_offsets_table, &info, ctx); |
1196 | 15.2M | if (CL_SUCCESS != ret) { |
1197 | 0 | goto done; |
1198 | 0 | } |
1199 | 15.2M | generic_pcre_offsets_table_initialized = true; |
1200 | 15.2M | } |
1201 | | |
1202 | 15.2M | if (target_ac_root) { |
1203 | | /* We have to match against target-specific signatures. |
1204 | | So initialize the ac data for the target-specific signatures root. */ |
1205 | | |
1206 | 9.87M | ret = cli_ac_initdata(&target_ac_data, target_ac_root->ac_partsigs, target_ac_root->ac_lsigs, target_ac_root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN); |
1207 | 9.87M | if (CL_SUCCESS != ret) { |
1208 | 0 | goto done; |
1209 | 0 | } |
1210 | 9.87M | tdata_initialized = true; |
1211 | | |
1212 | | /* Recalculate the relative offsets in ac sigs (e.g. those that are based on pe/elf/macho section start/end). */ |
1213 | 9.87M | ret = cli_ac_caloff(target_ac_root, &target_ac_data, &info); |
1214 | 9.87M | if (CL_SUCCESS != ret) { |
1215 | 0 | goto done; |
1216 | 0 | } |
1217 | | |
1218 | 9.87M | if (target_ac_root->bm_offmode) { |
1219 | 406k | if (ctx->fmap->len >= CLI_DEFAULT_BM_OFFMODE_FSIZE) { |
1220 | | /* Recalculate the relative offsets in boyer-moore signatures (e.g. those that are based on pe/elf/macho section start/end). */ |
1221 | 20.4k | ret = cli_bm_initoff(target_ac_root, &bm_offsets_table, &info); |
1222 | 20.4k | if (CL_SUCCESS != ret) { |
1223 | 0 | goto done; |
1224 | 0 | } |
1225 | 20.4k | bm_offsets_table_initialized = true; |
1226 | 20.4k | } |
1227 | 406k | } |
1228 | | |
1229 | | /* Recalculate the pcre offsets. |
1230 | | This does an allocation, that we will need to free later. */ |
1231 | 9.87M | ret = cli_pcre_recaloff(target_ac_root, &target_pcre_offsets_table, &info, ctx); |
1232 | 9.87M | if (CL_SUCCESS != ret) { |
1233 | 0 | goto done; |
1234 | 0 | } |
1235 | 9.87M | target_pcre_offsets_table_initialized = true; |
1236 | 9.87M | } |
1237 | | |
1238 | 15.2M | hdb = ctx->engine->hm_hdb; |
1239 | 15.2M | fp = ctx->engine->hm_fp; |
1240 | | |
1241 | 15.2M | if (!filetype_only && hdb) { |
1242 | | /* We're not just doing file typing, we're checking for viruses. |
1243 | | So we need to compute the hash sigs, if there are any. |
1244 | | |
1245 | | Computing the hash in chunks the same size and time that we do for |
1246 | | matching with the AC & BM pattern matchers is an optimization so we |
1247 | | we can do both processes while the cache is still hot. */ |
1248 | |
|
1249 | 0 | if (!refhash) { |
1250 | 0 | if (cli_hm_have_size(hdb, CLI_HASH_MD5, ctx->fmap->len) || |
1251 | 0 | cli_hm_have_size(fp, CLI_HASH_MD5, ctx->fmap->len) || |
1252 | 0 | cli_hm_have_wild(hdb, CLI_HASH_MD5) || |
1253 | 0 | cli_hm_have_wild(fp, CLI_HASH_MD5)) { |
1254 | 0 | compute_hash[CLI_HASH_MD5] = true; |
1255 | 0 | } else { |
1256 | 0 | compute_hash[CLI_HASH_MD5] = false; |
1257 | 0 | } |
1258 | 0 | } else { |
1259 | 0 | compute_hash[CLI_HASH_MD5] = 0; |
1260 | 0 | memcpy(digest[CLI_HASH_MD5], refhash, 16); |
1261 | 0 | } |
1262 | |
|
1263 | 0 | if (cli_hm_have_size(hdb, CLI_HASH_SHA1, ctx->fmap->len) || |
1264 | 0 | cli_hm_have_wild(hdb, CLI_HASH_SHA1) || |
1265 | 0 | cli_hm_have_size(fp, CLI_HASH_SHA1, ctx->fmap->len) || |
1266 | 0 | cli_hm_have_wild(fp, CLI_HASH_SHA1)) { |
1267 | 0 | compute_hash[CLI_HASH_SHA1] = true; |
1268 | 0 | } else { |
1269 | 0 | compute_hash[CLI_HASH_SHA1] = false; |
1270 | 0 | } |
1271 | |
|
1272 | 0 | if (cli_hm_have_size(hdb, CLI_HASH_SHA256, ctx->fmap->len) || |
1273 | 0 | cli_hm_have_wild(hdb, CLI_HASH_SHA256) || |
1274 | 0 | cli_hm_have_size(fp, CLI_HASH_SHA256, ctx->fmap->len) || |
1275 | 0 | cli_hm_have_wild(fp, CLI_HASH_SHA256)) { |
1276 | 0 | compute_hash[CLI_HASH_SHA256] = true; |
1277 | 0 | } else { |
1278 | 0 | compute_hash[CLI_HASH_SHA256] = false; |
1279 | 0 | } |
1280 | 0 | } |
1281 | | |
1282 | 15.6M | while (offset < ctx->fmap->len) { |
1283 | 15.6M | if (cli_checktimelimit(ctx) != CL_SUCCESS) { |
1284 | 0 | cli_dbgmsg("Exceeded scan time limit while scanning fmap (max: %u)\n", ctx->engine->maxscantime); |
1285 | 0 | ret = CL_ETIMEOUT; |
1286 | 0 | goto done; |
1287 | 0 | } |
1288 | | |
1289 | 15.6M | bytes = MIN(ctx->fmap->len - offset, SCANBUFF); |
1290 | 15.6M | if (!(buff = fmap_need_off_once(ctx->fmap, offset, bytes))) |
1291 | 0 | break; |
1292 | 15.6M | if (ctx->scanned) |
1293 | 15.6M | *ctx->scanned += bytes / CL_COUNT_PRECISION; |
1294 | | |
1295 | 15.6M | if (target_ac_root) { |
1296 | 10.2M | const char *virname = NULL; |
1297 | | |
1298 | 10.2M | ret = matcher_run(target_ac_root, buff, bytes, &virname, &target_ac_data, offset, |
1299 | 10.2M | &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, |
1300 | 10.2M | bm_offsets_table_initialized ? &bm_offsets_table : NULL, |
1301 | 10.2M | &target_pcre_offsets_table, ctx); |
1302 | 10.2M | if (ret == CL_VIRUS || ret == CL_EMEM) { |
1303 | 0 | goto done; |
1304 | 0 | } |
1305 | 10.2M | } |
1306 | | |
1307 | 15.6M | if (!filetype_only) { |
1308 | 15.6M | const char *virname = NULL; |
1309 | | |
1310 | 15.6M | ret = matcher_run(generic_ac_root, buff, bytes, &virname, &generic_ac_data, offset, |
1311 | 15.6M | &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, |
1312 | 15.6M | NULL, |
1313 | 15.6M | &generic_pcre_offsets_table, ctx); |
1314 | 15.6M | if (ret == CL_VIRUS || ret == CL_EMEM) { |
1315 | 0 | goto done; |
1316 | 15.6M | } else if ((acmode & AC_SCAN_FT) && ((cli_file_t)ret >= CL_TYPENO)) { |
1317 | 6.58M | if (ret > type) |
1318 | 6.52M | type = ret; |
1319 | 6.58M | } |
1320 | | |
1321 | | /* if (bytes <= (maxpatlen * (offset!=0))), it means the last window finished the file hashing * |
1322 | | * since the last window is responsible for adding intersection between windows (maxpatlen) */ |
1323 | 15.6M | if (hdb && (bytes > (maxpatlen * (offset != 0)))) { |
1324 | 0 | const void *data = buff + maxpatlen * (offset != 0); |
1325 | 0 | uint32_t data_len = bytes - maxpatlen * (offset != 0); |
1326 | |
|
1327 | 0 | if (compute_hash[CLI_HASH_MD5]) |
1328 | 0 | cl_update_hash(md5ctx, (void *)data, data_len); |
1329 | 0 | if (compute_hash[CLI_HASH_SHA1]) |
1330 | 0 | cl_update_hash(sha1ctx, (void *)data, data_len); |
1331 | 0 | if (compute_hash[CLI_HASH_SHA256]) |
1332 | 0 | cl_update_hash(sha256ctx, (void *)data, data_len); |
1333 | 0 | } |
1334 | 15.6M | } |
1335 | | |
1336 | 15.6M | if (bytes < SCANBUFF) |
1337 | 15.2M | break; |
1338 | | |
1339 | 448k | offset += bytes - maxpatlen; |
1340 | 448k | } |
1341 | | |
1342 | 15.2M | if (!filetype_only && hdb) { |
1343 | | /* We're not just doing file typing, we're scanning for malware. |
1344 | | So we need to check the hash sigs, if there are any. */ |
1345 | |
|
1346 | 0 | cli_hash_type_t hashtype; |
1347 | |
|
1348 | 0 | if (compute_hash[CLI_HASH_MD5]) { |
1349 | 0 | cl_finish_hash(md5ctx, digest[CLI_HASH_MD5]); |
1350 | 0 | md5ctx = NULL; |
1351 | | |
1352 | | // Save the MD5 hash for later use (e.g. in FP checks). |
1353 | 0 | fmap_set_hash(ctx->fmap, digest[CLI_HASH_MD5], CLI_HASH_MD5); |
1354 | 0 | } |
1355 | 0 | if (refhash) { |
1356 | | // Set "compute_hash" to 1 because we'll use this later to know if we have a hash to check. |
1357 | 0 | compute_hash[CLI_HASH_MD5] = 1; |
1358 | 0 | } |
1359 | |
|
1360 | 0 | if (compute_hash[CLI_HASH_SHA1]) { |
1361 | 0 | cl_finish_hash(sha1ctx, digest[CLI_HASH_SHA1]); |
1362 | 0 | sha1ctx = NULL; |
1363 | | |
1364 | | // Save the SHA1 hash for later use (e.g. in FP checks). |
1365 | 0 | fmap_set_hash(ctx->fmap, digest[CLI_HASH_SHA1], CLI_HASH_SHA1); |
1366 | 0 | } |
1367 | 0 | if (compute_hash[CLI_HASH_SHA256]) { |
1368 | 0 | cl_finish_hash(sha256ctx, digest[CLI_HASH_SHA256]); |
1369 | 0 | sha256ctx = NULL; |
1370 | | |
1371 | | // Save the SHA256 hash for later use (e.g. in FP checks). |
1372 | 0 | fmap_set_hash(ctx->fmap, digest[CLI_HASH_SHA256], CLI_HASH_SHA256); |
1373 | 0 | } |
1374 | |
|
1375 | 0 | for (hashtype = CLI_HASH_MD5; hashtype < CLI_HASH_AVAIL_TYPES; hashtype++) { |
1376 | 0 | const char *virname = NULL; |
1377 | 0 | const char *virname_w = NULL; |
1378 | | |
1379 | | /* If no hash, skip to next type */ |
1380 | 0 | if (!compute_hash[hashtype]) { |
1381 | 0 | continue; |
1382 | 0 | } |
1383 | | |
1384 | | /* Do hash scan checking hash sigs with specific size */ |
1385 | 0 | ret = cli_hm_scan(digest[hashtype], ctx->fmap->len, &virname, hdb, hashtype); |
1386 | 0 | if (ret == CL_VIRUS) { |
1387 | | /* Matched with size-based hash ... */ |
1388 | 0 | ret = cli_append_virus(ctx, virname); |
1389 | 0 | if (ret != CL_SUCCESS) { |
1390 | 0 | goto done; |
1391 | 0 | } |
1392 | 0 | } |
1393 | | |
1394 | | /* Do hash scan checking hash sigs with wildcard size */ |
1395 | 0 | ret = cli_hm_scan_wild(digest[hashtype], &virname_w, hdb, hashtype); |
1396 | 0 | if (ret == CL_VIRUS) { |
1397 | | /* Matched with size-agnostic hash ... */ |
1398 | 0 | ret = cli_append_virus(ctx, virname_w); |
1399 | 0 | if (ret != CL_SUCCESS) { |
1400 | 0 | goto done; |
1401 | 0 | } |
1402 | 0 | } |
1403 | 0 | } |
1404 | 0 | } |
1405 | | |
1406 | | /* |
1407 | | * Evaluate the logical expressions for clamav logical signatures and YARA rules. |
1408 | | */ |
1409 | | // Evaluate for the target-specific signature AC matches. |
1410 | 15.2M | if (NULL != target_ac_root) { |
1411 | 9.87M | if (ret != CL_VIRUS) { |
1412 | 9.87M | ret = cli_exp_eval(ctx, target_ac_root, &target_ac_data, &info, (const char *)refhash); |
1413 | 9.87M | } |
1414 | 9.87M | } |
1415 | | |
1416 | | // Evaluate for the generic signature AC matches. |
1417 | 15.2M | if (NULL != generic_ac_root) { |
1418 | 15.2M | if (ret != CL_VIRUS) { |
1419 | 15.2M | ret = cli_exp_eval(ctx, generic_ac_root, &generic_ac_data, &info, (const char *)refhash); |
1420 | 15.2M | } |
1421 | 15.2M | } |
1422 | | |
1423 | 15.2M | done: |
1424 | 15.2M | if (NULL != md5ctx) { |
1425 | 15.2M | cl_hash_destroy(md5ctx); |
1426 | 15.2M | } |
1427 | 15.2M | if (NULL != sha1ctx) { |
1428 | 15.2M | cl_hash_destroy(sha1ctx); |
1429 | 15.2M | } |
1430 | 15.2M | if (NULL != sha256ctx) { |
1431 | 15.2M | cl_hash_destroy(sha256ctx); |
1432 | 15.2M | } |
1433 | | |
1434 | 15.2M | if (gdata_initialized) { |
1435 | 15.2M | cli_ac_freedata(&generic_ac_data); |
1436 | 15.2M | } |
1437 | 15.2M | if (tdata_initialized) { |
1438 | 9.87M | cli_ac_freedata(&target_ac_data); |
1439 | 9.87M | } |
1440 | | |
1441 | 15.2M | if (generic_pcre_offsets_table_initialized) { |
1442 | 15.2M | cli_pcre_freeoff(&generic_pcre_offsets_table); |
1443 | 15.2M | } |
1444 | 15.2M | if (target_pcre_offsets_table_initialized) { |
1445 | 9.87M | cli_pcre_freeoff(&target_pcre_offsets_table); |
1446 | 9.87M | } |
1447 | | |
1448 | 15.2M | if (info_initialized) { |
1449 | 15.2M | cli_targetinfo_destroy(&info); |
1450 | 15.2M | } |
1451 | | |
1452 | 15.2M | if (bm_offsets_table_initialized) { |
1453 | 20.4k | cli_bm_freeoff(&bm_offsets_table); |
1454 | 20.4k | } |
1455 | | |
1456 | 15.2M | if (ret != CL_SUCCESS) { |
1457 | 0 | return ret; |
1458 | 0 | } |
1459 | | |
1460 | 15.2M | return (acmode & AC_SCAN_FT) ? type : CL_SUCCESS; |
1461 | 15.2M | } |
1462 | | |
1463 | | #define CDBRANGE(field, val) \ |
1464 | 0 | if (field[0] != CLI_OFF_ANY) { \ |
1465 | 0 | if (field[0] == field[1] && field[0] != val) \ |
1466 | 0 | continue; \ |
1467 | 0 | else if (field[0] != field[1] && ((field[0] && field[0] > val) || \ |
1468 | 0 | (field[1] && field[1] < val))) \ |
1469 | 0 | continue; \ |
1470 | 0 | } |
1471 | | |
1472 | | cl_error_t cli_matchmeta(cli_ctx *ctx, const char *fname, size_t fsizec, size_t fsizer, int encrypted, unsigned int filepos, int res1, void *res2) |
1473 | 5.68M | { |
1474 | 5.68M | const struct cli_cdb *cdb; |
1475 | 5.68M | cl_error_t ret = CL_SUCCESS; |
1476 | | |
1477 | 5.68M | cli_dbgmsg("CDBNAME:%s:%llu:%s:%llu:%llu:%d:%u:%u:%p\n", |
1478 | 5.68M | cli_ftname(cli_recursion_stack_get_type(ctx, -1)), (long long unsigned)fsizec, fname, (long long unsigned)fsizec, (long long unsigned)fsizer, |
1479 | 5.68M | encrypted, filepos, res1, res2); |
1480 | | |
1481 | 5.68M | if (ctx->engine && ctx->engine->cb_meta) { |
1482 | 0 | if (ctx->engine->cb_meta(cli_ftname(cli_recursion_stack_get_type(ctx, -1)), fsizec, fname, fsizer, encrypted, filepos, ctx->cb_ctx) == CL_VIRUS) { |
1483 | 0 | cli_dbgmsg("inner file blocked by callback: %s\n", fname); |
1484 | |
|
1485 | 0 | ret = cli_append_virus(ctx, "Detected.By.Callback"); |
1486 | 0 | if (ret != CL_SUCCESS) { |
1487 | 0 | return ret; |
1488 | 0 | } |
1489 | 0 | } |
1490 | 0 | } |
1491 | | |
1492 | 5.68M | if (NULL == ctx->engine || (NULL == (cdb = ctx->engine->cdb))) { |
1493 | 5.68M | return CL_CLEAN; |
1494 | 5.68M | } |
1495 | | |
1496 | 0 | do { |
1497 | 0 | if (cdb->ctype != CL_TYPE_ANY && cdb->ctype != cli_recursion_stack_get_type(ctx, -1)) |
1498 | 0 | continue; |
1499 | | |
1500 | 0 | if (cdb->encrypted != 2 && cdb->encrypted != encrypted) |
1501 | 0 | continue; |
1502 | | |
1503 | 0 | if (cdb->res1 && (cdb->ctype == CL_TYPE_ZIP || cdb->ctype == CL_TYPE_RAR) && cdb->res1 != res1) |
1504 | 0 | continue; |
1505 | | |
1506 | 0 | CDBRANGE(cdb->csize, cli_recursion_stack_get_size(ctx, -1)); |
1507 | 0 | CDBRANGE(cdb->fsizec, fsizec); |
1508 | 0 | CDBRANGE(cdb->fsizer, fsizer); |
1509 | 0 | CDBRANGE(cdb->filepos, filepos); |
1510 | |
|
1511 | 0 | if (cdb->name.re_magic && (!fname || cli_regexec(&cdb->name, fname, 0, NULL, 0) == REG_NOMATCH)) |
1512 | 0 | continue; |
1513 | | |
1514 | 0 | ret = cli_append_virus(ctx, cdb->virname); |
1515 | 0 | if (ret != CL_SUCCESS) { |
1516 | 0 | return ret; |
1517 | 0 | } |
1518 | |
|
1519 | 0 | } while ((cdb = cdb->next)); |
1520 | | |
1521 | 0 | return ret; |
1522 | 0 | } |