Initial support for VSS format

- normal, auth and apple variations supported
- some UI additions and code cleanup TBD
This commit is contained in:
Nikolaj Schlej 2016-03-28 15:03:32 +02:00
parent 5138a49591
commit 95290abb94
11 changed files with 690 additions and 26 deletions

View File

@ -17,7 +17,7 @@
UEFITool::UEFITool(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::UEFITool),
version(tr("0.30.0_alpha22"))
version(tr("0.30.0_alpha23"))
{
clipboard = QApplication::clipboard();
@ -175,7 +175,7 @@ void UEFITool::populateUi(const QModelIndex &current)
ui->menuVolumeActions->setEnabled(type == Types::Volume);
ui->menuFileActions->setEnabled(type == Types::File);
ui->menuSectionActions->setEnabled(type == Types::Section);
ui->menuVariableActions->setEnabled(type == Types::NvramVariableNvar);
ui->menuVariableActions->setEnabled(type == Types::NvramVariableNvar || type == Types::NvramVariableVss);
// Enable actions
ui->actionExtract->setDisabled(model->hasEmptyHeader(current) && model->hasEmptyBody(current));
@ -505,6 +505,8 @@ void UEFITool::extract(const UINT8 mode)
name = QDir::toNativeSeparators(currentDir + QDir::separator() + name);
//ui->statusBar->showMessage(name);
UINT8 type = model->type(index);
QString path;
if (mode == EXTRACT_MODE_AS_IS) {
@ -531,7 +533,8 @@ void UEFITool::extract(const UINT8 mode)
path = QFileDialog::getSaveFileName(this, tr("Save section to file"), name + ".sct", "Section files (*.sct *.bin);;All files (*)");
break;
case Types::NvramVariableNvar:
path = QFileDialog::getSaveFileName(this, tr("Save NVAR variable to file"), name + ".nvar", "NVAR variable files (*.nvar *.bin);;All files (*)");
case Types::NvramVariableVss:
path = QFileDialog::getSaveFileName(this, tr("Save variable to file"), name + ".var", "Variable files (*.var *.bin);;All files (*)");
break;
default:
path = QFileDialog::getSaveFileName(this, tr("Save object to file"), name + ".bin", "Binary files (*.bin);;All files (*)");
@ -566,7 +569,8 @@ void UEFITool::extract(const UINT8 mode)
}
break;
case Types::NvramVariableNvar:
path = QFileDialog::getSaveFileName(this, tr("Save NVAR variable body to file"), name + ".bin", "Binary files (*.bin);;All files (*)");
case Types::NvramVariableVss:
path = QFileDialog::getSaveFileName(this, tr("Save variable body to file"), name + ".bin", "Binary files (*.bin);;All files (*)");
break;
default:
path = QFileDialog::getSaveFileName(this, tr("Save object to file"), name + ".bin", "Binary files (*.bin);;All files (*)");
@ -930,6 +934,7 @@ void UEFITool::contextMenuEvent(QContextMenuEvent* event)
ui->menuSectionActions->exec(event->globalPos());
break;
case Types::NvramVariableNvar:
case Types::NvramVariableVss:
ui->menuVariableActions->exec(event->globalPos());
break;
}

View File

