Browse code

Heuristic macro detection for imp VBA extraction

Notably the commit adds a heuristic alert when VBA is extracted using
the new VBA extraction code and similarly adds "HasMacros":true to the
JSON scan properties.

In addition, a change was added to the cli_sanitize_filepath() function
so it converts posix pathseps to Windows pathseps on Windows and also
outputs a sanitized basename pointer (optional) which is used when
generating a temporary filename so that using a prefix with pathseps in
it won't cause file creation failures (observed with --leave-temps where
original filenames are incorporated into temporarily filenames).

Included soem error handling improvements for cli_vba_scandir() to
better track alert and macro detections.

Downgraded utf8 conversion error messages to debug messages because they
are too verbose in files with invalid filenames (observed in some
malware).

Changed the xlm macro and vba project temp filenames to include
"xlm_macros" and "vba_project" prefix, to make it easier to find them.

Relocated XLM and VBA temp files from the top-level tmp directory to the
current sub_tmpdir, so tempfiles for a given scan are more organized.

Micah Snyder authored on 2020/08/08 15:48:20
Showing 6 changed files
... ...
@@ -793,11 +793,16 @@ const char *cli_gettmpdir(void);
793 793
 /**
794 794
  * @brief Sanitize a relative path, so it cannot have a negative depth.
795 795
  *
796
- * Caller is responsible for freeing the filename.
797
- *
798
- * @return char* filename or NULL.
796
+ * Caller is responsible for freeing the sanitized filepath.
797
+ * The optioal sanitized_filebase output param is a pointer into the filepath,
798
+ * if set, and does not need to be freed.
799
+ *
800
+ * @param filepath                  The filepath to sanitize
801
+ * @param filepath_len              The length of the filepath
802
+ * @param[out] sanitized_filebase   Pointer to the basename portion of the sanitized filepath. (optional)
803
+ * @return char*
799 804
  */
800
-char *cli_sanitize_filepath(const char *filepath, size_t filepath_len);
805
+char *cli_sanitize_filepath(const char *filepath, size_t filepath_len, char **sanitized_filebase);
801 806
 
802 807
 /**
803 808
  * @brief Generate tempfile filename (no path) with a random MD5 hash.
... ...
@@ -55,6 +55,7 @@
55 55
 
56 56
 #include "clamav.h"
57 57
 #include "others.h"
58
+#include "str.h"
58 59
 #include "platform.h"
59 60
 #include "regex/regex.h"
60 61
 #include "ltdl.h"
... ...
@@ -849,7 +850,7 @@ unsigned int cli_rndnum(unsigned int max)
849 849
     return 1 + (unsigned int)(max * (rand() / (1.0 + RAND_MAX)));
850 850
 }
851 851
 
852
-char *cli_sanitize_filepath(const char *filepath, size_t filepath_len)
852
+char *cli_sanitize_filepath(const char *filepath, size_t filepath_len, char **sanitized_filebase)
853 853
 {
854 854
     uint32_t depth           = 0;
855 855
     size_t index             = 0;
... ...
@@ -900,9 +901,9 @@ char *cli_sanitize_filepath(const char *filepath, size_t filepath_len)
900 900
             }
901 901
 #ifdef _WIN32
902 902
             /*
903
-         * Windows' POSIX style API's accept both "/" and "\\" style path separators.
904
-         * The following checks using POSIX style path separators on Windows.
905
-         */
903
+             * Windows' POSIX style API's accept both "/" and "\\" style path separators.
904
+             * The following checks using POSIX style path separators on Windows.
905
+             */
906 906
         } else if (0 == strncmp(filepath + index, "/", strlen("/"))) {
907 907
             /*
908 908
              * Is "/".
... ...
@@ -931,17 +932,39 @@ char *cli_sanitize_filepath(const char *filepath, size_t filepath_len)
931 931
                 sanitized_index += strlen("../");
932 932
                 index += strlen("../");
933 933
                 depth--;
934
+
935
+                /* Convert path separator to Windows separator */
936
+                sanitized_filepath[sanitized_index - 1] = '\\';
934 937
             }
935 938
 #endif
