From 95290abb94076929fa2a00e57e7bc6a08c6acee6 Mon Sep 17 00:00:00 2001 From: Nikolaj Schlej Date: Mon, 28 Mar 2016 15:03:32 +0200 Subject: [PATCH] Initial support for VSS format - normal, auth and apple variations supported - some UI additions and code cleanup TBD --- UEFITool/uefitool.cpp | 13 +- common/basetypes.h | 18 +- common/ffs.h | 10 +- common/ffsops.cpp | 1 + common/ffsparser.cpp | 506 +++++++++++++++++++++++++++++++++++++++++- common/ffsparser.h | 6 + common/nvram.cpp | 15 +- common/nvram.h | 108 ++++++++- common/types.cpp | 23 +- common/types.h | 14 +- common/utility.cpp | 2 + 11 files changed, 690 insertions(+), 26 deletions(-) diff --git a/UEFITool/uefitool.cpp b/UEFITool/uefitool.cpp index 5fb0120..fcc5844 100644 --- a/UEFITool/uefitool.cpp +++ b/UEFITool/uefitool.cpp @@ -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 ¤t) 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; } diff --git a/common/basetypes.h b/common/basetypes.h index 27fdc6b..c76f40d 100644 --- a/common/basetypes.h +++ b/common/basetypes.h @@ -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 diff --git a/common/ffs.h b/common/ffs.h index a62b32e..4ea0b51 100644 --- a/common/ffs.h +++ b/common/ffs.h @@ -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 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 diff --git a/common/ffsops.cpp b/common/ffsops.cpp index 2d38cd5..39c6b44 100644 --- a/common/ffsops.cpp +++ b/common/ffsops.cpp @@ -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; diff --git a/common/ffsparser.cpp b/common/ffsparser.cpp index 3443957..7cfaed1 100644 --- a/common/ffsparser.cpp +++ b/common/ffsparser.cpp @@ -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; @@ -2911,9 +2924,7 @@ STATUS FfsParser::parseNvarStorage(const QByteArray & data, const QModelIndex & QByteArray header; 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; +} \ No newline at end of file diff --git a/common/ffsparser.h b/common/ffsparser.h index 8c3e292..9949c2c 100644 --- a/common/ffsparser.h +++ b/common/ffsparser.h @@ -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()); }; diff --git a/common/nvram.cpp b/common/nvram.cpp index fd2235a..b3cd8eb 100644 --- a/common/nvram.cpp +++ b/common/nvram.cpp @@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #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); +} diff --git a/common/nvram.h b/common/nvram.h index 0634e74..69b3f3f 100644 --- a/common/nvram.h +++ b/common/nvram.h @@ -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) diff --git a/common/types.cpp b/common/types.cpp index a541059..047d4a4 100644 --- a/common/types.cpp +++ b/common/types.cpp @@ -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: diff --git a/common/types.h b/common/types.h index 1f59d0f..32f71c8 100644 --- a/common/types.h +++ b/common/types.h @@ -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 diff --git a/common/utility.cpp b/common/utility.cpp index 47a1209..e984968 100644 --- a/common/utility.cpp +++ b/common/utility.cpp @@ -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); } }