clamav/libclamav/matcher.c
Micah Snyder 2e55c901b1 Fix byte-compare subsignature premature alert
The byte compare feature in logical signatures will cause the rule to
alert if it successfully matches regardless of the rest of the logical
signature.

An easy way to test this is with a logical signature that has two
bcomp subsignatures and requires both to match for the rule to alert.

In the following example, we have 4 signatures where
- the first will match both bcomp subsigs.
- the second will match neither.
- the last two match just one bcomp subsig.

In an --allmatch test, you'll find that the 3 of these match, with the
first one matching *twice*, once for each bcomp subsig.

test.ldb:
```
bcomp.both;Engine:51-255,Target:0;0&1&2&3;4141;0(>>5#hb2#=123);4242;2(>>5#hb2#=255)
bcomp.neither;Engine:51-255,Target:0;0&1&2&3;4141;0(>>5#hb2#=124);4242;2(>>5#hb2#=254)
bcomp.second;Engine:51-255,Target:0;0&1&2&3;4141;0(>>5#hb2#=124);4242;2(>>5#hb2#=255)
bcomp.first;Engine:51-255,Target:0;0&1&2&3;4141;0(>>5#hb2#=123);4242;2(>>5#hb2#=254)
```

test.sample:
```
AA = 7B; BB = FF
```

You can also try a similar test to compare the behavior with regular
ac-pattern-match subsigs with this lsig-test.ldb:
```
pattern.both;Engine:51-255,Target:0;0&1;4141;4242
pattern.neither;Engine:51-255,Target:0;0&1;4140;4241
pattern.second;Engine:51-255,Target:0;0&1;4140;4242
pattern.first;Engine:51-255,Target:0;0&1;4141;4241
```

This commit fixes the issue by incrementing the logical subsignature
count for each bcomp subsig match instead of appending an alert for
each bcomp match.

Also removed call to `lsig_sub_matched()` that didn't do anything.
2022-02-15 20:49:10 -07:00

1441 lines
52 KiB
C

