Browse code

Add support for cert blacklisting and whitelisting upfront

Instead of checking the Authenticode header as an FP prevention
mechanism, we now check it in the beginning if it exists. Also,
we can now do actual blacklisting with .crb rules (previously, a
blacklist rule just let you override a whitelist rule).

Andrew authored on 2019/02/13 05:10:04
Showing 13 changed files
... ...
@@ -1365,7 +1365,7 @@ static int asn1_parse_countersignature(fmap_t *map, const void **asn1data, unsig
1365 1365
     return 1;
1366 1366
 }
1367 1367
 
1368
-static cl_error_t asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmgr *cmgr, int embedded, const void **hashes, unsigned int *hashes_size, struct cl_engine *engine)
1368
+static cl_error_t asn1_parse_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, crtmgr *cmgr, int embedded, const void **hashes, unsigned int *hashes_size, char **certname)
1369 1369
 {
1370 1370
     struct cli_asn1 asn1, deep, deeper;
1371 1371
     uint8_t issuer[SHA1_HASH_SIZE], serial[SHA1_HASH_SIZE];
... ...
@@ -1555,6 +1555,11 @@ static cl_error_t asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size
1555 1555
                             sprintf(&serial[j * 2], "%02x", x509->serial[j]);
1556 1556
                         }
1557 1557
 
1558
+                        // TODO The raw information we print out here isn't
1559
+                        // very helpful, since it's only the first 64-bytes...
1560
+                        // Change this so that raw is only populated when the
1561
+                        // debug flag is set, and then copy/display the full
1562
+                        // contents.
1558 1563
                         cli_dbgmsg_internal("cert:\n");
1559 1564
                         cli_dbgmsg_internal("  subject: %s\n", subject);
1560 1565
                         cli_dbgmsg_internal("  serial: %s\n", serial);
... ...
@@ -1593,7 +1598,26 @@ static cl_error_t asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size
1593 1593
                             // in cmgr for this certificate, not a blacklist
1594 1594
                             // entry for this certificate's parent
1595 1595
                             ret = CL_VIRUS;
1596
-                            cli_dbgmsg("asn1_parse_mscat: Authenticode certificate %s is revoked. Flagging sample as virus.\n", (parent->name ? parent->name : "(no name)"));
1596
+                            cli_dbgmsg("asn1_parse_mscat: Authenticode certificate %s is revoked. Flagging sample as a virus.\n", (parent->name ? parent->name : "(no name)"));
1597
+                            if (NULL != certname) {
1598
+                                // We need to pass back the certname from a
1599
+                                // place where it won't go away (and nothing
1600
+                                // above here knows to free it) so just find it
1601
+                                // again in the engine->cmgr.
1602
+                                // TODO Refactor this so that blacklist cert
1603
+                                // lookups happen against engine->cmgr so we
1604
+                                // don't have to lookup twice
1605
+                                if (cmgr != &(engine->cmgr)) {
1606
+                                    parent = crtmgr_verify_crt(&(engine->cmgr), x509);
1607
+                                }
1608
+
1609
+                                if (NULL == parent) {
1610
+                                    cli_dbgmsg("asn1_parse_mscat: Unexpected case - blacklist cert can't be found in the global list\n");
1611
+                                    *certname = "(error fetching name)";
1612
+                                } else {
1613
+                                    *certname = parent->name ? parent->name : "(no name)";
1614
+                                }
1615
+                            }
1597 1616
                             crtmgr_free(&newcerts);
1598 1617
                             goto finish;
1599 1618
                         }
... ...
@@ -2046,7 +2070,7 @@ int asn1_load_mscat(fmap_t *map, struct cl_engine *engine)
2046 2046
     // TODO Since we pass engine->cmgr directly here, the whole chain of trust
2047 2047
     // for this .cat file will get added to the global trust store assuming it
2048 2048
     // verifies successfully.  Is this a bug for a feature?
2049
-    if (CL_CLEAN != asn1_parse_mscat(map, 0, map->len, &engine->cmgr, 0, &c.next, &size, engine))
2049
+    if (CL_CLEAN != asn1_parse_mscat(engine, map, 0, map->len, &engine->cmgr, 0, &c.next, &size, NULL))
2050 2050
         return 1;
2051 2051
 
2052 2052
     if (asn1_expect_objtype(map, c.next, &size, &c, ASN1_TYPE_SEQUENCE))
... ...
@@ -2179,6 +2203,9 @@ int asn1_load_mscat(fmap_t *map, struct cl_engine *engine)
2179 2179
                 engine->hm_fp->mempool = engine->mempool;
2180 2180
 #endif
2181 2181
             }
2182
+            /* Load the trusted hashes into hm_fp, using the size values
2183
+             * 1 and 2 as sentinel values corresponding to CAB and PE hashes
2184
+             * from .cat files respectively. */
2182 2185
             if (hm_addhash_bin(engine->hm_fp, tagval3.content, CLI_HASH_SHA1, hashtype, NULL)) {
2183 2186
                 cli_warnmsg("asn1_load_mscat: failed to add hash\n");
2184 2187
                 return 1;
... ...
@@ -2190,11 +2217,14 @@ int asn1_load_mscat(fmap_t *map, struct cl_engine *engine)
2190 2190
 }
