| 85 | } |
| 86 | |
| 87 | std::optional<std::vector<uint8_t>> ExtractEmbeddedPDB(const fs::path& filePath, bool forceScan) |
| 88 | { |
| 89 | std::ifstream file(filePath, std::ios::binary | std::ios::ate); |
| 90 | if (!file) return {}; |
| 91 | |
| 92 | auto fileSize = file.tellg(); |
| 93 | if (fileSize < 32) return {}; |
| 94 | |
| 95 | // --- Fast path: check the very end of the file --- |
| 96 | file.seekg(fileSize - std::streamoff(32)); |
| 97 | std::array<uint8_t, 32> tail{}; |
| 98 | file.read(reinterpret_cast<char*>(tail.data()), 32); |
| 99 | |
| 100 | if (std::equal(kMagic.begin(), kMagic.end(), tail.begin() + 16)) |
| 101 | { |
| 102 | uint64_t originalSize = *reinterpret_cast<uint64_t*>(tail.data()); |
| 103 | uint64_t compressedSize = *reinterpret_cast<uint64_t*>(tail.data() + 8); |
| 104 | |
| 105 | if (compressedSize == 0 || compressedSize > static_cast<uint64_t>(fileSize) - 32) |
| 106 | return {}; |
| 107 | |
| 108 | file.seekg(fileSize - std::streamoff(32) - std::streamoff(compressedSize)); |
| 109 | std::vector<uint8_t> compressed(compressedSize); |
| 110 | file.read(reinterpret_cast<char*>(compressed.data()), compressedSize); |
| 111 | |
| 112 | std::vector<uint8_t> decompressed; |
| 113 | if (DecompressData(compressed, originalSize, decompressed)) |
| 114 | return decompressed; |
| 115 | } |
| 116 | |
| 117 | // --- Signed PE path: check just before the Authenticode certificate --- |
| 118 | uint64_t secOffset = GetPESecurityOffset(file, fileSize); |
| 119 | if (secOffset >= 32) |
| 120 | { |
| 121 | // Signing tools may insert alignment padding (up to 7 bytes) before |
| 122 | // the certificate, so scan a small 64-byte window before it. |
| 123 | constexpr std::streamoff kScanWindow = 64; |
| 124 | auto scanStart = std::max(std::streamoff(0), std::streamoff(secOffset) - kScanWindow); |
| 125 | auto scanLen = static_cast<size_t>(std::streamoff(secOffset) - scanStart); |
| 126 | |
| 127 | if (scanLen >= 32) |
| 128 | { |
| 129 | file.seekg(scanStart); |
| 130 | std::vector<uint8_t> buf(scanLen); |
| 131 | file.read(reinterpret_cast<char*>(buf.data()), scanLen); |
| 132 | |
| 133 | if (file.good()) |
| 134 | { |
| 135 | for (size_t pos = scanLen - 16; pos >= 16; --pos) |
| 136 | { |
| 137 | if (std::equal(kMagic.begin(), kMagic.end(), buf.begin() + pos)) |
| 138 | { |
| 139 | uint64_t originalSize = *reinterpret_cast<uint64_t*>(buf.data() + pos - 16); |
| 140 | uint64_t compressedSize = *reinterpret_cast<uint64_t*>(buf.data() + pos - 8); |
| 141 | |
| 142 | auto trailerFileOff = scanStart + std::streamoff(pos - 16); |
| 143 | if (compressedSize > 0 && compressedSize <= static_cast<uint64_t>(trailerFileOff)) |
| 144 | { |
no test coverage detected