Add KaitaiStruct parsing of Phoenix VSS2

This commit is contained in:
Nikolaj Schlej 2025-02-23 05:39:23 +07:00
parent 489b85fd98
commit 34904bdc5d
5 changed files with 221 additions and 25 deletions

View file

@ -8,7 +8,7 @@ phoenix_vss2_t::phoenix_vss2_t(kaitai::kstream* p__io, kaitai::kstruct* p__paren
m__root = this; (void)p__root;
m_body = nullptr;
m__io__raw_body = nullptr;
f_header_size = false;
f_len_vss2_store_header = false;
_read();
}
@ -39,7 +39,7 @@ void phoenix_vss2_t::_read() {
m_vss2_size = m__io->read_u4le();
{
uint32_t _ = vss2_size();
if (!( ((_ > header_size()) && (_ < 4294967295UL)) )) {
if (!( ((_ > len_vss2_store_header()) && (_ < 4294967295UL)) )) {
throw kaitai::validation_expr_error<uint32_t>(vss2_size(), _io(), std::string("/seq/3"));
}
}
@ -53,7 +53,7 @@ void phoenix_vss2_t::_read() {
m_state = m__io->read_u1();
m_reserved = m__io->read_u2le();
m_reserved1 = m__io->read_u4le();
m__raw_body = m__io->read_bytes((vss2_size() - header_size()));
m__raw_body = m__io->read_bytes((vss2_size() - len_vss2_store_header()));
m__io__raw_body = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_body));
m_body = std::unique_ptr<vss2_store_body_t>(new vss2_store_body_t(m__io__raw_body.get(), this, m__root));
}
@ -125,10 +125,13 @@ phoenix_vss2_t::vss2_variable_t::vss2_variable_t(kaitai::kstream* p__io, phoenix
m__root = p__root;
m_attributes = nullptr;
f_is_auth = false;
f_len_standard_header = false;
f_end_offset_auth = false;
f_len_alignment_padding = false;
f_len_auth_header = false;
f_end_offset = false;
f_len_alignment_padding_auth = false;
f_is_valid = false;
f_offset = false;
_read();
}
@ -298,10 +301,18 @@ bool phoenix_vss2_t::vss2_variable_t::is_auth() {
return m_is_auth;
}
int8_t phoenix_vss2_t::vss2_variable_t::len_standard_header() {
if (f_len_standard_header)
return m_len_standard_header;
m_len_standard_header = 32;
f_len_standard_header = true;
return m_len_standard_header;
}
int32_t phoenix_vss2_t::vss2_variable_t::end_offset_auth() {
if (f_end_offset_auth)
return m_end_offset_auth;
m_end_offset_auth = _io()->pos();
m_end_offset_auth = (int32_t)_io()->pos();
f_end_offset_auth = true;
return m_end_offset_auth;
}
@ -314,10 +325,18 @@ int32_t phoenix_vss2_t::vss2_variable_t::len_alignment_padding() {
return m_len_alignment_padding;
}
int8_t phoenix_vss2_t::vss2_variable_t::len_auth_header() {
if (f_len_auth_header)
return m_len_auth_header;
m_len_auth_header = 60;
f_len_auth_header = true;
return m_len_auth_header;
}
int32_t phoenix_vss2_t::vss2_variable_t::end_offset() {
if (f_end_offset)
return m_end_offset;
m_end_offset = _io()->pos();
m_end_offset = (int32_t)_io()->pos();
f_end_offset = true;
return m_end_offset;
}
@ -330,18 +349,26 @@ int32_t phoenix_vss2_t::vss2_variable_t::len_alignment_padding_auth() {
return m_len_alignment_padding_auth;
}
bool phoenix_vss2_t::vss2_variable_t::is_valid() {
if (f_is_valid)
return m_is_valid;
m_is_valid = ((state() == 127) || (state() == 63)) ;
f_is_valid = true;
return m_is_valid;
}
int32_t phoenix_vss2_t::vss2_variable_t::offset() {
if (f_offset)
return m_offset;
m_offset = _io()->pos();
m_offset = (int32_t)_io()->pos();
f_offset = true;
return m_offset;
}
int32_t phoenix_vss2_t::header_size() {
if (f_header_size)
return m_header_size;
m_header_size = (7 * 4);
f_header_size = true;
return m_header_size;
int32_t phoenix_vss2_t::len_vss2_store_header() {
if (f_len_vss2_store_header)
return m_len_vss2_store_header;
m_len_vss2_store_header = (7 * 4);
f_len_vss2_store_header = true;
return m_len_vss2_store_header;
}

View file

@ -109,6 +109,13 @@ public:
public:
bool is_auth();
private:
bool f_len_standard_header;
int8_t m_len_standard_header;
public:
int8_t len_standard_header();
private:
bool f_end_offset_auth;
int32_t m_end_offset_auth;
@ -123,6 +130,13 @@ public:
public:
int32_t len_alignment_padding();
private:
bool f_len_auth_header;
int8_t m_len_auth_header;
public:
int8_t len_auth_header();
private:
bool f_end_offset;
int32_t m_end_offset;
@ -137,6 +151,13 @@ public:
public:
int32_t len_alignment_padding_auth();
private:
bool f_is_valid;
bool m_is_valid;
public:
bool is_valid();
private:
bool f_offset;
int32_t m_offset;
@ -316,11 +337,11 @@ public:
};
private:
bool f_header_size;
int32_t m_header_size;
bool f_len_vss2_store_header;
int32_t m_len_vss2_store_header;
public:
int32_t header_size();
int32_t len_vss2_store_header();
private:
uint32_t m_signature;