2191 2191
 
2192 2192
 /* Check an embedded PE Authenticode section to determine whether it's trusted.
2193
- * This will return CL_CLEAN if the file should be trusted, CL_EPARSE if an
2193
+ * This will return CL_VERIFIED if the file should be trusted, CL_EPARSE if an
2194 2194
  * error occurred while parsing the signature, CL_EVERIFY if parsing was
2195 2195
  * successful but there were no whitelist rules for the signature, and
2196
- * CL_VIRUS if a blacklist rule was found for an embedded certificate. */
2197
-cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, struct cli_mapped_region *regions, uint32_t nregions)
2196
+ * CL_VIRUS if a blacklist rule was found for an embedded certificate.
2197
+ *
2198
+ * If CL_VIRUS is returned, certname will be set to the certname of blacklist
2199
+ * rule that matched (unless certname is NULL). */
2200
+cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, struct cli_mapped_region *regions, uint32_t nregions, char **certname)
2198 2201
 {
2199 2202
     unsigned int content_size;
2200 2203
     struct cli_asn1 c;
... ...
@@ -2207,7 +2237,7 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset
2207 2207
     void *ctx;
2208 2208
     unsigned int i;
2209 2209
 
2210
-    // TODO Move these into cli_checkfp_pe
2210
+    // TODO Move these into cli_check_auth_header
2211 2211
     if (!(engine->dconf->pe & PE_CONF_CERTS))
2212 2212
         return CL_EVERIFY;
2213 2213
     if (engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_CERTS)
... ...
@@ -2219,7 +2249,7 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset
2219 2219
         crtmgr_free(&certs);
2220 2220
         return CL_EVERIFY;
2221 2221
     }
2222
-    ret = asn1_parse_mscat(map, offset, size, &certs, 1, &content, &content_size, engine);
2222
+    ret = asn1_parse_mscat(engine, map, offset, size, &certs, 1, &content, &content_size, certname);
2223 2223
     crtmgr_free(&certs);
2224 2224
     if (CL_CLEAN != ret)
2225 2225
         return ret;
... ...
@@ -2288,5 +2318,5 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset
2288 2288
     }
2289 2289
 
2290 2290
     cli_dbgmsg("asn1_check_mscat: file with valid authenticode signature, whitelisted\n");
2291
-    return CL_CLEAN;
2291
+    return CL_VERIFIED;
2292 2292
 }
... ...
@@ -31,6 +31,6 @@ struct cli_mapped_region {
31 31
 };
32 32
 
33 33
 int asn1_load_mscat(fmap_t *map, struct cl_engine *engine);
34
-cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, struct cli_mapped_region *regions, uint32_t nregions);
34
+cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, struct cli_mapped_region *regions, uint32_t nregions, char **certname);
35 35
 
36 36
 #endif
... ...
@@ -117,6 +117,8 @@ typedef enum cl_error_t {
117 117
     CL_EBUSY,
118 118
     CL_ESTATE,
119 119
 
120
+    CL_VERIFIED, /* The binary has been deemed trusted */
121
+
120 122
     /* no error codes below this line please */
121 123
     CL_ELAST_ERROR
122 124
 } cl_error_t;
... ...
@@ -107,8 +107,13 @@ cli_crt *crtmgr_blacklist_lookup(crtmgr *m, cli_crt *x509)
107 107
         // The CRB rules are based on subject, serial, and public key,
108 108
         // so do blacklist queries based on those fields
109 109
 
110
+        // TODO the rule format specifies CodeSign / TimeSign / CertSign
111
+        // which we could also match on, but we just ignore those fields
112
+        // for blacklist certs for now
113
+
110 114
         // TODO Handle the case where these items aren't specified in a CRB
111
-        // rule entry - substitute in default values instead.
115
+        // rule entry - substitute in default values instead (or make the
116
+        // crb parser not permit leaving these fields blank).
112 117
 