936 939
         } else {
937 940
             /*
938 941
              * Is not "/", "./", or "../".
939 942
              */
943
+
940 944
             /* Find the next path separator. */
941
-            next_pathsep = CLI_STRNSTR(filepath + index, PATHSEP, filepath_len - index);
945
+#ifdef _WIN32
946
+            char *next_windows_pathsep = NULL;
947
+#endif
948
+            next_pathsep = CLI_STRNSTR(filepath + index, "/", filepath_len - index);
949
+
950
+#ifdef _WIN32
951
+            /* Check for both types of separators. */
952
+            next_windows_pathsep = CLI_STRNSTR(filepath + index, "\\", filepath_len - index);
953
+            if (NULL != next_windows_pathsep) {
954
+                if ((NULL == next_pathsep) || (next_windows_pathsep < next_pathsep)) {
955
+                    next_pathsep = next_windows_pathsep;
956
+                }
957
+            }
958
+#endif
942 959
             if (NULL == next_pathsep) {
943 960
                 /* No more path separators, copy the rest (filename) into the sanitized path */
944 961
                 strncpy(sanitized_filepath + sanitized_index, filepath + index, filepath_len - index);
962
+
963
+                if (NULL != sanitized_filebase) {
964
+                    /* Set output variable to point to the file base name */
965
+                    *sanitized_filebase = sanitized_filepath + sanitized_index;
966
+                }
945 967
                 break;
946 968
             }
947 969
             next_pathsep += strlen(PATHSEP); /* Include the path separator in the copy */
... ...
@@ -951,6 +974,11 @@ char *cli_sanitize_filepath(const char *filepath, size_t filepath_len)
951 951
             sanitized_index += next_pathsep - (filepath + index);
952 952
             index += next_pathsep - (filepath + index);
953 953
             depth++;
954
+
955
+#ifdef _WIN32
956
+            /* Convert path separator to Windows separator */
957
+            sanitized_filepath[sanitized_index - 1] = '\\';
958
+#endif
954 959
         }
955 960
     }
956 961
 
... ...
@@ -966,16 +994,19 @@ done:
966 966
 #define SHORT_HASH_LENGTH 10
967 967
 char *cli_genfname(const char *prefix)
968 968
 {
969
-    char *sanitized_prefix = NULL;
970
-    char *fname            = NULL;
969
+    char *sanitized_prefix      = NULL;
970
+    char *sanitized_prefix_base = NULL;
971
+    char *fname                 = NULL;
971 972
     unsigned char salt[16 + 32];
972 973
     char *tmp;
973 974
     int i;
974 975
     size_t len;
975 976
 
976 977
     if (prefix && (strlen(prefix) > 0)) {
977
-        sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix));
978
-        len              = strlen(sanitized_prefix) + strlen(".") + SHORT_HASH_LENGTH + 1; /* {prefix}.{SHORT_HASH_LENGTH}\0 */
978
+        sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix), &sanitized_prefix_base);
979
+    }
980
+    if (NULL != sanitized_prefix_base) {
981
+        len = strlen(sanitized_prefix_base) + strlen(".") + SHORT_HASH_LENGTH + 1; /* {prefix}.{SHORT_HASH_LENGTH}\0 */
979 982
     } else {
980 983
         len = strlen("clamav-") + 48 + strlen(".tmp") + 1; /* clamav-{48}.tmp\0 */
981 984
     }
... ...
@@ -1001,21 +1032,21 @@ char *cli_genfname(const char *prefix)
1001 1001
     pthread_mutex_unlock(&cli_gentemp_mutex);
1002 1002
 #endif
1003 1003
 
