From a77a271fb529e3b7cf34c5f9af0a2e3cea917daf Mon Sep 17 00:00:00 2001 From: "Val S." Date: Tue, 23 Sep 2025 15:57:28 -0400 Subject: [PATCH] Reduce unnecessary scanning of embedded file FPs (#1571) When embedded file type recognition finds a possible embedded file, it is being scanned as a new embedded file even if it turns out it was a false positive and parsing fails. My solution is to pre-parse the file headers as little possible to determine if it is valid. If possible, also determine the file size based on the headers. That will make it so we don't have to scan additional data when the embedded file is not at the very end. This commit adds header checks prior to embedded ZIP, ARJ, and CAB scanning. For these types I was also able to use the header checks to determine the object size so as to prevent excessive pattern matching. TODO: Add the same for RAR, EGG, 7Z, NULSFT, AUTOIT, IShield, and PDF. This commit also removes duplicate matching for embedded MSEXE. The embedded MSEXE detection and scanning logic was accidentally creating an extra duplicate layer in between scanning and detection because of the logic within the `cli_scanembpe()` function. That function was effectively doing the header check which this commit adds for ZIP, ARJ, and CAB but minus the size check. Note: It is unfortunately not possible to get an accurage size from PE file headers. The `cli_scanembpe()` function also used to dump to a temp file for no reason since FMAPs were extended to support windows into other FMAPs. So this commit removes the intermediate layer as well as dropping a temp file for each embedded PE file. Further, this commit adds configuration and DCONF safeguards around all embedded file type scanning. Finally, this commit adds a set of tests to validate proper extraction of embedded ZIP, ARJ, CAB, and MSEXE files. CLAM-2862 Co-authored-by: TheRaynMan --- libclamav/libmspack.c | 83 ++++- libclamav/libmspack.h | 28 +- libclamav/matcher.c | 18 +- libclamav/matcher.h | 2 +- libclamav/pe.c | 5 +- libclamav/scanners.c | 305 ++++++++---------- libclamav/unarj.c | 139 ++++++-- libclamav/unarj.h | 17 +- libclamav/unzip.c | 42 +++ libclamav/unzip.h | 12 + unit_tests/clamscan/embedded_files_test.py | 130 ++++++++ .../embedded_testfiles/clam.exe.emb-exes | Bin 0 -> 6688 bytes .../embedded_testfiles/emb/1/test-file-2.ref | Bin 0 -> 16 bytes .../embedded_testfiles/emb/1/test-file.ref | Bin 0 -> 16 bytes .../embedded_testfiles/emb/2/test-file-2.ref | Bin 0 -> 27 bytes .../embedded_testfiles/emb/2/test-file.ref | Bin 0 -> 24 bytes .../emb/smol_exe/Cargo.lock | 7 + .../emb/smol_exe/Cargo.toml | 13 + .../emb/smol_exe/src/main.rs | 37 +++ .../embedded_testfiles/signatures/1.1.hsb | 1 + .../embedded_testfiles/signatures/1.2.hsb | 1 + .../embedded_testfiles/signatures/2.1.hsb | 1 + .../embedded_testfiles/signatures/2.2.hsb | 1 + .../embedded_testfiles/signatures/lil.exe.ldb | 2 + .../signatures/smol.exe.ldb | 2 + .../embedded_testfiles/test.png.emb-arjs | Bin 0 -> 25959 bytes .../embedded_testfiles/test.png.emb-cabs | Bin 0 -> 25761 bytes .../embedded_testfiles/test.png.emb-zips | Bin 0 -> 26203 bytes 28 files changed, 618 insertions(+), 228 deletions(-) create mode 100644 unit_tests/clamscan/embedded_files_test.py create mode 100644 unit_tests/input/embedded_testfiles/clam.exe.emb-exes create mode 100755 unit_tests/input/embedded_testfiles/emb/1/test-file-2.ref create mode 100755 unit_tests/input/embedded_testfiles/emb/1/test-file.ref create mode 100755 unit_tests/input/embedded_testfiles/emb/2/test-file-2.ref create mode 100755 unit_tests/input/embedded_testfiles/emb/2/test-file.ref create mode 100755 unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.lock create mode 100755 unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.toml create mode 100755 unit_tests/input/embedded_testfiles/emb/smol_exe/src/main.rs create mode 100644 unit_tests/input/embedded_testfiles/signatures/1.1.hsb create mode 100644 unit_tests/input/embedded_testfiles/signatures/1.2.hsb create mode 100644 unit_tests/input/embedded_testfiles/signatures/2.1.hsb create mode 100644 unit_tests/input/embedded_testfiles/signatures/2.2.hsb create mode 100644 unit_tests/input/embedded_testfiles/signatures/lil.exe.ldb create mode 100644 unit_tests/input/embedded_testfiles/signatures/smol.exe.ldb create mode 100644 unit_tests/input/embedded_testfiles/test.png.emb-arjs create mode 100644 unit_tests/input/embedded_testfiles/test.png.emb-cabs create mode 100644 unit_tests/input/embedded_testfiles/test.png.emb-zips diff --git a/libclamav/libmspack.c b/libclamav/libmspack.c index a9da08ce4..a4c98ff4d 100644 --- a/libclamav/libmspack.c +++ b/libclamav/libmspack.c @@ -133,7 +133,7 @@ static void mspack_fmap_close(struct mspack_file *file) static int mspack_fmap_read(struct mspack_file *file, void *buffer, int bytes) { struct mspack_handle *mspack_handle = (struct mspack_handle *)file; - off_t offset; + size_t offset; size_t count; int ret; @@ -150,7 +150,7 @@ static int mspack_fmap_read(struct mspack_file *file, void *buffer, int bytes) /* Use fmap */ offset = mspack_handle->offset + mspack_handle->org; - count = fmap_readn(mspack_handle->fmap, buffer, (size_t)offset, (size_t)bytes); + count = fmap_readn(mspack_handle->fmap, buffer, offset, (size_t)bytes); if (count == (size_t)-1) { cli_dbgmsg("%s() %d requested %d bytes, read failed (-1)\n", __func__, __LINE__, bytes); return -1; @@ -163,7 +163,7 @@ static int mspack_fmap_read(struct mspack_file *file, void *buffer, int bytes) return (int)count; } else { /* Use file descriptor */ - count = fread(buffer, bytes, 1, mspack_handle->f); + count = fread(buffer, (size_t)bytes, 1, mspack_handle->f); if (count < 1) { cli_dbgmsg("%s() %d requested %d bytes, read failed (%zu)\n", __func__, __LINE__, bytes, count); return -1; @@ -340,18 +340,83 @@ static struct mspack_system mspack_sys_fmap_ops = { .copy = mspack_fmap_copy, }; -cl_error_t cli_scanmscab(cli_ctx *ctx, off_t sfx_offset) +cl_error_t cli_mscab_header_check(cli_ctx *ctx, size_t offset, size_t *size) +{ + cl_error_t status = CL_EFORMAT; + + struct mscab_decompressor *cab_d = NULL; + struct mscabd_cabinet *cab_h = NULL; + struct mspack_name mspack_fmap = {0}; + struct mspack_system_ex ops_ex = {0}; + + if (NULL == ctx || NULL == size) { + cli_dbgmsg("%s() invalid argument\n", __func__); + status = CL_EARG; + goto done; + } + + *size = 0; + mspack_fmap.fmap = ctx->fmap; + + if (offset > INT32_MAX) { + cli_dbgmsg("%s() offset too large %zu\n", __func__, offset); + status = CL_EFORMAT; + goto done; + } + + mspack_fmap.org = (off_t)offset; + + ops_ex.ops = mspack_sys_fmap_ops; + + cab_d = mspack_create_cab_decompressor(&ops_ex.ops); + if (NULL == cab_d) { + cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); + status = CL_EUNPACK; + goto done; + } + + cab_h = cab_d->open(cab_d, (char *)&mspack_fmap); + if (NULL == cab_h) { + cli_dbgmsg("%s() failed at %d\n", __func__, __LINE__); + status = CL_EFORMAT; + goto done; + } + + *size = (size_t)cab_h->length; + + cli_dbgmsg("%s(): Successfully read CAB header for CAB of size %zu\n", __func__, *size); + status = CL_SUCCESS; + +done: + if (NULL != cab_d) { + if (NULL != cab_h) { + cab_d->close(cab_d, cab_h); + } + mspack_destroy_cab_decompressor(cab_d); + } + + return status; +} + +cl_error_t cli_scanmscab(cli_ctx *ctx, size_t sfx_offset) { cl_error_t ret = CL_SUCCESS; struct mscab_decompressor *cab_d = NULL; struct mscabd_cabinet *cab_h = NULL; struct mscabd_file *cab_f = NULL; int files; - struct mspack_name mspack_fmap = { - .fmap = ctx->fmap, - .org = sfx_offset, - }; - struct mspack_system_ex ops_ex; + struct mspack_name mspack_fmap = {0}; + struct mspack_system_ex ops_ex = {0}; + + mspack_fmap.fmap = ctx->fmap; + + if (sfx_offset > INT32_MAX) { + cli_dbgmsg("%s() offset too large %zu\n", __func__, sfx_offset); + ret = CL_EFORMAT; + goto done; + } + + mspack_fmap.org = (off_t)sfx_offset; char *tmp_fname = NULL; bool tempfile_exists = false; diff --git a/libclamav/libmspack.h b/libclamav/libmspack.h index b8626ee4c..ee4654119 100644 --- a/libclamav/libmspack.h +++ b/libclamav/libmspack.h @@ -10,7 +10,31 @@ #ifndef __LIBMSPACK_H__ #define __LIBMSPACK_H__ -int cli_scanmscab(cli_ctx *ctx, off_t sfx_offset); -int cli_scanmschm(cli_ctx *ctx); +/** + * @brief Check the CAB header for validity. + * + * @param fmap The fmap containing the CAB file. + * @param offset Offset of the start of a CAB file within the current fmap. + * @param size The size of the CAB file. + * @return cl_error_t + */ +cl_error_t cli_mscab_header_check(cli_ctx *ctx, size_t offset, size_t *size); + +/** + * @brief Open and extract a Microsoft CAB file, scanning each extracted file. + * + * @param ctx Scan context + * @param sfx_offset Offset of the start of a CAB file within the current fmap. + * @return cl_error_t CL_SUCCESS on success, or an error code on failure. + */ +cl_error_t cli_scanmscab(cli_ctx *ctx, size_t sfx_offset); + +/** + * @brief Open and extract a Microsoft CHM file, scanning each extracted file. + * + * @param ctx Scan context + * @return cl_error_t CL_SUCCESS on success, or an error code on failure. + */ +cl_error_t cli_scanmschm(cli_ctx *ctx); #endif diff --git a/libclamav/matcher.c b/libclamav/matcher.c index 568d8c11e..0367e3358 100644 --- a/libclamav/matcher.c +++ b/libclamav/matcher.c @@ -602,7 +602,10 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) need_hash[CLI_HASH_SHA2_256] = cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA2_256, map->len) || cli_hm_have_wild(ctx->engine->hm_fp, CLI_HASH_SHA2_256) || - cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA2_256, 1); + cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA2_256, 1) || + // If debug logging is enabled, we want to calculate SHA256 hashes for all layers. + // Some users rely on the debug log output to create new FP signatures. + cli_debug_flag; /* Set fmap to need hash later if required. * This is an optimization so we can calculate all needed hashes in one pass. */ @@ -629,13 +632,14 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname) goto done; } - /* Convert hash to string */ - for (i = 0; i < hash_len; i++) { - sprintf(hash_string + i * 2, "%02x", hash[i]); - } - hash_string[hash_len * 2] = 0; + if (cli_debug_flag || + ((CLI_HASH_MD5 == hash_type) && (ctx->engine->cb_hash))) { + /* Convert hash to string */ + for (i = 0; i < hash_len; i++) { + sprintf(hash_string + i * 2, "%02x", hash[i]); + } + hash_string[hash_len * 2] = 0; - 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); diff --git a/libclamav/matcher.h b/libclamav/matcher.h index 7c4664762..32581f0de 100644 --- a/libclamav/matcher.h +++ b/libclamav/matcher.h @@ -360,7 +360,7 @@ cl_error_t cli_scan_fmap(cli_ctx *ctx, cli_file_t ftype, bool filetype_only, str */ cl_error_t cli_exp_eval(cli_ctx *ctx, struct cli_matcher *root, struct cli_ac_data *acdata, struct cli_target_info *target_info); -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); +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); /** * @brief Determine if an alert is a known false positive, using each fmap in the ctx->container stack to check MD5, SHA1, and SHA2-256 hashes. diff --git a/libclamav/pe.c b/libclamav/pe.c index e3699b0d1..b899c1b9b 100644 --- a/libclamav/pe.c +++ b/libclamav/pe.c @@ -4658,10 +4658,7 @@ cl_error_t cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, pe_add_heuristic_property(ctx, "BadNumberOfSections"); } - // TODO Investigate how corrupted_input is set and whether this - // check is needed - if (opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO && - !ctx->corrupted_input) { + if ((opts & CLI_PEHEADER_OPT_DBG_PRINT_INFO) && !ctx->corrupted_input) { if (peinfo->nsections == 0) { cli_dbgmsg("cli_peheader: Invalid NumberOfSections (0)\n"); } diff --git a/libclamav/scanners.c b/libclamav/scanners.c index 0106fe7fa..6750fd435 100644 --- a/libclamav/scanners.c +++ b/libclamav/scanners.c @@ -997,7 +997,7 @@ static cl_error_t cli_scanarj(cli_ctx *ctx) do { metadata.filename = NULL; - ret = cli_unarj_prepare_file(dir, &metadata); + ret = cli_unarj_prepare_file(&metadata); if (ret != CL_SUCCESS) { cli_dbgmsg("ARJ: cli_unarj_prepare_file Error: %s\n", cl_strerror(ret)); break; @@ -3447,94 +3447,6 @@ static cl_error_t cli_scan_structured(cli_ctx *ctx) return CL_SUCCESS; } -static cl_error_t cli_scanembpe(cli_ctx *ctx, off_t offset) -{ - cl_error_t ret = CL_SUCCESS; - int fd; - size_t bytes; - size_t size = 0; - size_t todo; - const char *buff; - char *tmpname; - fmap_t *map = ctx->fmap; - unsigned int corrupted_input; - - tmpname = cli_gentemp_with_prefix(ctx->this_layer_tmpdir, "embedded-pe"); - if (!tmpname) - return CL_EMEM; - - if ((fd = open(tmpname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { - cli_errmsg("cli_scanembpe: Can't create file %s\n", tmpname); - free(tmpname); - return CL_ECREAT; - } - - todo = map->len - offset; - while (1) { - bytes = MIN(todo, map->pgsz); - if (!bytes) - break; - - if (!(buff = fmap_need_off_once(map, offset + size, bytes))) { - close(fd); - if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - free(tmpname); - return CL_EUNLINK; - } - } - free(tmpname); - return CL_EREAD; - } - size += bytes; - todo -= bytes; - - if (cli_checklimits("cli_scanembpe", ctx, size, 0, 0) != CL_SUCCESS) - break; - - if (cli_writen(fd, buff, bytes) != bytes) { - cli_dbgmsg("cli_scanembpe: Can't write to temporary file\n"); - close(fd); - if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - free(tmpname); - return CL_EUNLINK; - } - } - free(tmpname); - return CL_EWRITE; - } - } - - // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. - corrupted_input = ctx->corrupted_input; - ctx->corrupted_input = 1; - ret = cli_magic_scan_desc(fd, tmpname, ctx, NULL, LAYER_ATTRIBUTES_NONE); - ctx->corrupted_input = corrupted_input; - if (ret != CL_SUCCESS) { - close(fd); - if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - free(tmpname); - return CL_EUNLINK; - } - } - free(tmpname); - return ret; - } - - close(fd); - if (!ctx->engine->keeptmp) { - if (cli_unlink(tmpname)) { - free(tmpname); - return CL_EUNLINK; - } - } - free(tmpname); - - return CL_SUCCESS; -} - #if defined(_WIN32) || defined(C_LINUX) || defined(C_DARWIN) #define PERF_MEASURE #endif @@ -3720,26 +3632,33 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi cli_file_t found_type; if ((typercg) && - // We should also omit bzips, but DMG's may be detected in bzips. (type != CL_TYPE_BZ) && /* Omit BZ files because they can contain portions of original files like zip file entries that cause invalid extractions and lots of warnings. Decompress first, then scan! */ - (type != CL_TYPE_GZ) && /* Omit GZ files because they can contain portions of original files like zip file entries that cause invalid extractions and lots of warnings. Decompress first, then scan! */ - (type != CL_TYPE_CPIO_OLD) && /* Omit CPIO_OLD files because it's an image format that we can extract and scan manually. */ - (type != CL_TYPE_ZIP) && /* Omit ZIP files because it'll detect each zip file entry as SFXZIP, which is a waste. We'll extract it and then scan. */ - (type != CL_TYPE_ZIPSFX) && /* Omit SFX archive types from being checked for embedded content. They should only be parsed for contained files. Those contained files could be EXE's with more SFX, but that's the nature of containers. */ - (type != CL_TYPE_ARJSFX) && /* " */ - (type != CL_TYPE_RARSFX) && /* " */ - (type != CL_TYPE_EGGSFX) && /* " */ - (type != CL_TYPE_CABSFX) && /* " */ - (type != CL_TYPE_7ZSFX) && /* " */ - (type != CL_TYPE_OOXML_WORD) && /* Omit OOXML because they are ZIP-based and file-type scanning will double-extract their contents. */ - (type != CL_TYPE_OOXML_PPT) && /* " */ - (type != CL_TYPE_OOXML_XL) && /* " */ - (type != CL_TYPE_OOXML_HWP) && /* " */ - (type != CL_TYPE_OLD_TAR) && /* Omit OLD TAR files because it's a raw archive format that we can extract and scan manually. */ - (type != CL_TYPE_POSIX_TAR)) { /* Omit POSIX TAR files because it's a raw archive format that we can extract and scan manually. */ + // Omit embedded files or file types already identified via this process. + (!(ctx->recursion_stack[ctx->recursion_level].attributes & LAYER_ATTRIBUTES_EMBEDDED)) && + // Omit GZ files because they can contain portions of original files like zip file entries that cause invalid extractions and lots of warnings. Decompress first, then scan! + (type != CL_TYPE_GZ) && + // We should also omit bzips, but DMG's may be detected in bzips. + //(type != CL_TYPE_BZ) && + // Omit CPIO_OLD files because it's an image format that we can extract and scan manually. + (type != CL_TYPE_CPIO_OLD) && + // Omit ZIP files because it'll detect each zip file entry as SFXZIP, which is a waste. We'll extract it and then scan. + (type != CL_TYPE_ZIP) && + // Omit OOXML because they are ZIP-based and file-type scanning will double-extract their contents. + (type != CL_TYPE_OOXML_WORD) && + (type != CL_TYPE_OOXML_PPT) && + (type != CL_TYPE_OOXML_XL) && + (type != CL_TYPE_OOXML_HWP) && + // Omit OLD TAR files because it's a raw archive format that we can extract and scan manually. + (type != CL_TYPE_OLD_TAR) && + // Omit POSIX TAR files because it's a raw archive format that we can extract and scan manually. + (type != CL_TYPE_POSIX_TAR)) { /* * Enable file type recognition scan mode if requested, except for some problematic types (above). */ acmode |= AC_SCAN_FT; + } else { + cli_dbgmsg("scanraw: embedded type recognition disabled or not applicable for type %s %s\n", + cli_ftname(type), + (ctx->recursion_stack[ctx->recursion_level].attributes & LAYER_ATTRIBUTES_EMBEDDED) ? "(embedded layer)" : ""); } perf_start(ctx, PERFT_RAW); @@ -3960,8 +3879,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi * This restriction will prevent detecting the same embedded content more than once when recursing with * embedded file type recognition deeper within the same buffer. * - * This is necessary because we have no way of knowing the length of a file and cannot prevent a search - * for embedded files from finding the same embedded content multiple times (like a LOT of times). + * This is necessary because we have no way of knowing the length of a file for many formats and cannot + * prevent a search for embedded files from finding the same embedded content multiple times (like a LOT + * of times). * * E.g. if the file is like this: * @@ -3990,7 +3910,7 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi * Decompressed: [ data ] [ embedded file ] * * So if this happened... then we WOULD want to scan the decompressed file for embedded files. - * The problem is, we have way of knowing how long embedded files are. + * The problem is, we have no way of knowing how long embedded files are. * We don't know if we have: * * A. [ data ] [ embedded file ] [ data ] [ embedded file ] @@ -4035,7 +3955,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi switch (fpt->type) { case CL_TYPE_RARSFX: - if (type != CL_TYPE_RAR) { + if ((have_rar && SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_RAR)) && + (type != CL_TYPE_RAR)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4048,7 +3970,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_EGGSFX: - if (type != CL_TYPE_EGG) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_EGG)) && + (type != CL_TYPE_EGG)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4061,11 +3985,26 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_ZIPSFX: - if (type != CL_TYPE_ZIP) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_ZIP)) && + (type != CL_TYPE_ZIP) && + /* OOXML are ZIP-based. */ + (type != CL_TYPE_OOXML_WORD) && + (type != CL_TYPE_OOXML_PPT) && + (type != CL_TYPE_OOXML_XL) && + (type != CL_TYPE_OOXML_HWP)) { + // Header validity check to prevent false positives from being scanned. + size_t zip_size = 0; + + ret = cli_unzip_single_header_check(ctx, fpt->offset, &zip_size); + if (ret != CL_SUCCESS) { + cli_dbgmsg("ZIP single header check failed: %s (%d)\n", cl_strerror(ret), ret); + break; + } + nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, - ctx->fmap->len - fpt->offset, + zip_size, ctx, CL_TYPE_ZIP, NULL, @@ -4074,11 +4013,20 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_CABSFX: - if (type != CL_TYPE_MSCAB) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_CAB)) && + (type != CL_TYPE_MSCAB)) { + // Header validity check to prevent false positives from being scanned. + size_t cab_size = 0; + ret = cli_mscab_header_check(ctx, fpt->offset, &cab_size); + if (ret != CL_SUCCESS) { + cli_dbgmsg("CAB header check failed: %s (%d)\n", cl_strerror(ret), ret); + break; + } + nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, - ctx->fmap->len - fpt->offset, + cab_size, ctx, CL_TYPE_MSCAB, NULL, @@ -4087,11 +4035,21 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_ARJSFX: - if (type != CL_TYPE_ARJ) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_ARJ)) && + (type != CL_TYPE_ARJ)) { + // Header validity check to prevent false positives from being scanned. + size_t arj_size = 0; + + ret = cli_unarj_header_check(ctx, fpt->offset, &arj_size); + if (ret != CL_SUCCESS) { + cli_dbgmsg("ARJ header check failed: %s (%d)\n", cl_strerror(ret), ret); + break; + } + nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, - ctx->fmap->len - fpt->offset, + arj_size, ctx, CL_TYPE_ARJ, NULL, @@ -4100,7 +4058,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_7ZSFX: - if (type != CL_TYPE_7Z) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_7Z)) && + (type != CL_TYPE_7Z)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4113,8 +4073,10 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_NULSFT: - if (type == CL_TYPE_MSEXE && fpt->offset > 4) { - // Note: CL_TYPE_NULSFT is special, because the file actually starts 4 bytes before the start of the signature match + // Note: CL_TYPE_NULSFT is special, because the file actually starts 4 bytes before the start of the signature match + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_NSIS)) && + (type == CL_TYPE_MSEXE && fpt->offset > 4)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset - 4, @@ -4127,7 +4089,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_AUTOIT: - if (type == CL_TYPE_MSEXE) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_AUTOIT)) && + (type == CL_TYPE_MSEXE)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4140,7 +4104,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_ISHIELD_MSI: - if (type == CL_TYPE_MSEXE) { + if ((SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_ISHIELD)) && + (type == CL_TYPE_MSEXE)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4153,7 +4119,9 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_PDF: - if (type != CL_TYPE_PDF) { + if ((SCAN_PARSE_PDF && (DCONF_DOC & DOC_CONF_PDF)) && + (type != CL_TYPE_PDF)) { + // TODO: Add header validity check to prevent false positives from being scanned. nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, @@ -4166,23 +4134,48 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi break; case CL_TYPE_MSEXE: - if (type == CL_TYPE_MSEXE || type == CL_TYPE_ZIP || type == CL_TYPE_MSOLE2) { - - cli_dbgmsg("*** Detected embedded PE file at %u ***\n", (unsigned int)fpt->offset); + if (SCAN_PARSE_PE && ctx->dconf->pe && + (type == CL_TYPE_MSEXE || type == CL_TYPE_ZIP || type == CL_TYPE_MSOLE2)) { + struct cli_exe_info peinfo; if ((uint64_t)(ctx->fmap->len - fpt->offset) > ctx->engine->maxembeddedpe) { cli_dbgmsg("scanraw: MaxEmbeddedPE exceeded\n"); break; } + cli_exe_info_init(&peinfo, 0); + + // Header validity check to prevent false positives from being scanned. + ret = cli_peheader(ctx->fmap, &peinfo, CLI_PEHEADER_OPT_NONE, NULL); + + // peinfo memory may have been allocated and must be freed even if it failed. + cli_exe_info_destroy(&peinfo); + + if (CL_SUCCESS != ret) { + cli_dbgmsg("Header check for MSEXE detection failed, probably not actually an embedded PE file.\n"); + break; + } + + cli_dbgmsg("*** Detected embedded PE file at %u ***\n", (unsigned int)fpt->offset); + + // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. + // In here we're just carrying the corrupted_input flag from parent to child, in case the parent's flag was set. + unsigned int corrupted_input = ctx->corrupted_input; + + ctx->corrupted_input = 1; + nret = cli_magic_scan_nested_fmap_type( ctx->fmap, fpt->offset, + // Sadly, there is no way from the PE header to determine the length of the PE file. + // So we just pass the remaining length of the fmap. ctx->fmap->len - fpt->offset, ctx, CL_TYPE_MSEXE, NULL, LAYER_ATTRIBUTES_EMBEDDED); + + ctx->corrupted_input = corrupted_input; } break; @@ -4789,6 +4782,8 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) * If self protection mechanism enabled, do the scanraw() scan first * before extracting with a file type parser. */ + cli_dbgmsg("cli_magic_scan: Performing raw scan to pattern match\n"); + ret = scanraw(ctx, type, 0, &dettype); // Evaluate the result from the scan to see if it end the scan of this layer early, @@ -5252,6 +5247,8 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) (type != CL_TYPE_HTML || !(SCAN_PARSE_HTML) || !(DCONF_DOC & DOC_CONF_HTML_SKIPRAW)) && (!ctx->engine->sdb)) { + cli_dbgmsg("cli_magic_scan: Performing raw scan to pattern match and/or detect embedded files\n"); + ret = scanraw(ctx, type, typercg, &dettype); // Evaluate the result from the scan to see if it end the scan of this layer early, @@ -5290,56 +5287,11 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) case CL_TYPE_MSEXE: perf_nested_start(ctx, PERFT_PE, PERFT_SCAN); if (SCAN_PARSE_PE && ctx->dconf->pe) { - if (ctx->recursion_stack[ctx->recursion_level].attributes & LAYER_ATTRIBUTES_EMBEDDED) { - /* - * Embedded PE files are PE files that were found within another file using file-type scanning in scanraw() - * They are parsed differently than normal PE files. - */ - struct cli_exe_info peinfo; - - cli_exe_info_init(&peinfo, 0); - - // TODO We could probably substitute in a quicker - // method of determining whether a PE file exists - // at this offset. - if (cli_peheader(ctx->fmap, &peinfo, CLI_PEHEADER_OPT_NONE, NULL) != 0) { - cli_dbgmsg("Header check for MSEXE detection failed, probably not actually an embedded PE file.\n"); - - /* Despite failing, peinfo memory may have been allocated and must be freed. */ - cli_exe_info_destroy(&peinfo); - - } else { - /* Immediately free up peinfo allocated memory, prior to any recursion */ - cli_exe_info_destroy(&peinfo); - - ret = cli_scanembpe(ctx, 0); - - // TODO This method of embedded PE extraction - // is kinda gross in that: - // - if you have an executable that contains - // 20 other exes, the bytes associated with - // the last exe will have been included in - // hash computations and things 20 times - // (as overlay data to the previously - // extracted exes). - // - if you have a signed embedded exe, it - // will fail to validate after extraction - // bc it has overlay data, which is a - // violation of the Authenticode spec. - // - this method of extraction is subject to - // the recursion limit, which is fairly low. - // - // It'd be awesome if we could compute the PE - // size from the PE header and just extract - // that. - } - } else { - // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. - // In here we're just carrying the corrupted_input flag from parent to child, in case the parent's flag was set. - unsigned int corrupted_input = ctx->corrupted_input; - ret = cli_scanpe(ctx); - ctx->corrupted_input = corrupted_input; - } + // Setting ctx->corrupted_input will prevent the PE parser from reporting "broken executable" for unpacked/reconstructed files that may not be 100% to spec. + // In here we're just carrying the corrupted_input flag from parent to child, in case the parent's flag was set. + unsigned int corrupted_input = ctx->corrupted_input; + ret = cli_scanpe(ctx); + ctx->corrupted_input = corrupted_input; } perf_nested_stop(ctx, PERFT_PE, PERFT_SCAN); break; @@ -5364,8 +5316,9 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) break; case CL_TYPE_PDF: /* FIXMELIMITS: pdf should be an archive! */ - if (SCAN_PARSE_PDF && (DCONF_DOC & DOC_CONF_PDF)) + if (SCAN_PARSE_PDF && (DCONF_DOC & DOC_CONF_PDF)) { ret = cli_scanpdf(ctx, 0); + } break; default: diff --git a/libclamav/unarj.c b/libclamav/unarj.c index cd8968de7..c5ad3fa54 100644 --- a/libclamav/unarj.c +++ b/libclamav/unarj.c @@ -56,10 +56,6 @@ #define CHAR_BIT (8) #endif #define MAXMATCH 256 -#ifndef FALSE -#define FALSE (0) -#define TRUE (1) -#endif #define CODE_BIT 16 #define NT (CODE_BIT + 3) @@ -814,23 +810,25 @@ static cl_error_t arj_unstore(arj_metadata_t *metadata, int ofd, uint32_t len) return CL_SUCCESS; } -static int is_arj_archive(arj_metadata_t *metadata) +static bool is_arj_archive(arj_metadata_t *metadata) { const char header_id[2] = {0x60, 0xea}; const char *mark; mark = fmap_need_off_once(metadata->map, metadata->offset, 2); - if (!mark) - return FALSE; + if (!mark) { + cli_dbgmsg("is_arj_archive: Failed to read the two-byte ARJ header ID at offset %zu\n", metadata->offset); + return false; + } metadata->offset += 2; if (memcmp(&mark[0], &header_id[0], 2) == 0) { - return TRUE; + return true; } - cli_dbgmsg("Not an ARJ archive\n"); - return FALSE; + cli_dbgmsg("is_arj_archive: The two-byte ARJ header ID did not match; This is not an ARJ archive\n"); + return false; } -static int arj_read_main_header(arj_metadata_t *metadata) +static bool arj_read_main_header(arj_metadata_t *metadata) { uint16_t header_size, count; arj_main_hdr_t main_hdr; @@ -839,7 +837,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) struct text_norm_state fnstate, comstate; unsigned char *fnnorm = NULL; unsigned char *comnorm = NULL; - uint32_t ret = TRUE; + bool ret = true; size_t filename_max_len = 0; size_t filename_len = 0; @@ -848,28 +846,28 @@ static int arj_read_main_header(arj_metadata_t *metadata) size_t orig_offset = metadata->offset; if (fmap_readn(metadata->map, &header_size, metadata->offset, 2) != 2) - return FALSE; + return false; metadata->offset += 2; header_size = le16_to_host(header_size); cli_dbgmsg("Header Size: %d\n", header_size); if (header_size == 0) { /* End of archive */ - ret = FALSE; + ret = false; goto done; } if (header_size > HEADERSIZE_MAX) { cli_dbgmsg("arj_read_header: invalid header_size: %u\n", header_size); - ret = FALSE; + ret = false; goto done; } if ((header_size + sizeof(header_size)) > (metadata->map->len - metadata->offset)) { cli_dbgmsg("arj_read_header: invalid header_size: %u, exceeds length of file.\n", header_size); - ret = FALSE; + ret = false; goto done; } if (fmap_readn(metadata->map, &main_hdr, metadata->offset, 30) != 30) { - ret = FALSE; + ret = false; goto done; } metadata->offset += 30; @@ -885,7 +883,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) if (main_hdr.first_hdr_size < 30) { cli_dbgmsg("Format error. First Header Size < 30\n"); - ret = FALSE; + ret = false; goto done; } if (main_hdr.first_hdr_size > 30) { @@ -895,7 +893,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) filename_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset); if (filename_max_len > header_size) { cli_dbgmsg("UNARJ: Format error. First Header Size invalid\n"); - ret = FALSE; + ret = false; goto done; } if (filename_max_len > 0) { @@ -903,7 +901,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) filename = fmap_need_offstr(metadata->map, metadata->offset, filename_max_len + 1); if (!filename || !fnnorm) { cli_dbgmsg("UNARJ: Unable to allocate memory for filename\n"); - ret = FALSE; + ret = false; goto done; } filename_len = CLI_STRNLEN(filename, filename_max_len); @@ -913,7 +911,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) comment_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset); if (comment_max_len > header_size) { cli_dbgmsg("UNARJ: Format error. First Header Size invalid\n"); - ret = FALSE; + ret = false; goto done; } if (comment_max_len > 0) { @@ -921,7 +919,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) comment = fmap_need_offstr(metadata->map, metadata->offset, comment_max_len + 1); if (!comment || !comnorm) { cli_dbgmsg("UNARJ: Unable to allocate memory for comment\n"); - ret = FALSE; + ret = false; goto done; } comment_len = CLI_STRNLEN(comment, comment_max_len); @@ -942,7 +940,7 @@ static int arj_read_main_header(arj_metadata_t *metadata) for (;;) { const uint16_t *countp = fmap_need_off_once(metadata->map, metadata->offset, 2); if (!countp) { - ret = FALSE; + ret = false; goto done; } count = cli_readint16(countp); @@ -1118,7 +1116,7 @@ static cl_error_t arj_read_file_header(arj_metadata_t *metadata) metadata->comp_size = file_hdr.comp_size; metadata->orig_size = file_hdr.orig_size; metadata->method = file_hdr.method; - metadata->encrypted = ((file_hdr.flags & GARBLE_FLAG) != 0) ? TRUE : FALSE; + metadata->encrypted = ((file_hdr.flags & GARBLE_FLAG) != 0) ? true : false; metadata->ofd = -1; if (!metadata->filename) { ret = CL_EMEM; @@ -1146,27 +1144,112 @@ cl_error_t cli_unarj_open(fmap_t *map, const char *dirname, arj_metadata_t *meta metadata->map = map; metadata->offset = 0; if (!is_arj_archive(metadata)) { - cli_dbgmsg("Not in ARJ format\n"); + cli_dbgmsg("cli_unarj_open: is_arj_archive check failed\n"); return CL_EFORMAT; } if (!arj_read_main_header(metadata)) { - cli_dbgmsg("Failed to read main header\n"); + cli_dbgmsg("cli_unarj_open: Failed to read main header\n"); return CL_EFORMAT; } return CL_SUCCESS; } -cl_error_t cli_unarj_prepare_file(const char *dirname, arj_metadata_t *metadata) +cl_error_t cli_unarj_header_check( + cli_ctx *ctx, + uint32_t offset, + size_t *size) +{ + cl_error_t status = CL_EFORMAT; + bool bool_ret; + cl_error_t ret; + arj_metadata_t metadata = {0}; + int files_found = 0; + + cli_dbgmsg("in cli_unarj_header_check\n"); + + if (!ctx || !ctx->fmap || !size) { + status = CL_ENULLARG; + goto done; + } + + metadata.map = ctx->fmap; + metadata.offset = offset; + *size = 0; + + bool_ret = is_arj_archive(&metadata); + if (false == bool_ret) { + cli_dbgmsg("Not in ARJ format\n"); + status = CL_EFORMAT; + goto done; + } + + cli_dbgmsg("cli_unarj_header_check: is_arj_archive-check passed\n"); + + bool_ret = arj_read_main_header(&metadata); + if (false == bool_ret) { + cli_dbgmsg("Failed to read main header\n"); + status = CL_EFORMAT; + goto done; + } + + cli_dbgmsg("cli_unarj_header_check: Successfully read main header\n"); + + do { + metadata.filename = NULL; + metadata.comp_size = 0; + metadata.orig_size = 0; + + ret = cli_unarj_prepare_file(&metadata); + if (ret == CL_SUCCESS) { + cli_dbgmsg("cli_unarj_header_check: Successfully read file header\n"); + files_found++; + + /* Skip the file data */ + metadata.offset += metadata.comp_size; + + } else if (ret == CL_BREAK) { + cli_dbgmsg("cli_unarj_header_check: End of archive\n"); + status = CL_BREAK; + + } else { + cli_dbgmsg("cli_unarj_header_check: Error reading file header: %s\n", cl_strerror(ret)); + status = ret; + } + + CLI_FREE_AND_SET_NULL(metadata.filename); + } while (ret == CL_SUCCESS); + + if (files_found > 0) { + /* Successfully found at least one file */ + status = CL_SUCCESS; + *size = metadata.offset - offset; + cli_dbgmsg("cli_unarj_header_check: Successfully found %d files in valid ARJ archive of %zu bytes\n", files_found, *size); + } else { + status = CL_EFORMAT; + cli_dbgmsg("cli_unarj_header_check: No files found; Invalid ARJ archive\n"); + } + +done: + CLI_FREE_AND_SET_NULL(metadata.filename); + + return status; +} + +cl_error_t cli_unarj_prepare_file(arj_metadata_t *metadata) { cli_dbgmsg("in cli_unarj_prepare_file\n"); - if (!metadata || !dirname) { + + if (NULL == metadata) { + cli_dbgmsg("cli_unarj_prepare_file: invalid NULL arguments\n"); return CL_ENULLARG; } + /* Each file is preceded by the ARJ file marker */ if (!is_arj_archive(metadata)) { cli_dbgmsg("Not in ARJ format\n"); return CL_EFORMAT; } + return arj_read_file_header(metadata); } diff --git a/libclamav/unarj.h b/libclamav/unarj.h index 18e6df245..d2bbe2a96 100644 --- a/libclamav/unarj.h +++ b/libclamav/unarj.h @@ -24,7 +24,10 @@ #ifndef __UNARJ_H #define __UNARJ_H +#include "clamav.h" +#include "others.h" #include "fmap.h" + typedef struct arj_metadata_tag { char *filename; uint32_t comp_size; @@ -36,8 +39,20 @@ typedef struct arj_metadata_tag { size_t offset; } arj_metadata_t; +/** + * @brief Verify ARJ file header and get size of ARJ based on headers. + * + * Does not extract or scan the file. + * + * @param[in,out] ctx Scan context + * @param offset Offset of the file header + * @param[out] size Will be set to the size of the file header + file data. + * @return cl_error_t CL_SUCCESS on success, or an error code on failure. + */ +cl_error_t cli_unarj_header_check(cli_ctx *ctx, uint32_t offset, size_t *size); + cl_error_t cli_unarj_open(fmap_t *map, const char *dirname, arj_metadata_t *metadata); -cl_error_t cli_unarj_prepare_file(const char *dirname, arj_metadata_t *metadata); +cl_error_t cli_unarj_prepare_file(arj_metadata_t *metadata); cl_error_t cli_unarj_extract_file(const char *dirname, arj_metadata_t *metadata); #endif diff --git a/libclamav/unzip.c b/libclamav/unzip.c index a9f7242f9..c1fb87055 100644 --- a/libclamav/unzip.c +++ b/libclamav/unzip.c @@ -825,6 +825,48 @@ done: return status; } +cl_error_t cli_unzip_single_header_check( + cli_ctx *ctx, + uint32_t offset, + size_t *size) +{ + cl_error_t status = CL_ERROR; + struct zip_record file_record = {0}; + cl_error_t ret; + + ret = parse_local_file_header( + ctx, + offset, + NULL, /* num_files_unzipped */ + 0, /* file_count */ + NULL, /* central_header */ + NULL, /* tmpd */ + false, /* detect_encrypted */ + NULL, /* zcb */ + &file_record, + size); + if (ret != CL_SUCCESS) { + cli_dbgmsg("cli_unzip: single header check - failed to parse local file header: %s (%d)\n", cl_strerror(ret), ret); + status = ret; + goto done; + } + + if (file_record.compressed_size == 0 || file_record.uncompressed_size == 0) { + cli_dbgmsg("cli_unzip: single header check - empty file\n"); + status = CL_EFORMAT; + goto done; + } + + status = CL_SUCCESS; + +done: + if (file_record.original_filename) { + free(file_record.original_filename); + } + + return status; +} + /** * @brief Parse, extract, and scan a file by iterating the central directory. * diff --git a/libclamav/unzip.h b/libclamav/unzip.h index 96eca4988..9bf4e35cd 100644 --- a/libclamav/unzip.h +++ b/libclamav/unzip.h @@ -119,6 +119,18 @@ cl_error_t cli_unzip(cli_ctx *ctx); */ cl_error_t cli_unzip_single(cli_ctx *ctx, size_t local_header_offset); +/** + * @brief Verify a single local file header. + * + * Does not extract or scan the file. + * + * @param[in,out] ctx Scan context + * @param offset Offset of the local file header + * @param[out] size Will be set to the size of the file header + file data. + * @return cl_error_t CL_SUCCESS on success, or an error code on failure. + */ +cl_error_t cli_unzip_single_header_check(cli_ctx *ctx, uint32_t offset, size_t *size); + /** * @brief Unzip a single file from a zip archive. * diff --git a/unit_tests/clamscan/embedded_files_test.py b/unit_tests/clamscan/embedded_files_test.py new file mode 100644 index 000000000..28f77be69 --- /dev/null +++ b/unit_tests/clamscan/embedded_files_test.py @@ -0,0 +1,130 @@ +# Copyright (C) 2020-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + +""" +Run clamscan tests. +""" + +import sys +from zipfile import ZIP_DEFLATED, ZipFile + +sys.path.append('../unit_tests') +import testcase + + +class TC(testcase.TestCase): + @classmethod + def setUpClass(cls): + super(TC, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TC, cls).tearDownClass() + + def setUp(self): + super(TC, self).setUp() + + def tearDown(self): + super(TC, self).tearDown() + self.verify_valgrind_log() + + def test_embedded_zips(self): + self.step_name('Test that clamav can successfully extract and alert on multiple embedded ZIP files') + + path_db = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'signatures' + testfiles = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'test.png.emb-zips' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'test.png.emb-zips: test-file-1-1.UNOFFICIAL FOUND', + 'test.png.emb-zips: test-file-1-2.UNOFFICIAL FOUND', + 'test.png.emb-zips: test-file-2-1.UNOFFICIAL FOUND', + 'test.png.emb-zips: test-file-2-2.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'OK', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) + + def test_embedded_arjs(self): + self.step_name('Test that clamav can successfully extract and alert on multiple embedded ARJ files') + + path_db = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'signatures' + testfiles = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'test.png.emb-arjs' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'test.png.emb-arjs: test-file-1-1.UNOFFICIAL FOUND', + 'test.png.emb-arjs: test-file-1-2.UNOFFICIAL FOUND', + 'test.png.emb-arjs: test-file-2-1.UNOFFICIAL FOUND', + 'test.png.emb-arjs: test-file-2-2.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'OK', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) + + def test_embedded_cabs(self): + self.step_name('Test that clamav can successfully extract and alert on multiple embedded CAB files') + + path_db = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'signatures' + testfiles = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'test.png.emb-cabs' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'test.png.emb-cabs: test-file-1-1.UNOFFICIAL FOUND', + 'test.png.emb-cabs: test-file-1-2.UNOFFICIAL FOUND', + 'test.png.emb-cabs: test-file-2-1.UNOFFICIAL FOUND', + 'test.png.emb-cabs: test-file-2-2.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'OK', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) + + def test_embedded_exes(self): + self.step_name('Test that clamav can successfully extract and alert on multiple embedded EXE files') + + path_db = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'signatures' + testfiles = TC.path_source / 'unit_tests' / 'input' / 'embedded_testfiles' / 'clam.exe.emb-exes' + + command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfiles} --gen-json --debug --allmatch'.format( + valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan, + path_db=path_db, + testfiles=testfiles, + ) + output = self.execute_command(command) + + assert output.ec == 1 # no virus, no failures + + expected_stdout = [ + 'clam.exe.emb-exes: Win.Test.LilEXE.UNOFFICIAL FOUND', + 'clam.exe.emb-exes: Win.Test.SmolEXE.UNOFFICIAL FOUND', + ] + unexpected_stdout = [ + 'OK', + ] + self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout) diff --git a/unit_tests/input/embedded_testfiles/clam.exe.emb-exes b/unit_tests/input/embedded_testfiles/clam.exe.emb-exes new file mode 100644 index 0000000000000000000000000000000000000000..53cccc42c168389a0b88f6da97a9275281d7102c GIT binary patch literal 6688 zcmeHKT}V_>5T3h=l>bmsN#t2Ds)yZNt)Qo_KN~?;UEKaK0{2h7u)BBLz1rrJiu6OU z2GvtRuRav?;DeczLC}kW&r%>kVH9NPixF%ych6aOw-t#%GVieGoSARtoI7XExpQY* zI$U4@fI_H%WD;N;c`G&zFcQ^_CV(mPp@j%l_8V!XtCjmD$gjLU|wun?rdy2NSc-INMvD zN`0NZSy2F-aaDIkHD4eWgL7_YJI9-ph8Bc8-GN3e-T)u@S|^cQP5?!cz|-Vu6l=~y zVz5NA?8Tq}6s1s^=3tWz5qpv`a2NuE3ZOW}FdS$e5~`8Vknxe`Ao0QAhfx?XxQ*>D zQA`@nUpAZ}hcfn(;mK4>#~mm@fVL#=2kB+%)}dL{msd}e1D*Z2gol5&M~$&aRO^m< zLd@q0hc%sf11#DbW@?x60In?&6n*B3_RG)_ zj`#QX`@P+WQ(mV%^EK;`g~@EJp0HWODE)G~ql8h+>6^f@kRLIFJDalDy$Vtpi;^hJWXb1=C literal 0 HcmV?d00001 diff --git a/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.lock b/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.lock new file mode 100755 index 000000000..8f66ef6d7 --- /dev/null +++ b/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "t1" +version = "0.1.0" diff --git a/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.toml b/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.toml new file mode 100755 index 000000000..db3f4fe78 --- /dev/null +++ b/unit_tests/input/embedded_testfiles/emb/smol_exe/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "t1" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[profile.release] +strip = true +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" diff --git a/unit_tests/input/embedded_testfiles/emb/smol_exe/src/main.rs b/unit_tests/input/embedded_testfiles/emb/smol_exe/src/main.rs new file mode 100755 index 000000000..e9547e65b --- /dev/null +++ b/unit_tests/input/embedded_testfiles/emb/smol_exe/src/main.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use core::arch::asm; + +/// +/// This is basically a Windows port of the example from: +/// With one minor change to print a message instead of only exiting with a code. +/// Thank you to the author. +/// +/// Build with: +/// ```powershell +/// $env:RUSTFLAGS="-Ctarget-cpu=native -Clink-args=/ENTRY:_start -Clink-args=/SUBSYSTEM:CONSOLE -Clink-args=/LARGEADDRESSAWARE:NO -Clink-args=ucrt.lib -Crelocation-model=static -Clink-args=-Wl,-n,-N,--no-dynamic-linker,--no-pie,--build-id=none,--no-eh-frame-hdr" +/// cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" --target x86_64-pc-windows-msvc --release +/// ``` +/// + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + let s = b"Lil EXE\n\0"; + unsafe { + asm!( + "mov rcx, {0}", + "call puts", + in(reg) s.as_ptr(), + options(nostack, noreturn) + ) + // nostack prevents `asm!` from push/pop rax + // noreturn prevents it putting a 'ret' at the end + // but it does put a ud2 (undefined instruction) instead + } +} + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/unit_tests/input/embedded_testfiles/signatures/1.1.hsb b/unit_tests/input/embedded_testfiles/signatures/1.1.hsb new file mode 100644 index 000000000..49396d4de --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/1.1.hsb @@ -0,0 +1 @@ +579de681add9f8c686fa791c49d1222a63c236febff37769b5fb50659b007491:16:test-file-1-1 diff --git a/unit_tests/input/embedded_testfiles/signatures/1.2.hsb b/unit_tests/input/embedded_testfiles/signatures/1.2.hsb new file mode 100644 index 000000000..5c77dd648 --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/1.2.hsb @@ -0,0 +1 @@ +b56f04ceb6cadbc0f50f9acfadbedc81257a6af21f6212ef57a70a599fc8bf38:16:test-file-1-2 diff --git a/unit_tests/input/embedded_testfiles/signatures/2.1.hsb b/unit_tests/input/embedded_testfiles/signatures/2.1.hsb new file mode 100644 index 000000000..1e024befe --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/2.1.hsb @@ -0,0 +1 @@ +1cce5c6d7f11469ffa6153481b7d6275534ce7c62bc34f12f7d742c5e6cf026b:24:test-file-2-1 diff --git a/unit_tests/input/embedded_testfiles/signatures/2.2.hsb b/unit_tests/input/embedded_testfiles/signatures/2.2.hsb new file mode 100644 index 000000000..f6b3592ff --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/2.2.hsb @@ -0,0 +1 @@ +6d1c6cae0a30435b52d362544bea666492d06173ded04504bf30f369abfadd50:27:test-file-2-2 diff --git a/unit_tests/input/embedded_testfiles/signatures/lil.exe.ldb b/unit_tests/input/embedded_testfiles/signatures/lil.exe.ldb new file mode 100644 index 000000000..fcc2b116c --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/lil.exe.ldb @@ -0,0 +1,2 @@ +# Match on "Lil EXE\n" +Win.Test.LilEXE;Engine:90-255,Target:1;0;1552:4c696c204558450a diff --git a/unit_tests/input/embedded_testfiles/signatures/smol.exe.ldb b/unit_tests/input/embedded_testfiles/signatures/smol.exe.ldb new file mode 100644 index 000000000..24e1aeb84 --- /dev/null +++ b/unit_tests/input/embedded_testfiles/signatures/smol.exe.ldb @@ -0,0 +1,2 @@ +# Match on "Smol EXE\n" +Win.Test.SmolEXE;Engine:90-255,Target:1;0;1552:536d6f6c204558450a diff --git a/unit_tests/input/embedded_testfiles/test.png.emb-arjs b/unit_tests/input/embedded_testfiles/test.png.emb-arjs new file mode 100644 index 0000000000000000000000000000000000000000..ca6b00ef05a2ed36ac0027e9b841e8ceb8145f3a GIT binary patch literal 25959 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 literal 0 HcmV?d00001 diff --git a/unit_tests/input/embedded_testfiles/test.png.emb-cabs b/unit_tests/input/embedded_testfiles/test.png.emb-cabs new file mode 100644 index 0000000000000000000000000000000000000000..fa8d912258635cc1fcbf8c8bb0875bae01ca8505 GIT binary patch literal 25761 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 GF#ZcEFY+A# literal 0 HcmV?d00001 diff --git a/unit_tests/input/embedded_testfiles/test.png.emb-zips b/unit_tests/input/embedded_testfiles/test.png.emb-zips new file mode 100644 index 0000000000000000000000000000000000000000..f3540f5e0d63baa57bfa8ab6905ec9758c5cb5a0 GIT binary patch literal 26203 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 literal 0 HcmV?d00001