Browse code

Use genhash_pe instead of checkfp_pe for section hash computation

cli_checkfp_pe is now effectively the function that just checks
the Authenticode hash. This makes the code less complicated,
and adds some minor improvements:
- section hashes are no longer computed if there is no stats
callback function (at least in that part of the code)
- We now actually set the len field in the stats_section_t
structure
- If an error occurs when computing a section hash, we skip
that section instead of not computing any hashes

Andrew authored on 2019/02/05 08:48:22
Showing 4 changed files
... ...
@@ -567,7 +567,6 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
567 567
     uint8_t shash1[SHA1_HASH_SIZE * 2 + 1];
568 568
     uint8_t shash256[SHA256_HASH_SIZE * 2 + 1];
569 569
     int have_sha1, have_sha256, do_dsig_check = 1;
570
-    stats_section_t sections;
571 570
 
572 571
     if (cli_hm_scan(digest, size, &virname, ctx->engine->hm_fp, CLI_HASH_MD5) == CL_VIRUS) {
573 572
         cli_dbgmsg("cli_checkfp(md5): Found false positive detection (fp sig: %s), size: %d\n", virname, (int)size);
... ...
@@ -585,6 +584,8 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
585 585
                    vname ? vname : "Name");
586 586
     }
587 587
 
588
+    // TODO Replace this with the ability to actually perform detection with
589
+    // the blacklisted sig entries
588 590
     if (vname)
589 591
         do_dsig_check = strncmp("W32S.", vname, 5);
590 592
 
... ...
@@ -652,17 +653,10 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
652 652
     }
653 653
 #endif
654 654
 