1004
-    if (!tmp) {
1004
+    if (NULL == tmp) {
1005 1005
         free(fname);
1006 1006
         cli_dbgmsg("cli_genfname: out of memory\n");
1007 1007
         return NULL;
1008 1008
     }
1009 1009
 
1010
-    if (sanitized_prefix) {
1011
-        if (strlen(sanitized_prefix) > 0) {
1012
-            snprintf(fname, len, "%s.%.*s", sanitized_prefix, SHORT_HASH_LENGTH, tmp);
1013
-        }
1014
-        free(sanitized_prefix);
1010
+    if (NULL != sanitized_prefix_base) {
1011
+        snprintf(fname, len, "%s.%.*s", sanitized_prefix_base, SHORT_HASH_LENGTH, tmp);
1015 1012
     } else {
1016 1013
         snprintf(fname, len, "clamav-%s.tmp", tmp);
1017 1014
     }
1018 1015
 
1016
+    if (NULL != sanitized_prefix) {
1017
+        free(sanitized_prefix);
1018
+    }
1019 1019
     free(tmp);
1020 1020
 
1021 1021
     return (fname);
... ...
@@ -1567,14 +1567,15 @@ cl_error_t find_file(const char *filename, const char *dir, char *result, size_t
1567 1567
  * Scan an OLE directory for a VBA project.
1568 1568
  * Contrary to cli_vba_scandir, this function uses the dir file to locate VBA modules.
1569 1569
  */
1570
-static cl_error_t cli_vba_scandir_new(const char *dirname, cli_ctx *ctx, struct uniq *U)
1570
+static cl_error_t cli_vba_scandir_new(const char *dirname, cli_ctx *ctx, struct uniq *U, int *has_macros)
1571 1571
 {
1572 1572
     cl_error_t ret   = CL_SUCCESS;
1573 1573
     uint32_t hashcnt = 0;
1574 1574
     char *hash       = NULL;
1575 1575
     char path[PATH_MAX];
1576 1576
     char filename[PATH_MAX];
1577
-    int tempfd = -1;
1577
+    int tempfd        = -1;
1578
+    int viruses_found = 0;
1578 1579
 
1579 1580
     if (CL_SUCCESS != (ret = uniq_get(U, "dir", 3, &hash, &hashcnt))) {
1580 1581
         cli_dbgmsg("cli_vba_scandir_new: uniq_get('dir') failed with ret code (%d)!\n", ret);
... ...
@@ -1591,7 +1592,7 @@ static cl_error_t cli_vba_scandir_new(const char *dirname, cli_ctx *ctx, struct
1591 1591
 
1592 1592
         if (CL_SUCCESS == find_file(filename, dirname, path, sizeof(path))) {
1593 1593
             cli_dbgmsg("cli_vba_scandir_new: Found dir file: %s\n", path);
1594
-            if ((ret = cli_vba_readdir_new(ctx, path, U, hash, hashcnt, &tempfd)) != CL_SUCCESS) {
1594
+            if ((ret = cli_vba_readdir_new(ctx, path, U, hash, hashcnt, &tempfd, has_macros)) != CL_SUCCESS) {
1595 1595
                 //FIXME: Since we only know the stream name of the OLE2 stream, but not its path inside the
1596 1596
                 //       OLE2 archive, we don't know if we have the right file. The only thing we can do is
1597 1597
                 //       iterate all of them until one succeeds.
... ...
@@ -1601,6 +1602,30 @@ static cl_error_t cli_vba_scandir_new(const char *dirname, cli_ctx *ctx, struct
1601 1601
                 continue;
1602 1602
             }
1603 1603
 
1604
+#if HAVE_JSON
1605
+            if (*has_macros && SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) {
1606
+                cli_jsonbool(ctx->wrkproperty, "HasMacros", 1);
1607
+                json_object *macro_languages = cli_jsonarray(ctx->wrkproperty, "MacroLanguages");
1608
+                if (macro_languages) {
1609
+                    cli_jsonstr(macro_languages, NULL, "VBA");
1610
+                } else {
1611
+                    cli_dbgmsg("[cli_vba_scandir_new] Failed to add \"VBA\" entry to MacroLanguages JSON array\n");
1612
+                }
1613
+            }
1614
+#endif
1615
+            if (SCAN_HEURISTIC_MACROS && *has_macros) {
1616
+                ret = cli_append_virus(ctx, "Heuristics.OLE2.ContainsMacros");
1617
+                if (ret == CL_VIRUS) {
1618
+                    viruses_found++;
1619
+                    if (!SCAN_ALLMATCHES) {
1620
+                        goto done;
1621
+                    }
1622
+                }
1623
+            }
1624
+
1625
+            /*
1626
+             * Now rewind the extracted vba-project output FD and scan it!
1627
+             */
1604 1628
             if (lseek(tempfd, 0, SEEK_SET) != 0) {
1605 1629
                 cli_dbgmsg("cli_vba_scandir_new: Failed to seek to beginning of temporary VBA project file\n");
1606 1630
                 ret = CL_ESEEK;
... ...
@@ -1610,15 +1635,18 @@ static cl_error_t cli_vba_scandir_new(const char *dirname, cli_ctx *ctx, struct
1610 1610
             ctx->recursion += 1;
1611 1611
             cli_set_container(ctx, CL_TYPE_MSOLE2, 0); //TODO: set correct container size
1612 1612
 
1613
-            if (cli_scan_desc(tempfd, ctx, CL_TYPE_SCRIPT, 0, NULL, AC_SCAN_VIR, NULL, NULL) == CL_VIRUS) {
1614
-                ctx->recursion -= 1;
1615
-                ret = CL_VIRUS;
1616
-                goto done;
1617
-            }
1613
+            ret = cli_scan_desc(tempfd, ctx, CL_TYPE_SCRIPT, 0, NULL, AC_SCAN_VIR, NULL, NULL);
1618 1614
 
1619 1615
             close(tempfd);
1620 1616
             tempfd = -1;
1621 1617
             ctx->recursion -= 1;
1618
+
1619
+            if (CL_VIRUS == ret) {
1620
+                viruses_found++;
1621
+                if (!SCAN_ALLMATCHES) {
1622
+                    goto done;
1623
+                }
1624
+            }
1622 1625
         }
1623 1626
 
1624 1627
         hashcnt--;
... ...
@@ -1629,16 +1657,20 @@ done:
1629 1629
         close(tempfd);
1630 1630
         tempfd = -1;
1631 1631
     }
1632
+
1633
+    if (viruses_found > 0)
1634
+        ret = CL_VIRUS;
1632 1635
     return ret;
1633 1636
 }
1634 1637
 
1635
-static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq *U, int *hasmacros)
1638
+static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq *U, int *has_macros)
1636 1639
 {
1637
-    cl_error_t ret = CL_CLEAN;
1640
+    cl_error_t status = CL_CLEAN;
1641
+    cl_error_t ret;
1638 1642
     int i, j, fd;
1639 1643
     size_t data_len;
1640 1644
     vba_project_t *vba_project;
1641
-    DIR *dd;
1645
+    DIR *dd = NULL;
1642 1646
     struct dirent *dent;
1643 1647
     STATBUF statbuf;
1644 1648
     char *fullname, vbaname[1024];
... ...
@@ -1650,7 +1682,8 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1650 1650
     cli_dbgmsg("VBADir: %s\n", dirname);
1651 1651
     if (CL_SUCCESS != (ret = uniq_get(U, "_vba_project", 12, NULL, &hashcnt))) {
1652 1652
         cli_dbgmsg("VBADir: uniq_get('_vba_project') failed with ret code (%d)!\n", ret);
1653
-        return ret;
1653
+        status = ret;
1654
+        goto done;
1654 1655
     }
1655 1656
     while (hashcnt) {
1656 1657
         if (!(vba_project = (vba_project_t *)cli_vba_readdir(dirname, U, hashcnt))) {
... ...
@@ -1669,7 +1702,7 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1669 1669
                 cli_dbgmsg("VBADir: Decompress VBA project '%s_%u'\n", vba_project->name[i], j);
1670 1670
                 data = (unsigned char *)cli_vba_inflate(fd, vba_project->offset[i], &data_len);
1671 1671
                 close(fd);
1672
-                *hasmacros = *hasmacros + 1;
1672
+                *has_macros = *has_macros + 1;
1673 1673
                 if (!data) {
1674 1674
                 } else {
1675 1675
                     /* cli_dbgmsg("Project content:\n%s", data); */
... ...
@@ -1681,13 +1714,15 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1681 1681
 
1682 1682
                         if ((ret = cli_gentempfd(ctx->sub_tmpdir, &tempfile, &of)) != CL_SUCCESS) {
1683 1683
                             cli_warnmsg("VBADir: WARNING: VBA project '%s_%u' cannot be dumped to file\n", vba_project->name[i], j);
1684
-                            return ret;
1684
+                            status = ret;
1685
+                            goto done;
1685 1686
                         }
1686 1687
                         if (cli_writen(of, data, data_len) != data_len) {
1687 1688
                             cli_warnmsg("VBADir: WARNING: VBA project '%s_%u' failed to write to file\n", vba_project->name[i], j);
1688 1689
                             close(of);
1689 1690
                             free(tempfile);
1690
-                            return CL_EWRITE;
1691
+                            status = CL_EWRITE;
1692
+                            goto done;
1691 1693
                         }
1692 1694
 
1693 1695
                         cli_dbgmsg("VBADir: VBA project '%s_%u' dumped to %s\n", vba_project->name[i], j, tempfile);
... ...
@@ -1698,7 +1733,7 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1698 1698
                         viruses_found++;
1699 1699
                         if (!SCAN_ALLMATCHES) {
1700 1700
                             free(data);
1701
-                            ret = CL_VIRUS;
1701
+                            status = CL_VIRUS;
1702 1702
                             break;
1703 1703
                         }
1704 1704
                     }
... ...
@@ -1706,23 +1741,24 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1706 1706
                 }
1707 1707
             }
1708 1708
 
1709
-            if (ret == CL_VIRUS && !SCAN_ALLMATCHES)
1709
+            if (status == CL_VIRUS)
1710 1710
                 break;
1711 1711
         }
1712 1712
 
1713 1713
         cli_free_vba_project(vba_project);
1714 1714
         vba_project = NULL;
1715 1715
 
1716
-        if (ret == CL_VIRUS && !SCAN_ALLMATCHES)
1716
+        if (status == CL_VIRUS)
1717 1717
             break;
1718 1718
 
1719 1719
         hashcnt--;
1720 1720
     }
1721 1721
 
1722
-    if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) {
1722
+    if (status == CL_CLEAN || (status == CL_VIRUS && SCAN_ALLMATCHES)) {
1723 1723
         if (CL_SUCCESS != (ret = uniq_get(U, "powerpoint document", 19, &hash, &hashcnt))) {
1724 1724
             cli_dbgmsg("VBADir: uniq_get('powerpoint document') failed with ret code (%d)!\n", ret);
1725
-            return ret;
1725
+            status = ret;
1726
+            goto done;
1726 1727
         }
1727 1728
         while (hashcnt) {
1728 1729
             snprintf(vbaname, 1024, "%s" PATHSEP "%s_%u", dirname, hash, hashcnt);
... ...
@@ -1734,7 +1770,7 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1734 1734
             }
1735 1735
             if ((fullname = cli_ppt_vba_read(fd, ctx))) {
1736 1736
                 if (cli_magic_scan_dir(fullname, ctx) == CL_VIRUS) {
1737
-                    ret = CL_VIRUS;
1737
+                    status = CL_VIRUS;
1738 1738
                     viruses_found++;
1739 1739
                 }
1740 1740
                 if (!ctx->engine->keeptmp)
... ...
@@ -1746,10 +1782,11 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1746 1746
         }
1747 1747
     }
1748 1748
 
1749
-    if (ret == CL_CLEAN || (ret == CL_VIRUS && SCAN_ALLMATCHES)) {
1749
+    if (status == CL_CLEAN || (status == CL_VIRUS && SCAN_ALLMATCHES)) {
1750 1750
         if (CL_SUCCESS != (ret = uniq_get(U, "worddocument", 12, &hash, &hashcnt))) {
1751 1751
             cli_dbgmsg("VBADir: uniq_get('worddocument') failed with ret code (%d)!\n", ret);
1752
-            return ret;
1752
+            status = ret;
1753
+            goto done;
1753 1754
         }
1754 1755
         while (hashcnt) {
1755 1756
             snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dirname, hash, hashcnt);
... ...
@@ -1779,7 +1816,7 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1779 1779
                         viruses_found++;
1780 1780
                         if (!SCAN_ALLMATCHES) {
1781 1781
                             free(data);
1782
-                            ret = CL_VIRUS;
1782
+                            status = CL_VIRUS;
1783 1783
                             break;
1784 1784
                         }
1785 1785
                     }
... ...
@@ -1791,24 +1828,20 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1791 1791
             cli_free_vba_project(vba_project);
1792 1792
             vba_project = NULL;
1793 1793
 
1794
-            if (ret == CL_VIRUS) {
1795
-                viruses_found++;
1796
-                if (!SCAN_ALLMATCHES)
1797
-                    break;
1794
+            if (status == CL_VIRUS && !SCAN_ALLMATCHES) {
1795
+                break;
1798 1796
             }
1799 1797
             hashcnt--;
1800 1798
         }
1801 1799
     }
1802 1800
 
1803
-    if (ret != CL_CLEAN && !(ret == CL_VIRUS && SCAN_ALLMATCHES))
1804
-        return ret;
1805
-
1806 1801
 #if HAVE_JSON
1807 1802
     /* JSON Output Summary Information */
1808 1803
     if (SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) {
1809 1804
         if (CL_SUCCESS != (ret = uniq_get(U, "_5_summaryinformation", 21, &hash, &hashcnt))) {
1810 1805
             cli_dbgmsg("VBADir: uniq_get('_5_summaryinformation') failed with ret code (%d)!\n", ret);
1811
-            return ret;
1806
+            status = ret;
1807
+            goto done;
1812 1808
         }
1813 1809
         while (hashcnt) {
1814 1810
             snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dirname, hash, hashcnt);
... ...
@@ -1826,7 +1859,8 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1826 1826
 
1827 1827
         if (CL_SUCCESS != (ret = uniq_get(U, "_5_documentsummaryinformation", 29, &hash, &hashcnt))) {
1828 1828
             cli_dbgmsg("VBADir: uniq_get('_5_documentsummaryinformation') failed with ret code (%d)!\n", ret);
1829
-            return ret;
1829
+            status = ret;
1830
+            goto done;
1830 1831
         }
1831 1832
         while (hashcnt) {
1832 1833
             snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dirname, hash, hashcnt);
... ...
@@ -1844,10 +1878,15 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1844 1844
     }
1845 1845
 #endif
1846 1846
 
1847
+    if (status != CL_CLEAN && !(status == CL_VIRUS && SCAN_ALLMATCHES)) {
1848
+        goto done;
1849
+    }
1850
+
1847 1851
     /* Check directory for embedded OLE objects */
1848 1852
     if (CL_SUCCESS != (ret = uniq_get(U, "_1_ole10native", 14, &hash, &hashcnt))) {
1849 1853
         cli_dbgmsg("VBADir: uniq_get('_1_ole10native') failed with ret code (%d)!\n", ret);
1850
-        return ret;
1854
+        status = ret;
1855
+        goto done;
1851 1856
     }
1852 1857
     while (hashcnt) {
1853 1858
         snprintf(vbaname, sizeof(vbaname), "%s" PATHSEP "%s_%u", dirname, hash, hashcnt);
... ...
@@ -1857,8 +1896,13 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1857 1857
         if (fd >= 0) {
1858 1858
             ret = cli_scan_ole10(fd, ctx);
1859 1859
             close(fd);
1860
-            if (ret != CL_CLEAN && !(ret == CL_VIRUS && SCAN_ALLMATCHES))
1861
-                return ret;
1860
+            if (CL_VIRUS == ret) {
1861
+                viruses_found++;
1862
+                if (!SCAN_ALLMATCHES) {
1863
+                    status = ret;
1864
+                    goto done;
1865
+                }
1866
+            }
1862 1867
         }
1863 1868
         hashcnt--;
1864 1869
     }
... ...
@@ -1883,7 +1927,7 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1883 1883
                     /* stat the file */
1884 1884
                     if (LSTAT(fullname, &statbuf) != -1) {
1885 1885
                         if (S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode))
1886
-                            if (cli_vba_scandir(fullname, ctx, U, hasmacros) == CL_VIRUS) {
1886
+                            if (cli_vba_scandir(fullname, ctx, U, has_macros) == CL_VIRUS) {
1887 1887
                                 viruses_found++;
1888 1888
                                 if (!SCAN_ALLMATCHES) {
1889 1889
                                     ret = CL_VIRUS;
... ...
@@ -1898,12 +1942,17 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1898 1898
         }
1899 1899
     } else {
1900 1900
         cli_dbgmsg("VBADir: Can't open directory %s.\n", dirname);
1901
-        return CL_EOPEN;
1901
+        status = CL_EOPEN;
1902
+        goto done;
1903
+    }
1904
+
1905
+done:
1906
+    if (NULL != dd) {
1907
+        closedir(dd);
1902 1908
     }
1903 1909
 
1904
-    closedir(dd);
1905 1910
 #if HAVE_JSON
1906
-    if (*hasmacros && SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) {
1911
+    if (*has_macros && SCAN_COLLECT_METADATA && (ctx->wrkproperty != NULL)) {
1907 1912
         cli_jsonbool(ctx->wrkproperty, "HasMacros", 1);
1908 1913
         json_object *macro_languages = cli_jsonarray(ctx->wrkproperty, "MacroLanguages");
1909 1914
         if (macro_languages) {
... ...
@@ -1913,14 +1962,16 @@ static cl_error_t cli_vba_scandir(const char *dirname, cli_ctx *ctx, struct uniq
1913 1913
         }
1914 1914
     }
1915 1915
 #endif
1916
-    if (SCAN_HEURISTIC_MACROS && *hasmacros) {
1916
+    if (SCAN_HEURISTIC_MACROS && *has_macros) {
1917 1917
         ret = cli_append_virus(ctx, "Heuristics.OLE2.ContainsMacros");
1918 1918
         if (ret == CL_VIRUS)
1919 1919
             viruses_found++;
1920 1920
     }
1921
-    if (SCAN_ALLMATCHES && viruses_found)
1922
-        return CL_VIRUS;
1923
-    return ret;
1921
+
1922
+    if (SCAN_ALLMATCHES && viruses_found) {
1923
+        status = CL_VIRUS;
1924
+    }
1925
+    return status;
1924 1926
 }
1925 1927
 
1926 1928
 static cl_error_t cli_xlm_scandir(const char *dirname, cli_ctx *ctx, struct uniq *U)
... ...
@@ -2341,7 +2392,6 @@ static cl_error_t cli_scanole2(cli_ctx *ctx)
2341 2341
     if (CL_VIRUS == ret) {
2342 2342
         viruses_found++;
2343 2343
         if (!SCAN_ALLMATCHES) {
2344
-            ctx->recursion--;
2345 2344
             goto done;
2346 2345
         }
2347 2346
     }
... ...
@@ -2358,7 +2408,7 @@ static cl_error_t cli_scanole2(cli_ctx *ctx)
2358 2358
             }
2359 2359
         }
2360 2360
 
2361
-        ret = cli_vba_scandir_new(dir, ctx, files);
2361
+        ret = cli_vba_scandir_new(dir, ctx, files, &has_macros);
2362 2362
         if (CL_VIRUS == ret) {
2363 2363
             viruses_found++;
2364 2364
             if (!SCAN_ALLMATCHES) {
... ...
@@ -2367,9 +2417,6 @@ static cl_error_t cli_scanole2(cli_ctx *ctx)
2367 2367
             }
2368 2368
         }
2369 2369
 
2370
-        if (cli_magic_scan_dir(dir, ctx) == CL_VIRUS)
2371
-            ret = CL_VIRUS;
2372
-
2373 2370
         ctx->recursion--;
2374 2371
     }
2375 2372
 
... ...
@@ -2392,14 +2439,16 @@ static cl_error_t cli_scanole2(cli_ctx *ctx)
2392 2392
             }
2393 2393
         }
2394 2394
 
2395
-        if (cli_magic_scan_dir(dir, ctx) == CL_VIRUS)
2396
-            ret = CL_VIRUS;
2397
-
2398 2395
         ctx->recursion--;
2399 2396
     }
2400 2397
 
2401
-    if (viruses_found > 0) {
2402
-        ret = CL_VIRUS;
2398
+    if ((has_xlm || has_vba) && files) {
2399
+        if (CL_VIRUS == cli_magic_scan_dir(dir, ctx)) {
2400
+            viruses_found++;
2401
+            if (!SCAN_ALLMATCHES) {
2402
+                goto done;
2403
+            }
2404
+        }
2403 2405
     }
2404 2406
 
2405 2407
 done:
... ...
@@ -2413,6 +2462,10 @@ done:
2413 2413
         free(dir);
2414 2414
     }
2415 2415
 
2416
+    if (viruses_found > 0) {
2417
+        ret = CL_VIRUS;
2418
+    }
2419
+
2416 2420
     return ret;
2417 2421
 }
2418 2422
 
... ...
@@ -357,8 +357,7 @@ static size_t vba_normalize(unsigned char *buffer, size_t size)
357 357
  * Read a VBA project in an OLE directory.
358 358
  * Contrary to cli_vba_readdir, this function uses the dir file to locate VBA modules.
359 359
  */
360
-cl_error_t
361
-cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *hash, uint32_t which, int *tempfd)
360
+cl_error_t cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *hash, uint32_t which, int *tempfd, int *has_macros)
362 361
 {
363 362
     cl_error_t ret = CL_SUCCESS;
364 363
     char fullname[1024];
... ...
@@ -375,7 +374,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
375 375
     unsigned char *module_data = NULL, *module_data_utf8 = NULL;
376 376
     size_t module_data_size = 0, module_data_utf8_size = 0;
377 377
 
378
-    if (dir == NULL || hash == NULL || tempfd == NULL) {
378
+    if (dir == NULL || hash == NULL || tempfd == NULL || has_macros == NULL) {
379 379
         return CL_EARG;
380 380
     }
381 381
 
... ...
@@ -396,7 +395,9 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
396 396
         goto done;
397 397
     }
398 398
 
399
-    if ((ret = cli_gentempfd(ctx->engine->tmpdir, &tempfile, tempfd)) != CL_SUCCESS) {
399
+    *has_macros = *has_macros + 1;
400
+
401
+    if ((ret = cli_gentempfd_with_prefix(ctx->sub_tmpdir, "vba_project", &tempfile, tempfd)) != CL_SUCCESS) {
400 402
         cli_warnmsg("vba_readdir_new: VBA project cannot be dumped to file\n");
401 403
         goto done;
402 404
     }
... ...
@@ -418,7 +419,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
418 418
         for (i = 0; i < size; ++i) {                                                       \
419 419
             char buf[4];                                                                   \
420 420
             if (snprintf(buf, sizeof(buf), "%02x", (msg)[i]) != 2) {                       \
421
-                cli_warnmsg("vba_readdir_new: Failed to write nex data to output file\n"); \
421
+                cli_warnmsg("vba_readdir_new: Failed to write hex data to output file\n"); \
422 422
                 ret = CL_EWRITE;                                                           \
423 423
                 goto done;                                                                 \
424 424
             }                                                                              \
... ...
@@ -436,7 +437,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
436 436
                 free(utf8);                                                                                          \
437 437
                 utf8 = NULL;                                                                                         \
438 438
             } else {                                                                                                 \
439
-                cli_errmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);      \
439
+                cli_dbgmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);      \
440 440
                 CLI_WRITEN("<error decoding string>", 23);                                                           \
441 441
             }                                                                                                        \
