Skip to content
API Documentation
BastionGuard™
encrypted_volume_support.cpp
Go to the documentation of this file.
2#include "i18n.hpp"
3
4#include <array>
5#include <set>
6#include <sstream>
7#include <string>
8#include <vector>
9
10namespace {
11uint16_t be16(const unsigned char* p) {
12 return (static_cast<uint16_t>(p[0]) << 8) | static_cast<uint16_t>(p[1]);
13}
14
15uint16_t le16(const unsigned char* p) {
16 return static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8);
17}
18
19uint32_t le32(const unsigned char* p) {
20 return static_cast<uint32_t>(p[0]) |
21 (static_cast<uint32_t>(p[1]) << 8) |
22 (static_cast<uint32_t>(p[2]) << 16) |
23 (static_cast<uint32_t>(p[3]) << 24);
24}
25
26uint64_t le64(const unsigned char* p) {
27 return static_cast<uint64_t>(p[0]) |
28 (static_cast<uint64_t>(p[1]) << 8) |
29 (static_cast<uint64_t>(p[2]) << 16) |
30 (static_cast<uint64_t>(p[3]) << 24) |
31 (static_cast<uint64_t>(p[4]) << 32) |
32 (static_cast<uint64_t>(p[5]) << 40) |
33 (static_cast<uint64_t>(p[6]) << 48) |
34 (static_cast<uint64_t>(p[7]) << 56);
35}
36
37bool read_bytes(DiskReader& reader, uint64_t offset, void* buffer, size_t len) {
38 return reader.read_at(offset, buffer, len);
39}
40
41ScanResult luks_probe(DiskReader& reader, uint64_t offset) {
42 std::array<unsigned char, 4096> hdr{};
43 if (!read_bytes(reader, offset, hdr.data(), hdr.size())) return {};
44 if (!(hdr[0] == 'L' && hdr[1] == 'U' && hdr[2] == 'K' && hdr[3] == 'S' && hdr[4] == 0xBA && hdr[5] == 0xBE)) {
45 return {};
46 }
47
48 const uint16_t version = be16(hdr.data() + 6);
49 std::ostringstream detail;
50 uint64_t payload_offset = 0;
51 if (version == 1) {
52 const uint32_t payload_sectors = le32(hdr.data() + 104);
53 payload_offset = static_cast<uint64_t>(payload_sectors) * 512ull;
54 detail << "LUKS1, payload offset: " << payload_offset << " bytes";
55 } else if (version == 2) {
56 // We intentionally avoid a full JSON parser here; this is enough to identify container geometry safely.
57 const uint64_t hdr_size = le64(hdr.data() + 8);
58 payload_offset = hdr_size >= 4096 ? hdr_size * 2ull : (16ull * 1024 * 1024);
59 detail << "LUKS2, metadata area: " << hdr_size << " bytes, estimated payload offset: " << payload_offset << " bytes";
60 } else {
61 detail << "LUKS version " << version;
62 }
63 detail << ", encrypted container detected (data recovery requires passphrase/key material or an already unlocked mapper device)";
64
66 offset,
67 reader.size() > offset ? (reader.size() - offset) : 0,
68 version == 2 ? "LUKS2 container" : "LUKS1 container",
69 "Encrypted volume probe",
70 detail.str(),
71 0.98};
72}
73
74ScanResult bitlocker_probe(DiskReader& reader, uint64_t offset) {
75 std::array<unsigned char, 512> sector{};
76 if (!read_bytes(reader, offset, sector.data(), sector.size())) return {};
77
78 const std::string oem(reinterpret_cast<const char*>(sector.data() + 3), 8);
79 const std::string fve(reinterpret_cast<const char*>(sector.data() + 0x03), 11);
80 const bool has_fvefs = oem == "-FVE-FS-" || fve.find("FVE-FS") != std::string::npos;
81 if (!has_fvefs) return {};
82
83 std::ostringstream detail;
84 detail << "BitLocker/FVE metadata detected";
85 const uint16_t bps = le16(sector.data() + 11);
86 const uint8_t spc = sector[13];
87 if (bps != 0 && spc != 0) {
88 detail << ", bytes/sector: " << bps << ", sectors/cluster: " << static_cast<unsigned>(spc);
89 }
90 detail << ", encrypted container detected (data recovery requires recovery key/password or an already unlocked device)";
91
93 offset,
94 reader.size() > offset ? (reader.size() - offset) : 0,
95 "BitLocker volume",
96 "Encrypted volume probe",
97 detail.str(),
98 0.97};
99}
100}
101
102std::vector<ScanResult> EncryptedVolumeSupport::probe(DiskReader& reader,
103 const std::vector<ScanResult>& seed_results,
104 std::vector<std::string>& logs) {
105 std::vector<ScanResult> out;
106 std::set<uint64_t> seen_offsets;
107 logs.push_back(trs("Encrypted volume probes: analysis started."));
108
109 for (const auto& seed : seed_results) {
110 const bool interesting_seed =
111 seed.kind == ResultKind::Partition ||
113 seed.kind == ResultKind::FilesystemCandidate ||
114 seed.kind == ResultKind::FilesystemVolume ||
115 seed.kind == ResultKind::NtfsVolume;
116 if (!interesting_seed || seen_offsets.count(seed.offset_bytes) != 0) continue;
117 seen_offsets.insert(seed.offset_bytes);
118
119 if (auto r = luks_probe(reader, seed.offset_bytes); !r.type.empty()) out.push_back(r);
120 if (auto r = bitlocker_probe(reader, seed.offset_bytes); !r.type.empty()) out.push_back(r);
121 }
122
123 // Also probe the whole source offset 0 for raw encrypted-device headers.
124 if (seen_offsets.insert(0).second) {
125 if (auto r = luks_probe(reader, 0); !r.type.empty()) out.push_back(r);
126 if (auto r = bitlocker_probe(reader, 0); !r.type.empty()) out.push_back(r);
127 }
128
129 logs.push_back(trs("Encrypted volume probes: analysis completed."));
130 return out;
131}
132
134 std::vector<EncryptedContainerInfo>& out) {
135 out.clear();
136 for (const auto& r : report.results) {
138 if (r.type == "LUKS1 container") info.scheme = "LUKS1";
139 else if (r.type == "LUKS2 container") info.scheme = "LUKS2";
140 else if (r.type == "BitLocker volume") info.scheme = "BitLocker";
141 else continue;
142 info.offset_bytes = r.offset_bytes;
143 info.size_bytes = r.size_bytes;
144 info.detail = r.detail;
145 // Best-effort payload offset extraction from detail string.
146 const std::string needle = "payload offset: ";
147 const auto pos = r.detail.find(needle);
148 if (pos != std::string::npos) {
149 const auto start = pos + needle.size();
150 const auto end = r.detail.find(' ', start);
151 try {
152 info.payload_offset_bytes = std::stoull(r.detail.substr(start, end - start));
153 } catch (...) {
154 info.payload_offset_bytes = 0;
155 }
156 }
157 out.push_back(info);
158 }
159 return !out.empty();
160}
bool read_at(uint64_t offset, void *buffer, size_t len)
uint64_t size() const
static bool collect_detected_containers(const ScanReport &report, std::vector< EncryptedContainerInfo > &out)
static std::vector< ScanResult > probe(DiskReader &reader, const std::vector< ScanResult > &seed_results, std::vector< std::string > &logs)
std::string trs(const char *msgid)
Definition i18n.cpp:22
@ Partition
Definition models.hpp:8
@ LostPartitionCandidate
Definition models.hpp:9
@ FilesystemCandidate
Definition models.hpp:10
@ FilesystemVolume
Definition models.hpp:11
ScanResult bitlocker_probe(DiskReader &reader, uint64_t offset)
bool read_bytes(DiskReader &reader, uint64_t offset, void *buffer, size_t len)
ScanResult luks_probe(DiskReader &reader, uint64_t offset)
std::vector< ScanResult > results
Definition models.hpp:40