655
-    memset(&sections, 0x00, sizeof(stats_section_t));
656
-    if (do_dsig_check || ctx->engine->cb_stats_add_sample) {
657
-        uint32_t flags = (do_dsig_check ? CL_CHECKFP_PE_FLAG_AUTHENTICODE : 0);
658
-        if (!(ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_STATS) && !(ctx->engine->dconf->stats & (DCONF_STATS_DISABLED | DCONF_STATS_PE_SECTION_DISABLED)))
659
-            flags |= CL_CHECKFP_PE_FLAG_STATS;
660
-
661
-        switch (cli_checkfp_pe(ctx, &sections, flags)) {
655
+    if (do_dsig_check) {
656
+        switch (cli_checkfp_pe(ctx)) {
662 657
             case CL_CLEAN:
663 658
                 cli_dbgmsg("cli_checkfp(pe): PE file whitelisted due to valid digital signature\n");
664
-                if (sections.sections)
665
-                    free(sections.sections);
666 659
                 return CL_CLEAN;
667 660
             default:
668 661
                 break;
... ...
@@ -672,11 +666,22 @@ int cli_checkfp_virus(unsigned char *digest, size_t size, cli_ctx *ctx, const ch
672 672
     if (ctx->engine->cb_hash)
673 673
         ctx->engine->cb_hash(fmap_fd(*ctx->fmap), size, (const unsigned char *)md5, vname ? vname : "noname", ctx->cb_ctx);
674 674
 
675
-    if (ctx->engine->cb_stats_add_sample)
675
+    if (ctx->engine->cb_stats_add_sample) {
676
+        stats_section_t sections;
677
+        memset(&sections, 0x00, sizeof(stats_section_t));
678
+
679
+        if (!(ctx->engine->engine_options & ENGINE_OPTIONS_DISABLE_PE_STATS) &&
680
+            !(ctx->engine->dconf->stats & (DCONF_STATS_DISABLED | DCONF_STATS_PE_SECTION_DISABLED)))
681
+            cli_genhash_pe(ctx, CL_GENHASH_PE_CLASS_SECTION, 1, &sections);
682
+
683
+        // TODO We probably only want to call cb_stats_add_sample when
684
+        // sections.section != NULL... leaving as is for now
676 685
         ctx->engine->cb_stats_add_sample(vname ? vname : "noname", digest, size, &sections, ctx->engine->stats_data);
677 686
 
678
-    if (sections.sections)
679
-        free(sections.sections);
687
+        if (sections.sections) {
688
+            free(sections.sections);
689
+        }
690
+    }
680 691
 
681 692
     return CL_VIRUS;
682 693
 }
... ...
@@ -5508,29 +5508,8 @@ static int sort_sects(const void *first, const void *second)
5508 5508
  * CL_CLEAN will be returned if the file was whitelisted based on its
5509 5509
  * signature.  CL_VIRUS will be returned if the file was blacklisted based on
5510 5510
  * its signature.  Otherwise, an cl_error_t error value will be returned.
5511
- * 
5512
- * Also, this function computes the hashes of each section (sorted based on the
5513
- * RVAs of the sections) if the CL_CHECKFP_PE_FLAG_STATS flag exists in flags
5514
- *
5515
- * TODO The code to compute the section hashes is copied from
5516
- * cli_genhash_pe - we should use that function instead where this
5517
- * functionality is needed, since we no longer need to compute the section
5518
- * hashes as part of the authenticode hash calculation.
5519
- * 
5520
- * If the section hashes are to be computed and returned, this function
5521
- * allocates memory for the section hashes, and it's up to the caller to free
5522
- * it.  hashes->sections will be initialized to NULL at the beginning of the
5523
- * function, and if after the call it's value is non-NULL, the memory should be
5524
- * freed.  Furthermore, if hashes->sections is non-NULL, the hashes can assume
5525
- * to be valid regardless of the return code.
5526
- *
5527
- * Also, a few other notes:
5528
- *  - If a section has a virtual size of zero, it's corresponding hash value
5529
- *    will not be computed and the hash contents will be all zeroes.
5530
- *  - TODO Instead of not providing back any hashes when an invalid section is
5531
- *    encountered, would it be better to still compute hashes for the valid
5532
- *    sections? */
5533
-cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5511
+ */
5512
+cl_error_t cli_checkfp_pe(cli_ctx *ctx)
5534 5513
 {
5535 5514
     size_t at;
5536 5515
     unsigned int i, hlen;
... ...
@@ -5554,15 +5533,6 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5554 5554
     if (!(DCONF & PE_CONF_CATALOG))
5555 5555
         return CL_EFORMAT;
5556 5556
 
5557
-    if (flags == CL_CHECKFP_PE_FLAG_NONE)
5558
-        return CL_BREAK;
5559
-
5560
-    if (flags & CL_CHECKFP_PE_FLAG_STATS) {
5561
-        if (!(hashes))
5562
-            return CL_ENULLARG;
5563
-        hashes->sections = NULL;
5564
-    }
5565
-
5566 5557
     // TODO see if peinfo can be passed in (or lives in ctx or something) and
5567 5558
     // if so, use that to avoid having to re-parse the header
5568 5559
     cli_exe_info_init(peinfo, 0);
... ...
@@ -5579,68 +5549,12 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5579 5579
     // the section hashes), bail out if we don't have any Authenticode hashes
5580 5580
     // loaded from .cat files
5581 5581
     if (sec_dir_size < 8 && !cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 2)) {
5582
-        if (flags & CL_CHECKFP_PE_FLAG_STATS) {
5583
-            /* If stats is enabled, continue parsing the sample */
5584
-            flags ^= CL_CHECKFP_PE_FLAG_AUTHENTICODE;
5585
-        } else {
5586
-            cli_exe_info_destroy(peinfo);
5587
-            return CL_BREAK;
5588
-        }
5582
+        cli_exe_info_destroy(peinfo);
5583
+        return CL_BREAK;
5589 5584
     }
5590 5585
     fsize = map->len;
5591 5586
 
5592
-    if (flags & CL_CHECKFP_PE_FLAG_STATS) {
5593
-        hashes->nsections = peinfo->nsections;
5594
-        hashes->sections  = cli_calloc(peinfo->nsections, sizeof(struct cli_section_hash));
5595
-        ;
5596
-        if (!(hashes->sections)) {
5597
-            cli_exe_info_destroy(peinfo);
5598
-            return CL_EMEM;
5599
-        }
5600
-    }
5601
-
5602
-#define free_section_hashes()                   \
5603
-    do {                                        \
5604
-        if (flags & CL_CHECKFP_PE_FLAG_STATS) { \
5605
-            free(hashes->sections);             \
5606
-            hashes->sections = NULL;            \
5607
-        }                                       \
5608
-    } while (0)
5609
-
5610
-    // TODO This likely isn't needed anymore, since we no longer compute
5611
-    // the authenticode hash like the 2008 spec doc says (sort sections
5612
-    // and use the section info to compute the hash)
5613
-    cli_qsort(peinfo->sections, peinfo->nsections, sizeof(*peinfo->sections), sort_sects);
5614
-
5615
-    /* Hash the sections */
5616
-    if (flags & CL_CHECKFP_PE_FLAG_STATS) {
5617
-
5618
-        for (i = 0; i < peinfo->nsections; i++) {
5619
-            const uint8_t *hptr;
5620
-            void *md5ctx;
5621
-
5622
-            if (!peinfo->sections[i].rsz)
5623
-                continue;
5624
-
5625
-            if (!(hptr = fmap_need_off_once(map, peinfo->sections[i].raw, peinfo->sections[i].rsz))) {
5626
-                cli_exe_info_destroy(peinfo);
5627
-                free_section_hashes();
5628
-                return CL_EFORMAT;
5629
-            }
5630
-            md5ctx = cl_hash_init("md5");
5631
-            if (md5ctx) {
5632
-                cl_update_hash(md5ctx, (void *)hptr, peinfo->sections[i].rsz);
5633
-                cl_finish_hash(md5ctx, hashes->sections[i].md5);
5634
-            }
5635
-        }
5636
-    }
5637
-
5638
-    /* After this point it's the caller's responsibility to free
5639
-     * hashes->sections. Also, in the case where we are just computing the
5640
-     * stats, we are finished */
5641
-
5642
-    while (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE) {
5643
-
5587
+    do {
5644 5588
         // We'll build a list of the regions that need to be hashed and pass it to
5645 5589
         // asn1_check_mscat to do hash verification there (the hash algorithm is
5646 5590
         // specified in the PKCS7 structure).  We need to hash up to 4 regions
... ...
@@ -5651,13 +5565,11 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5651 5651
         }
5652 5652
         nregions = 0;
5653 5653
 
5654
-#define add_chunk_to_hash_list(_offset, _size)         \
5655
-    do {                                               \
5656
-        if (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE) { \
5657
-            regions[nregions].offset = (_offset);      \
5658
-            regions[nregions].size   = (_size);        \
5659
-            nregions++;                                \
5660
-        }                                              \
5654
+#define add_chunk_to_hash_list(_offset, _size) \
5655
+    do {                                       \
5656
+        regions[nregions].offset = (_offset);  \
5657
+        regions[nregions].size   = (_size);    \
5658
+        nregions++;                            \
5661 5659
     } while (0)
5662 5660
 
5663 5661
         // Pretty much every case below should return CL_EFORMAT
... ...
@@ -5802,7 +5714,8 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5802 5802
 
5803 5803
         ret = CL_EVERIFY;
5804 5804
         break;
5805
-    } /* while(flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE) */
5805
+
5806
+    } while (0);
5806 5807
 
5807 5808
     if (NULL != hashctx) {
5808 5809
         cl_hash_destroy(hashctx);
... ...
@@ -5816,7 +5729,26 @@ cl_error_t cli_checkfp_pe(cli_ctx *ctx, stats_section_t *hashes, uint32_t flags)
5816 5816
     return ret;
5817 5817
 }
5818 5818
 
5819
-int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type)
5819
+/* Print out either the MD5, SHA1, or SHA256 associated with the imphash or
5820
+ * the individual sections. Also, this function computes the hashes of each
5821
+ * section (sorted based on the RVAs of the sections) if hashes is non-NULL.
5822
+ *
5823
+ * If the section hashes are to be computed and returned, this function
5824
+ * allocates memory for the section hashes, and it's up to the caller to free
5825
+ * it.  hashes->sections will be initialized to NULL at the beginning of the
5826
+ * function, and if after the call it's value is non-NULL, the memory should be
5827
+ * freed.  Furthermore, if hashes->sections is non-NULL, the hashes can assume
5828
+ * to be valid regardless of the return code.
5829
+ *
5830
+ * Also, a few other notes:
5831
+ *  - If a section has a virtual size of zero, it's corresponding hash value
5832
+ *    will not be computed and the hash contents will be all zeroes.
5833
+ *  - If a section extends beyond the end of the file, the section data and
5834
+ *    length will be truncated, and the hash generated accordingly
5835
+ *  - If a section exists completely outside of the file, it won't be included
5836
+ *    in the list of sections, and nsections will be adjusted accordingly.
5837
+ */
5838
+int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type, stats_section_t *hashes)
5820 5839
 {
5821 5840
     unsigned int i;
5822 5841
     struct cli_exe_info _peinfo;
... ...
@@ -5826,9 +5758,20 @@ int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type)
5826 5826
     int genhash[CLI_HASH_AVAIL_TYPES];
5827 5827
     int hlen = 0;
5828 5828
 
5829
+    if (hashes) {
5830
+        hashes->sections = NULL;
5831
+
5832
+        if (class != CL_GENHASH_PE_CLASS_SECTION || type != 1) {
5833
+            cli_dbgmsg("`hashes` can only be populated with MD5 PE section data\n");
5834
+            return CL_EARG;
5835
+        }
5836
+    }
5837
+
5829 5838
     if (class >= CL_GENHASH_PE_CLASS_LAST)
5830 5839
         return CL_EARG;
5831 5840
 
5841
+    // TODO see if peinfo can be passed in (or lives in ctx or something) and
5842
+    // if so, use that to avoid having to re-parse the header
5832 5843
     cli_exe_info_init(peinfo, 0);
5833 5844
 
5834 5845
     if (cli_peheader(*ctx->fmap, peinfo, CLI_PEHEADER_OPT_NONE, NULL) != CLI_PEHEADER_RET_SUCCESS) {
... ...
@@ -5864,35 +5807,54 @@ int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type)
5864 5864
         return CL_EMEM;
5865 5865
     }
5866 5866
 
5867
+    if (hashes) {
5868
+        hashes->nsections = peinfo->nsections;
5869
+        hashes->sections  = cli_calloc(peinfo->nsections, sizeof(struct cli_section_hash));
5870
+
5871
+        if (!(hashes->sections)) {
5872
+            cli_exe_info_destroy(peinfo);
5873
+            free(hash);
5874
+            return CL_EMEM;
5875
+        }
5876
+    }
5877
+
5867 5878
     if (class == CL_GENHASH_PE_CLASS_SECTION) {
5868
-        char *dstr = NULL;
5879
+        char *dstr;
5869 5880
 
5870 5881
         for (i = 0; i < peinfo->nsections; i++) {
5871 5882
             /* Generate hashes */
5872 5883
             if (cli_hashsect(*ctx->fmap, &peinfo->sections[i], hashset, genhash, genhash) == 1) {
5873
-                dstr = cli_str2hex((char *)hash, hlen);
5874
-                cli_dbgmsg("Section{%u}: %u:%s\n", i, peinfo->sections[i].rsz, dstr ? (char *)dstr : "(NULL)");
5875
-                if (dstr != NULL) {
5876
-                    free(dstr);
5877
-                    dstr = NULL;
5884
+                if (cli_debug_flag) {
5885
+                    dstr = cli_str2hex((char *)hash, hlen);
5886
+                    cli_dbgmsg("Section{%u}: %u:%s\n", i, peinfo->sections[i].rsz, dstr ? (char *)dstr : "(NULL)");
5887
+                    if (dstr != NULL) {
5888
+                        free(dstr);
5889
+                    }
5878 5890
                 }
5879
-            } else {
5891
+                if (hashes) {
5892
+                    memcpy(hashes->sections[i].md5, hash, sizeof(hashes->sections[i].md5));
5893
+                    hashes->sections[i].len = peinfo->sections[i].rsz;
5894
+                }
5895
+            } else if (peinfo->sections[i].rsz) {
5880 5896
                 cli_dbgmsg("Section{%u}: failed to generate hash for section\n", i);
5897
+            } else {
5898
+                cli_dbgmsg("Section{%u}: section contains no data\n", i);
5881 5899
             }
5882 5900
         }
5883 5901
     } else if (class == CL_GENHASH_PE_CLASS_IMPTBL) {
5884
-        char *dstr     = NULL;
5902
+        char *dstr;
5885 5903
         uint32_t impsz = 0;
5886 5904
         int ret;
5887 5905
 
5888 5906
         /* Generate hash */
5889 5907
         ret = hash_imptbl(ctx, hashset, &impsz, genhash, peinfo);
5890 5908
         if (ret == CL_SUCCESS) {
5891
-            dstr = cli_str2hex((char *)hash, hlen);
5892
-            cli_dbgmsg("Imphash: %s:%u\n", dstr ? (char *)dstr : "(NULL)", impsz);
5893
-            if (dstr != NULL) {
5894
-                free(dstr);
5895
-                dstr = NULL;
5909
+            if (cli_debug_flag) {
5910
+                dstr = cli_str2hex((char *)hash, hlen);
5911
+                cli_dbgmsg("Imphash: %s:%u\n", dstr ? (char *)dstr : "(NULL)", impsz);
5912
+                if (dstr != NULL) {
5913
+                    free(dstr);
5914
+                }
5896 5915
             }
5897 5916
         } else {
5898 5917
             cli_dbgmsg("Imphash: failed to generate hash for import table (%d)\n", ret);
... ...
@@ -5901,8 +5863,7 @@ int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type)
5901 5901
         cli_dbgmsg("cli_genhash_pe: unknown pe genhash class: %u\n", class);
5902 5902
     }
5903 5903
 
5904
-    if (hash)
5905
-        free(hash);
5904
+    free(hash);
5906 5905
     cli_exe_info_destroy(peinfo);
5907 5906
     return CL_SUCCESS;
5908 5907
 }
... ...
@@ -98,8 +98,8 @@ 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, stats_section_t *hashes, uint32_t flags);
102
-int cli_genhash_pe(cli_ctx *ctx, unsigned int class, int type);
101
+cl_error_t cli_checkfp_pe(cli_ctx *ctx);
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);
105 105
 void findres(uint32_t, uint32_t, fmap_t *map, struct cli_exe_info *, int (*)(void *, uint32_t, uint32_t, uint32_t, uint32_t), void *);