442 442
         }                                                                                                            \
... ...
@@ -452,7 +453,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
452 452
                 free(utf8);                                                                                                   \
453 453
                 utf8 = NULL;                                                                                                  \
454 454
             } else {                                                                                                          \
455
-                cli_errmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");                                      \
455
+                cli_dbgmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");                                      \
456 456
                 CLI_WRITEN("<error decoding string>", 23);                                                                    \
457 457
             }                                                                                                                 \
458 458
         }                                                                                                                     \
... ...
@@ -779,7 +780,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
779 779
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], size, codepage, &mbcs_name, &mbcs_name_size)) {
780 780
                         CLI_WRITEN(mbcs_name, mbcs_name_size);
781 781
                     } else {
782
-                        cli_errmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
782
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
783 783
                         CLI_WRITEN("<error decoding string>", 23);
784 784
                     }
785 785
                 }
... ...
@@ -813,7 +814,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
813 813
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], size, CODEPAGE_UTF16_LE, &utf16_name, &utf16_name_size)) {
814 814
                         CLI_WRITEN(utf16_name, utf16_name_size);
815 815
                     } else {
816
-                        cli_errmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
816
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
817 817
                         CLI_WRITEN("<error decoding string>", 23);
818 818
                     }
819 819
                 }
... ...
@@ -862,7 +863,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
862 862
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], size, codepage, &mbcs_name, &mbcs_name_size)) {
863 863
                         CLI_WRITEN(mbcs_name, mbcs_name_size);
864 864
                     } else {
865
-                        cli_errmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
865
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
866 866
                         CLI_WRITEN("<error decoding string>", 23);
867 867
                     }
