Skip to content

Commit 870b7a8

Browse files
Addressing issue #49, offset calculation (#52)
- In the previous implementation there were two issues which this fixes: 1. While the offset would have increased across loop runs, the end iterator was always stuck at the start of the security directory plus the length of the current certificate. There was no correction for further certificates, as the comments suggested. 2. The offset after populating the vector was already at value 8 and so the calculation of the offset to the next WIN_CERTIFICATE struct would end up 8 bytes beyond where it should have. - Added some additional bounds checking and commented on it in the code - Adding a doctored PE file based on pegoat-authenticode.exe by merely appending the whole WIN_CERTIFICATE once again and doubling the size of the security directory given in the optional header. While this isn't a validly signed PE file anymore, it provides a contrived test case for uthenticode::read_certs() nevertheless. There still no good test cases for this, other than the possibility to doctor files with multiple WIN_CERTIFICATE structs in their security directory, thereby invalidating the actual signature (since we need to adjust the size of the security directory in the optional header, too).
1 parent ea3538c commit 870b7a8

6 files changed

+114
-11
lines changed

src/uthenticode.cpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -389,25 +389,30 @@ std::vector<WinCert> read_certs(peparse::parsed_pe *pe) {
389389
* bCertificate (uint8_t[dwLength - 8]): the raw certificate data in this entry
390390
*/
391391
std::vector<WinCert> certs;
392-
size_t offset = 0;
393-
while (offset < raw_cert_table.size()) {
394-
std::uint32_t length = *reinterpret_cast<std::uint32_t *>(raw_cert_table.data() + offset);
392+
auto *current_wincert = raw_cert_table.data();
393+
auto const *const past_secdir = raw_cert_table.data() + raw_cert_table.size();
394+
// Let us tread carefully, we expect all members of WIN_CERTIFICATE up to bCertificate
395+
while (current_wincert + 8 < past_secdir) {
396+
size_t offset = 0;
397+
std::uint32_t length = *reinterpret_cast<std::uint32_t *>(current_wincert + offset);
395398
offset += sizeof(length);
396399

397-
std::uint16_t revision = *reinterpret_cast<std::uint16_t *>(raw_cert_table.data() + offset);
400+
std::uint16_t revision = *reinterpret_cast<std::uint16_t *>(current_wincert + offset);
398401
offset += sizeof(revision);
399402

400-
std::uint16_t type = *reinterpret_cast<std::uint16_t *>(raw_cert_table.data() + offset);
403+
std::uint16_t type = *reinterpret_cast<std::uint16_t *>(current_wincert + offset);
401404
offset += sizeof(type);
402405

403-
std::vector<std::uint8_t> cert_data(raw_cert_table.data() + offset,
404-
raw_cert_table.data() + length);
406+
// Continue only we can satisfy the length
407+
if (current_wincert + length <= past_secdir) {
408+
std::vector<std::uint8_t> cert_data(current_wincert + offset, current_wincert + length);
405409

406-
certs.emplace_back(static_cast<certificate_revision>(revision),
407-
static_cast<certificate_type>(type),
408-
cert_data);
410+
certs.emplace_back(static_cast<certificate_revision>(revision),
411+
static_cast<certificate_type>(type),
412+
cert_data);
413+
}
409414

410-
offset += round(length, 8);
415+
current_wincert += round(length, 8);
411416
}
412417

413418
return certs;
Binary file not shown.

test/helpers.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ class Auth32Test : public ::testing::Test {
3232
peparse::parsed_pe *pe{nullptr};
3333
};
3434

35+
class Auth32DupeTest : public ::testing::Test {
36+
protected:
37+
void SetUp() override {
38+
auto *file = UTHENTICODE_TEST_ASSETS "/32/pegoat-authenticode_duplicated.exe";
39+
40+
pe = peparse::ParsePEFromFile(file);
41+
ASSERT_TRUE(pe != nullptr);
42+
}
43+
44+
void TearDown() override {
45+
peparse::DestructParsedPE(pe);
46+
}
47+
48+
peparse::parsed_pe *pe{nullptr};
49+
};
50+
3551
class Auth32PlusTest : public ::testing::Test {
3652
protected:
3753
void SetUp() override {

test/signeddata-test.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ TEST_F(Auth32Test, SignedData_properties) {
2525
ASSERT_EQ(signed_data->get_nested_signed_data(), std::nullopt);
2626
}
2727

28+
TEST_F(Auth32DupeTest, SignedData_properties) {
29+
auto certs = uthenticode::read_certs(pe);
30+
EXPECT_EQ(certs.size(), 2);
31+
32+
for (size_t index = 0; index < certs.size(); index++) {
33+
SCOPED_TRACE("certificate # = " + std::to_string(index));
34+
auto signed_data = certs[index].as_signed_data();
35+
36+
ASSERT_TRUE(signed_data.has_value());
37+
38+
ASSERT_EQ(signed_data->get_signers().size(), 1);
39+
ASSERT_EQ(signed_data->get_certificates().size(), 1);
40+
ASSERT_TRUE(signed_data->verify_signature());
41+
42+
auto checksum = signed_data->get_checksum();
43+
ASSERT_EQ(std::get<uthenticode::checksum_kind>(checksum), uthenticode::checksum_kind::SHA1);
44+
auto const checksumstr = std::get<std::string>(checksum);
45+
ASSERT_EQ(checksumstr.size(), 40);
46+
ASSERT_STRCASEEQ(checksumstr.c_str(), "6663dd7c24fa84fce7f16e0b02689952c06cfa22");
47+
48+
ASSERT_EQ(signed_data->get_nested_signed_data(), std::nullopt);
49+
}
50+
}
51+
2852
TEST_F(Auth32PlusTest, SignedData_properties) {
2953
auto certs = uthenticode::read_certs(pe);
3054
auto signed_data = certs[0].as_signed_data();

test/uthenticode-test.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ TEST_F(Auth32Test, read_certs) {
2525
EXPECT_TRUE(certs[0].as_signed_data().has_value());
2626
}
2727

28+
TEST_F(Auth32DupeTest, read_certs) {
29+
auto certs = uthenticode::read_certs(pe);
30+
31+
EXPECT_EQ(certs.size(), 2);
32+
for (size_t index = 0; index < certs.size(); index++) {
33+
SCOPED_TRACE("certificate # = " + std::to_string(index));
34+
EXPECT_TRUE(certs[index].as_signed_data().has_value());
35+
}
36+
}
37+
2838
TEST_F(Auth32PlusTest, read_certs) {
2939
auto certs = uthenticode::read_certs(pe);
3040

@@ -45,6 +55,17 @@ TEST_F(Auth32Test, get_checksums) {
4555
EXPECT_EQ(std::get<uthenticode::checksum_kind>(checksums[0]), uthenticode::checksum_kind::SHA1);
4656
}
4757

58+
TEST_F(Auth32DupeTest, get_checksums) {
59+
auto checksums = uthenticode::get_checksums(pe);
60+
61+
EXPECT_EQ(checksums.size(), 2);
62+
for (size_t index = 0; index < checksums.size(); index++) {
63+
SCOPED_TRACE("checksum # = " + std::to_string(index));
64+
EXPECT_EQ(std::get<uthenticode::checksum_kind>(checksums[index]),
65+
uthenticode::checksum_kind::SHA1);
66+
}
67+
}
68+
4869
TEST_F(Auth32PlusTest, get_checksums) {
4970
auto checksums = uthenticode::get_checksums(pe);
5071

@@ -88,6 +109,24 @@ TEST_F(Auth32Test, calculate_checksum) {
88109
"ea013992f99840f76dcac225dd1262edcec3254511b250a6d1e98d99fc48f815");
89110
}
90111

112+
TEST_F(Auth32DupeTest, calculate_checksum) {
113+
auto unk = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::UNKNOWN);
114+
EXPECT_TRUE(unk.empty());
115+
116+
auto md5 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::MD5);
117+
EXPECT_EQ(md5.size(), 32);
118+
EXPECT_STRCASEEQ(md5.c_str(), "64c29391b57679b2973ac562cf64685d");
119+
120+
auto sha1 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::SHA1);
121+
EXPECT_EQ(sha1.size(), 40);
122+
EXPECT_STRCASEEQ(sha1.c_str(), "6663dd7c24fa84fce7f16e0b02689952c06cfa22");
123+
124+
auto sha256 = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::SHA256);
125+
EXPECT_EQ(sha256.size(), 64);
126+
EXPECT_STRCASEEQ(sha256.c_str(),
127+
"ea013992f99840f76dcac225dd1262edcec3254511b250a6d1e98d99fc48f815");
128+
}
129+
91130
TEST_F(Auth32PlusTest, calculate_checksum) {
92131
auto unk = uthenticode::calculate_checksum(pe, uthenticode::checksum_kind::UNKNOWN);
93132
EXPECT_TRUE(unk.empty());
@@ -118,6 +157,13 @@ TEST_F(Auth32Test, verify) {
118157
EXPECT_TRUE(uthenticode::verify(pe));
119158
}
120159

160+
TEST_F(Auth32DupeTest, verify) {
161+
// File is doctored and therefore signatures (and PE checksum) are invalid, but
162+
// this isn't checked here. Instead this checks the signature, but not _against_
163+
// the file hash (minus checksum, minus security data directory).
164+
EXPECT_TRUE(uthenticode::verify(pe));
165+
}
166+
121167
TEST_F(Auth32PlusTest, verify) {
122168
EXPECT_TRUE(uthenticode::verify(pe));
123169
}

test/wincert-test.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ TEST_F(Auth32Test, WinCert_properties) {
1414
ASSERT_EQ(certs[0].get_type(), uthenticode::certificate_type::CERT_TYPE_PKCS_SIGNED_DATA);
1515
}
1616

17+
TEST_F(Auth32DupeTest, WinCert_properties) {
18+
auto certs = uthenticode::read_certs(pe);
19+
20+
ASSERT_EQ(certs.size(), 2);
21+
for (size_t index = 0; index < certs.size(); index++) {
22+
SCOPED_TRACE("certificate # = " + std::to_string(index));
23+
ASSERT_TRUE(certs[index].get_length() > 0);
24+
ASSERT_EQ(certs[index].get_revision(), uthenticode::certificate_revision::CERT_REVISION_2_0);
25+
ASSERT_EQ(certs[index].get_type(), uthenticode::certificate_type::CERT_TYPE_PKCS_SIGNED_DATA);
26+
}
27+
}
28+
1729
TEST_F(Auth32PlusTest, WinCert_properties) {
1830
auto certs = uthenticode::read_certs(pe);
1931

0 commit comments

Comments
 (0)