@ -80,6 +80,7 @@ typedef UINT8 STATUS;
#define ERR_DIR_CREATE 34
#define ERR_TRUNCATED_IMAGE 35
#define ERR_INVALID_CAPSULE 36
#define ERR_STORAGES_NOT_FOUND 37
#define ERR_NOT_IMPLEMENTED 0xFF
// UDK porting definitions
@ -140,6 +141,21 @@ typedef struct _EFI_GUID {
UINT8 Data[16];
} EFI_GUID;
// EFI Time
typedef struct _EFI_TIME {
UINT16 Year; // Year: 2000 - 20XX
UINT8 Month; // Month: 1 - 12
UINT8 Day; // Day: 1 - 31
UINT8 Hour; // Hour: 0 - 23
UINT8 Minute; // Minute: 0 - 59
UINT8 Second; // Second: 0 - 59
UINT8: 8;
UINT32 Nanosecond; // Nanosecond: 0 - 999,999,999
INT16 TimeZone; // TimeZone: -1440 to 1440 or UNSPECIFIED (0x07FF)
UINT8 Daylight; // Daylight: ADJUST_DAYLIGHT (1) or IN_DAYLIGHT (2)
UINT8: 8;
} EFI_TIME;
#define ALIGN4(Value) (((Value)+3) & ~3)
#define ALIGN8(Value) (((Value)+7) & ~7)
@ -148,7 +164,7 @@ typedef struct _EFI_GUID {
//Hexarg macros
#define hexarg(X) arg(QString("%1").arg((X),0,16).toUpper())
#define hexarg2(X, Y) arg(QString("%1").arg((X),(Y),16,QChar('0')).toUpper())
#define hexarg2(X, Y) arg(QString("%1").arg((X),(Y),16,QLatin1Char('0')).toUpper())
#endif

View File

@ -126,20 +126,22 @@ const QByteArray EFI_APPLE_BOOT_VOLUME_FILE_SYSTEM_GUID
("\xAD\xEE\xAD\x04\xFF\x61\x31\x4D\xB6\xBA\x64\xF8\xBF\x90\x1F\x5A", 16);
const QByteArray EFI_APPLE_BOOT_VOLUME_FILE_SYSTEM2_GUID
("\x8C\x1B\x00\xBD\x71\x6A\x7B\x48\xA1\x4F\x0C\x2A\x2D\xCF\x7A\x5D", 16);
// AD3FFFFF-D28B-44C4-9F13-9EA98A97F9F0 // Intel 1
const QByteArray EFI_INTEL_FILE_SYSTEM_GUID
("\xFF\xFF\x3F\xAD\x8B\xD2\xC4\x44\x9F\x13\x9E\xA9\x8A\x97\xF9\xF0", 16);
// AD3FFFFF-D28B-44C4-9F13-9EA98A97F9F0 // Intel 1
// D6A1CD70-4B33-4994-A6EA-375F2CCC5437 // Intel 2
const QByteArray EFI_INTEL_FILE_SYSTEM2_GUID
("\x70\xCD\xA1\xD6\x33\x4B\x94\x49\xA6\xEA\x37\x5F\x2C\xCC\x54\x37", 16);
// D6A1CD70-4B33-4994-A6EA-375F2CCC5437 // Intel 2
// 4F494156-AED6-4D64-A537-B8A5557BCEEC // Sony 1
const QByteArray EFI_SONY_FILE_SYSTEM_GUID
("\x56\x41\x49\x4F\xD6\xAE\x64\x4D\xA5\x37\xB8\xA5\x55\x7B\xCE\xEC", 16);
// 4F494156-AED6-4D64-A537-B8A5557BCEEC // Sony 1
// Vector of volume GUIDs with FFSv2-compatible files
extern const std::vector<QByteArray> FFSv2Volumes;
const QByteArray EFI_FIRMWARE_FILE_SYSTEM3_GUID // 5473C07A-3DCB-4dca-BD6F-1E9689E7349A
const QByteArray EFI_FIRMWARE_FILE_SYSTEM3_GUID // 5473C07A-3DCB-4DCA-BD6F-1E9689E7349A
("\x7A\xC0\x73\x54\xCB\x3D\xCA\x4D\xBD\x6F\x1E\x96\x89\xE7\x34\x9A", 16);
// Vector of volume GUIDs with FFSv3-compatible files

View File

@ -57,6 +57,7 @@ STATUS FfsOperations::extract(const QModelIndex & index, QString & name, QByteAr
name = itemName;
} break;
case Types::NvramVariableNvar:
case Types::NvramVariableVss:
case Types::File: {
name = itemText.isEmpty() ? itemName : itemText.replace(' ', '_');
} break;

View File

@ -847,7 +847,7 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde
if (result)
return result;
// First volume is not at the beginning of BIOS space
// First volume is not at the beginning of RAW area
QString name;
QString info;
if (prevVolumeOffset > 0) {
@ -928,13 +928,13 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde
// Parse current volume's header
QModelIndex volumeIndex;
result = parseVolumeHeader(volume, model->header(index).size() + volumeOffset, index, volumeIndex);
result = parseVolumeHeader(volume, headerSize + volumeOffset, index, volumeIndex);
if (result)
msg(QObject::tr("parseRawArea: volume header parsing failed with error \"%1\"").arg(errorCodeToQString(result)), index);
else {
// Show messages
if (volumeSize != bmVolumeSize)
msg(QObject::tr("parseBiosBody: volume size stored in header %1h (%2) differs from calculated using block map %3h (%4)")
msg(QObject::tr("parseRawArea: volume size stored in header %1h (%2) differs from calculated using block map %3h (%4)")
.hexarg(volumeSize).arg(volumeSize)
.hexarg(bmVolumeSize).arg(bmVolumeSize),
volumeIndex);
@ -946,7 +946,7 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde
result = findNextVolume(index, data, offset, volumeOffset + prevVolumeSize, volumeOffset);
}
// Padding at the end of BIOS space
// Padding at the end of RAW area
volumeOffset = prevVolumeOffset + prevVolumeSize;
if ((UINT32)data.size() > volumeOffset) {
QByteArray padding = data.mid(volumeOffset);
@ -1029,6 +1029,7 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare
// Check for volume structure to be known
bool isUnknown = true;
bool isVssNvramVolume = false;
UINT8 ffsVersion = 0;
// Check for FFS v2 volume
@ -1044,6 +1045,12 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare
ffsVersion = 3;
}
// Check for VSS NVRAM volume
if (guid == NVRAM_VSS_STORAGE_VOLUME_GUID) {
isUnknown = false;
isVssNvramVolume = true;
}
// Check volume revision and alignment
bool msgAlignmentBitsSet = false;
bool msgUnaligned = false;
@ -1172,6 +1179,8 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare
subtype = Subtypes::Ffs2Volume;
else if (ffsVersion == 3)
subtype = Subtypes::Ffs3Volume;
else if (isVssNvramVolume)
subtype = Subtypes::VssNvramVolume;
}
index = model->addItem(Types::Volume, subtype, name, text, info, header, body, TRUE, parsingDataToQByteArray(pdata), parent);
@ -1214,7 +1223,7 @@ STATUS FfsParser::findNextVolume(const QModelIndex & index, const QByteArray & b
// All checks passed, volume found
break;
}
// No additional volumes found
// No more volumes found
if (nextIndex < EFI_FV_SIGNATURE_OFFSET)
return ERR_VOLUMES_NOT_FOUND;
@ -1335,6 +1344,10 @@ STATUS FfsParser::parseVolumeBody(const QModelIndex & index)
PARSING_DATA pdata = parsingDataFromQModelIndex(index);
UINT32 offset = pdata.offset;
// Parse VSS NVRAM volumes with a dedicated function
if (model->subtype(index) == Subtypes::VssNvramVolume)
return parseStorageArea(volumeBody, index);
if (pdata.ffsVersion != 2 && pdata.ffsVersion != 3) // Don't parse unknown volumes
return ERR_SUCCESS;
@ -2912,8 +2925,6 @@ STATUS FfsParser::parseNvarStorage(const QByteArray & data, const QModelIndex &
QByteArray body;
QByteArray extendedData;
UINT32 guidAreaSize = guidsInStorage * sizeof(EFI_GUID);
UINT32 unparsedSize = (UINT32)data.size() - offset - guidAreaSize;
@ -2939,7 +2950,7 @@ STATUS FfsParser::parseNvarStorage(const QByteArray & data, const QModelIndex &
// Nothing is parsed yet, but the file is not empty
if (!offset) {
msg(QObject::tr("parseNvarStorage: file can't be parsed as NVAR variables storage"), index);
return ERR_INVALID_FILE;
return ERR_SUCCESS;
}
// It's a padding
@ -3142,7 +3153,7 @@ parsing_done:
info += QObject::tr("\nAttributes: %1h").hexarg2(variableHeader->Attributes, 2);
// Translate attributes to text
if (variableHeader->Attributes)
info += QObject::tr("\nAttributes as text: %1").arg(variableAttributesToQstring(variableHeader->Attributes));
info += QObject::tr("\nAttributes as text: %1").arg(nvarAttributesToQString(variableHeader->Attributes));
pdata.nvram.nvar.attributes = variableHeader->Attributes;
// Add next node info
@ -3202,3 +3213,478 @@ parsing_done:
return ERR_SUCCESS;
}
STATUS FfsParser::parseStorageArea(const QByteArray & data, const QModelIndex & index)
{
// Sanity check
if (!index.isValid())
return ERR_INVALID_PARAMETER;
// Get parsing data
PARSING_DATA pdata = parsingDataFromQModelIndex(index);
UINT32 parentOffset = pdata.offset + model->header(index).size();
// Search for first volume
STATUS result;
UINT32 prevStorageOffset;
result = findNextStorage(index, data, parentOffset, 0, prevStorageOffset);
if (result)
return result;
// First storage is not at the beginning of volume body
QString name;
QString info;
if (prevStorageOffset > 0) {
// Get info
QByteArray padding = data.left(prevStorageOffset);
name = QObject::tr("Padding");
info = QObject::tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Construct parsing data
pdata.offset = parentOffset;
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index);
}
// Search for and parse all storages
UINT32 storageOffset = prevStorageOffset;
UINT32 prevStorageSize = 0;
while (!result)
{
// Padding between storages
if (storageOffset > prevStorageOffset + prevStorageSize) {
UINT32 paddingOffset = prevStorageOffset + prevStorageSize;
UINT32 paddingSize = storageOffset - paddingOffset;
QByteArray padding = data.mid(paddingOffset, paddingSize);
// Get info
name = QObject::tr("Padding");
info = QObject::tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Construct parsing data
pdata.offset = parentOffset + paddingOffset;
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index);
}
// Get storage size
UINT32 storageSize = 0;
result = getStorageSize(data, storageOffset, storageSize);
if (result) {
msg(QObject::tr("parseStorageArea: getVssStorageSize failed with error \"%1\"").arg(errorCodeToQString(result)), index);
return result;
}
// Check that storage is fully present in input
if (storageSize > (UINT32)data.size() || storageOffset + storageSize > (UINT32)data.size()) {
msg(QObject::tr("parseVssStorageArea: one of storages inside overlaps the end of data"), index);
return ERR_INVALID_VOLUME;
}
QByteArray storage = data.mid(storageOffset, storageSize);
if (storageSize > (UINT32)storage.size()) {
// Mark the rest as padding and finish the parsing
QByteArray padding = data.right(storage.size());
// Get info
name = QObject::tr("Padding");
info = QObject::tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Construct parsing data
pdata.offset = parentOffset + storageOffset;
// Add tree item
QModelIndex paddingIndex = model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index);
msg(QObject::tr("parseStorageArea: one of storages inside overlaps the end of data"), paddingIndex);
// Update variables
prevStorageOffset = storageOffset;
prevStorageSize = padding.size();
break;
}
// Parse current volume's header
QModelIndex storageIndex;
result = parseStorageHeader(storage, parentOffset + storageOffset, index, storageIndex);
if (result) {
msg(QObject::tr("parseStorageArea: storage header parsing failed with error \"%1\"").arg(errorCodeToQString(result)), index);
}
// Go to next volume
prevStorageOffset = storageOffset;
prevStorageSize = storageSize;
result = findNextStorage(index, data, parentOffset, storageOffset + prevStorageSize, storageOffset);
}
// Padding/free space at the end of volume
storageOffset = prevStorageOffset + prevStorageSize;
if ((UINT32)data.size() > storageOffset) {
QByteArray padding = data.mid(storageOffset);
UINT8 type;
UINT8 subtype;
if (padding.count(pdata.emptyByte) == padding.size()) {
// It's a free space
name = QObject::tr("Free space");
type = Types::FreeSpace;
subtype = 0;
}
else {
// Nothing is parsed yet, but the file is not empty
if (!storageOffset) {
msg(QObject::tr("parseStorageArea: area can't be parsed as storage"), index);
return ERR_SUCCESS;
}
// It's a padding
name = QObject::tr("Padding");
type = Types::Padding;
subtype = getPaddingType(padding);
}
// Add info
info = QObject::tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Construct parsing data
pdata.offset = parentOffset + storageOffset;
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index);
}
// Parse bodies
/* for (int i = 0; i < model->rowCount(index); i++) {
QModelIndex current = index.child(i, 0);
switch (model->type(current)) {
case Types::Volume:
parseVolumeBody(current);
break;
case Types::Padding:
// No parsing required
break;
default:
return ERR_UNKNOWN_ITEM_TYPE;
}
}
*/
return ERR_SUCCESS;
}
STATUS FfsParser::findNextStorage(const QModelIndex & index, const QByteArray & data, const UINT32 parentOffset, const UINT32 storageOffset, UINT32 & nextStorageOffset)
{
UINT32 dataSize = data.size();
if (dataSize < sizeof(UINT32))
return ERR_STORAGES_NOT_FOUND;
UINT32 offset = storageOffset;
for (; offset < dataSize - sizeof(UINT32); offset++) {
const UINT32* currentPos = (const UINT32*)(data.constData() + offset);
if (*currentPos == NVRAM_VSS_STORE_SIGNATURE || *currentPos == NVRAM_APPLE_SVS_STORE_SIGNATURE) { //$VSS or $SVS signatures found, perform checks
const VSS_VARIABLE_STORE_HEADER* vssHeader = (const VSS_VARIABLE_STORE_HEADER*)currentPos;
if (vssHeader->Format != NVRAM_VSS_VARIABLE_STORE_FORMATTED) {
msg(QObject::tr("findNextStorage: VSS storage candidate at offset %1h skipped, has invalid format %2h").hexarg(parentOffset + offset).hexarg2(vssHeader->Format, 2), index);
continue;
}
if (vssHeader->Size == 0 || vssHeader->Size == 0xFFFFFFFF) {
msg(QObject::tr("findNextStorage: VSS storage candidate at offset %1h skipped, has invalid size %2h").hexarg(parentOffset + offset).hexarg2(vssHeader->Size, 8), index);
continue;
}
//if (vssHeader->State != NVRAM_VSS_VARIABLE_STORE_HEALTHY) {
// msg(QObject::tr("findNextStorage: VSS storage candidate at offset %1h skipped, has invalid state %2h").hexarg(parentOffset + offset).hexarg2(vssHeader->State, 2), index);
// continue;
//}
// All checks passed, storage found
break;
}
//else if (*currentPos == NVRAM_APPLE_FSYS_STORE_SIGNATURE) { //Fsys signature found
// // No checks yet
// break;
//}
}
// No more storages found
if (offset == dataSize - sizeof(UINT32))
return ERR_STORAGES_NOT_FOUND;
nextStorageOffset = offset;
return ERR_SUCCESS;
}
STATUS FfsParser::getStorageSize(const QByteArray & data, const UINT32 storageOffset, UINT32 & storageSize)
{
//TODO: add Fsys support
const VSS_VARIABLE_STORE_HEADER* vssHeader = (const VSS_VARIABLE_STORE_HEADER*)(data.constData() + storageOffset);
storageSize = vssHeader->Size;
return ERR_SUCCESS;
}
STATUS FfsParser::parseStorageHeader(const QByteArray & storage, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index)
{
// Parse VSS volume like raw area, seen for now
// $VSS, $SVS, Fsys, full volume GUID, _FDC and paddings
// The volume must begin with VSS storage to be valid, but after the first one, there can be many variants
const UINT32 dataSize = (UINT32)storage.size();
if (dataSize < sizeof(VSS_VARIABLE_STORE_HEADER)) {
msg(QObject::tr("parseStorageHeader: volume body is too small even for VSS storage header"), parent);
return ERR_SUCCESS;
}
// Get VSS storage header
const VSS_VARIABLE_STORE_HEADER* vssStorageHeader = (const VSS_VARIABLE_STORE_HEADER*)storage.constData();
// Check signature
if (vssStorageHeader->Signature != NVRAM_VSS_STORE_SIGNATURE && vssStorageHeader->Signature != NVRAM_APPLE_SVS_STORE_SIGNATURE) {
msg(QObject::tr("parseStorageHeader: invalid storage signature %1h").hexarg2(vssStorageHeader->Signature, 8), parent);
return ERR_SUCCESS;
}
// Check storage size
if (dataSize < vssStorageHeader->Size) {
msg(QObject::tr("parseStorageHeader: first VSS storage size %1h (%2) is greater than volume body size %3h (%4)")
.hexarg2(vssStorageHeader->Size, 8).arg(vssStorageHeader->Size)
.hexarg2(dataSize, 8).arg(dataSize), parent);
return ERR_SUCCESS;
}
// Get parsing data
PARSING_DATA pdata = parsingDataFromQModelIndex(parent);
// Construct header and body
QByteArray header = storage.left(sizeof(VSS_VARIABLE_STORE_HEADER));
QByteArray body = storage.mid(sizeof(VSS_VARIABLE_STORE_HEADER), vssStorageHeader->Size - sizeof(VSS_VARIABLE_STORE_HEADER));
// Add info
QString name = QObject::tr("VSS storage");
QString info = QObject::tr("Signature: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nFormat: %8h\nState: %9h")
.hexarg2(vssStorageHeader->Signature, 8)
.hexarg(vssStorageHeader->Size).arg(vssStorageHeader->Size)
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.hexarg2(vssStorageHeader->Format, 2)
.hexarg2(vssStorageHeader->State, 2);
// Add correct offset
pdata.offset = parentOffset;
// Add tree item
index = model->addItem(Types::NvramStorageVss, 0, name, QString(), info, header, body, TRUE, parsingDataToQByteArray(pdata), parent);
//Parse the storage
parseVssStorageBody(body, index);
return ERR_SUCCESS;
}
STATUS FfsParser::parseVssStorageBody(const QByteArray & data, const QModelIndex & index)
{
// Sanity check
if (!index.isValid())
return ERR_INVALID_PARAMETER;
// Get parsing data for the current item
PARSING_DATA pdata = parsingDataFromQModelIndex(index);
UINT32 parentOffset = pdata.offset + model->header(index).size();
// Check that the is enough space for variable header
const UINT32 dataSize = (UINT32)data.size();
if (dataSize < sizeof(VSS_VARIABLE_HEADER)) {
msg(QObject::tr("parseVssStorageBody: storage body is too small even for VSS variable header"), index);
return ERR_SUCCESS;
}
UINT32 offset = 0;
// Parse all variables
while (1) {
bool isInvalid = false;
bool isAuthenticated = false;
bool isAppleCrc32 = false;
UINT32 storedCrc32 = 0;
UINT32 calculatedCrc32 = 0;
UINT64 monotonicCounter = 0;
EFI_TIME timestamp = { 0 };
UINT32 pubKeyIndex = 0;
UINT8 subtype = 0;
QString name;
QString text;
EFI_GUID* variableGuid;
CHAR16* variableName;
QByteArray header;
QByteArray body;
UINT32 unparsedSize = dataSize - offset;
// Get variable header
const VSS_VARIABLE_HEADER* variableHeader = (const VSS_VARIABLE_HEADER*)(data.constData() + offset);
// Check variable header to fit in still unparsed data
UINT32 variableSize = 0;
if (unparsedSize >= sizeof(VSS_VARIABLE_HEADER)
&& variableHeader->StartId == NVRAM_VSS_VARIABLE_START_ID) {
// Apple VSS variable with CRC32 of the data
if (variableHeader->Attributes & NVRAM_VSS_VARIABLE_APPLE_DATA_CHECKSUM) {
isAppleCrc32 = true;
if (unparsedSize < sizeof(VSS_APPLE_VARIABLE_HEADER)) {
variableSize = 0;
}
else {
const VSS_APPLE_VARIABLE_HEADER* appleVariableHeader = (const VSS_APPLE_VARIABLE_HEADER*)variableHeader;
variableSize = sizeof(VSS_APPLE_VARIABLE_HEADER) + appleVariableHeader->NameSize + appleVariableHeader->DataSize;
variableGuid = (EFI_GUID*)&appleVariableHeader->VendorGuid;
variableName = (CHAR16*)(appleVariableHeader + 1);
header = data.mid(offset, sizeof(VSS_APPLE_VARIABLE_HEADER) + appleVariableHeader->NameSize);
body = data.mid(offset + header.size(), appleVariableHeader->DataSize);
// Calculate CRC32 of the variable data
storedCrc32 = appleVariableHeader->DataCrc32;
calculatedCrc32 = crc32(0, (const UINT8*)body.constData(), body.size());
}
}
// Authenticated variable
else if ((variableHeader->Attributes & NVRAM_VSS_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
|| (variableHeader->Attributes & NVRAM_VSS_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
|| (variableHeader->Attributes & NVRAM_VSS_VARIABLE_APPEND_WRITE)
|| (variableHeader->NameSize == 0 && variableHeader->DataSize == 0)) { // If both NameSize and DataSize are zeros, it's auth variable with zero montonic counter
isAuthenticated = true;
if (unparsedSize < sizeof(VSS_AUTH_VARIABLE_HEADER)) {
variableSize = 0;
}
else {
const VSS_AUTH_VARIABLE_HEADER* authVariableHeader = (const VSS_AUTH_VARIABLE_HEADER*)variableHeader;
variableSize = sizeof(VSS_AUTH_VARIABLE_HEADER) + authVariableHeader->NameSize + authVariableHeader->DataSize;
variableGuid = (EFI_GUID*)&authVariableHeader->VendorGuid;
variableName = (CHAR16*)(authVariableHeader + 1);
header = data.mid(offset, sizeof(VSS_AUTH_VARIABLE_HEADER) + authVariableHeader->NameSize);
body = data.mid(offset + header.size(), authVariableHeader->DataSize);
monotonicCounter = authVariableHeader->MonotonicCounter;
timestamp = authVariableHeader->Timestamp;
pubKeyIndex = authVariableHeader->PubKeyIndex;
}
}
// Normal VSS variable
if (!isAuthenticated && !isAppleCrc32) {
variableSize = sizeof(VSS_VARIABLE_HEADER) + variableHeader->NameSize + variableHeader->DataSize;
variableGuid = (EFI_GUID*)&variableHeader->VendorGuid;
variableName = (CHAR16*)(variableHeader + 1);
header = data.mid(offset, sizeof(VSS_VARIABLE_HEADER) + variableHeader->NameSize);
body = data.mid(offset + header.size(), variableHeader->DataSize);
}
// There is also a case of authenticated Apple variables, but I haven't seen one yet
// Check variable state
if (variableHeader->State != NVRAM_VSS_VARIABLE_ADDED && variableHeader->State != NVRAM_VSS_VARIABLE_HEADER_VALID) {
isInvalid = true;
}
}
// Can't parse further, add the last element and break the loop
if (!variableSize) {
// Check if the data left is a free space or a padding
QByteArray padding = data.mid(offset, unparsedSize);
UINT8 type;
if (padding.count(pdata.emptyByte) == padding.size()) {
// It's a free space
name = QObject::tr("Free space");
type = Types::FreeSpace;
subtype = 0;
}
else {
// Nothing is parsed yet, but the storage is not empty
if (!offset) {
msg(QObject::tr("parseVssStorageBody: storage can't be parsed as VSS storage"), index);
return ERR_SUCCESS;
}
// It's a padding
name = QObject::tr("Padding");
type = Types::Padding;
subtype = getPaddingType(padding);
}
// Get info
QString info = QObject::tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Construct parsing data
pdata.offset = parentOffset + offset;
// Add tree item
model->addItem(type, subtype, name, QString(), info, QByteArray(), padding, FALSE, parsingDataToQByteArray(pdata), index);
return ERR_SUCCESS;
}
QString info;
// Rename deleted variables
if (isInvalid) {
name = QObject::tr("Invalid");
}
else { // Add GUID and text for valid variables
name = guidToQString(*variableGuid);
info += QObject::tr("Variable GUID: %1\n").arg(name);
text = QString::fromUtf16(variableName);
}
// Add header, body and extended data info
info += QObject::tr("Full size: %1h (%2)\nHeader size %3h (%4)\nBody size: %5h (%6)")
.hexarg(variableSize).arg(variableSize)
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Add state info
info += QObject::tr("\nState: %1h").hexarg2(variableHeader->State, 2);
// Add attributes info
info += QObject::tr("\nAttributes: %1h").hexarg2(variableHeader->Attributes, 8);
// Set subtype and add related info
if (isInvalid)
subtype = Subtypes::InvalidVss;
else if (isAuthenticated) {
subtype = Subtypes::AuthVss;
info += QObject::tr("\nMonotonic counter: %1h\nTimestamp: %2\nPubKey index: %3")
.hexarg(monotonicCounter).arg(efiTimeToQString(timestamp)).arg(pubKeyIndex);
}
else if (isAppleCrc32) {
subtype = Subtypes::AppleCrc32Vss;
info += QObject::tr("\nCRC32: %1h%2").hexarg2(storedCrc32, 8)
.arg(storedCrc32 == calculatedCrc32 ? QObject::tr(", valid") : QObject::tr(", invalid, should be %1h").hexarg2(calculatedCrc32,8));
}
else
subtype = Subtypes::StandardVss;
// Add correct offset to parsing data
pdata.offset = parentOffset + offset;
// Add tree item
QModelIndex varIndex = model->addItem(Types::NvramVariableVss, subtype, name, text, info, header, body, FALSE, parsingDataToQByteArray(pdata), index);
// Move to next variable
offset += variableSize;
}
return ERR_SUCCESS;
}

View File

@ -109,6 +109,12 @@ private:
// NVRAM parsing
STATUS parseNvarStorage(const QByteArray & data, const QModelIndex & index);
STATUS parseStorageArea(const QByteArray & data, const QModelIndex & index);
STATUS findNextStorage(const QModelIndex & index, const QByteArray & data, const UINT32 parentOffset, const UINT32 storageOffset, UINT32 & nextStorageOffset);
STATUS getStorageSize(const QByteArray & data, const UINT32 storageOffset, UINT32 & storageSize);
STATUS parseStorageHeader(const QByteArray & storage, const UINT32 parentOffset, const QModelIndex & parent, QModelIndex & index);
STATUS parseVssStorageBody(const QByteArray & data, const QModelIndex & index);
// Message helper
void msg(const QString & message, const QModelIndex &index = QModelIndex());
};

View File

@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#include <QObject>
#include "nvram.h"
QString variableAttributesToQstring(UINT8 attributes)
QString nvarAttributesToQString(const UINT8 attributes)
{
if (attributes == 0x00 || attributes == 0xFF)
return QString();
@ -38,6 +38,17 @@ QString variableAttributesToQstring(UINT8 attributes)
if (attributes & NVRAM_NVAR_VARIABLE_ATTRIB_VALID)
str += QObject::tr(", Valid");
return str.mid(2); // Remove the first comma and space
return str.mid(2); // Remove first comma and space
}
QString efiTimeToQString(const EFI_TIME & time)
{
return QObject::tr("%1-%2-%3T%4:%5:%6.%7")
.arg(time.Year, 4, 10, QLatin1Char('0'))
.arg(time.Month, 2, 10, QLatin1Char('0'))
.arg(time.Day, 2, 10, QLatin1Char('0'))
.arg(time.Hour, 2, 10, QLatin1Char('0'))
.arg(time.Minute, 2, 10, QLatin1Char('0'))
.arg(time.Second, 2, 10, QLatin1Char('0'))
.arg(time.Nanosecond);
}

View File

@ -31,7 +31,9 @@ const QByteArray NVRAM_NVAR_STORAGE_FILE_GUID
const QByteArray NVRAM_NVAR_EXTERNAL_DEFAULTS_FILE_GUID
("\x5B\x31\x21\x92\xBB\x30\xB5\x46\x81\x3E\x1B\x1B\xF4\x71\x2B\xD3", 16);
extern QString variableAttributesToQstring(UINT8 attributes);
extern QString nvarAttributesToQString(const UINT8 attributes);
extern QString efiTimeToQString(const EFI_TIME & time);
// Make sure we use right packing rules
#pragma pack(push,1)
@ -62,6 +64,110 @@ typedef struct _NVAR_VARIABLE_HEADER {
#define NVRAM_NVAR_VARIABLE_EXT_ATTRIB_AUTH_WRITE 0x10
#define NVRAM_NVAR_VARIABLE_EXT_ATTRIB_TIME_BASED 0x20
//
// Next format is TianoCore VSS and it's variations
//
// FFF12B8D-7696-4C8B-A985-2747075B4F50
const QByteArray NVRAM_VSS_STORAGE_VOLUME_GUID
("\x8D\x2B\xF1\xFF\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50", 16);
#define NVRAM_VSS_STORE_SIGNATURE 0x53535624 // $VSS
#define NVRAM_APPLE_SVS_STORE_SIGNATURE 0x53565324 // $SVS
#define NVRAM_APPLE_FSYS_STORE_SIGNATURE 0x73797346 // Fsys
#define NVRAM_VSS_VARIABLE_START_ID 0x55AA
// Variable store header flags
#define NVRAM_VSS_VARIABLE_STORE_FORMATTED 0x5a
#define NVRAM_VSS_VARIABLE_STORE_HEALTHY 0xfe
// Variable store status
#define NVRAM_VSS_VARIABLE_STORE_STATUS_RAW 0
#define NVRAM_VSS_VARIABLE_STORE_STATUS_VALID 1
#define NVRAM_VSS_VARIABLE_STORE_STATUS_INVALID 2
#define NVRAM_VSS_VARIABLE_STORE_STATUS_UNKNOWN 3
// Variable store header
typedef struct _VSS_VARIABLE_STORE_HEADER {
UINT32 Signature; // $VSS signature
UINT32 Size; // Size of variable storage, including storage header
UINT8 Format; // Storage format state
UINT8 State; // Storage health state
UINT16 Unknown; // Used in Apple $SVS varstores
UINT32 : 32;
} VSS_VARIABLE_STORE_HEADER;
// Apple Fsys store header
typedef struct _APPLE_FSYS_STORE_HEADER {
UINT32 Signature; // Fsys signature
UINT8 Unknown; // Still unknown
UINT32 Unknown2; // Still unknown
UINT16 Size; // Size of variable storage
} APPLE_FSYS_STORE_HEADER;
// Apple Fsys variable format
// UINT8 NameLength;
// CHAR8 Name[];
// UINT16 DataLength;
// UINT8 Data[]
// End with a chunk named "EOF" without data
// All free bytes are zeros
// Has CRC32 of the whole store without checksum field at the end
// Normal variable header
typedef struct _VSS_VARIABLE_HEADER {
UINT16 StartId; // Variable start marker AA55
UINT8 State; // Variable state
UINT8 : 8;
UINT32 Attributes; // Variable attributes
UINT32 NameSize; // Size of variable name, null-terminated UCS2 string
UINT32 DataSize; // Size of variable data without header and name
EFI_GUID VendorGuid; // Variable vendor GUID
} VSS_VARIABLE_HEADER;
// Apple variation of normal variable header, with one new field
typedef struct _VSS_APPLE_VARIABLE_HEADER {
UINT16 StartId; // Variable start marker AA55
UINT8 State; // Variable state
UINT8 : 8;
UINT32 Attributes; // Variable attributes
UINT32 NameSize; // Size of variable name, null-terminated UCS2 string
UINT32 DataSize; // Size of variable data without header and name
EFI_GUID VendorGuid; // Variable vendor GUID
UINT32 DataCrc32; // CRC32 of the data
} VSS_APPLE_VARIABLE_HEADER;
// Authenticated variable header, used for SecureBoot vars
typedef struct _VSS_AUTH_VARIABLE_HEADER {
UINT16 StartId; // Variable start marker AA55
UINT8 State; // Variable state
UINT8 : 8;
UINT32 Attributes; // Variable attributes
UINT64 MonotonicCounter; // Monotonic counter against replay attack
EFI_TIME Timestamp; // Time stamp against replay attack
UINT32 PubKeyIndex; // Index in PubKey database
UINT32 NameSize; // Size of variable name, null-terminated UCS2 string
UINT32 DataSize; // Size of variable data without header and name
EFI_GUID VendorGuid; // Variable vendor GUID
} VSS_AUTH_VARIABLE_HEADER;
// VSS variable states
#define NVRAM_VSS_VARIABLE_IN_DELETED_TRANSITION 0xfe // Variable is in obsolete transistion
#define NVRAM_VSS_VARIABLE_DELETED 0xfd // Variable is obsolete
#define NVRAM_VSS_VARIABLE_HEADER_VALID 0x7f // Variable has valid header
#define NVRAM_VSS_VARIABLE_ADDED 0x3f // Variable has been completely added
#define NVRAM_VSS_IS_VARIABLE_STATE(_c, _Mask) (BOOLEAN) (((~_c) & (~_Mask)) != 0)
// VSS variable attributes
#define NVRAM_VSS_VARIABLE_NON_VOLATILE 0x00000001
#define NVRAM_VSS_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define NVRAM_VSS_VARIABLE_RUNTIME_ACCESS 0x00000004
#define NVRAM_VSS_VARIABLE_HARDWARE_ERROR_RECORD 0x00000008
#define NVRAM_VSS_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x00000010
#define NVRAM_VSS_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x00000020
#define NVRAM_VSS_VARIABLE_APPEND_WRITE 0x00000040
#define NVRAM_VSS_VARIABLE_APPLE_DATA_CHECKSUM 0x80000000
// Restore previous packing rules
#pragma pack(pop)

View File

@ -66,7 +66,11 @@ QString itemTypeToQString(const UINT8 type)
case Types::FreeSpace:
return QObject::tr("Free space");
case Types::NvramVariableNvar:
return QObject::tr("NVAR");
return QObject::tr("NVAR variable");
case Types::NvramStorageVss:
return QObject::tr("VSS storage");
case Types::NvramVariableVss:
return QObject::tr("VSS variable");
default:
return QObject::tr("Unknown");
}
@ -99,6 +103,8 @@ QString itemSubtypeToQString(const UINT8 type, const UINT8 subtype)
return QObject::tr("FFSv2");
else if (subtype == Subtypes::Ffs3Volume)
return QObject::tr("FFSv3");
else if (subtype == Subtypes::VssNvramVolume)
return QObject::tr("VSS NVRAM");
else
return QObject::tr("Unknown subtype");
case Types::Capsule:
@ -133,6 +139,19 @@ QString itemSubtypeToQString(const UINT8 type, const UINT8 subtype)
return QObject::tr("Full");
else
return QObject::tr("Unknown subtype");
case Types::NvramStorageVss:
return QString();
case Types::NvramVariableVss:
if (subtype == Subtypes::InvalidVss)
return QObject::tr("Invalid");
if (subtype == Subtypes::StandardVss)
return QObject::tr("Standard");
if (subtype == Subtypes::AppleCrc32Vss)
return QObject::tr("Apple CRC32");
if (subtype == Subtypes::AuthVss)
return QObject::tr("Auth");
else
return QObject::tr("Unknown subtype");
default:
return QObject::tr("Unknown subtype");
}
@ -162,7 +181,7 @@ QString actionTypeToQString(const UINT8 action)
{
switch (action) {
case Actions::NoAction:
return "";
return QString();
case Actions::Create:
return QObject::tr("Create");
case Actions::Insert:

View File

@ -43,7 +43,9 @@ namespace Types {
File,
Section,
FreeSpace,
NvramVariableNvar
NvramVariableNvar,
NvramStorageVss,
NvramVariableVss
};
}
@ -63,7 +65,8 @@ namespace Subtypes {
enum VolumeSubtypes {
UnknownVolume = 90,
Ffs2Volume,
Ffs3Volume
Ffs3Volume,
VssNvramVolume
};
enum RegionSubtypes {
@ -92,6 +95,13 @@ namespace Subtypes {
DataNvar,
FullNvar
};
enum VssVariableSubtypes {
InvalidVss = 130,
StandardVss,
AppleCrc32Vss,
AuthVss
};
};
// *ToQString conversion routines

View File

@ -32,6 +32,7 @@ PARSING_DATA parsingDataFromQModelIndex(const QModelIndex & index)
data.offset = 0;
data.address = 0;
data.ffsVersion = 0; // Unknown by default
data.emptyByte = 0xFF; // Default value for SPI flash
// Type-specific parts remain unitialized
return data;
@ -94,6 +95,7 @@ QString errorCodeToQString(UINT8 errorCode)
case ERR_DEPEX_PARSE_FAILED: return QObject::tr("Dependency expression parsing failed");
case ERR_TRUNCATED_IMAGE: return QObject::tr("Image is truncated");
case ERR_INVALID_CAPSULE: return QObject::tr("Invalid capsule");
case ERR_STORAGES_NOT_FOUND: return QObject::tr("Storages not found");
default: return QObject::tr("Unknown error %1").arg(errorCode);
}
}