868 868
                 }
... ...
@@ -896,7 +897,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
896 896
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], module_stream_name_size, CODEPAGE_UTF16_LE, &utf16_name, &utf16_name_size)) {
897 897
                         CLI_WRITEN(utf16_name, utf16_name_size);
898 898
                     } else {
899
-                        cli_errmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
899
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
900 900
                         CLI_WRITEN("<error decoding string>", 23);
901 901
                     }
902 902
                 }
... ...
@@ -945,7 +946,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
945 945
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], size, codepage, &mbcs_name, &mbcs_name_size)) {
946 946
                         CLI_WRITEN(mbcs_name, mbcs_name_size);
947 947
                     } else {
948
-                        cli_errmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
948
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert codepage %" PRIu16 " to UTF-8\n", codepage);
949 949
                         CLI_WRITEN("<error decoding string>", 23);
950 950
                     }
951 951
                 }
... ...
@@ -978,7 +979,7 @@ cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *h
978 978
                     if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_offset], size, CODEPAGE_UTF16_LE, &utf16_name, &utf16_name_size)) {
979 979
                         CLI_WRITEN(utf16_name, utf16_name_size);
980 980
                     } else {
981
-                        cli_errmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
981
+                        cli_dbgmsg("cli_vba_readdir_new: failed to convert UTF16LE to UTF-8\n");
982 982
                         CLI_WRITEN("<error decoding string>", 23);
983 983
                     }
