...
|
...
|
@@ -5405,6 +5405,7 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5405
|
5405
|
struct pe_image_data_dir *dirs;
|
5406
|
5406
|
fmap_t *map = *ctx->fmap;
|
5407
|
5407
|
void *hashctx=NULL;
|
|
5408
|
+ struct pe_certificate_hdr cert_hdr;
|
5408
|
5409
|
|
5409
|
5410
|
if (flags & CL_CHECKFP_PE_FLAG_STATS)
|
5410
|
5411
|
if (!(hashes))
|
...
|
...
|
@@ -5439,8 +5440,14 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5439
|
5439
|
if(nsections < 1 || nsections > 96)
|
5440
|
5440
|
return CL_EFORMAT;
|
5441
|
5441
|
|
5442
|
|
- if(EC16(file_hdr.SizeOfOptionalHeader) < sizeof(struct pe_image_optional_hdr32))
|
|
5442
|
+ // TODO the pe_image_optional_hdr32 structure includes space for all 16
|
|
5443
|
+ // data directories, but these might not all exist in a given binary.
|
|
5444
|
+ // We need to check NumberOfRvaAndSizes instead, and allow through any
|
|
5445
|
+ // with at least 5 (the security DataDirectory)
|
|
5446
|
+ if(EC16(file_hdr.SizeOfOptionalHeader) < sizeof(struct pe_image_optional_hdr32)) {
|
|
5447
|
+ cli_dbgmsg("cli_checkfp_pe: SizeOfOptionalHeader < less than the size expected (%lu)\n", sizeof(struct pe_image_optional_hdr32));
|
5443
|
5448
|
return CL_EFORMAT;
|
|
5449
|
+ }
|
5444
|
5450
|
|
5445
|
5451
|
at = e_lfanew + sizeof(struct pe_image_file_hdr);
|
5446
|
5452
|
if(fmap_readn(map, &optional_hdr32, at, sizeof(struct pe_image_optional_hdr32)) != sizeof(struct pe_image_optional_hdr32))
|
...
|
...
|
@@ -5475,6 +5482,17 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5475
|
5475
|
dirs = optional_hdr64.DataDirectory;
|
5476
|
5476
|
}
|
5477
|
5477
|
|
|
5478
|
+ // As an optimization, check the security DataDirectory here and if
|
|
5479
|
+ // it's less than 8-bytes (and we aren't relying on this code to compute
|
|
5480
|
+ // the section hashes), bail out
|
|
5481
|
+ if (EC32(dirs[4].Size) < 8) {
|
|
5482
|
+ if (flags & CL_CHECKFP_PE_FLAG_STATS) {
|
|
5483
|
+ /* If stats is enabled, continue parsing the sample */
|
|
5484
|
+ flags ^= CL_CHECKFP_PE_FLAG_AUTHENTICODE;
|
|
5485
|
+ } else {
|
|
5486
|
+ return CL_VIRUS;
|
|
5487
|
+ }
|
|
5488
|
+ }
|
5478
|
5489
|
fsize = map->len;
|
5479
|
5490
|
|
5480
|
5491
|
valign = (pe_plus)?EC32(optional_hdr64.SectionAlignment):EC32(optional_hdr32.SectionAlignment);
|
...
|
...
|
@@ -5490,12 +5508,17 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5490
|
5490
|
if(!exe_sections)
|
5491
|
5491
|
return CL_EMEM;
|
5492
|
5492
|
|
|
5493
|
+ // TODO I'm not sure why this is necessary since the specification says
|
|
5494
|
+ // that PointerToRawData is expected to be a multiple of the file
|
|
5495
|
+ // alignment. Should we report this is as a PE with an error?
|
5493
|
5496
|
for(i = 0; falign!=0x200 && i<nsections; i++) {
|
5494
|
5497
|
/* file alignment fallback mode - blah */
|
5495
|
5498
|
if (falign && section_hdr[i].SizeOfRawData && EC32(section_hdr[i].PointerToRawData)%falign && !(EC32(section_hdr[i].PointerToRawData)%0x200))
|
5496
|
5499
|
falign = 0x200;
|
5497
|
5500
|
}
|
5498
|
5501
|
|
|
5502
|
+ // TODO Why is this needed? hdr_size should already be rounded up
|
|
5503
|
+ // to a multiple of the file alignment.
|
5499
|
5504
|
hdr_size = PESALIGN(hdr_size, falign); /* Aligned headers virtual size */
|
5500
|
5505
|
|
5501
|
5506
|
if (flags & CL_CHECKFP_PE_FLAG_STATS) {
|
...
|
...
|
@@ -5507,23 +5530,32 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5507
|
5507
|
}
|
5508
|
5508
|
}
|
5509
|
5509
|
|
|
5510
|
+ // TODO Why do we fix up these alignments? This shouldn't be needed?
|
5510
|
5511
|
for(i = 0; i < nsections; i++) {
|
5511
|
5512
|
exe_sections[i].rva = PEALIGN(EC32(section_hdr[i].VirtualAddress), valign);
|
5512
|
5513
|
exe_sections[i].vsz = PESALIGN(EC32(section_hdr[i].VirtualSize), valign);
|
5513
|
5514
|
exe_sections[i].raw = PEALIGN(EC32(section_hdr[i].PointerToRawData), falign);
|
5514
|
5515
|
exe_sections[i].rsz = PESALIGN(EC32(section_hdr[i].SizeOfRawData), falign);
|
5515
|
5516
|
|
|
5517
|
+ // TODO exe_sections[i].ursz is not assigned to (will always be 0)
|
|
5518
|
+ // Figure out what this is meant to do and ensure that happens
|
5516
|
5519
|
if (!exe_sections[i].vsz && exe_sections[i].rsz)
|
5517
|
5520
|
exe_sections[i].vsz=PESALIGN(exe_sections[i].ursz, valign);
|
5518
|
5521
|
|
5519
|
|
- if (exe_sections[i].rsz && fsize>exe_sections[i].raw && !CLI_ISCONTAINED(0, (uint32_t) fsize, exe_sections[i].raw, exe_sections[i].rsz))
|
5520
|
|
- exe_sections[i].rsz = fsize - exe_sections[i].raw;
|
|
5522
|
+ if (exe_sections[i].rsz && fsize>exe_sections[i].raw && !CLI_ISCONTAINED(0, (uint32_t) fsize, exe_sections[i].raw, exe_sections[i].rsz)) {
|
|
5523
|
+ cli_dbgmsg("cli_checkfp_pe: encountered section not fully contained within the file\n");
|
|
5524
|
+ free(exe_sections);
|
|
5525
|
+ return CL_EFORMAT;
|
|
5526
|
+ }
|
5521
|
5527
|
|
5522
|
5528
|
if (exe_sections[i].rsz && exe_sections[i].raw >= fsize) {
|
|
5529
|
+ cli_dbgmsg("cli_checkfp_pe: encountered section that doesn't exist within the file\n");
|
5523
|
5530
|
free(exe_sections);
|
5524
|
5531
|
return CL_EFORMAT;
|
5525
|
5532
|
}
|
5526
|
5533
|
|
|
5534
|
+ // TODO These checks aren't needed because the u vars are never assigned (always 0)
|
|
5535
|
+ // Figure out what this is meant to do and ensure that happens
|
5527
|
5536
|
if (exe_sections[i].urva>>31 || exe_sections[i].uvsz>>31 || (exe_sections[i].rsz && exe_sections[i].uraw>>31) || exe_sections[i].ursz>>31) {
|
5528
|
5537
|
free(exe_sections);
|
5529
|
5538
|
return CL_EFORMAT;
|
...
|
...
|
@@ -5539,16 +5571,33 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5539
|
5539
|
|
5540
|
5540
|
if (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE) {
|
5541
|
5541
|
/* Check to see if we have a security section. */
|
5542
|
|
- if(!cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 2) && dirs[4].Size < 8) {
|
|
5542
|
+ if(!cli_hm_have_size(ctx->engine->hm_fp, CLI_HASH_SHA1, 2) && EC32(dirs[4].Size) < 8) {
|
5543
|
5543
|
if (flags & CL_CHECKFP_PE_FLAG_STATS) {
|
5544
|
5544
|
/* If stats is enabled, continue parsing the sample */
|
5545
|
5545
|
flags ^= CL_CHECKFP_PE_FLAG_AUTHENTICODE;
|
5546
|
5546
|
} else {
|
|
5547
|
+ free(exe_sections);
|
5547
|
5548
|
if (hashctx)
|
5548
|
5549
|
cl_hash_destroy(hashctx);
|
5549
|
5550
|
return CL_BREAK;
|
5550
|
5551
|
}
|
5551
|
5552
|
}
|
|
5553
|
+
|
|
5554
|
+
|
|
5555
|
+ // Verify that we have all the bytes we expect in the authenticode sig
|
|
5556
|
+ // and that the certificate table is the last thing in the file
|
|
5557
|
+ // (according to the MS13-098 bulletin, this is a requirement)
|
|
5558
|
+ if (fsize != EC32(dirs[4].Size) + EC32(dirs[4].VirtualAddress)) {
|
|
5559
|
+ if (flags & CL_CHECKFP_PE_FLAG_STATS) {
|
|
5560
|
+ flags ^= CL_CHECKFP_PE_FLAG_AUTHENTICODE;
|
|
5561
|
+ } else {
|
|
5562
|
+ cli_dbgmsg("cli_checkfp_pe: expected authenticode data at the end of the file\n");
|
|
5563
|
+ free(exe_sections);
|
|
5564
|
+ if (hashctx)
|
|
5565
|
+ cl_hash_destroy(hashctx);
|
|
5566
|
+ return CL_EFORMAT;
|
|
5567
|
+ }
|
|
5568
|
+ }
|
5552
|
5569
|
}
|
5553
|
5570
|
|
5554
|
5571
|
#define hash_chunk(where, size, isStatAble, section) \
|
...
|
...
|
@@ -5614,33 +5663,8 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5614
|
5614
|
continue;
|
5615
|
5615
|
|
5616
|
5616
|
hash_chunk(exe_sections[i].raw, exe_sections[i].rsz, 1, i);
|
5617
|
|
- if (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE)
|
5618
|
|
- at += exe_sections[i].rsz;
|
5619
|
5617
|
}
|
5620
|
5618
|
|
5621
|
|
- while (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE) {
|
5622
|
|
- if((size_t)at < fsize) {
|
5623
|
|
- hlen = fsize - at;
|
5624
|
|
- if(dirs[4].Size > hlen) {
|
5625
|
|
- if (flags & CL_CHECKFP_PE_FLAG_STATS) {
|
5626
|
|
- flags ^= CL_CHECKFP_PE_FLAG_AUTHENTICODE;
|
5627
|
|
- break;
|
5628
|
|
- } else {
|
5629
|
|
- free(exe_sections);
|
5630
|
|
- if (hashctx)
|
5631
|
|
- cl_hash_destroy(hashctx);
|
5632
|
|
- return CL_EFORMAT;
|
5633
|
|
- }
|
5634
|
|
- }
|
5635
|
|
-
|
5636
|
|
- hlen -= dirs[4].Size;
|
5637
|
|
- hash_chunk(at, hlen, 0, 0);
|
5638
|
|
- at += hlen;
|
5639
|
|
- }
|
5640
|
|
-
|
5641
|
|
- break;
|
5642
|
|
- } while (0);
|
5643
|
|
-
|
5644
|
5619
|
free(exe_sections);
|
5645
|
5620
|
|
5646
|
5621
|
if (flags & CL_CHECKFP_PE_FLAG_AUTHENTICODE && hashctx) {
|
...
|
...
|
@@ -5653,13 +5677,30 @@ int cli_checkfp_pe(cli_ctx *ctx, uint8_t *authsha1, stats_section_t *hashes, uin
|
5653
|
5653
|
cli_dbgmsg("Authenticode: %s\n", shatxt);
|
5654
|
5654
|
}
|
5655
|
5655
|
|
5656
|
|
- hlen = dirs[4].Size;
|
5657
|
|
- if(hlen < 8)
|
|
5656
|
+ hlen = EC32(dirs[4].Size);
|
|
5657
|
+
|
|
5658
|
+ if(fmap_readn(map, &cert_hdr, EC32(dirs[4].VirtualAddress), sizeof(cert_hdr)) != sizeof(cert_hdr))
|
|
5659
|
+ return CL_EFORMAT;
|
|
5660
|
+
|
|
5661
|
+ if (EC16(cert_hdr.revision) != WIN_CERT_REV_2) {
|
|
5662
|
+ cli_dbgmsg("cli_checkfp_pe: unsupported authenticode data revision\n");
|
5658
|
5663
|
return CL_VIRUS;
|
|
5664
|
+ }
|
|
5665
|
+
|
|
5666
|
+ if (EC16(cert_hdr.type) != WIN_CERT_TYPE_PKCS7) {
|
|
5667
|
+ cli_dbgmsg("cli_checkfp_pe: unsupported authenticode data type\n");
|
|
5668
|
+ return CL_VIRUS;
|
|
5669
|
+ }
|
|
5670
|
+
|
|
5671
|
+ if (EC32(cert_hdr.length) != hlen) {
|
|
5672
|
+ cli_dbgmsg("cli_checkfp_pe: unexpected authenticode data length\n");
|
|
5673
|
+ return CL_VIRUS;
|
|
5674
|
+ }
|
5659
|
5675
|
|
5660
|
|
- hlen -= 8;
|
|
5676
|
+ at = EC32(dirs[4].VirtualAddress) + sizeof(cert_hdr);
|
|
5677
|
+ hlen -= sizeof(cert_hdr);
|
5661
|
5678
|
|
5662
|
|
- return asn1_check_mscat((struct cl_engine *)(ctx->engine), map, at + 8, hlen, authsha1);
|
|
5679
|
+ return asn1_check_mscat((struct cl_engine *)(ctx->engine), map, at, hlen, authsha1);
|
5663
|
5680
|
} else {
|
5664
|
5681
|
if (hashctx)
|
5665
|
5682
|
cl_hash_destroy(hashctx);
|