113 118
         if (i->isBlacklisted &&
114 119
             !memcmp(i->subject, x509->subject, sizeof(i->subject)) &&
... ...
@@ -407,16 +412,24 @@ cli_crt *crtmgr_verify_crt(crtmgr *m, cli_crt *x509)
407 407
     int score             = 0;
408 408
     unsigned int possible = 0;
409 409
 
410
+    // For a blacklist cert, if it exists in the signature then that is
411
+    // enough to report back a match.  We can't whitelist this way, though,
412
+    // or an attacker could just include a known-good certificate in the
413
+    // signature and just not use it.
410 414
     if (NULL != (i = crtmgr_blacklist_lookup(m, x509))) {
411 415
         return i;
412 416
     }
413 417
 
418
+    // Loop through each of the certificates in our trust store and see whether
419
+    // x509 is signed with it.  If it is, it's trusted
420
+
414 421
     // TODO Technically we should loop through all of the blacklisted certs
415 422
     // first to see whether one of those is used to sign x509.  This case
416 423
     // will get handled if the blacklisted certificate is embedded, since we
417 424
     // will call crtmgr_verify_crt on it and match against the blacklist entry
418 425
     // that way, but the cert doesn't HAVE to be embedded.  This case seems
419
-    // unlikely enough to ignore, though.
426
+    // unlikely enough to ignore, though.  If we ever want to blacklist a
427
+    // stolen CA cert or something, then we will need to revisit this.
420 428
 
421 429
     for (i = m->crts; i; i = i->next) {
422 430
         if (i->certSign &&
... ...
@@ -36,7 +36,10 @@ typedef enum { CLI_SHA1RSA,
36 36
 typedef enum { VRFY_CODE,
37 37
                VRFY_TIME } cli_vrfy_type;
38 38
 
39
+#ifndef CRT_RAWMAXLEN
39 40
 #define CRT_RAWMAXLEN 64
41
+#endif
42
+
40 43
 typedef struct cli_crt_t {
41 44
     char *name;
42 45
     uint8_t raw_subject[CRT_RAWMAXLEN];
... ...
@@ -176,7 +176,7 @@ CLAMAV_PRIVATE {
176 176
     cli_initroots;
177 177
     cli_scanbuff;
178 178
     cli_fmap_scandesc;
179
-    cli_checkfp_pe;
179
+    cli_check_auth_header;
180 180
     cli_genhash_pe;
181 181
     html_screnc_decode;
182 182
     mpool_create;
... ...
@@ -566,7 +566,7 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
566 566
     const char *ptr;
567 567
     uint8_t shash1[SHA1_HASH_SIZE * 2 + 1];
568 568
     uint8_t shash256[SHA256_HASH_SIZE * 2 + 1];
569
-    int have_sha1, have_sha256, do_dsig_check = 1;
569
+    int have_sha1, have_sha256;
570 570
 
571 571
     if (cli_hm_scan(digest, size, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) {
572 572
         cli_dbgmsg("cli_checkfp(md5): Found false positive detection (fp sig: %s), size: %d\n", virname, (int)size);
... ...
@@ -584,13 +584,8 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
584 584
                    vname ? vname : "Name");
585 585
     }
586 586
 
587
-    // TODO Replace this with the ability to actually perform detection with
588
-    // the blacklisted sig entries
589
-    if (vname)
590
-        do_dsig_check = strncmp("W32S.", vname, 5);
591
-
592 587
     map         = *ctx->fmap;
593
-    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) && do_dsig_check);
588
+    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);
594 589
     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);
595 590
     if (have_sha1 || have_sha256) {
596 591
         if ((ptr = fmap_need_off_once(map, 0, size))) {
... ...
@@ -605,7 +600,9 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
605 605
                     cli_dbgmsg("cli_checkfp(sha1): Found false positive detection (fp sig: %s)\n", virname);
606 606
                     return CL_CLEAN;
607 607
                 }
608
-                if (do_dsig_check && cli_hm_scan(&shash1[SHA1_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
608
+                /* See whether the hash matches those loaded in from .cat files
609
+                 * (associated with the .CAB file type) */
610
+                if (cli_hm_scan(&shash1[SHA1_HASH_SIZE], 1, &virname, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
609 611
                     cli_dbgmsg("cli_checkfp(sha1): Found false positive detection via catalog file\n");
610 612
                     return CL_CLEAN;
611 613
                 }
... ...
@@ -653,16 +650,6 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
653 653
     }
654 654
 #endif
655 655
 
656
-    if (do_dsig_check) {
657
-        switch (cli_checkfp_pe(ctx)) {
658
-            case CL_CLEAN:
659
-                cli_dbgmsg("cli_checkfp(pe): PE file whitelisted due to valid digital signature\n");
660
-                return CL_CLEAN;
661
-            default:
662
-                break;
663
-        }
664
-    }
665
-
666 656
     if (ctx->engine->cb_hash)
667 657
         ctx->engine->cb_hash(fmap_fd(*ctx->fmap), size, (const unsigned char *)md5, vname ? vname : "noname", ctx->cb_ctx);
668 658
 
... ...
@@ -918,7 +905,7 @@ int cli_fmap_scandesc(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli
918 918
     struct cli_target_info info;
919 919
     fmap_t *map = *ctx->fmap;
920 920
     struct cli_matcher *hdb, *fp;
921
-    const char *virname    = NULL;
921
+    const char *virname;
922 922
     uint32_t viruses_found = 0;
923 923
     void *md5ctx, *sha1ctx, *sha256ctx;
924 924
 
... ...
@@ -987,6 +974,33 @@ int cli_fmap_scandesc(cli_ctx *ctx, cli_file_t ftype, uint8_t ftonly, struct cli
987 987
                    "and bytecode sigs that require exe metadata\n");
988 988
     }
989 989
 
990
+    /* If it's a PE, check the Authenticode header.  This would be more
991
+     * appropriate in cli_scanpe, but cli_scanraw->cli_fmap_scandesc gets
992
+     * called first for PEs, and we want to determine the whitelist/blacklist
993
+     * status early on so we can skip things like embedded PE extraction
994
+     * (which is broken for signed binaries within signed binaries).
995
+     * 
996
+     * If we want to add support for more signature parsing in the future
997
+     * (Ex: MachO sigs), do that here too. */
998
+    if (1 == info.status && i == 1) {
999
+        char *certname = NULL;
1000
+        ret            = cli_check_auth_header(ctx, &(info.exeinfo), &certname);
1001
+
1002
+        if (CL_VIRUS == ret) {
1003
+            ret = cli_append_virus(ctx, certname);
1004
+        }
1005
+
1006
+        if ((ret == CL_VIRUS || ret == CL_VERIFIED) && !SCAN_ALLMATCHES) {
1007
+            cli_targetinfo_destroy(&info);
1008
+            cl_hash_destroy(md5ctx);
1009
+            cl_hash_destroy(sha1ctx);
1010
+            cl_hash_destroy(sha256ctx);
1011
+            return ret;
1012
+        }
1013
+
1014
+        ret = CL_CLEAN;
1015
+    }
1016
+
990 1017
     if (!ftonly) {
991 1018
         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))) {
992 1019
             cli_targetinfo_destroy(&info);
... ...
@@ -282,6 +282,8 @@ const char *cl_strerror(int clerror)
282 282
             return "Scanner still active";
283 283
         case CL_ESTATE:
284 284
             return "Bad state (engine not initialized, or already initialized)";
285
+        case CL_VERIFIED:
286
+            return "The scanned object was verified and deemed trusted";
285 287
         default:
286 288
             return "Unknown error code";
287 289
     }
... ...
@@ -2,7 +2,7 @@
2 2
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3 3
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4 4
  *
5
- *  Authors: Alberto Wu, Tomasz Kojm
5
+ *  Authors: Alberto Wu, Tomasz Kojm, Andrew Williams
6 6
  *
7 7
  *  Acknowledgements: The header structures were based upon a PE format 
8 8
  *                    analysis by B. Luevelsmeyer.
... ...
@@ -5505,11 +5505,16 @@ static int sort_sects(const void *first, const void *second)
5505 5505
  * - A PE file has an embedded Authenticode section
5506 5506
  * - The PE file has no embedded Authenticode section but is covered by a
5507 5507
  *   catalog file that was loaded in via a -d 
5508
- * CL_CLEAN will be returned if the file was whitelisted based on its
5508
+ * 
5509
+ * If peinfo is NULL, one will be created internally and used
5510
+ * 
5511
+ * CL_VERIFIED will be returned if the file was whitelisted based on its
5509 5512
  * signature.  CL_VIRUS will be returned if the file was blacklisted based on
5510 5513
  * its signature.  Otherwise, an cl_error_t error value will be returned.
5511
- */
5512
-cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5514
+ * 
5515
+ * If CL_VIRUS is returned, certname will be set to the certname of blacklist
5516
+ * rule that matched (unless certname is NULL). */
5517
+cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo, char **certname)
5513 5518
 {
5514 5519
     size_t at;
5515 5520
     unsigned int i, hlen;
... ...
@@ -5524,7 +5529,6 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5524 5524
     uint32_t sec_dir_offset;
5525 5525
     uint32_t sec_dir_size;
5526 5526
     struct cli_exe_info _peinfo;
5527
-    struct cli_exe_info *peinfo = &_peinfo;
5528 5527
 
5529 5528
     // If Authenticode parsing has been disabled via DCONF, then don't
5530 5529
     // continue on.
... ...
@@ -5533,12 +5537,15 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5533 5533
     if (!(DCONF & PE_CONF_CATALOG))
5534 5534
         return CL_EFORMAT;
5535 5535
 
5536
-    // TODO see if peinfo can be passed in (or lives in ctx or something) and
5537
-    // if so, use that to avoid having to re-parse the header
5538
-    cli_exe_info_init(peinfo, 0);
5536
+    // If peinfo is NULL, initialize one.  This makes it so that this function
5537
+    // can be used easily by sigtool
5538
+    if (NULL == peinfo) {
5539
+        peinfo = &_peinfo;
5540
+        cli_exe_info_init(peinfo, 0);
5539 5541
 
5540
-    if (cli_peheader(*ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CLI_PEHEADER_RET_SUCCESS) {
5541
-        return CL_EFORMAT;
5542
+        if (cli_peheader(*ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CLI_PEHEADER_RET_SUCCESS) {
5543
+            return CL_EFORMAT;
5544
+        }
5542 5545
     }
5543 5546
 
5544 5547
     sec_dir_offset = EC32(peinfo->dirs[4].VirtualAddress);
... ...
@@ -5549,21 +5556,20 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5549 5549
     // the section hashes), bail out if we don't have any Authenticode hashes
5550 5550
     // loaded from .cat files
5551 5551
     if (sec_dir_size < 8 && !cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 2)) {
5552
-        cli_exe_info_destroy(peinfo);
5553
-        return CL_BREAK;
5552
+        ret = CL_BREAK;
5553
+        goto finish;
5554 5554
     }
5555 5555
     fsize = map->len;
5556 5556
 
5557
-    do {
5558
-        // We'll build a list of the regions that need to be hashed and pass it to
5559
-        // asn1_check_mscat to do hash verification there (the hash algorithm is
5560
-        // specified in the PKCS7 structure).  We need to hash up to 4 regions
5561
-        regions = (struct cli_mapped_region *)cli_calloc(4, sizeof(struct cli_mapped_region));
5562
-        if (!regions) {
5563
-            cli_exe_info_destroy(peinfo);
5564
-            return CL_EMEM;
5565
-        }
5566
-        nregions = 0;
5557
+    // We'll build a list of the regions that need to be hashed and pass it to
5558
+    // asn1_check_mscat to do hash verification there (the hash algorithm is
5559
+    // specified in the PKCS7 structure).  We need to hash up to 4 regions
5560
+    regions = (struct cli_mapped_region *)cli_calloc(4, sizeof(struct cli_mapped_region));
5561
+    if (!regions) {
5562
+        ret = CL_EMEM;
5563
+        goto finish;
5564
+    }
5565
+    nregions = 0;
5567 5566
 
5568 5567
 #define add_chunk_to_hash_list(_offset, _size) \
5569 5568
     do {                                       \
... ...
@@ -5572,74 +5578,74 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5572 5572
         nregions++;                            \
5573 5573
     } while (0)
5574 5574
 
5575
-        // Pretty much every case below should return CL_EFORMAT
5576
-        ret = CL_EFORMAT;
5575
+    // Pretty much every case below should return CL_EFORMAT
5576
+    ret = CL_EFORMAT;
5577 5577
 
5578
-        /* MZ to checksum */
5579
-        at   = 0;
5580
-        hlen = peinfo->e_lfanew + sizeof(struct pe_image_file_hdr) + (peinfo->is_pe32plus ? offsetof(struct pe_image_optional_hdr64, CheckSum) : offsetof(struct pe_image_optional_hdr32, CheckSum));
5581
-        add_chunk_to_hash_list(0, hlen);
5582
-        at = hlen + 4;
5578
+    /* MZ to checksum */
5579
+    at   = 0;
5580
+    hlen = peinfo->e_lfanew + sizeof(struct pe_image_file_hdr) + (peinfo->is_pe32plus ? offsetof(struct pe_image_optional_hdr64, CheckSum) : offsetof(struct pe_image_optional_hdr32, CheckSum));
5581
+    add_chunk_to_hash_list(0, hlen);
5582
+    at = hlen + 4;
5583 5583
 
5584
-        /* Checksum to security */
5585
-        if (peinfo->is_pe32plus)
5586
-            hlen = sizeof(struct pe_image_optional_hdr64) - offsetof(struct pe_image_optional_hdr64, CheckSum) - 4;
5587
-        else
5588
-            hlen = sizeof(struct pe_image_optional_hdr32) - offsetof(struct pe_image_optional_hdr32, CheckSum) - 4;
5584
+    /* Checksum to security */
5585
+    if (peinfo->is_pe32plus)
5586
+        hlen = sizeof(struct pe_image_optional_hdr64) - offsetof(struct pe_image_optional_hdr64, CheckSum) - 4;
5587
+    else
5588
+        hlen = sizeof(struct pe_image_optional_hdr32) - offsetof(struct pe_image_optional_hdr32, CheckSum) - 4;
5589 5589
 
5590
-        hlen += sizeof(struct pe_image_data_dir) * 4;
5591
-        add_chunk_to_hash_list(at, hlen);
5592
-        at += hlen + 8;
5590
+    hlen += sizeof(struct pe_image_data_dir) * 4;
5591
+    add_chunk_to_hash_list(at, hlen);
5592
+    at += hlen + 8;
5593 5593
 
5594
-        if (at > peinfo->hdr_size) {
5595
-            break;
5596
-        }
5594
+    if (at > peinfo->hdr_size) {
5595
+        goto finish;
5596
+    }
5597 5597
 
5598
-        /* Security to End of header */
5599
-        hlen = peinfo->hdr_size - at;
5600
-        add_chunk_to_hash_list(at, hlen);
5601
-        at += hlen;
5598
+    /* Security to End of header */
5599
+    hlen = peinfo->hdr_size - at;
5600
+    add_chunk_to_hash_list(at, hlen);
5601
+    at += hlen;
5602 5602
 
5603
-        if (sec_dir_offset) {
5603
+    if (sec_dir_offset) {
5604 5604
 
5605
-            // Verify that we have all the bytes we expect in the authenticode sig
5606
-            // and that the certificate table is the last thing in the file
5607
-            // (according to the MS13-098 bulletin, this is a requirement)
5608
-            if (fsize != sec_dir_size + sec_dir_offset) {
5609
-                cli_dbgmsg("cli_checkfp_pe: expected authenticode data at the end of the file\n");
5610
-                break;
5611
-            }
5605
+        // Verify that we have all the bytes we expect in the authenticode sig
5606
+        // and that the certificate table is the last thing in the file
5607
+        // (according to the MS13-098 bulletin, this is a requirement)
5608
+        if (fsize != sec_dir_size + sec_dir_offset) {
5609
+            cli_dbgmsg("cli_check_auth_header: expected authenticode data at the end of the file\n");
5610
+            goto finish;
5611
+        }
5612 5612
 
5613
-            // Hash everything from the end of the header to the start of the
5614
-            // security section
5615
-            if (at < sec_dir_offset) {
5616
-                hlen = sec_dir_offset - at;
5617
-                add_chunk_to_hash_list(at, hlen);
5618
-            } else {
5619
-                cli_dbgmsg("cli_checkfp_pe: security directory offset appears to overlap with the PE header\n");
5620
-                break;
5621
-            }
5613
+        // Hash everything from the end of the header to the start of the
5614
+        // security section
5615
+        if (at < sec_dir_offset) {
5616
+            hlen = sec_dir_offset - at;
5617
+            add_chunk_to_hash_list(at, hlen);
5618
+        } else {
5619
+            cli_dbgmsg("cli_check_auth_header: security directory offset appears to overlap with the PE header\n");
5620
+            goto finish;
5621
+        }
5622 5622
 
5623
-            // Parse the security directory header
5623
+        // Parse the security directory header
5624 5624
 
5625
-            if (fmap_readn(map, &cert_hdr, sec_dir_offset, sizeof(cert_hdr)) != sizeof(cert_hdr)) {
5626
-                break;
5627
-            }
5625
+        if (fmap_readn(map, &cert_hdr, sec_dir_offset, sizeof(cert_hdr)) != sizeof(cert_hdr)) {
5626
+            goto finish;
5627
+        }
5628 5628
 
5629
-            if (EC16(cert_hdr.revision) != WIN_CERT_REV_2) {
5630
-                cli_dbgmsg("cli_checkfp_pe: unsupported authenticode data revision\n");
5631
-                break;
5632
-            }
5629
+        if (EC16(cert_hdr.revision) != WIN_CERT_REV_2) {
5630
+            cli_dbgmsg("cli_check_auth_header: unsupported authenticode data revision\n");
5631
+            goto finish;
5632
+        }
5633 5633
 
5634
-            if (EC16(cert_hdr.type) != WIN_CERT_TYPE_PKCS7) {
5635
-                cli_dbgmsg("cli_checkfp_pe: unsupported authenticode data type\n");
5636
-                break;
5637
-            }
5634
+        if (EC16(cert_hdr.type) != WIN_CERT_TYPE_PKCS7) {
5635
+            cli_dbgmsg("cli_check_auth_header: unsupported authenticode data type\n");
5636
+            goto finish;
5637
+        }
5638 5638
 
5639
-            hlen = sec_dir_size;
5639
+        hlen = sec_dir_size;
5640 5640
 
5641
-            if (EC32(cert_hdr.length) != hlen) {
5642
-                /* This is the case that MS13-098 aimed to address, but it got
5641
+        if (EC32(cert_hdr.length) != hlen) {
5642
+            /* This is the case that MS13-098 aimed to address, but it got
5643 5643
                  * pushback to where the fix (not allowing additional, non-zero
5644 5644
                  * bytes in the security directory) is now opt-in via a registry
5645 5645
                  * key.  Given that most machines will treat these binaries as
... ...
@@ -5647,76 +5653,74 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5647 5647
                  * our whitelist signatures are tailored enough to where any
5648 5648
                  * instances of this are reasonable (for instance, I saw one
5649 5649
                  * binary that appeared to use this to embed a license key.) */
5650
-                cli_dbgmsg("cli_checkfp_pe: MS13-098 violation detected, but continuing on to verify certificate\n");
5651
-            }
5652
-
5653
-            at = sec_dir_offset + sizeof(cert_hdr);
5654
-            hlen -= sizeof(cert_hdr);
5650
+            cli_dbgmsg("cli_check_auth_header: MS13-098 violation detected, but continuing on to verify certificate\n");
5651
+        }
5655 5652
 
5656
-            ret = asn1_check_mscat((struct cl_engine *)(ctx->engine), map, at, hlen, regions, nregions);
5653
+        at = sec_dir_offset + sizeof(cert_hdr);
5654
+        hlen -= sizeof(cert_hdr);
5657 5655
 
5658
-            if (CL_CLEAN == ret) {
5659
-                // We validated the embedded signature.  Hooray!
5660
-                break;
5661
-            } else if (CL_VIRUS == ret) {
5662
-                // A blacklist rule hit - don't continue on to check hm_fp for a match
5663
-                break;
5664
-            }
5656
+        ret = asn1_check_mscat((struct cl_engine *)(ctx->engine), map, at, hlen, regions, nregions, certname);
5665 5657
 
5666
-            // Otherwise, we still need to check to see whether this file is
5667
-            // covered by a .cat file (it's common these days for driver files
5668
-            // to have .cat files covering PEs with embedded signatures)
5658
+        if (CL_VERIFIED == ret) {
5659
+            // We validated the embedded signature.  Hooray!
5660
+            goto finish;
5661
+        } else if (CL_VIRUS == ret) {
5662
+            // A blacklist rule hit - don't continue on to check hm_fp for a match
5663
+            goto finish;
5664
+        }
5669 5665
 
5670
-        } else {
5666
+        // Otherwise, we still need to check to see whether this file is
5667
+        // covered by a .cat file (it's common these days for driver files
5668
+        // to have .cat files covering PEs with embedded signatures)
5671 5669
 
5672
-            // Hash everything from the end of the header to the end of the
5673
-            // file
5674
-            if (at < fsize) {
5675
-                hlen = fsize - at;
5676
-                add_chunk_to_hash_list(at, hlen);
5677
-            }
5678
-        }
5670
+    } else {
5679 5671
 
5680
-        // At this point we should compute the SHA1 authenticode hash to see
5681
-        // whether we've had any hashes added from external catalog files
5682
-        // TODO Is it gauranteed that the hashing algorithm will be SHA1?  If
5683
-        // not, figure out how to handle that case
5684
-        hashctx = cl_hash_init("sha1");
5685
-        if (NULL == hashctx) {
5686
-            ret = CL_EMEM;
5687
-            break;
5672
+        // Hash everything from the end of the header to the end of the
5673
+        // file
5674
+        if (at < fsize) {
5675
+            hlen = fsize - at;
5676
+            add_chunk_to_hash_list(at, hlen);
5688 5677
         }
5678
+    }
5689 5679
 
5690
-        for (i = 0; i < nregions; i++) {
5691
-            const uint8_t *hptr;
5692
-            if (0 == regions[i].size) {
5693
-                continue;
5694
-            }
5695
-            if (!(hptr = fmap_need_off_once(map, regions[i].offset, regions[i].size))) {
5696
-                break;
5697
-            }
5680
+    // At this point we should compute the SHA1 authenticode hash to see
5681
+    // whether we've had any hashes added from external catalog files
5682
+    // TODO Is it gauranteed that the hashing algorithm will be SHA1?  If
5683
+    // not, figure out how to handle that case
5684
+    hashctx = cl_hash_init("sha1");
5685
+    if (NULL == hashctx) {
5686
+        ret = CL_EMEM;
5687
+        goto finish;
5688
+    }
5698 5689
 
5699
-            cl_update_hash(hashctx, hptr, regions[i].size);
5690
+    for (i = 0; i < nregions; i++) {
5691
+        const uint8_t *hptr;
5692
+        if (0 == regions[i].size) {
5693
+            continue;
5700 5694
         }
5701
-
5702
-        if (i != nregions) {
5695
+        if (!(hptr = fmap_need_off_once(map, regions[i].offset, regions[i].size))) {
5703 5696
             break;
5704 5697
         }
5705 5698
 
5706
-        cl_finish_hash(hashctx, authsha1);
5707
-        hashctx = NULL;
5699
+        cl_update_hash(hashctx, hptr, regions[i].size);
5700
+    }
5708 5701
 
5709
-        if (cli_hm_scan(authsha1, 2, NULL, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
5710
-            cli_dbgmsg("cli_checkfp_pe: PE file whitelisted by catalog file\n");
5711
-            ret = CL_CLEAN;
5712
-            break;
5713
-        }
5702
+    if (i != nregions) {
5703
+        goto finish;
5704
+    }
5714 5705
 
5715
-        ret = CL_EVERIFY;
5716
-        break;
5706
+    cl_finish_hash(hashctx, authsha1);
5707
+    hashctx = NULL;
5708
+
5709
+    if (cli_hm_scan(authsha1, 2, NULL, ctx->engine->hm_fp, CLI_HASH_SHA1) == CL_VIRUS) {
5710
+        cli_dbgmsg("cli_check_auth_header: PE file whitelisted by catalog file\n");
5711
+        ret = CL_CLEAN;
5712
+        goto finish;
5713
+    }
5717 5714
 
5718
-    } while (0);
5715
+    ret = CL_EVERIFY;
5719 5716
 
5717
+finish:
5720 5718
     if (NULL != hashctx) {
5721 5719
         cl_hash_destroy(hashctx);
5722 5720
     }
... ...
@@ -5725,7 +5729,10 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5725 5725
         free(regions);
5726 5726
     }
5727 5727
 
5728
-    cli_exe_info_destroy(peinfo);
5728
+    // If we created the peinfo, then destroy it.  Otherwise we don't own it
5729
+    if (&_peinfo == peinfo) {
5730
+        cli_exe_info_destroy(peinfo);
5731
+    }
5729 5732
     return ret;
5730 5733
 }
5731 5734
 
... ...
@@ -2,7 +2,7 @@
2 2
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3 3
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4 4
  *
5
- *  Authors: Alberto Wu, Tomasz Kojm
5
+ *  Authors: Alberto Wu, Tomasz Kojm, Andrew Williams
6 6
  * 
7 7
  *  Acknowledgements: The header structures were based upon a PE format 
8 8
  *                    analysis by B. Luevelsmeyer.
... ...
@@ -98,7 +98,7 @@ enum {
98 98
 int cli_pe_targetinfo(fmap_t *map, struct cli_exe_info *peinfo);
99 99
 int cli_peheader(fmap_t *map, struct cli_exe_info *peinfo, uint32_t opts, cli_ctx *ctx);
100 100
 
101
-cl_error_t cli_checkfp_pe(cli_ctx *ctx);
101
+cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo, char **certname);
102 102
 int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type, stats_section_t *hashes);
103 103
 
104 104
 uint32_t cli_rawaddr(uint32_t, const struct cli_exe_section *, uint16_t, unsigned int *, size_t, uint32_t);
... ...
@@ -2402,6 +2402,9 @@ static int cli_scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_file_
2402 2402
     ret = cli_fmap_scandesc(ctx, type == CL_TYPE_TEXT_ASCII ? 0 : type, 0, &ftoffset, acmode, NULL, refhash);
2403 2403
     perf_stop(ctx, PERFT_RAW);
2404 2404
 
2405
+    // TODO I think this causes embedded file extraction to stop when a
2406
+    // signature has matched in cli_fmap_scandesc, which wouldn't be what
2407
+    // we want if allmatch is specified.
2405 2408
     if (ret >= CL_TYPENO) {
2406 2409
         perf_nested_start(ctx, PERFT_RAWTYPENO, PERFT_SCAN);
2407 2410
         ctx->recursion++;
... ...
@@ -2625,6 +2628,25 @@ static int cli_scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_file_
2625 2625
                                              * be found recursively
2626 2626
                                              * through the above call
2627 2627
                                              */
2628
+                                // TODO This method of embedded PE extraction
2629
+                                // is kinda gross in that:
2630
+                                //   - if you have an executable that contains
2631
+                                //     20 other exes, the bytes associated with
2632
+                                //     the last exe will have been included in
2633
+                                //     hash computations and things 20 times
2634
+                                //     (as overlay data to the previously
2635
+                                //     extracted exes).
2636
+                                //   - if you have a signed embedded exe, it
2637
+                                //     will fail to validate after extraction
2638
+                                //     bc it has overlay data, which is a
2639
+                                //     violation of the Authenticode spec.
2640
+                                //   - this method of extraction is subject to
2641
+                                //     the recursion limit, which is fairly
2642
+                                //     low by default (I think 16)
2643
+                                //
2644
+                                // It'd be awesome if we could compute the PE
2645
+                                // size from the PE header and just extract
2646
+                                // that.
2628 2647
                             }
2629 2648
                         }
2630 2649
                         break;
... ...
@@ -3403,7 +3425,15 @@ static int magic_scandesc(cli_ctx *ctx, cli_file_t type)
3403 3403
                     cli_bitset_free(ctx->hook_lsig_matches);
3404 3404
                     ctx->hook_lsig_matches = old_hook_lsig_matches;
3405 3405
                     return magic_scandesc_cleanup(ctx, type, hash, hashed_size, cache_clean, res, parent_property);
3406
-                /* CL_VIRUS = malware found, check FP and report */
3406
+                /* CL_VIRUS = malware found, check FP and report.
3407
+                 * Likewise, if the file was determined to be trusted, then we
3408
+                 * can also finish with the scan. (Ex: EXE with a valid
3409
+                 * Authenticode sig.) */
3410
+                case CL_VERIFIED:
3411
+                    // For now just conver CL_VERIFIED to CL_CLEAN, since
3412
+                    // CL_VERIFIED isn't used elsewhere
3413
+                    res = CL_CLEAN;
3414
+                    // Fall through
3407 3415
                 case CL_VIRUS:
3408 3416
                     ret = res;
3409 3417
                     if (SCAN_ALLMATCHES)
... ...
@@ -3455,26 +3455,25 @@ static int dumpcerts(const struct optstruct *opts)
3455 3455
         return -1;
3456 3456
     }
3457 3457
 
3458
-    ret = cli_checkfp_pe(&ctx);
3458
+    ret = cli_check_auth_header(&ctx, NULL, NULL);
3459 3459
 
3460 3460
     switch (ret) {
3461
-        case CL_CLEAN:
3462
-            mprintf("*dumpcerts: CL_CLEAN after cli_checkfp_pe()!\n");
3463
-            break;
3461
+        case CL_VERIFIED:
3464 3462
         case CL_VIRUS:
3465
-            mprintf("*dumpcerts: CL_VIRUS after cli_checkfp_pe()!\n");
3466
-            break;
3467
-        case CL_BREAK:
3468
-            mprintf("*dumpcerts: CL_BREAK after cli_checkfp_pe()!\n");
3463
+            // These shouldn't happen, since sigtool doesn't load in any sigs
3469 3464
             break;
3470 3465
         case CL_EVERIFY:
3471
-            mprintf("!dumpcerts: CL_EVERIFY after cli_checkfp_pe()!\n");
3466
+            // The Authenticode header was parsed successfully but there were
3467
+            // no applicable whitelist/blacklist rules
3468
+            break;
3469
+        case CL_BREAK:
3470
+            mprintf("*dumpcerts: No Authenticode signature detected\n");
3472 3471
             break;
3473 3472
         case CL_EFORMAT:
3474 3473
             mprintf("!dumpcerts: An error occurred when parsing the file\n");
3475 3474
             break;
3476 3475
         default:
3477
-            mprintf("!dumpcerts: Other error %d inside cli_checkfp_pe.\n", ret);
3476
+            mprintf("!dumpcerts: Other error %d inside cli_check_auth_header.\n", ret);
3478 3477
             break;
3479 3478
     }
3480 3479
 
... ...
@@ -163,7 +163,7 @@ EXPORTS cli_fmap_scandesc @44265 NONAME
163 163
 EXPORTS cli_hashset_destroy @44266 NONAME
164 164
 EXPORTS cli_detect_environment @44267 NONAME
165 165
 EXPORTS cli_filecopy @44268 NONAME
166
-EXPORTS cli_checkfp_pe @44369 NONAME
166
+EXPORTS cli_check_auth_header @44369 NONAME
167 167
 EXPORTS cli_bytefunc_describe @44370 NONAME
168 168
 EXPORTS cli_bytetype_describe @44371 NONAME
169 169
 EXPORTS cli_bytevalue_describe @44372 NONAME