... ...
@@ -255,10 +255,10 @@ static int hashpe(const char *filename, unsigned int class, int type)
255 255
     /* Send to PE-specific hasher */
256 256
     switch (class) {
257 257
         case 1:
258
-            ret = cli_genhash_pe(&ctx, CL_GENHASH_PE_CLASS_SECTION, type);
258
+            ret = cli_genhash_pe(&ctx, CL_GENHASH_PE_CLASS_SECTION, type, NULL);
259 259
             break;
260 260
         case 2:
261
-            ret = cli_genhash_pe(&ctx, CL_GENHASH_PE_CLASS_IMPTBL, type);
261
+            ret = cli_genhash_pe(&ctx, CL_GENHASH_PE_CLASS_IMPTBL, type, NULL);
262 262
             break;
263 263
         default:
264 264
             mprintf("!hashpe: unknown classification(%u) for pe hash!\n", class);
... ...
@@ -3455,7 +3455,7 @@ static int dumpcerts(const struct optstruct *opts)
3455 3455
         return -1;
3456 3456
     }
3457 3457
 
3458
-    ret = cli_checkfp_pe(&ctx, NULL, CL_CHECKFP_PE_FLAG_AUTHENTICODE);
3458
+    ret = cli_checkfp_pe(&ctx);
3459 3459
 
3460 3460
     switch (ret) {
3461 3461
         case CL_CLEAN:
... ...
@@ -3467,6 +3467,9 @@ static int dumpcerts(const struct optstruct *opts)
3467 3467
         case CL_BREAK:
3468 3468
             mprintf("*dumpcerts: CL_BREAK after cli_checkfp_pe()!\n");
3469 3469
             break;
3470
+        case CL_EVERIFY:
3471
+            mprintf("!dumpcerts: CL_EVERIFY after cli_checkfp_pe()!\n");
3472
+            break;
3470 3473
         case CL_EFORMAT:
3471 3474
             mprintf("!dumpcerts: An error occurred when parsing the file\n");
3472 3475
             break;