984 984
                 }
... ...
@@ -40,7 +40,7 @@ typedef struct vba_project_tag {
40 40
 } vba_project_t;
41 41
 
42 42
 vba_project_t *cli_vba_readdir(const char *dir, struct uniq *U, uint32_t which);
43
-cl_error_t cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *hash, uint32_t which, int *tempfd);
43
+cl_error_t cli_vba_readdir_new(cli_ctx *ctx, const char *dir, struct uniq *U, const char *hash, uint32_t which, int *tempfd, int *has_macros);
44 44
 vba_project_t *cli_wm_readdir(int fd);
45 45
 void cli_free_vba_project(vba_project_t *vba_project);
46 46
 
... ...
@@ -4116,7 +4116,7 @@ cl_error_t cli_xlm_extract_macros(const char *dir, cli_ctx *ctx, struct uniq *U,
4116 4116
         goto done;
4117 4117
     }
4118 4118
 
4119
-    if ((ret = cli_gentempfd(dir, &tempfile, &out_fd)) != CL_SUCCESS) {
4119
+    if ((ret = cli_gentempfd_with_prefix(ctx->sub_tmpdir, "xlm_macros", &tempfile, &out_fd)) != CL_SUCCESS) {
4120 4120
         cli_dbgmsg("[cli_xlm_extract_macros] Failed to open output file descriptor\n");
4121 4121
         goto done;
4122 4122
     }