Browse code

Make --dumpcerts be more consistent, improve cert processing This commit makes the following changes: - --dumpcerts will print certificates even if they already exist in any .crb files loaded - --dumpcerts will print certificates only once - Having a whitelist CRB rule on a leaf certificate should no longer prevent signature verification from happening. NOTE, this doesn't mean that you can have whitelist rules for leaf certificates and have that result in a trusted signature - that doesn't work yet - Determining whether a certificate is blacklisted now includes comparing the public key data (modulus and exponent) in addition to the subject and serial hashes - If a blacklisted certificate is detected, the code will return immediately instead of continuing on to parse the rest of the signature

Andrew authored on 2018/09/14 01:04:17
Showing 2 changed files
... ...
@@ -695,12 +695,12 @@ static int asn1_get_rsa_pubkey(fmap_t *map, const void **asn1data, unsigned int
695 695
 #define ASN1_GET_X509_UNRECOVERABLE_ERROR 2
696 696
 
697 697
 /* Parse the asn1data associated with an x509 certificate and add the cert
698
- * to the crtmgr other if it doesn't already exist in master or other.
698
+ * to the crtmgr certs if it doesn't already exist there.
699 699
  * ASN1_GET_X509_CERT_ERROR will be returned in the case that an invalid x509
700 700
  * certificate is encountered but asn1data and size are suitable for continued
701 701
  * signature parsing.  ASN1_GET_X509_UNRECOVERABLE_ERROR will be returned in
702 702
  * the case where asn1data and size are not suitable for continued use. */
703
-static int asn1_get_x509(fmap_t *map, const void **asn1data, unsigned int *size, crtmgr *master, crtmgr *other) {
703
+static int asn1_get_x509(fmap_t *map, const void **asn1data, unsigned int *size, crtmgr *crts) {
704 704
     struct cli_asn1 crt, tbs, obj;
705 705
     unsigned int avail, tbssize, issuersize;
706 706
     cli_crt_hashtype hashtype1, hashtype2;
... ...
@@ -1029,8 +1029,8 @@ static int asn1_get_x509(fmap_t *map, const void **asn1data, unsigned int *size,
1029 1029
         }
1030 1030
 
1031 1031
 
1032
-        if(crtmgr_lookup(master, &x509) || crtmgr_lookup(other, &x509)) {
1033
-            cli_dbgmsg("asn1_get_x509: certificate already exists\n");
1032
+        if(crtmgr_lookup(crts, &x509)) {
1033
+            cli_dbgmsg("asn1_get_x509: duplicate embedded certificates detected\n");
1034 1034
             cli_crt_clear(&x509);
1035 1035
             return ASN1_GET_X509_SUCCESS;
1036 1036
         }
... ...
@@ -1076,7 +1076,7 @@ static int asn1_get_x509(fmap_t *map, const void **asn1data, unsigned int *size,
1076 1076
 
1077 1077
         }
1078 1078
 
1079
-        if(crtmgr_add(other, &x509))
1079
+        if(crtmgr_add(crts, &x509))
1080 1080
             break;
1081 1081
         cli_crt_clear(&x509);
1082 1082
         return ASN1_GET_X509_SUCCESS;
... ...
@@ -1510,7 +1510,7 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1510 1510
             crtmgr newcerts;
1511 1511
             crtmgr_init(&newcerts);
1512 1512
             while(dsize) {
1513
-                result = asn1_get_x509(map, &asn1.content, &dsize, cmgr, &newcerts);
1513
+                result = asn1_get_x509(map, &asn1.content, &dsize, &newcerts);
1514 1514
                 if(ASN1_GET_X509_UNRECOVERABLE_ERROR == result) {
1515 1515
                     dsize = 1;
1516 1516
                     break;
... ...
@@ -1526,12 +1526,10 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1526 1526
             }
1527 1527
             if(newcerts.crts) {
1528 1528
                 x509 = newcerts.crts;
1529
-                cli_dbgmsg("asn1_parse_mscat: %u new certificates collected\n", newcerts.items);
1530
-                while(x509) {
1531
-                    cli_crt *parent = crtmgr_verify_crt(cmgr, x509);
1532
-
1533
-                    /* Dump the cert if requested before anything happens to it */
1534
-                    if (engine->engine_options & ENGINE_OPTIONS_PE_DUMPCERTS) {
1529
+                cli_dbgmsg("asn1_parse_mscat: %u embedded certificates collected\n", newcerts.items);
1530
+                if (engine->engine_options & ENGINE_OPTIONS_PE_DUMPCERTS) {
1531
+                    /* Dump the certs if requested before anything happens to them */
1532
+                    while(x509) {
1535 1533
                         char raw_issuer[CRT_RAWMAXLEN*2+1], raw_subject[CRT_RAWMAXLEN*2+1], raw_serial[CRT_RAWMAXLEN*3+1];
1536 1534
                         char issuer[SHA1_HASH_SIZE*2+1], subject[SHA1_HASH_SIZE*2+1], serial[SHA1_HASH_SIZE*2+1];
1537 1535
                         char mod[1024+1], exp[1024+1];
... ...
@@ -1563,8 +1561,29 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1563 1563
                         cli_dbgmsg_internal("  raw_subject: %s\n", raw_subject);
1564 1564
                         cli_dbgmsg_internal("  raw_serial: %s\n", raw_serial);
1565 1565
                         cli_dbgmsg_internal("  raw_issuer: %s\n", raw_issuer);
1566
+
1567
+                        x509 = x509->next;
1568
+                    }
1569
+                    x509 = newcerts.crts;
1570
+                }
1571
+
1572
+                while(x509) {
1573
+                    cli_crt *parent;
1574
+
1575
+                    /* If the certificate is in the trust store already, remove
1576
+                     * it from the newcerts list */
1577
+                    if (crtmgr_lookup(cmgr, x509)) {
1578
+                        cli_crt *tmp = x509->next;
1579
+                        cli_dbgmsg("asn1_parse_mscat: found embedded certificate matching one in the trust store\n");
1580
+                        crtmgr_del(&newcerts, x509);
1581
+                        x509 = tmp;
1582
+                        continue;
1566 1583
                     }
1567 1584
 
1585
+                    /* Determine whether the cert is signed by one in our trust
1586
+                     * store or has a blacklist entry */
1587
+                    parent = crtmgr_verify_crt(cmgr, x509);
1588
+
1568 1589
                     if(parent) {
1569 1590
                         if (parent->isBlacklisted) {
1570 1591
                             // NOTE: In this case, parent is a blacklist entry
... ...
@@ -1572,10 +1591,7 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1572 1572
                             // entry for this certificate's parent
1573 1573
                             isBlacklisted = 1;
1574 1574
                             cli_dbgmsg("asn1_parse_mscat: Authenticode certificate %s is revoked. Flagging sample as virus.\n", (parent->name ? parent->name : "(no name)"));
1575
-
1576
-                            // TODO In this case cmgr already has a blacklisted
1577
-                            // cert for this x509, so I don't think we need to
1578
-                            // continue on below and try to add it to cmgr
1575
+                            break;
1579 1576
                         }
1580 1577
 
1581 1578
                         // TODO Why is this done?
... ...
@@ -1587,9 +1603,16 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1587 1587
                             break;
1588 1588
                         }
1589 1589
                         crtmgr_del(&newcerts, x509);
1590
+
1591
+                        /* Start at the beginning of newcerts so that we can see
1592
+                         * whether adding this new trusted cert causes more
1593
+                         * certs to be trusted (via chaining).  Otherwise we
1594
+                         * might miss valid certs if the ordering in the binary
1595
+                         * doesn't align with the chain ordering. */
1590 1596
                         x509 = newcerts.crts;
1591 1597
                         continue;
1592 1598
                     }
1599
+
1593 1600
                     x509 = x509->next;
1594 1601
                 }
1595 1602
                 if(x509) {
... ...
@@ -1895,7 +1918,6 @@ static int asn1_parse_mscat(fmap_t *map, size_t offset, unsigned int size, crtmg
1895 1895
         deep.next = asn1.content;
1896 1896
         result = 0;
1897 1897
         while(dsize) {
1898
-            struct cli_asn1 cobj;
1899 1898
             int content;
1900 1899
             if(asn1_expect_objtype(map, deep.next, &dsize, &deep, ASN1_TYPE_SEQUENCE)) {
1901 1900
                 cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE starting an unauthenticatedAttribute\n");
... ...
@@ -2210,7 +2232,7 @@ int asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsig
2210 2210
 
2211 2211
     // Now that we know the hash algorithm, compute the authenticode hash
2212 2212
     // across the required regions of memory.
2213
-    for(int i = 0; i < nregions; i++) {
2213
+    for(unsigned int i = 0; i < nregions; i++) {
2214 2214
         const uint8_t *hptr; \
2215 2215
         if(!(hptr = fmap_need_off_once(map, regions[i].offset, regions[i].size))){
2216 2216
             return CL_VIRUS;
... ...
@@ -2223,7 +2245,7 @@ int asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsig
2223 2223
 
2224 2224
     if(cli_debug_flag) {
2225 2225
         char hashtxt[MAX_HASH_SIZE*2+1];
2226
-        for(int i=0; i<hashsize; i++)
2226
+        for(unsigned int i=0; i<hashsize; i++)
2227 2227
             sprintf(&hashtxt[i*2], "%02x", hash[i]);
2228 2228
         cli_dbgmsg("Authenticode: %s\n", hashtxt);
2229 2229
     }
... ...
@@ -62,66 +62,85 @@ void cli_crt_clear(cli_crt *x509) {
62 62
     mp_clear_multi(&x509->n, &x509->e, &x509->sig, NULL);
63 63
 }
64 64
 
65
-cli_crt *crtmgr_lookup(crtmgr *m, cli_crt *x509) {
65
+/* Look for an existing certificate in the trust store m.  This search allows
66
+ * the not_before / not_after / certSign / codeSign / timeSign fields to be
67
+ * more restrictive than the values associated with a cert in the trust store,
68
+ * but not less.  It's probably overkill to not do exact matching on those
69
+ * fields... TODO Is there a case where this is needed
70
+ *
71
+ * There are two ways that things get added to the whitelist - through the CRB
72
+ * rules, and through embedded signatures / catalog files that we parse.  CRB
73
+ * rules only specify the subject, serial, public key, and whether the cert
74
+ * can be used for cert/code/time signing, so in those cases the issuer and
75
+ * hashtype get set to a hardcoded value.  Those values are important for
76
+ * doing signature verification, though, so we include them when doing this
77
+ * lookup.  That way, certs with more specific values can get added to the
78
+ * whitelist by functions like crtmgr_add and increase the probability of
79
+ * successful signature verification. */
80
+cli_crt *crtmgr_whitelist_lookup(crtmgr *m, cli_crt *x509) {
66 81
     cli_crt *i;
67 82
     for(i = m->crts; i; i = i->next) {
68
-        if(x509->not_before >= i->not_before &&
83
+        if(!i->isBlacklisted &&
84
+           x509->not_before >= i->not_before &&
69 85
            x509->not_after <= i->not_after &&
70 86
            (i->certSign | x509->certSign) == i->certSign &&
71 87
            (i->codeSign | x509->codeSign) == i->codeSign &&
72 88
            (i->timeSign | x509->timeSign) == i->timeSign &&
73 89
            !memcmp(x509->subject, i->subject, sizeof(i->subject)) &&
74 90
            !memcmp(x509->serial, i->serial, sizeof(i->serial)) &&
91
+           !memcmp(x509->issuer, i->issuer, sizeof(i->issuer)) &&
92
+           x509->hashtype == i->hashtype &&
75 93
            !mp_cmp(&x509->n, &i->n) &&
76
-           !mp_cmp(&x509->e, &i->e) && !(i->isBlacklisted)) {
94
+           !mp_cmp(&x509->e, &i->e)) {
77 95
             return i;
78 96
         }
79 97
     }
80 98
     return NULL;
81 99
 }
82 100
 
83
-int crtmgr_add(crtmgr *m, cli_crt *x509) {
101
+cli_crt *crtmgr_blacklist_lookup(crtmgr *m, cli_crt *x509) {
84 102
     cli_crt *i;
85
-    int ret = 0;
86
-
87
-    for(i = m->crts; i; i = i->next) {
88
-        if(!memcmp(x509->subject, i->subject, sizeof(i->subject)) &&
89
-           !memcmp(x509->serial, i->subject, sizeof(i->serial)) &&
90
-           !mp_cmp(&x509->n, &i->n) &&
91
-           !mp_cmp(&x509->e, &i->e)) {
92
-            if(x509->not_before >= i->not_before && x509->not_after <= i->not_after) {
93
-                /* Already same or broader */
94
-                ret = 1;
95
-            }
96
-            if(i->not_before > x509->not_before && i->not_before <= x509->not_after) {
97
-                /* Extend left */
98
-                i->not_before = x509->not_before;
99
-                ret = 1;
100
-            }
101
-            if(i->not_after >= x509->not_before && i->not_after < x509->not_after) {
102
-                /* Extend right */
103
-                i->not_after = x509->not_after;
104
-                ret = 1;
105
-            }
106
-            if(!ret)
107
-                continue;
108
-            i->certSign |= x509->certSign;
109
-            i->codeSign |= x509->codeSign;
110
-            i->timeSign |= x509->timeSign;
103
+    for (i = m->crts; i; i = i->next) {
104
+        // The CRB rules are based on subject, serial, and public key,
105
+        // so do blacklist queries based on those fields
111 106
 
112
-            return 0;
113
-        }
107
+        // TODO Handle the case where these items aren't specified in a CRB
108
+        // rule entry - substitute in default values instead.
114 109
 
115
-        /* If certs match, we're likely just revoking it */
116
-        if (!memcmp(x509->subject, i->subject, sizeof(x509->subject)) &&
117
-            !memcmp(x509->issuer, i->issuer, sizeof(x509->issuer)) &&
118
-            !memcmp(x509->serial, i->serial, sizeof(x509->serial)) &&
110
+        if (i->isBlacklisted &&
111
+            !memcmp(i->subject, x509->subject, sizeof(i->subject)) &&
112
+            !memcmp(i->serial, x509->serial, sizeof(i->serial)) &&
119 113
             !mp_cmp(&x509->n, &i->n) &&
120 114
             !mp_cmp(&x509->e, &i->e)) {
121
-                if (i->isBlacklisted != x509->isBlacklisted)
122
-                    i->isBlacklisted = x509->isBlacklisted;
115
+            return i;
116
+        }
117
+    }
118
+    return NULL;
119
+}
123 120
 
124
-                return 0;
121
+/* Determine whether x509 already exists in m. The fields compared depend on
122
+ * whether x509 is a blacklist entry or a trusted certificate */
123
+cli_crt *crtmgr_lookup(crtmgr *m, cli_crt *x509) {
124
+    if (x509->isBlacklisted) {
125
+        return crtmgr_blacklist_lookup(m, x509);
126
+    } else {
127
+        return crtmgr_whitelist_lookup(m, x509);
128
+    }
129
+}
130
+
131
+int crtmgr_add(crtmgr *m, cli_crt *x509) {
132
+    cli_crt *i;
133
+    int ret = 0;
134
+
135
+    if (x509->isBlacklisted) {
136
+        if (crtmgr_blacklist_lookup(m, x509)) {
137
+            cli_dbgmsg("crtmgr_add: duplicate blacklist entry detected - not adding\n");
138
+            return 0;
139
+        }
140
+    } else {
141
+        if (crtmgr_whitelist_lookup(m, x509)) {
142
+            cli_dbgmsg("crtmgr_add: duplicate trusted certificate detected - not adding\n");
143
+            return 0;
125 144
         }
126 145
     }
127 146
 
... ...
@@ -239,7 +258,7 @@ static int crtmgr_rsa_verify(cli_crt *x509, mp_int *sig, cli_crt_hashtype hashty
239 239
             cli_dbgmsg("crtmgr_rsa_verify: keylen-1 doesn't match expected size of exptmod result\n");
240 240
             break;
241 241
         }
242
-        if(mp_unsigned_bin_size(&x) > sizeof(d)) {
242
+        if(((unsigned int) mp_unsigned_bin_size(&x)) > sizeof(d)) {
243 243
             cli_dbgmsg("crtmgr_rsa_verify: exptmod result would overrun working buffer\n");
244 244
             break;
245 245
         }
... ...
@@ -376,29 +395,28 @@ static int crtmgr_rsa_verify(cli_crt *x509, mp_int *sig, cli_crt_hashtype hashty
376 376
 cli_crt *crtmgr_verify_crt(crtmgr *m, cli_crt *x509) {
377 377
     cli_crt *i = m->crts, *best = NULL;
378 378
     int score = 0;
379
+    unsigned int possible = 0;
379 380
 
380
-    for (i = m->crts; i; i = i->next) {
381
-        if (!memcmp(i->subject, x509->subject, sizeof(i->subject)) &&
382
-            !memcmp(i->serial, x509->serial, sizeof(i->serial))) {
383
-
384
-            // TODO Shouldn't we compare public keys in this case as well?  I'd
385
-            // think that it's the public key that really makes a certificate
386
-            // unique (not subject/serial).  Otherwise, you could have malware
387
-            // use the subject/serial from a popular company's cert and if we
388
-            // blacklisted that it would cause FPs on the popular software.
389
-
390
-            if (i->isBlacklisted)
391
-                return i;
392
-        }
381
+    if (NULL != (i = crtmgr_blacklist_lookup(m, x509))) {
382
+        return i;
393 383
     }
394 384
 
385
+    // TODO Technically we should loop through all of the blacklisted certs
386
+    // first to see whether one of those is used to sign x509.  This case
387
+    // will get handled if the blacklisted certificate is embedded, since we
388
+    // will call crtmgr_verify_crt on it and match against the blacklist entry
389
+    // that way, but the cert doesn't HAVE to be embedded.  This case seems
390
+    // unlikely enough to ignore, though.
391
+
395 392
     for(i = m->crts; i; i = i->next) {
396 393
         if(i->certSign &&
394
+           !i->isBlacklisted &&
397 395
            !memcmp(i->subject, x509->issuer, sizeof(i->subject)) &&
398 396
            !crtmgr_rsa_verify(i, &x509->sig, x509->hashtype, x509->tbshash)) {
399 397
             int curscore;
400 398
             if((x509->codeSign & i->codeSign) == x509->codeSign && (x509->timeSign & i->timeSign) == x509->timeSign)
401 399
                 return i;
400
+            possible++;
402 401
             curscore = (x509->codeSign & i->codeSign) + (x509->timeSign & i->timeSign);
403 402
             if(curscore > score) {
404 403
                 best = i;
... ...
@@ -406,6 +424,12 @@ cli_crt *crtmgr_verify_crt(crtmgr *m, cli_crt *x509) {
406 406
             }
407 407
         }
408 408
     }
409
+
410
+    if (possible > 1) {
411
+        // If this is ever triggered, it's probably an indication of an error
412
+        // in the CRB being used.
413
+        cli_warnmsg("crtmgr_verify_crt: choosing between codeSign cert and timeSign cert without enough info - errors may result\n");
414
+    }
409 415
     return best;
410 416
 }
411 417