View file

@ -23,7 +23,7 @@ seq:
- id: vss2_size
type: u4
valid:
expr: _ > header_size and _ < 0xFFFFFFFF
expr: _ > len_vss2_store_header and _ < 0xFFFFFFFF
- id: format
type: u1
valid:
@ -36,9 +36,9 @@ seq:
type: u4
- id: body
type: vss2_store_body
size: vss2_size - header_size
size: vss2_size - len_vss2_store_header
instances:
header_size:
len_vss2_store_header:
value: 7 * sizeof<u4>
types:
@ -150,5 +150,11 @@ types:
value: (((end_offset - offset)+3) & ~3) - (end_offset - offset)
len_alignment_padding_auth:
value: (((end_offset_auth - offset)+3) & ~3) - (end_offset - offset)
is_valid:
value: state == 0x7F or state == 0x3F
is_auth:
value: (attributes.auth_write or attributes.time_based_auth or attributes.append_write) or (len_name == 0 or len_data == 0)
len_auth_header:
value: 60
len_standard_header:
value: 32

View file

@ -361,7 +361,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
UString text;
info.clear();
name.clear();
// This is thew terminating entry, needs special processing
if (variable->_is_null_signature_last()) {
// Add free space or padding after all variables, if needed
@ -385,7 +385,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
// This is a normal entry
UINT32 variableSize;
if (variable->is_intel_legacy()) {
if (variable->is_intel_legacy()) { // Intel legacy
subtype = Subtypes::IntelVssEntry;
// Needs some additional parsing of variable->intel_legacy_data to separate the name from the value
text = uFromUcs2(variable->intel_legacy_data().c_str());
@ -397,7 +397,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
name = guidToUString(variableGuid);
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
}
else if (variable->is_auth()) {
else if (variable->is_auth()) { // Authenticated
subtype = Subtypes::AuthVssEntry;
header = vss.mid(vssVariableOffset, variable->len_auth_header() + variable->len_name_auth());
body = vss.mid(vssVariableOffset + header.size(), variable->len_data_auth());
@ -407,7 +407,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
text = uFromUcs2(variable->name_auth().c_str());
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
}
else if (!variable->_is_null_apple_data_crc32()) {
else if (!variable->_is_null_apple_data_crc32()) { // Apple CRC32
subtype = Subtypes::AppleVssEntry;
header = vss.mid(vssVariableOffset, variable->len_apple_header() + variable->len_name());
body = vss.mid(vssVariableOffset + header.size(), variable->len_data());
@ -417,7 +417,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
text = uFromUcs2(variable->name().c_str());
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
}
else {
else { // Standard
subtype = Subtypes::StandardVssEntry;
header = vss.mid(vssVariableOffset, variable->len_standard_header() + variable->len_name());
body = vss.mid(vssVariableOffset + header.size(), variable->len_data());
@ -445,7 +445,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
+ (variable->attributes()->apple_data_checksum() << 31);
// Add generic info
info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nReserved: %02X\nAttributes: %08Xh (",
info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nReserved: %02Xh\nAttributes: %08Xh (",
variableSize, variableSize,
(UINT32)header.size(), (UINT32)header.size(),
(UINT32)body.size(), (UINT32)body.size(),
@ -481,7 +481,141 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
}
// VSS2
try {
UByteArray vss2 = volumeBody.mid(storeOffset);
umemstream is(vss2.constData(), vss2.size());
kaitai::kstream ks(&is);
phoenix_vss2_t parsed(&ks);
// VSS2 store at current offset parsed correctly
// Check if we need to add a padding before it
if (!outerPadding.isEmpty()) {
UString info = usprintf("Full size: %Xh (%u)", (UINT32)outerPadding.size(), (UINT32)outerPadding.size());
model->addItem(previousStoreEndOffset, Types::Padding, getPaddingType(outerPadding), UString("Padding"), UString(), info, UByteArray(), outerPadding, UByteArray(), Fixed, index);
outerPadding.clear();
}
// Construct header and body
UByteArray header = vss2.left(parsed.len_vss2_store_header());
UByteArray body = vss2.mid(header.size(), parsed.vss2_size() - header.size());
// Add info
UString name = UString("VSS2 store");
UString info;
if (parsed.signature() == NVRAM_VSS2_AUTH_VAR_KEY_DATABASE_GUID_PART1) {
info = UString("Signature: AAF32C78-947B-439A-A180-2E144EC37792\n");
}
else {
info = UString("Signature: DDCF3617-3275-4164-98B6-FE85707FFE7D\n");
}
info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nFormat: %02Xh\nState: %02Xh\nReserved: %02Xh\nReserved1: %04Xh",
parsed.vss2_size() , parsed.vss2_size(),
(UINT32)header.size(), (UINT32)header.size(),
(UINT32)body.size(), (UINT32)body.size(),
parsed.format(),
parsed.state(),
parsed.reserved(),
parsed.reserved1());
// Add header tree item
UModelIndex headerIndex = model->addItem(localOffset + storeOffset, Types::VssStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
UINT32 vss2VariableOffset = storeOffset + parsed.len_vss2_store_header();
for (const auto & variable : *parsed.body()->variables()) {
UINT8 subtype;
UString text;
info.clear();
name.clear();
// This is thew terminating entry, needs special processing
if (variable->_is_null_signature_last()) {
// Add free space or padding after all variables, if needed
if (vss2VariableOffset < parsed.vss2_size()) {
UByteArray freeSpace = vss2.mid(vss2VariableOffset, parsed.vss2_size() - vss2VariableOffset);
// Add info
info = usprintf("Full size: %Xh (%u)", (UINT32)freeSpace.size(), (UINT32)freeSpace.size());
// Check that remaining unparsed bytes are actually empty
if (freeSpace.count(emptyByte) == freeSpace.size()) { // Free space
// Add tree item
model->addItem(vss2VariableOffset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), freeSpace, UByteArray(), Fixed, headerIndex);
}
else {
// Add tree item
model->addItem(vss2VariableOffset, Types::Padding, getPaddingType(freeSpace), UString("Padding"), UString(), info, UByteArray(), freeSpace, UByteArray(), Fixed, headerIndex);
}
}
break;
}
// This is a normal entry
UINT32 variableSize;
if (variable->is_auth()) { // Authenticated
subtype = Subtypes::AuthVssEntry;
header = vss2.mid(vss2VariableOffset, variable->len_auth_header() + variable->len_name_auth());
body = vss2.mid(vss2VariableOffset + header.size(), variable->len_data_auth());
variableSize = (UINT32)(header.size() + body.size());
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
name = guidToUString(variableGuid);
text = uFromUcs2(variable->name_auth().c_str());
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
}
else { // Standard
subtype = Subtypes::StandardVssEntry;
header = vss2.mid(vss2VariableOffset, variable->len_standard_header() + variable->len_name());
body = vss2.mid(vss2VariableOffset + header.size(), variable->len_data());
variableSize = (UINT32)(header.size() + body.size());
const EFI_GUID variableGuid = readUnaligned((const EFI_GUID*)(variable->vendor_guid().c_str()));
name = guidToUString(variableGuid);
text = uFromUcs2(variable->name().c_str());
info += UString("Variable GUID: ") + guidToUString(variableGuid, false) + "\n";
}
// Override variable type to Invalid if needed
if (!variable->is_valid()) {
subtype = Subtypes::InvalidVssEntry;
name = UString("Invalid");
text.clear();
}
const UINT32 variableAttributes = variable->attributes()->non_volatile()
+ (variable->attributes()->boot_service() << 1)
+ (variable->attributes()->runtime() << 2)
+ (variable->attributes()->hw_error_record() << 3)
+ (variable->attributes()->auth_write() << 4)
+ (variable->attributes()->time_based_auth() << 5)
+ (variable->attributes()->append_write() << 6);
// Add generic info
info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nState: %02Xh\nReserved: %02Xh\nAttributes: %08Xh (",
variableSize, variableSize,
(UINT32)header.size(), (UINT32)header.size(),
(UINT32)body.size(), (UINT32)body.size(),
variable->state(),
variable->reserved(),
variableAttributes) + vssAttributesToUString(variableAttributes) + UString(")");
// Add specific info
if (variable->is_auth()) {
UINT64 monotonicCounter = (UINT64)variable->len_name() + ((UINT64)variable->len_data() << 32);
info += usprintf("\nMonotonic counter: %" PRIX64 "h\nTimestamp: ", monotonicCounter) + efiTimeToUString(*(const EFI_TIME*)variable->timestamp().c_str())
+ usprintf("\nPubKey index: %u", variable->pubkey_index());
}
// Add tree item
model->addItem(vss2VariableOffset, Types::VssEntry, subtype, name, text, info, header, body, UByteArray(), Fixed, headerIndex);
vss2VariableOffset += variableSize;
}
storeFound = true;
storeOffset += parsed.vss2_size();
previousStoreEndOffset = storeOffset;
} catch (...) {
// Parsing failed, try something else
}
// FDC
// EVSA
@ -496,7 +630,7 @@ USTATUS NvramParser::parseNvramVolumeBody(const UModelIndex & index)
// Intel uCode
// Padding
outerPadding += volumeBody.at(storeOffset);
outerPadding.append(volumeBody.at(storeOffset));
}
// Add padding at the very end

View file

@ -50,6 +50,14 @@ ${UFIND} common/generated ${UFINDOPT} \
-name 'ami_nvar.cpp' \
-exec sed -i.bak 's/_offset = _io()->pos();/_offset = (int32_t)_io()->pos();/g' {} + || exit 1
# Suppress type downcast warning in phoenix_vss2.cpp
${UFIND} common/generated ${UFINDOPT} \
-name 'phoenix_vss2.cpp' \
-exec sed -i.bak 's/_offset = _io()->pos();/_offset = (int32_t)_io()->pos();/g' {} + || exit 1
${UFIND} common/generated ${UFINDOPT} \
-name 'phoenix_vss2.cpp' \
-exec sed -i.bak 's/_offset_auth = _io()->pos();/_offset_auth = (int32_t)_io()->pos();/g' {} + || exit 1
# Remove backup files
${UFIND} common/generated ${UFINDOPT} \
-regex '.*\.(bak)' \