/*
* Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2007-2013 Sourcefire, Inc.
*
* Authors: Tomasz Kojm
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdbool.h>
#include "clamav.h"
#include "others.h"
#include "matcher-ac.h"
#include "matcher-bm.h"
#include "matcher-pcre.h"
#include "filetypes.h"
#include "matcher.h"
#include "pe.h"
#include "elf.h"
#include "execs.h"
#include "special.h"
#include "scanners.h"
#include "str.h"
#include "default.h"
#include "macho.h"
#include "fmap.h"
#include "pe_icons.h"
#include "regex/regex.h"
#include "filtering.h"
#include "perflogging.h"
#include "bytecode_priv.h"
#include "bytecode_api_impl.h"
#ifdef HAVE_YARA
#include "yara_clam.h"
#include "yara_exec.h"
#endif
#ifdef CLI_PERF_LOGGING
static inline void perf_log_filter(int32_t pos, int32_t length, int8_t trie)
{
cli_perf_log_add(RAW_BYTES_SCANNED, length);
cli_perf_log_add(FILTER_BYTES_SCANNED, length - pos);
cli_perf_log_count2(TRIE_SCANNED, trie, length - pos);
}
static inline int perf_log_tries(int8_t acmode, int8_t bm_called, int32_t length)
{
if (bm_called)
cli_perf_log_add(BM_SCANNED, length);
if (acmode)
cli_perf_log_add(AC_SCANNED, length);
return 0;
}
#else
static inline void perf_log_filter(int32_t pos, uint32_t length, int8_t trie)
{
UNUSEDPARAM(pos);
UNUSEDPARAM(length);
UNUSEDPARAM(trie);
}
static inline int perf_log_tries(int8_t acmode, int8_t bm_called, int32_t length)
{
UNUSEDPARAM(acmode);
UNUSEDPARAM(bm_called);
UNUSEDPARAM(length);
return 0;
}
#endif
static inline cl_error_t matcher_run(const struct cli_matcher *root,
const unsigned char *buffer, uint32_t length,
const char **virname, struct cli_ac_data *mdata,
uint32_t offset,
const struct cli_target_info *tinfo,
cli_file_t ftype,
struct cli_matched_type **ftoffset,
unsigned int acmode,
unsigned int pcremode,
struct cli_ac_result **acres,
fmap_t *map,
struct cli_bm_off *offdata,
struct cli_pcre_off *poffdata,
cli_ctx *ctx)
{
cl_error_t ret, saved_ret = CL_CLEAN;
int32_t pos = 0;
struct filter_match_info info;
uint32_t orig_length, orig_offset;
const unsigned char *orig_buffer;
unsigned int viruses_found = 0;
if (root->filter) {
if (filter_search_ext(root->filter, buffer, length, &info) == -1) {
/* for safety always scan last maxpatlen bytes */
pos = length - root->maxpatlen - 1;
if (pos < 0) pos = 0;
perf_log_filter(pos, length, root->type);
} else {
/* must not cut buffer for 64[4-4]6161, because we must be able to check
* 64! */
pos = info.first_match - root->maxpatlen - 1;
if (pos < 0) pos = 0;
perf_log_filter(pos, length, root->type);
}
} else {
perf_log_filter(0, length, root->type);
}
orig_length = length;
orig_buffer = buffer;
orig_offset = offset;
length -= pos;
buffer += pos;
offset += pos;
if (!root->ac_only) {
perf_log_tries(0, 1, length);
if (root->bm_offmode) {
/* Don't use prefiltering for BM offset mode, since BM keeps tracks
* of offsets itself, and doesn't work if we skip chunks of input
* data */
ret = cli_bm_scanbuff(orig_buffer, orig_length, virname, NULL, root, orig_offset, tinfo, offdata, ctx);
} else {
ret = cli_bm_scanbuff(buffer, length, virname, NULL, root, offset, tinfo, offdata, ctx);
}
if (ret != CL_CLEAN) {
if (ret != CL_VIRUS)
return ret;
/* else (ret == CL_VIRUS) */
if (SCAN_ALLMATCHES)
viruses_found = 1;
else {
ret = cli_append_virus(ctx, *virname);
if (ret != CL_CLEAN)
return ret;
}
}
}
perf_log_tries(acmode, 0, length);
ret = cli_ac_scanbuff(buffer, length, virname, NULL, acres, root, mdata, offset, ftype, ftoffset, acmode, ctx);
if (ret != CL_CLEAN) {
if (ret == CL_VIRUS) {
if (SCAN_ALLMATCHES)
viruses_found = 1;
else {
ret = cli_append_virus(ctx, *virname);
if (ret != CL_CLEAN)
return ret;
}
} else if (ret > CL_TYPENO && acmode & AC_SCAN_VIR)
saved_ret = ret;
else
return ret;
}
if (root->bcomp_metas) {
ret = cli_bcomp_scanbuf(orig_buffer, orig_length, acres, root, mdata, ctx);
if (ret != CL_CLEAN) {
if (ret > CL_TYPENO && acmode & AC_SCAN_VIR)
saved_ret = ret;
else
return ret;
}
}
/* due to logical triggered, pcres cannot be evaluated until after full subsig matching */
/* cannot save pcre execution state without possible evasion; must scan entire buffer */
/* however, scanning the whole buffer may require the whole buffer being loaded into memory */
#if HAVE_PCRE
if (root->pcre_metas) {
int rc;
uint64_t maxfilesize;
if (map && (pcremode == PCRE_SCAN_FMAP)) {
if (offset + length >= map->len) {
/* check that scanned map does not exceed pcre maxfilesize limit */
maxfilesize = (uint64_t)cl_engine_get_num(ctx->engine, CL_ENGINE_PCRE_MAX_FILESIZE, &rc);
if (rc != CL_SUCCESS)
return rc;
if (maxfilesize && (map->len > maxfilesize)) {
cli_dbgmsg("matcher_run: pcre max filesize (map) exceeded (limit: %llu, needed: %llu)\n",
(long long unsigned)maxfilesize, (long long unsigned)map->len);
return CL_EMAXSIZE;
}
cli_dbgmsg("matcher_run: performing regex matching on full map: %u+%u(%u) >= %zu\n", offset, length, offset + length, map->len);
buffer = fmap_need_off_once(map, 0, map->len);
if (!buffer)
return CL_EMEM;
/* scan the full buffer */
ret = cli_pcre_scanbuf(buffer, map->len, virname, acres, root, mdata, poffdata, ctx);
}
} else if (pcremode == PCRE_SCAN_BUFF) {
/* check that scanned buffer does not exceed pcre maxfilesize limit */
maxfilesize = (uint64_t)cl_engine_get_num(ctx->engine, CL_ENGINE_PCRE_MAX_FILESIZE, &rc);
if (rc != CL_SUCCESS)
return rc;
if (maxfilesize && (length > maxfilesize)) {
cli_dbgmsg("matcher_run: pcre max filesize (buf) exceeded (limit: %llu, needed: %u)\n", (long long unsigned)maxfilesize, length);
return CL_EMAXSIZE;
}
cli_dbgmsg("matcher_run: performing regex matching on buffer with no map: %u+%u(%u)\n", offset, length, offset + length);
/* scan the specified buffer */
ret = cli_pcre_scanbuf(buffer, length, virname, acres, root, mdata, poffdata, ctx);
}
}
#endif /* HAVE_PCRE */
/* end experimental fragment */
if (ctx && !SCAN_ALLMATCHES && ret == CL_VIRUS) {
return cli_append_virus(ctx, *virname);
}
if (ctx && SCAN_ALLMATCHES && viruses_found) {
return CL_VIRUS;
}
if (saved_ret && ret == CL_CLEAN) {
return saved_ret;
}
return ret;
}
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)
{
cl_error_t ret = CL_CLEAN;
unsigned int i = 0, j = 0, viruses_found = 0;
struct cli_ac_data mdata;
struct cli_matcher *groot, *troot = NULL;
const char *virname = NULL;
const struct cl_engine *engine = ctx->engine;
if (!engine) {
cli_errmsg("cli_scan_buff: engine == NULL\n");
return CL_ENULLARG;
}
groot = engine->root[0]; /* generic signatures */
if (ftype) {
for (i = 1; i < CLI_MTARGETS; i++) {
for (j = 0; j < cli_mtargets[i].target_count; ++j) {
if (cli_mtargets[i].target[j] == ftype) {
troot = ctx->engine->root[i];
break;
}
}
if (troot) break;
}
}
if (troot) {
if (!acdata && (ret = cli_ac_initdata(&mdata, troot->ac_partsigs, troot->ac_lsigs, troot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)))
return ret;
ret = matcher_run(troot, buffer, length, &virname, acdata ? (acdata[0]) : (&mdata), offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx);
if (!acdata)
cli_ac_freedata(&mdata);
if (ret == CL_EMEM)
return ret;
if (ret == CL_VIRUS) {
viruses_found = 1;
if (ctx && !SCAN_ALLMATCHES) {
return ret;
}
}
}
virname = NULL;
if (!acdata && (ret = cli_ac_initdata(&mdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)))
return ret;
ret = matcher_run(groot, buffer, length, &virname, acdata ? (acdata[1]) : (&mdata), offset, NULL, ftype, NULL, AC_SCAN_VIR, PCRE_SCAN_BUFF, NULL, ctx->fmap, NULL, NULL, ctx);
if (!acdata)
cli_ac_freedata(&mdata);
if (viruses_found)
return CL_VIRUS;
return ret;
}
/*
* offdata[0]: type
* offdata[1]: offset value
* offdata[2]: max shift
* offdata[3]: section number
*/
cl_error_t cli_caloff(const char *offstr, const struct cli_target_info *info, unsigned int target, uint32_t *offdata, uint32_t *offset_min, uint32_t *offset_max)
{
char offcpy[65] = {0};
unsigned int n = 0, val = 0;
char *pt = NULL;
if (!info) { /* decode offset string */
if (!offstr) {
cli_errmsg("cli_caloff: offstr == NULL\n");
return CL_ENULLARG;
}
if (!strcmp(offstr, "*")) {
offdata[0] = *offset_max = *offset_min = CLI_OFF_ANY;
return CL_SUCCESS;
}
if (strlen(offstr) > 64) {
cli_errmsg("cli_caloff: Offset string too long\n");
return CL_EMALFDB;
}
strcpy(offcpy, offstr);
if ((pt = strchr(offcpy, ','))) {
if (!cli_isnumber(pt + 1)) {
cli_errmsg("cli_caloff: Invalid offset shift value\n");
return CL_EMALFDB;
}
offdata[2] = atoi(pt + 1);
*pt = 0;
} else {
offdata[2] = 0;
}
*offset_max = *offset_min = CLI_OFF_NONE;
if (!strncmp(offcpy, "EP+", 3) || !strncmp(offcpy, "EP-", 3)) {
if (offcpy[2] == '+')
offdata[0] = CLI_OFF_EP_PLUS;
else
offdata[0] = CLI_OFF_EP_MINUS;
if (!cli_isnumber(&offcpy[3])) {
cli_errmsg("cli_caloff: Invalid offset value\n");
return CL_EMALFDB;
}
offdata[1] = atoi(&offcpy[3]);
} else if (offcpy[0] == 'S') {
if (offcpy[1] == 'E') {
if (!cli_isnumber(&offcpy[2])) {
cli_errmsg("cli_caloff: Invalid section number\n");
return CL_EMALFDB;
}
offdata[0] = CLI_OFF_SE;
offdata[3] = atoi(&offcpy[2]);
} else if (!strncmp(offstr, "SL+", 3)) {
offdata[0] = CLI_OFF_SL_PLUS;
if (!cli_isnumber(&offcpy[3])) {
cli_errmsg("cli_caloff: Invalid offset value\n");
return CL_EMALFDB;
}
offdata[1] = atoi(&offcpy[3]);
} else if (sscanf(offcpy, "S%u+%u", &n, &val) == 2) {
offdata[0] = CLI_OFF_SX_PLUS;
offdata[1] = val;
offdata[3] = n;
} else {
cli_errmsg("cli_caloff: Invalid offset string\n");
return CL_EMALFDB;
}
} else if (!strncmp(offcpy, "EOF-", 4)) {
offdata[0] = CLI_OFF_EOF_MINUS;
if (!cli_isnumber(&offcpy[4])) {
cli_errmsg("cli_caloff: Invalid offset value\n");
return CL_EMALFDB;
}
offdata[1] = atoi(&offcpy[4]);
} else if (!strncmp(offcpy, "VI", 2)) {
/* versioninfo */
offdata[0] = CLI_OFF_VERSION;
} else if (strchr(offcpy, '$')) {
if (sscanf(offcpy, "$%u$", &n) != 1) {
cli_errmsg("cli_caloff: Invalid macro($) in offset: %s\n", offcpy);
return CL_EMALFDB;
}
if (n >= 32) {
cli_errmsg("cli_caloff: at most 32 macro groups supported\n");
return CL_EMALFDB;
}
offdata[0] = CLI_OFF_MACRO;
offdata[1] = n;
} else {
offdata[0] = CLI_OFF_ABSOLUTE;
if (!cli_isnumber(offcpy)) {
cli_errmsg("cli_caloff: Invalid offset value\n");
return CL_EMALFDB;
}
*offset_min = offdata[1] = atoi(offcpy);
*offset_max = *offset_min + offdata[2];
}
if (offdata[0] != CLI_OFF_ANY && offdata[0] != CLI_OFF_ABSOLUTE &&
offdata[0] != CLI_OFF_EOF_MINUS && offdata[0] != CLI_OFF_MACRO) {
if (target != 1 && target != 6 && target != 9) {
cli_errmsg("cli_caloff: Invalid offset type for target %u\n", target);
return CL_EMALFDB;
}
}
} else {
/* calculate relative offsets */
*offset_min = CLI_OFF_NONE;
if (offset_max)
*offset_max = CLI_OFF_NONE;
if (info->status == -1) {
// If the executable headers weren't parsed successfully then we
// can't process any ndb/ldb EOF-n/EP+n/EP-n/Sx+n/SEx/SL+n subsigs
return CL_SUCCESS;
}
switch (offdata[0]) {
case CLI_OFF_EOF_MINUS:
*offset_min = info->fsize - offdata[1];
break;
case CLI_OFF_EP_PLUS:
*offset_min = info->exeinfo.ep + offdata[1];
break;
case CLI_OFF_EP_MINUS:
*offset_min = info->exeinfo.ep - offdata[1];
break;
case CLI_OFF_SL_PLUS:
*offset_min = info->exeinfo.sections[info->exeinfo.nsections - 1].raw + offdata[1];
break;
case CLI_OFF_SX_PLUS:
if (offdata[3] >= info->exeinfo.nsections)
*offset_min = CLI_OFF_NONE;
else
*offset_min = info->exeinfo.sections[offdata[3]].raw + offdata[1];
break;
case CLI_OFF_SE:
if (offdata[3] >= info->exeinfo.nsections) {
*offset_min = CLI_OFF_NONE;
} else {
*offset_min = info->exeinfo.sections[offdata[3]].raw;
if (offset_max)
*offset_max = *offset_min + info->exeinfo.sections[offdata[3]].rsz + offdata[2];
// TODO offdata[2] == MaxShift. Won't this make offset_max
// extend beyond the end of the section? This doesn't seem like
// what we want...
}
break;
case CLI_OFF_VERSION:
if (offset_max)
*offset_min = *offset_max = CLI_OFF_ANY;
break;
default:
cli_errmsg("cli_caloff: Not a relative offset (type: %u)\n", offdata[0]);
return CL_EARG;
}
if (offset_max && *offset_max == CLI_OFF_NONE && *offset_min != CLI_OFF_NONE)
*offset_max = *offset_min + offdata[2];
}
return CL_SUCCESS;
}
void cli_targetinfo_init(struct cli_target_info *info)
{
if (NULL == info) {
return;
}
info->status = 0;
cli_exe_info_init(&(info->exeinfo), 0);
}
void cli_targetinfo(struct cli_target_info *info, unsigned int target, cli_ctx *ctx)
{
int (*einfo)(cli_ctx *, struct cli_exe_info *) = NULL;
info->fsize = ctx->fmap->len;
if (target == 1)
einfo = cli_pe_targetinfo;
else if (target == 6)
einfo = cli_elfheader;
else if (target == 9)
einfo = cli_machoheader;
else
return;
if (einfo(ctx, &info->exeinfo))
info->status = -1;
else
info->status = 1;
}
void cli_targetinfo_destroy(struct cli_target_info *info)
{
if (NULL == info) {
return;
}
cli_exe_info_destroy(&(info->exeinfo));
info->status = 0;
}
cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
{
cl_error_t status = CL_VIRUS;
char md5[33];
unsigned int i;
const char *virname = NULL;
fmap_t *map;
int32_t stack_index;
const char *ptr;
uint8_t shash1[SHA1_HASH_SIZE * 2 + 1];
uint8_t shash256[SHA256_HASH_SIZE * 2 + 1];
int have_sha1, have_sha256;
unsigned char *digest;
size_t size;
stack_index = (int32_t)ctx->recursion_level;
while (stack_index >= 0) {
map = ctx->recursion_stack[stack_index].fmap;
if (CL_SUCCESS != fmap_get_MD5(map, &digest)) {
cli_dbgmsg("cli_check_fp: Failed to get a hash for the map at stack index # %u\n", stack_index);
stack_index--;
continue;
}
size = map->len;
/*
* First, check the MD5 digest.
* MD5 is default, so it always exists.
*/
if (cli_hm_scan(digest, size, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(md5): Found false positive detection (fp sig: %s), size: %d\n", virname, (int)size);
return CL_CLEAN;
} else if (cli_hm_scan_wild(digest, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(md5): Found false positive detection (fp sig: %s), size: *\n", virname);
return CL_CLEAN;
}
if (cli_debug_flag || ctx->engine->cb_hash) {
const char *name = ctx->recursion_stack[stack_index].fmap->name;
const char *type = cli_ftname(ctx->recursion_stack[stack_index].type);
for (i = 0; i < 16; i++)
sprintf(md5 + i * 2, "%02x", digest[i]);
md5[32] = 0;
cli_dbgmsg("FP SIGNATURE: %s:%u:%s # Name: %s, Type: %s\n",
md5, (unsigned int)size, vname ? vname : "Name", name ? name : "n/a", type);
}
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);
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);
if (have_sha1 || have_sha256) {
if ((ptr = fmap_need_off_once(map, 0, size))) {
if (have_sha1) {
cl_sha1(ptr, size, &shash1[SHA1_HASH_SIZE], NULL);
if (cli_hm_scan(&shash1[SHA1_HASH_SIZE], size, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha1): Found false positive detection (fp sig: %s)\n", virname);
return CL_CLEAN;
}
if (cli_hm_scan_wild(&shash1[SHA1_HASH_SIZE], &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha1): Found false positive detection (fp sig: %s)\n", virname);
return CL_CLEAN;
}
/* See whether the hash matches those loaded in from .cat files
* (associated with the .CAB file type) */
if (cli_hm_scan(&shash1[SHA1_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha1): Found .CAB false positive detection via catalog file\n");
return CL_CLEAN;
}
}
if (have_sha256) {
cl_sha256(ptr, size, &shash256[SHA256_HASH_SIZE], NULL);
if (cli_hm_scan(&shash256[SHA256_HASH_SIZE], size, &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha256): Found false positive detection (fp sig: %s)\n", virname);
return CL_CLEAN;
}
if (cli_hm_scan_wild(&shash256[SHA256_HASH_SIZE], &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha256): Found false positive detection (fp sig: %s)\n", virname);
return CL_CLEAN;
}
/* See whether the hash matches those loaded in from .cat files
* (associated with the .CAB file type) */
if (cli_hm_scan(&shash256[SHA256_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA256) == CL_VIRUS) {
cli_dbgmsg("cli_check_fp(sha256): Found .CAB false positive detection via catalog file\n");
return CL_CLEAN;
}
}
}
}
#ifdef HAVE__INTERNAL__SHA_COLLECT
if (SCAN_DEV_COLLECT_SHA && (ctx->sha_collect > 0)) {
if ((ptr = fmap_need_off_once(map, 0, size))) {
if (!have_sha256)
cl_sha256(ptr, size, shash256 + SHA256_HASH_SIZE, NULL);
for (i = 0; i < SHA256_HASH_SIZE; i++)
sprintf((char *)shash256 + i * 2, "%02x", shash256[SHA256_HASH_SIZE + i]);
if (!have_sha1)
cl_sha1(ptr, size, shash1 + SHA1_HASH_SIZE);
for (i = 0; i < SHA1_HASH_SIZE; i++)
sprintf((char *)shash1 + i * 2, "%02x", shash1[SHA1_HASH_SIZE + i]);
if (NULL == ctx->target_filepath) {
cli_errmsg("COLLECT:%s:%s:%u:%s:%s\n", shash256, shash1, size, vname ? vname : "noname", "NO_IDEA");
} else {
cli_errmsg("COLLECT:%s:%s:%u:%s:%s\n", shash256, shash1, size, vname ? vname : "noname", ctx->target_filepath);
}
} else
cli_errmsg("can't compute sha\n!");
ctx->sha_collect = -1;
}
#endif
if (ctx->engine->cb_hash)
ctx->engine->cb_hash(fmap_fd(ctx->fmap), size, (const unsigned char *)md5, vname ? vname : "noname", ctx->cb_ctx);
if (ctx->engine->cb_stats_add_sample) {
stats_section_t sections;
memset(&sections, 0x00, sizeof(stats_section_t));
if (!(ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_STATS) &&
!(ctx->engine->dconf->stats & (DCONF_STATS_DISABLED | DCONF_STATS_PE_SECTION_DISABLED)))
cli_genhash_pe(ctx, CL_GENHASH_PE_CLASS_SECTION, 1, &sections);
// TODO We probably only want to call cb_stats_add_sample when
// sections.section != NULL... leaving as is for now
ctx->engine->cb_stats_add_sample(vname ? vname : "noname", digest, size, &sections, ctx->engine->stats_data);
if (sections.sections) {
free(sections.sections);
}
}
stack_index -= 1;
}
return status;
}
static cl_error_t matchicon(cli_ctx *ctx, struct cli_exe_info *exeinfo, const char *grp1, const char *grp2)
{
icon_groupset iconset;
if (!ctx ||
!ctx->engine ||
!ctx->engine->iconcheck ||
!ctx->engine->iconcheck->group_counts[0] ||
!ctx->engine->iconcheck->group_counts[1] ||
!exeinfo->res_addr) return CL_CLEAN;
if (!(ctx->dconf->pe & PE_CONF_MATCHICON))
return CL_CLEAN;
cli_icongroupset_init(&iconset);
cli_icongroupset_add(grp1 ? grp1 : "*", &iconset, 0, ctx);
cli_icongroupset_add(grp2 ? grp2 : "*", &iconset, 1, ctx);
return cli_scanicon(&iconset, ctx, exeinfo);
}
int32_t cli_bcapi_matchicon(struct cli_bc_ctx *ctx, const uint8_t *grp1, int32_t grp1len,
const uint8_t *grp2, int32_t grp2len)
{
cl_error_t ret;
char group1[128], group2[128];
const char **oldvirname;
struct cli_exe_info info;
// TODO This isn't a good check, since EP will be zero for DLLs and
// (assuming pedata->ep is populated from exeinfo->pe) non-zero for
// some MachO and ELF executables
if (!ctx->hooks.pedata->ep) {
cli_dbgmsg("bytecode: matchicon only works with PE files\n");
return -1;
}
if ((size_t)grp1len > sizeof(group1) - 1 ||
(size_t)grp2len > sizeof(group2) - 1)
return -1;
oldvirname = ((cli_ctx *)ctx->ctx)->virname;
((cli_ctx *)ctx->ctx)->virname = NULL;
memcpy(group1, grp1, grp1len);
memcpy(group2, grp2, grp2len);
group1[grp1len] = 0;
group2[grp2len] = 0;
memset(&info, 0, sizeof(info));
if (ctx->bc->kind == BC_PE_UNPACKER || ctx->bc->kind == BC_PE_ALL) {
if (le16_to_host(ctx->hooks.pedata->file_hdr.Characteristics) & 0x2000 ||
!ctx->hooks.pedata->dirs[2].Size)
info.res_addr = 0;
else
info.res_addr = le32_to_host(ctx->hooks.pedata->dirs[2].VirtualAddress);
} else
info.res_addr = ctx->resaddr; /* from target_info */
info.sections = (struct cli_exe_section *)ctx->sections;
info.nsections = ctx->hooks.pedata->nsections;
info.hdr_size = ctx->hooks.pedata->hdr_size;
cli_dbgmsg("bytecode matchicon %s %s\n", group1, group2);
ret = matchicon(ctx->ctx, &info, group1[0] ? group1 : NULL,
group2[0] ? group2 : NULL);
((cli_ctx *)ctx->ctx)->virname = oldvirname;
return (int32_t)ret;
}
cl_error_t cli_scan_desc(int desc, cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, const char *name)
{
cl_error_t status = CL_CLEAN;
int empty;
fmap_t *new_map = NULL;
fmap_t *map = ctx->fmap; /* Store off the parent fmap for easy reference */
new_map = fmap_check_empty(desc, 0, 0, &empty, name);
if (NULL == new_map) {
if (!empty) {
cli_dbgmsg("cli_scan_desc: Failed to allocate new map for file descriptor scan.\n");
status = CL_EMEM;
}
goto done;
}
status = cli_recursion_stack_push(ctx, new_map, ftype, true); /* Perform scan with child fmap */
if (CL_SUCCESS != status) {
cli_dbgmsg("cli_scan_desc: Failed to scan fmap.\n");
goto done;
}
status = cli_scan_fmap(ctx, ftype, ftonly, ftoffset, acmode, acres, NULL);
map->dont_cache_flag = ctx->fmap->dont_cache_flag; /* Set the parent layer's "don't cache" flag to match the child.
TODO: This may not be needed since `emax_reached()` should've
already done that for us. */
(void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */
done:
if (NULL != new_map) {
funmap(new_map);
}
return status;
}
static int intermediates_eval(cli_ctx *ctx, struct cli_ac_lsig *ac_lsig)
{
uint32_t i, icnt = ac_lsig->tdb.intermediates[0];
int32_t j = -1;
if (ctx->recursion_level < icnt)
return 0;
for (i = icnt; i > 0; i--) {
if (ac_lsig->tdb.intermediates[i] == CL_TYPE_ANY)
continue;
if (ac_lsig->tdb.intermediates[i] != cli_recursion_stack_get_type(ctx, j--))
return 0;
}
return 1;
}
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)
{
cl_error_t status = CL_CLEAN;
unsigned evalcnt = 0;
uint64_t evalids = 0;
fmap_t *new_map = NULL;
struct cli_ac_lsig *ac_lsig = root->ac_lsigtable[lsid];
char *exp = ac_lsig->u.logic;
char *exp_end = exp + strlen(exp);
status = cli_ac_chkmacro(root, acdata, lsid);
if (status != CL_SUCCESS)
return status;
if (cli_ac_chklsig(exp, exp_end, acdata->lsigcnt[lsid], &evalcnt, &evalids, 0) == 1) {
if (ac_lsig->tdb.container && ac_lsig->tdb.container[0] != cli_recursion_stack_get_type(ctx, -2))
goto done;
if (ac_lsig->tdb.intermediates && !intermediates_eval(ctx, ac_lsig))
goto done;
if (ac_lsig->tdb.filesize && (ac_lsig->tdb.filesize[0] > ctx->fmap->len || ac_lsig->tdb.filesize[1] < ctx->fmap->len))
goto done;
if (ac_lsig->tdb.ep || ac_lsig->tdb.nos) {
if (!target_info || target_info->status != 1)
goto done;
if (ac_lsig->tdb.ep && (ac_lsig->tdb.ep[0] > target_info->exeinfo.ep || ac_lsig->tdb.ep[1] < target_info->exeinfo.ep))
goto done;
if (ac_lsig->tdb.nos && (ac_lsig->tdb.nos[0] > target_info->exeinfo.nsections || ac_lsig->tdb.nos[1] < target_info->exeinfo.nsections))
goto done;
}
if (hash && ac_lsig->tdb.handlertype) {
if (0 != memcmp(ctx->handlertype_hash, hash, 16)) {
/*
* Create an fmap window into our current fmap using the original offset & length, and rescan as the new type
*
* TODO: Unsure if creating an fmap is the right move, or if we should rescan with the current fmap as-is,
* since it's not really a container so much as it is type reassignment. This new fmap layer protect agains
* a possible infinite loop by applying the scan recursion limit, but maybe there's a better way?
* Testing with both HandlerType type reassignment sigs + Container/Intermediates sigs should indicate if
* a change is needed.
*/
new_map = fmap_duplicate(ctx->fmap, 0, ctx->fmap->len, ctx->fmap->name);
if (NULL == new_map) {
status = CL_EMEM;
cli_dbgmsg("Failed to duplicate the current fmap for a re-scan as a different type.\n");
goto done;
}
memcpy(ctx->handlertype_hash, hash, 16);
status = cli_recursion_stack_push(ctx, new_map, ac_lsig->tdb.handlertype[0], true); /* Perform scan with child fmap */
if (CL_SUCCESS != status) {
cli_dbgmsg("Failed to re-scan fmap as a new type.\n");
goto done;
}
status = cli_magic_scan(ctx, ac_lsig->tdb.handlertype[0]);
(void)cli_recursion_stack_pop(ctx); /* Restore the parent fmap */
if (CL_VIRUS == status) {
status = CL_VIRUS;
goto done;
}
goto done;
}
}
if (ac_lsig->tdb.icongrp1 || ac_lsig->tdb.icongrp2) {
if (!target_info || target_info->status != 1) {
goto done;
}
if (CL_VIRUS == matchicon(ctx, &target_info->exeinfo, ac_lsig->tdb.icongrp1, ac_lsig->tdb.icongrp2)) {
if (!ac_lsig->bc_idx) {
status = cli_append_virus(ctx, ac_lsig->virname);
if (status != CL_CLEAN) {
goto done;
}
} else if (CL_VIRUS == cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap)) {
status = CL_VIRUS;
goto done;
}
}
goto done;
}
if (!ac_lsig->bc_idx) {
status = cli_append_virus(ctx, ac_lsig->virname);
if (status != CL_CLEAN) {
goto done;
}
}
if (CL_VIRUS == cli_bytecode_runlsig(ctx, target_info, &ctx->engine->bcs, ac_lsig->bc_idx, acdata->lsigcnt[lsid], acdata->lsigsuboff_first[lsid], ctx->fmap)) {
status = CL_VIRUS;
goto done;
}
}
done:
if (NULL != new_map) {
free_duplicate_fmap(new_map);
}
return status;
}
#ifdef HAVE_YARA
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)
{
struct cli_ac_lsig *ac_lsig = root->ac_lsigtable[lsid];
cl_error_t rc;
YR_SCAN_CONTEXT context;
(void)hash;
memset(&context, 0, sizeof(YR_SCAN_CONTEXT));
context.fmap = ctx->fmap;
context.file_size = ctx->fmap->len;
if (target_info != NULL) {
if (target_info->status == 1)
context.entry_point = target_info->exeinfo.ep;
}
rc = yr_execute_code(ac_lsig, acdata, &context, 0, 0);
if (rc == CL_VIRUS) {
if (ac_lsig->flag & CLI_LSIG_FLAG_PRIVATE) {
rc = CL_CLEAN;
} else {
rc = cli_append_virus(ctx, ac_lsig->virname);
}
}
return rc;
}
#endif
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)
{
uint8_t viruses_found = 0;
uint32_t i;
cl_error_t rc = CL_SUCCESS;
for (i = 0; i < root->ac_lsigs; i++) {
if (root->ac_lsigtable[i]->type == CLI_LSIG_NORMAL)
rc = lsig_eval(ctx, root, acdata, target_info, hash, i);
#ifdef HAVE_YARA
else if (root->ac_lsigtable[i]->type == CLI_YARA_NORMAL || root->ac_lsigtable[i]->type == CLI_YARA_OFFSET)
rc = yara_eval(ctx, root, acdata, target_info, hash, i);
#endif
if (rc == CL_VIRUS) {
viruses_found = 1;
if (SCAN_ALLMATCHES)
continue;
break;
}
}
if (viruses_found)
return CL_VIRUS;
return CL_CLEAN;
}
cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli_matched_type **ftoffset, unsigned int acmode, struct cli_ac_result **acres, unsigned char *refhash)
{
const unsigned char *buff;
cl_error_t ret = CL_CLEAN, type = CL_CLEAN;
int compute_hash[CLI_HASH_AVAIL_TYPES];
unsigned int i = 0, j = 0, bm_offmode = 0;
uint32_t maxpatlen, bytes, offset = 0;
struct cli_ac_data gdata, tdata;
struct cli_bm_off toff;
struct cli_pcre_off gpoff, tpoff;
unsigned char digest[CLI_HASH_AVAIL_TYPES][32];
struct cli_matcher *groot = NULL, *troot = NULL;
struct cli_target_info info;
struct cli_matcher *hdb, *fp;
const char *virname;
uint32_t viruses_found = 0;
void *md5ctx, *sha1ctx, *sha256ctx;
if (!ctx->engine) {
cli_errmsg("cli_scan_fmap: engine == NULL\n");
return CL_ENULLARG;
}
md5ctx = cl_hash_init("md5");
if (!(md5ctx))
return CL_EMEM;
sha1ctx = cl_hash_init("sha1");
if (!(sha1ctx)) {
cl_hash_destroy(md5ctx);
return CL_EMEM;
}
sha256ctx = cl_hash_init("sha256");
if (!(sha256ctx)) {
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
return CL_EMEM;
}
if (!ftonly)
groot = ctx->engine->root[0]; /* generic signatures */
if (ftype) {
for (i = 1; i < CLI_MTARGETS; i++) {
for (j = 0; j < cli_mtargets[i].target_count; ++j) {
if (cli_mtargets[i].target[j] == ftype) {
troot = ctx->engine->root[i];
break;
}
}
if (troot) break;
}
}
if (ftonly) {
if (!troot) {
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return CL_CLEAN;
}
maxpatlen = troot->maxpatlen;
} else {
if (troot)
maxpatlen = MAX(troot->maxpatlen, groot->maxpatlen);
else
maxpatlen = groot->maxpatlen;
}
cli_targetinfo_init(&info);
cli_targetinfo(&info, i, ctx);
if (-1 == info.status) {
cli_dbgmsg("cli_scan_fmap: Failed to successfully parse the executable header. "
"Scan features will be disabled, such as "
"NDB/LDB subsigs using EOF-n/EP+n/EP-n/Sx+n/SEx/SL+n, "
"fuzzy icon matching, "
"MDB/IMP sigs, "
"and bytecode sigs that require exe metadata\n");
}
/* If it's a PE, check the Authenticode header. This would be more
* appropriate in cli_scanpe, but scanraw->cli_scan_fmap gets
* called first for PEs, and we want to determine the trust/block
* status early on so we can skip things like embedded PE extraction
* (which is broken for signed binaries within signed binaries).
*
* If we want to add support for more signature parsing in the future
* (Ex: MachO sigs), do that here too.
*
* One benefit of not continuing on to scan files with trusted signatures
* is that the bytes associated with the exe won't get counted against the
* scansize limits, which means we have an increased chance of catching
* malware in container types (NSIS, iShield, etc.) where the file size is
* large. A common case where this occurs is installers that embed one
* or more of the various Microsoft Redistributable Setup packages. These
* can easily be 5 MB or more in size, and might appear before malware
* does in a given sample.
*/
if (1 == info.status && i == 1) {
ret = cli_check_auth_header(ctx, &(info.exeinfo));
if ((ret == CL_VIRUS || ret == CL_VERIFIED) && !SCAN_ALLMATCHES) {
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
ret = CL_CLEAN;
}
if (!ftonly) {
if ((ret = cli_ac_initdata(&gdata, groot->ac_partsigs, groot->ac_lsigs, groot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) ||
(ret = cli_ac_caloff(groot, &gdata, &info))) {
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
if ((ret = cli_pcre_recaloff(groot, &gpoff, &info, ctx))) {
cli_ac_freedata(&gdata);
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
}
if (troot) {
if ((ret = cli_ac_initdata(&tdata, troot->ac_partsigs, troot->ac_lsigs, troot->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) ||
(ret = cli_ac_caloff(troot, &tdata, &info))) {
if (!ftonly) {
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
}
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
if (troot->bm_offmode) {
if (ctx->fmap->len >= CLI_DEFAULT_BM_OFFMODE_FSIZE) {
if ((ret = cli_bm_initoff(troot, &toff, &info))) {
if (!ftonly) {
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
}
cli_ac_freedata(&tdata);
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
bm_offmode = 1;
}
}
if ((ret = cli_pcre_recaloff(troot, &tpoff, &info, ctx))) {
if (!ftonly) {
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
}
cli_ac_freedata(&tdata);
if (bm_offmode)
cli_bm_freeoff(&toff);
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
}
hdb = ctx->engine->hm_hdb;
fp = ctx->engine->hm_fp;
if (!ftonly && hdb) {
if (!refhash) {
if (cli_hm_have_size(hdb, CLI_HASH_MD5, ctx->fmap->len) ||
cli_hm_have_size(fp, CLI_HASH_MD5, ctx->fmap->len) ||
cli_hm_have_wild(hdb, CLI_HASH_MD5) ||
cli_hm_have_wild(fp, CLI_HASH_MD5)) {
compute_hash[CLI_HASH_MD5] = 1;
} else {
compute_hash[CLI_HASH_MD5] = 0;
}
} else {
compute_hash[CLI_HASH_MD5] = 0;
memcpy(digest[CLI_HASH_MD5], refhash, 16);
}
if (cli_hm_have_size(hdb, CLI_HASH_SHA1, ctx->fmap->len) ||
cli_hm_have_wild(hdb, CLI_HASH_SHA1) ||
cli_hm_have_size(fp, CLI_HASH_SHA1, ctx->fmap->len) ||
cli_hm_have_wild(fp, CLI_HASH_SHA1)) {
compute_hash[CLI_HASH_SHA1] = 1;
} else {
compute_hash[CLI_HASH_SHA1] = 0;
}
if (cli_hm_have_size(hdb, CLI_HASH_SHA256, ctx->fmap->len) ||
cli_hm_have_wild(hdb, CLI_HASH_SHA256) ||
cli_hm_have_size(fp, CLI_HASH_SHA256, ctx->fmap->len) ||
cli_hm_have_wild(fp, CLI_HASH_SHA256)) {
compute_hash[CLI_HASH_SHA256] = 1;
} else {
compute_hash[CLI_HASH_SHA256] = 0;
}
}
while (offset < ctx->fmap->len) {
bytes = MIN(ctx->fmap->len - offset, SCANBUFF);
if (!(buff = fmap_need_off_once(ctx->fmap, offset, bytes)))
break;
if (ctx->scanned)
*ctx->scanned += bytes / CL_COUNT_PRECISION;
if (troot) {
virname = NULL;
ret = matcher_run(troot, buff, bytes, &virname, &tdata, offset, &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, bm_offmode ? &toff : NULL, &tpoff, ctx);
if (virname) {
/* virname already appended by matcher_run */
viruses_found = 1;
}
if ((ret == CL_VIRUS && !SCAN_ALLMATCHES) || ret == CL_EMEM) {
if (!ftonly) {
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
}
cli_ac_freedata(&tdata);
if (bm_offmode)
cli_bm_freeoff(&toff);
cli_pcre_freeoff(&tpoff);
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
}
}
if (!ftonly) {
virname = NULL;
ret = matcher_run(groot, buff, bytes, &virname, &gdata, offset, &info, ftype, ftoffset, acmode, PCRE_SCAN_FMAP, acres, ctx->fmap, NULL, &gpoff, ctx);
if (virname) {
/* virname already appended by matcher_run */
viruses_found = 1;
}
if ((ret == CL_VIRUS && !SCAN_ALLMATCHES) || ret == CL_EMEM) {
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
if (troot) {
cli_ac_freedata(&tdata);
if (bm_offmode)
cli_bm_freeoff(&toff);
cli_pcre_freeoff(&tpoff);
}
cli_targetinfo_destroy(&info);
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
return ret;
} else if ((acmode & AC_SCAN_FT) && ((cli_file_t)ret >= CL_TYPENO)) {
if (ret > type)
type = ret;
}
/* if (bytes <= (maxpatlen * (offset!=0))), it means the last window finished the file hashing *
* since the last window is responsible for adding intersection between windows (maxpatlen) */
if (hdb && (bytes > (maxpatlen * (offset != 0)))) {
const void *data = buff + maxpatlen * (offset != 0);
uint32_t data_len = bytes - maxpatlen * (offset != 0);
if (compute_hash[CLI_HASH_MD5])
cl_update_hash(md5ctx, (void *)data, data_len);
if (compute_hash[CLI_HASH_SHA1])
cl_update_hash(sha1ctx, (void *)data, data_len);
if (compute_hash[CLI_HASH_SHA256])
cl_update_hash(sha256ctx, (void *)data, data_len);
}
}
if (bytes < SCANBUFF)
break;
offset += bytes - maxpatlen;
}
if (!ftonly && hdb) {
enum CLI_HASH_TYPE hashtype, hashtype2;
if (compute_hash[CLI_HASH_MD5]) {
cl_finish_hash(md5ctx, digest[CLI_HASH_MD5]);
md5ctx = NULL;
}
if (refhash)
compute_hash[CLI_HASH_MD5] = 1;
if (compute_hash[CLI_HASH_SHA1]) {
cl_finish_hash(sha1ctx, digest[CLI_HASH_SHA1]);
sha1ctx = NULL;
}
if (compute_hash[CLI_HASH_SHA256]) {
cl_finish_hash(sha256ctx, digest[CLI_HASH_SHA256]);
sha256ctx = NULL;
}
virname = NULL;
for (hashtype = CLI_HASH_MD5; hashtype < CLI_HASH_AVAIL_TYPES; hashtype++) {
const char *virname_w = NULL;
int found = 0;
/* If no hash, skip to next type */
if (!compute_hash[hashtype])
continue;
/* Do hash scan */
if ((ret = cli_hm_scan(digest[hashtype], ctx->fmap->len, &virname, hdb, hashtype)) == CL_VIRUS) {
found += 1;
}
if (!found || SCAN_ALLMATCHES) {
if ((ret = cli_hm_scan_wild(digest[hashtype], &virname_w, hdb, hashtype)) == CL_VIRUS)
found += 2;
}
/* If found, do immediate hash-only FP check */
if (found && fp) {
for (hashtype2 = CLI_HASH_MD5; hashtype2 < CLI_HASH_AVAIL_TYPES; hashtype2++) {
if (!compute_hash[hashtype2])
continue;
if (cli_hm_scan(digest[hashtype2], ctx->fmap->len, NULL, fp, hashtype2) == CL_VIRUS) {
found = 0;
ret = CL_CLEAN;
break;
} else if (cli_hm_scan_wild(digest[hashtype2], NULL, fp, hashtype2) == CL_VIRUS) {
found = 0;
ret = CL_CLEAN;
break;
}
}
}
/* If matched size-based hash ... */
if (found % 2) {
viruses_found = 1;
ret = cli_append_virus(ctx, virname);
if (ret != CL_CLEAN && !SCAN_ALLMATCHES)
break;
virname = NULL;
}
/* If matched size-agnostic hash ... */
if (found > 1) {
viruses_found = 1;
ret = cli_append_virus(ctx, virname_w);
if (ret != CL_CLEAN && !SCAN_ALLMATCHES)
break;
}
}
}
cl_hash_destroy(md5ctx);
cl_hash_destroy(sha1ctx);
cl_hash_destroy(sha256ctx);
if (troot) {
if (ret != CL_VIRUS || SCAN_ALLMATCHES)
ret = cli_exp_eval(ctx, troot, &tdata, &info, (const char *)refhash);
if (ret == CL_VIRUS)
viruses_found++;
cli_ac_freedata(&tdata);
if (bm_offmode)
cli_bm_freeoff(&toff);
cli_pcre_freeoff(&tpoff);
}
if (groot) {
if (ret != CL_VIRUS || SCAN_ALLMATCHES)
ret = cli_exp_eval(ctx, groot, &gdata, &info, (const char *)refhash);
cli_ac_freedata(&gdata);
cli_pcre_freeoff(&gpoff);
}
cli_targetinfo_destroy(&info);
if (SCAN_ALLMATCHES && viruses_found) {
return CL_VIRUS;
}
if (ret == CL_VIRUS) {
return CL_VIRUS;
}
return (acmode & AC_SCAN_FT) ? type : CL_CLEAN;
}
#define CDBRANGE(field, val) \
if (field[0] != CLI_OFF_ANY) { \
if (field[0] == field[1] && field[0] != val) \
continue; \
else if (field[0] != field[1] && ((field[0] && field[0] > val) || \
(field[1] && field[1] < val))) \
continue; \
}
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)
{
const struct cli_cdb *cdb;
unsigned int viruses_found = 0;
cl_error_t ret = CL_CLEAN;
cli_dbgmsg("CDBNAME:%s:%llu:%s:%llu:%llu:%d:%u:%u:%p\n",
cli_ftname(cli_recursion_stack_get_type(ctx, -1)), (long long unsigned)fsizec, fname, (long long unsigned)fsizec, (long long unsigned)fsizer,
encrypted, filepos, res1, res2);
if (ctx->engine && ctx->engine->cb_meta)
if (ctx->engine->cb_meta(cli_ftname(cli_recursion_stack_get_type(ctx, -1)), fsizec, fname, fsizer, encrypted, filepos, ctx->cb_ctx) == CL_VIRUS) {
cli_dbgmsg("inner file blocked by callback: %s\n", fname);
ret = cli_append_virus(ctx, "Detected.By.Callback");
viruses_found++;
if (!SCAN_ALLMATCHES || ret != CL_CLEAN)
return ret;
}
if (!ctx->engine || !(cdb = ctx->engine->cdb))
return CL_CLEAN;
do {
if (cdb->ctype != CL_TYPE_ANY && cdb->ctype != cli_recursion_stack_get_type(ctx, -1))
continue;
if (cdb->encrypted != 2 && cdb->encrypted != encrypted)
continue;
if (cdb->res1 && (cdb->ctype == CL_TYPE_ZIP || cdb->ctype == CL_TYPE_RAR) && cdb->res1 != res1)
continue;
CDBRANGE(cdb->csize, cli_recursion_stack_get_size(ctx, -1));
CDBRANGE(cdb->fsizec, fsizec);
CDBRANGE(cdb->fsizer, fsizer);
CDBRANGE(cdb->filepos, filepos);
if (cdb->name.re_magic && (!fname || cli_regexec(&cdb->name, fname, 0, NULL, 0) == REG_NOMATCH))
continue;
ret = cli_append_virus(ctx, cdb->virname);
viruses_found++;
if (!SCAN_ALLMATCHES || ret != CL_CLEAN)
return ret;
} while ((cdb = cdb->next));
if (SCAN_ALLMATCHES && viruses_found)
return CL_VIRUS;
return CL_CLEAN;
}