Skip to content

Commit d16e9ec

Browse files
authored
Fix hash calculation (#62)
* Fix hash calculation * Use clang-format
1 parent f5877f9 commit d16e9ec

File tree

2 files changed

+39
-58
lines changed

2 files changed

+39
-58
lines changed

src/uthenticode.cpp

Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -449,13 +449,6 @@ std::string calculate_checksum(peparse::parsed_pe *pe, checksum_kind kind) {
449449
return {};
450450
}
451451

452-
/* We'll stash the bits of the PE that we need to hash in this buffer.
453-
* Reserve the original PE's size upfront, since we expect the hashed data
454-
* to be only slightly smaller.
455-
*/
456-
std::vector<std::uint8_t> pe_bits;
457-
pe_bits.reserve(pe->fileBuffer->bufLen);
458-
459452
/* In both PEs and PE32+s, the PE checksum is 64 bytes into the optional header,
460453
* which itself is 24 bytes after the PE magic and COFF header from the offset
461454
* specified in the DOS header.
@@ -499,48 +492,13 @@ std::string calculate_checksum(peparse::parsed_pe *pe, checksum_kind kind) {
499492
return {};
500493
}
501494

502-
pe_bits.insert(pe_bits.begin(), header_buf->buf, header_buf->buf + header_buf->bufLen);
503-
delete header_buf;
504-
505-
/* Erase the PE checksum and certificate table entry from pe_bits.
506-
* Do the certificate table entry first, so that we don't have to rescale the checksum's offset.
507-
*/
508-
pe_bits.erase(pe_bits.begin() + cert_table_offset, pe_bits.begin() + cert_table_offset + 8);
509-
pe_bits.erase(pe_bits.begin() + pe_checksum_offset, pe_bits.begin() + pe_checksum_offset + 4);
510-
511-
/* Build up the list of sections in the PE, in ascending order by PointerToRawData
512-
* (i.e., by file offset).
513-
*
514-
* NOTE(ww): Ideally we'd use a capture with the C++ lambda here, but C++ lambdas can't be
515-
* used within C callbacks unless they're captureless.
516-
*/
517-
impl::SectionList sections;
518-
peparse::IterSec(
519-
pe,
520-
[](void *cbd,
521-
[[maybe_unused]] const peparse::VA &secBase,
522-
[[maybe_unused]] const std::string &sectionName,
523-
[[maybe_unused]] const peparse::image_section_header &sec,
524-
const peparse::bounded_buffer *b) -> int {
525-
auto &sections = *static_cast<impl::SectionList *>(cbd);
526-
sections.emplace_back(b);
527-
return 0;
528-
},
529-
&sections);
530-
531-
/* Copy each section's data into pe_bits, in ascending order.
495+
/* We'll stash the bits of the PE that we need to hash in this buffer.
532496
*/
533-
for (const auto &section : sections) {
534-
pe_bits.insert(pe_bits.end(), section->buf, section->buf + section->bufLen);
535-
}
497+
std::vector<std::uint8_t> pe_bits;
498+
pe_bits.reserve(size_of_headers);
536499

537-
/* Also copy any data that happens to be trailing the certificate table into pe_bits.
538-
* Most PEs won't have any trailing data but the Authenticode specification is explicit about
539-
* hashing any if it exists.
540-
*/
541-
pe_bits.insert(pe_bits.end(),
542-
pe->fileBuffer->buf + security_dir.VirtualAddress + security_dir.Size,
543-
pe->fileBuffer->buf + pe->fileBuffer->bufLen);
500+
pe_bits.insert(pe_bits.begin(), header_buf->buf, header_buf->buf + header_buf->bufLen);
501+
delete header_buf;
544502

545503
/* This won't happen under normal conditions, but could with a tampered input.
546504
* We don't have to check pe_checksum_offset here since it'll always be strictly less
@@ -550,19 +508,42 @@ std::string calculate_checksum(peparse::parsed_pe *pe, checksum_kind kind) {
550508
return {};
551509
}
552510

553-
/* Finally, hash the damn thing.
554-
*
555-
* NOTE(ww): Instead of building up pe_bits and hashing it in one pass, we
556-
* could hash it incrementally with each section. This would also solve
557-
* the capture problem with the C++ callback above and would reduce
558-
* the number of needed allocations.
511+
/* Erase the PE checksum and certificate table entry from pe_bits.
512+
* Do the certificate table entry first, so that we don't have to rescale the checksum's offset.
513+
*/
514+
pe_bits.erase(pe_bits.begin() + cert_table_offset, pe_bits.begin() + cert_table_offset + 8);
515+
pe_bits.erase(pe_bits.begin() + pe_checksum_offset, pe_bits.begin() + pe_checksum_offset + 4);
516+
517+
/* Hash pe_bits, which contains the rest of the header.
559518
*/
560-
std::array<std::uint8_t, EVP_MAX_MD_SIZE> md_buf;
561519
const auto *md = EVP_get_digestbynid(nid);
562520
auto *md_ctx = EVP_MD_CTX_new();
563-
564521
EVP_DigestInit(md_ctx, md);
565522
EVP_DigestUpdate(md_ctx, pe_bits.data(), pe_bits.size());
523+
524+
if (security_dir.VirtualAddress > 0) {
525+
/* If a certificate table exists, hash everything before and after it.
526+
*/
527+
EVP_DigestUpdate(md_ctx,
528+
pe->fileBuffer->buf + size_of_headers,
529+
security_dir.VirtualAddress - size_of_headers);
530+
531+
/* Most PEs won't have any trailing data but the Authenticode specification is explicit about
532+
* hashing any if it exists.
533+
*/
534+
EVP_DigestUpdate(md_ctx,
535+
pe->fileBuffer->buf + security_dir.VirtualAddress + security_dir.Size,
536+
pe->fileBuffer->bufLen - (security_dir.VirtualAddress + security_dir.Size));
537+
} else {
538+
/* If there's no certificate table, just hash the rest of the file.
539+
*/
540+
EVP_DigestUpdate(
541+
md_ctx, pe->fileBuffer->buf + size_of_headers, pe->fileBuffer->bufLen - size_of_headers);
542+
}
543+
544+
/* Finally, finish hashing the damn thing.
545+
*/
546+
std::array<std::uint8_t, EVP_MAX_MD_SIZE> md_buf;
566547
EVP_DigestFinal(md_ctx, md_buf.data(), nullptr);
567548
EVP_MD_CTX_free(md_ctx);
568549

test/uthenticode-test.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,16 @@ TEST_F(NoAuthTest, calculate_checksum) {
7979

8080
auto md5 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::MD5);
8181
EXPECT_EQ(md5.size(), 32);
82-
EXPECT_STRCASEEQ(md5.c_str(), "6f7ac8c17504fad04fba2a552a122e07");
82+
EXPECT_STRCASEEQ(md5.c_str(), "A31557B1E39554C88C69AAE1DFAAF314");
8383

8484
auto sha1 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::SHA1);
8585
EXPECT_EQ(sha1.size(), 40);
86-
EXPECT_STRCASEEQ(sha1.c_str(), "4ba40c91418e28cb630b97cfde1cff0905a91139");
86+
EXPECT_STRCASEEQ(sha1.c_str(), "2B316F0552972605D509321F31F4274533C93161");
8787

8888
auto sha256 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::SHA256);
8989
EXPECT_EQ(sha256.size(), 64);
9090
EXPECT_STRCASEEQ(sha256.c_str(),
91-
"e260f8a57faa823453bd95525068780fcbfb0332003d9b606cb9a27e605a8d7b");
91+
"6B7FA3E8298F33BC47F4ABB9C845930B1EACC0DAD96503CFA52D4EA18DDC89F0");
9292
}
9393

9494
TEST_F(Auth32Test, calculate_checksum) {

0 commit comments

Comments
 (0)