UEFITool/ffsengine.cpp
Nikolaj Schlej 64a7c2ce2c GCC and clang adaptations
- it seems that C99-style arrays init is MSVC-specific feature
- some more const qualifiers
2015-01-31 15:46:17 +01:00

4093 lines
166 KiB
C++

/* ffsengine.cpp
Copyright (c) 2015, Nikolaj Schlej. All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*/
#include <math.h>
#include "ffsengine.h"
#include "types.h"
#include "treemodel.h"
#include "descriptor.h"
#include "ffs.h"
#include "gbe.h"
#include "me.h"
#include "Tiano/EfiTianoCompress.h"
#include "Tiano/EfiTianoDecompress.h"
#include "LZMA/LzmaCompress.h"
#include "LZMA/LzmaDecompress.h"
#ifdef _CONSOLE
#include <iostream>
#endif
QString errorMessage(UINT8 errorCode)
{
switch (errorCode) {
case ERR_SUCCESS: return QObject::tr("Success");
case ERR_NOT_IMPLEMENTED: return QObject::tr("Not implemented");
case ERR_INVALID_PARAMETER: return QObject::tr("Function called with invalid parameter");
case ERR_BUFFER_TOO_SMALL: return QObject::tr("Buffer too small");
case ERR_OUT_OF_RESOURCES: return QObject::tr("Out of resources");
case ERR_OUT_OF_MEMORY: return QObject::tr("Out of memory");
case ERR_FILE_OPEN: return QObject::tr("File can't be opened");
case ERR_FILE_READ: return QObject::tr("File can't be read");
case ERR_FILE_WRITE: return QObject::tr("File can't be written");
case ERR_ITEM_NOT_FOUND: return QObject::tr("Item not found");
case ERR_UNKNOWN_ITEM_TYPE: return QObject::tr("Unknown item type");
case ERR_INVALID_FLASH_DESCRIPTOR: return QObject::tr("Invalid flash descriptor");
case ERR_INVALID_REGION: return QObject::tr("Invalid region");
case ERR_EMPTY_REGION: return QObject::tr("Empty region");
case ERR_BIOS_REGION_NOT_FOUND: return QObject::tr("BIOS region not found");
case ERR_VOLUMES_NOT_FOUND: return QObject::tr("UEFI volumes not found");
case ERR_INVALID_VOLUME: return QObject::tr("Invalid UEFI volume");
case ERR_VOLUME_REVISION_NOT_SUPPORTED: return QObject::tr("Volume revision not supported");
case ERR_VOLUME_GROW_FAILED: return QObject::tr("Volume grow failed");
case ERR_UNKNOWN_FFS: return QObject::tr("Unknown file system");
case ERR_INVALID_FILE: return QObject::tr("Invalid file");
case ERR_INVALID_SECTION: return QObject::tr("Invalid section");
case ERR_UNKNOWN_SECTION: return QObject::tr("Unknown section");
case ERR_STANDARD_COMPRESSION_FAILED: return QObject::tr("Standard compression failed");
case ERR_CUSTOMIZED_COMPRESSION_FAILED: return QObject::tr("Customized compression failed");
case ERR_STANDARD_DECOMPRESSION_FAILED: return QObject::tr("Standard decompression failed");
case ERR_CUSTOMIZED_DECOMPRESSION_FAILED: return QObject::tr("Customized compression failed");
case ERR_UNKNOWN_COMPRESSION_ALGORITHM: return QObject::tr("Unknown compression method");
case ERR_UNKNOWN_EXTRACT_MODE: return QObject::tr("Unknown extract mode");
case ERR_UNKNOWN_INSERT_MODE: return QObject::tr("Unknown insert mode");
case ERR_UNKNOWN_IMAGE_TYPE: return QObject::tr("Unknown executable image type");
case ERR_UNKNOWN_PE_OPTIONAL_HEADER_TYPE: return QObject::tr("Unknown PE optional header type");
case ERR_UNKNOWN_RELOCATION_TYPE: return QObject::tr("Unknown relocation type");
case ERR_GENERIC_CALL_NOT_SUPPORTED: return QObject::tr("Generic call not supported");
case ERR_VOLUME_BASE_NOT_FOUND: return QObject::tr("Volume base address not found");
case ERR_PEI_CORE_ENTRY_POINT_NOT_FOUND: return QObject::tr("PEI core entry point not found");
case ERR_COMPLEX_BLOCK_MAP: return QObject::tr("Block map structure too complex for correct analysis");
case ERR_DIR_ALREADY_EXIST: return QObject::tr("Directory already exists");
case ERR_DIR_CREATE: return QObject::tr("Directory can't be created");
case ERR_UNKNOWN_PATCH_TYPE: return QObject::tr("Unknown patch type");
case ERR_PATCH_OFFSET_OUT_OF_BOUNDS: return QObject::tr("Patch offset out of bounds");
case ERR_INVALID_SYMBOL: return QObject::tr("Invalid symbol");
case ERR_NOTHING_TO_PATCH: return QObject::tr("Nothing to patch");
case ERR_DEPEX_PARSE_FAILED: return QObject::tr("Dependency expression parsing failed");
default: return QObject::tr("Unknown error %1").arg(errorCode);
}
}
FfsEngine::FfsEngine(QObject *parent)
: QObject(parent)
{
model = new TreeModel();
oldPeiCoreEntryPoint = 0;
newPeiCoreEntryPoint = 0;
dumped = false;
}
FfsEngine::~FfsEngine(void)
{
delete model;
}
TreeModel* FfsEngine::treeModel() const
{
return model;
}
void FfsEngine::msg(const QString & message, const QModelIndex & index)
{
#ifndef _DISABLE_ENGINE_MESSAGES
#ifndef _CONSOLE
messageItems.enqueue(MessageListItem(message, NULL, 0, index));
#else
(void) index;
std::cout << message.toLatin1().constData() << std::endl;
#endif
#else
(void)message;
(void)index;
#endif
}
#ifndef _CONSOLE
QQueue<MessageListItem> FfsEngine::messages() const
{
return messageItems;
}
void FfsEngine::clearMessages()
{
messageItems.clear();
}
#endif
bool FfsEngine::hasIntersection(const UINT32 begin1, const UINT32 end1, const UINT32 begin2, const UINT32 end2)
{
if (begin1 < begin2 && begin2 < end1)
return true;
if (begin1 < end2 && end2 < end1)
return true;
if (begin2 < begin1 && begin1 < end2)
return true;
if (begin2 < end1 && end1 < end2)
return true;
return false;
}
// Firmware image parsing
UINT8 FfsEngine::parseImageFile(const QByteArray & buffer)
{
oldPeiCoreEntryPoint = 0;
newPeiCoreEntryPoint = 0;
// Check buffer size to be more then or equal to size of EFI_CAPSULE_HEADER
if ((UINT32)buffer.size() <= sizeof(EFI_CAPSULE_HEADER)) {
msg(tr("parseImageFile: Image file is smaller then minimum size of %1 bytes").arg(sizeof(EFI_CAPSULE_HEADER)));
return ERR_INVALID_PARAMETER;
}
// Check buffer for being normal EFI capsule header
UINT32 capsuleHeaderSize = 0;
QModelIndex index;
if (buffer.startsWith(EFI_CAPSULE_GUID)) {
// Get info
const EFI_CAPSULE_HEADER* capsuleHeader = (const EFI_CAPSULE_HEADER*)buffer.constData();
capsuleHeaderSize = capsuleHeader->HeaderSize;
QByteArray header = buffer.left(capsuleHeaderSize);
QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize);
QString name = tr("UEFI capsule");
QString info = tr("Capsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h")
.arg(guidToQString(capsuleHeader->CapsuleGuid))
.hexarg(buffer.size()).arg(buffer.size())
.hexarg(capsuleHeader->HeaderSize).arg(capsuleHeader->HeaderSize)
.hexarg(capsuleHeader->CapsuleImageSize).arg(capsuleHeader->CapsuleImageSize)
.hexarg2(capsuleHeader->Flags, 8);
// Add tree item
index = model->addItem(Types::Capsule, ATTR_CAPSULE_TYPE_UEFI20, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body);
}
// Check buffer for being extended Aptio signed capsule header
else if (buffer.startsWith(APTIO_SIGNED_CAPSULE_GUID) || buffer.startsWith(APTIO_UNSIGNED_CAPSULE_GUID)) {
// Get info
bool signedCapsule = buffer.startsWith(APTIO_SIGNED_CAPSULE_GUID);
const APTIO_CAPSULE_HEADER* capsuleHeader = (const APTIO_CAPSULE_HEADER*)buffer.constData();
capsuleHeaderSize = capsuleHeader->RomImageOffset;
QByteArray header = buffer.left(capsuleHeaderSize);
QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize);
QString name = tr("AMI Aptio capsule");
QString info = tr("Capsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h")
.arg(guidToQString(capsuleHeader->CapsuleHeader.CapsuleGuid))
.hexarg(buffer.size()).arg(buffer.size())
.hexarg(capsuleHeaderSize).arg(capsuleHeaderSize)
.hexarg(capsuleHeader->CapsuleHeader.CapsuleImageSize - capsuleHeaderSize).arg(capsuleHeader->CapsuleHeader.CapsuleImageSize - capsuleHeaderSize)
.hexarg2(capsuleHeader->CapsuleHeader.Flags, 8);
//!TODO: more info about Aptio capsule
// Fill attributes
UINT32 attr = 0;
CAPSULE_ATTRIBUTES* attributes = (CAPSULE_ATTRIBUTES*)&attr;
attributes->Type = ATTR_CAPSULE_TYPE_APTIO;
attributes->Signed = signedCapsule;
// Add tree item
index = model->addItem(Types::Capsule, attr, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body);
// Show message about possible Aptio signature break
if (signedCapsule) {
msg(tr("parseImageFile: Aptio capsule signature may become invalid after image modifications"), index);
}
}
// Skip capsule header to have flash chip image
QByteArray flashImage = buffer.right(buffer.size() - capsuleHeaderSize);
// Check for Intel flash descriptor presence
const FLASH_DESCRIPTOR_HEADER* descriptorHeader = (const FLASH_DESCRIPTOR_HEADER*)flashImage.constData();
// Check descriptor signature
UINT8 result;
if (descriptorHeader->Signature == FLASH_DESCRIPTOR_SIGNATURE) {
// Parse as Intel image
QModelIndex imageIndex;
result = parseIntelImage(flashImage, imageIndex, index);
if (result != ERR_INVALID_FLASH_DESCRIPTOR)
return result;
}
// Get info
QString name = tr("UEFI image");
QString info = tr("Full size: %1h (%2)")
.hexarg(flashImage.size()).arg(flashImage.size());
// Add tree item
index = model->addItem(Types::Image, ATTR_IMAGE_TYPE_UEFI, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), flashImage, index);
return parseBios(flashImage, index);
}
UINT8 FfsEngine::parseIntelImage(const QByteArray & intelImage, QModelIndex & index, const QModelIndex & parent)
{
// Sanity check
if (intelImage.isEmpty())
return EFI_INVALID_PARAMETER;
// Store the beginning of descriptor as descriptor base address
const UINT8* descriptor = (const UINT8*)intelImage.constData();
UINT32 descriptorBegin = 0;
UINT32 descriptorEnd = FLASH_DESCRIPTOR_SIZE;
// Check for buffer size to be greater or equal to descriptor region size
if (intelImage.size() < FLASH_DESCRIPTOR_SIZE) {
msg(tr("parseIntelImage: Input file is smaller then minimum descriptor size of %1 bytes").arg(FLASH_DESCRIPTOR_SIZE));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
// Parse descriptor map
const FLASH_DESCRIPTOR_MAP* descriptorMap = (const FLASH_DESCRIPTOR_MAP*)(descriptor + sizeof(FLASH_DESCRIPTOR_HEADER));
const FLASH_DESCRIPTOR_UPPER_MAP* upperMap = (const FLASH_DESCRIPTOR_UPPER_MAP*)(descriptor + FLASH_DESCRIPTOR_UPPER_MAP_BASE);
const FLASH_DESCRIPTOR_REGION_SECTION* regionSection = (const FLASH_DESCRIPTOR_REGION_SECTION*)calculateAddress8(descriptor, descriptorMap->RegionBase);
const FLASH_DESCRIPTOR_MASTER_SECTION* masterSection = (const FLASH_DESCRIPTOR_MASTER_SECTION*)calculateAddress8(descriptor, descriptorMap->MasterBase);
// GbE region
QByteArray gbe;
UINT32 gbeBegin = 0;
UINT32 gbeEnd = 0;
if (regionSection->GbeLimit) {
gbeBegin = calculateRegionOffset(regionSection->GbeBase);
gbeEnd = calculateRegionSize(regionSection->GbeBase, regionSection->GbeLimit);
gbe = intelImage.mid(gbeBegin, gbeEnd);
gbeEnd += gbeBegin;
}
// ME region
QByteArray me;
UINT32 meBegin = 0;
UINT32 meEnd = 0;
if (regionSection->MeLimit) {
meBegin = calculateRegionOffset(regionSection->MeBase);
meEnd = calculateRegionSize(regionSection->MeBase, regionSection->MeLimit);
me = intelImage.mid(meBegin, meEnd);
meEnd += meBegin;
}
// PDR region
QByteArray pdr;
UINT32 pdrBegin = 0;
UINT32 pdrEnd = 0;
if (regionSection->PdrLimit) {
pdrBegin = calculateRegionOffset(regionSection->PdrBase);
pdrEnd = calculateRegionSize(regionSection->PdrBase, regionSection->PdrLimit);
pdr = intelImage.mid(pdrBegin, pdrEnd);
pdrEnd += pdrBegin;
}
// BIOS region
QByteArray bios;
UINT32 biosBegin = 0;
UINT32 biosEnd = 0;
if (regionSection->BiosLimit) {
biosBegin = calculateRegionOffset(regionSection->BiosBase);
biosEnd = calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit);
// Check for Gigabyte specific descriptor map
if (biosEnd - biosBegin == (UINT32)intelImage.size()) {
if (!meEnd) {
msg(tr("parseIntelImage: can determine BIOS region start from Gigabyte-specific descriptor"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
biosBegin = meEnd;
}
bios = intelImage.mid(biosBegin, biosEnd);
biosEnd += biosBegin;
}
else {
msg(tr("parseIntelImage: descriptor parsing failed, BIOS region not found in descriptor"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
// Check for intersections between regions
if (hasIntersection(descriptorBegin, descriptorEnd, gbeBegin, gbeEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, descriptor region has intersection with GbE region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(descriptorBegin, descriptorEnd, meBegin, meEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, descriptor region has intersection with ME region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(descriptorBegin, descriptorEnd, biosBegin, biosEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, descriptor region has intersection with BIOS region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(descriptorBegin, descriptorEnd, pdrBegin, pdrEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, descriptor region has intersection with PDR region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(gbeBegin, gbeEnd, meBegin, meEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, GbE region has intersection with ME region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(gbeBegin, gbeEnd, biosBegin, biosEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, GbE region has intersection with BIOS region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(gbeBegin, gbeEnd, pdrBegin, pdrEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, GbE region has intersection with PDR region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(meBegin, meEnd, biosBegin, biosEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, ME region has intersection with BIOS region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(meBegin, meEnd, pdrBegin, pdrEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, ME region has intersection with PDR region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
if (hasIntersection(biosBegin, biosEnd, pdrBegin, pdrEnd)) {
msg(tr("parseIntelImage: descriptor parsing failed, BIOS region has intersection with PDR region"));
return ERR_INVALID_FLASH_DESCRIPTOR;
}
// Region map is consistent
// Intel image
QString name = tr("Intel image");
QString info = tr("Full size: %1h (%2)\nFlash chips: %3\nRegions: %4\nMasters: %5\nPCH straps: %6\nPROC straps: %7\nICC table entries: %8")
.hexarg(intelImage.size()).arg(intelImage.size())
.arg(descriptorMap->NumberOfFlashChips + 1) //
.arg(descriptorMap->NumberOfRegions + 1) // Zero-based numbers in storage
.arg(descriptorMap->NumberOfMasters + 1) //
.arg(descriptorMap->NumberOfPchStraps)
.arg(descriptorMap->NumberOfProcStraps)
.arg(descriptorMap->NumberOfIccTableEntries);
// Add Intel image tree item
index = model->addItem(Types::Image, ATTR_IMAGE_TYPE_DESCRIPTOR, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), intelImage, parent);
// Descriptor
// Get descriptor info
QByteArray body = intelImage.left(FLASH_DESCRIPTOR_SIZE);
name = tr("Descriptor region");
info = tr("Full size: %1h (%2)").hexarg(FLASH_DESCRIPTOR_SIZE).arg(FLASH_DESCRIPTOR_SIZE);
// Check regions presence once again
QVector<UINT32> offsets;
if (regionSection->GbeLimit) {
offsets.append(gbeBegin);
info += tr("\nGbE region offset: %1h").hexarg(gbeBegin);
}
if (regionSection->MeLimit) {
offsets.append(meBegin);
info += tr("\nME region offset: %1h").hexarg(meBegin);
}
if (regionSection->BiosLimit) {
offsets.append(biosBegin);
info += tr("\nBIOS region offset: %1h").hexarg(biosBegin);
}
if (regionSection->PdrLimit) {
offsets.append(pdrBegin);
info += tr("\nPDR region offset: %1h").hexarg(pdrBegin);
}
// Region access settings
info += tr("\nRegion access settings:");
info += tr("\nBIOS:%1%2h ME:%3%4h GbE:%5%6h")
.hexarg2(masterSection->BiosRead, 2)
.hexarg2(masterSection->BiosWrite, 2)
.hexarg2(masterSection->MeRead, 2)
.hexarg2(masterSection->MeWrite, 2)
.hexarg2(masterSection->GbeRead, 2)
.hexarg2(masterSection->GbeWrite, 2);
// BIOS access table
info += tr("\nBIOS access table:");
info += tr("\n Read Write");
info += tr("\nDesc %1 %2")
.arg(masterSection->BiosRead & FLASH_DESCRIPTOR_REGION_ACCESS_DESC ? "Yes " : "No ")
.arg(masterSection->BiosWrite & FLASH_DESCRIPTOR_REGION_ACCESS_DESC ? "Yes " : "No ");
info += tr("\nBIOS Yes Yes");
info += tr("\nME %1 %2")
.arg(masterSection->BiosRead & FLASH_DESCRIPTOR_REGION_ACCESS_ME ? "Yes " : "No ")
.arg(masterSection->BiosWrite & FLASH_DESCRIPTOR_REGION_ACCESS_ME ? "Yes " : "No ");
info += tr("\nGbE %1 %2")
.arg(masterSection->BiosRead & FLASH_DESCRIPTOR_REGION_ACCESS_GBE ? "Yes " : "No ")
.arg(masterSection->BiosWrite & FLASH_DESCRIPTOR_REGION_ACCESS_GBE ? "Yes " : "No ");
info += tr("\nPDR %1 %2")
.arg(masterSection->BiosRead & FLASH_DESCRIPTOR_REGION_ACCESS_PDR ? "Yes " : "No ")
.arg(masterSection->BiosWrite & FLASH_DESCRIPTOR_REGION_ACCESS_PDR ? "Yes " : "No ");
// VSCC table
const VSCC_TABLE_ENTRY* vsccTableEntry = (const VSCC_TABLE_ENTRY*)(descriptor + ((UINT16)upperMap->VsccTableBase << 4));
info += tr("\nFlash chips in VSCC table:");
UINT8 vsscTableSize = upperMap->VsccTableSize * sizeof(UINT32) / sizeof(VSCC_TABLE_ENTRY);
for (int i = 0; i < vsscTableSize; i++) {
info += tr("\n%1%2%3h")
.hexarg2(vsccTableEntry->VendorId, 2)
.hexarg2(vsccTableEntry->DeviceId0, 2)
.hexarg2(vsccTableEntry->DeviceId1, 2);
vsccTableEntry++;
}
// Add descriptor tree item
model->addItem(Types::Region, ATTR_REGION_TYPE_DESCRIPTOR, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), body, index);
// Sort regions in ascending order
qSort(offsets);
// Parse regions
UINT8 result = 0;
for (int i = 0; i < offsets.count(); i++) {
// Parse GbE region
if (offsets.at(i) == gbeBegin) {
QModelIndex gbeIndex;
result = parseGbeRegion(gbe, gbeIndex, index);
}
// Parse ME region
else if (offsets.at(i) == meBegin) {
QModelIndex meIndex;
result = parseMeRegion(me, meIndex, index);
}
// Parse BIOS region
else if (offsets.at(i) == biosBegin) {
QModelIndex biosIndex;
result = parseBiosRegion(bios, biosIndex, index);
}
// Parse PDR region
else if (offsets.at(i) == pdrBegin) {
QModelIndex pdrIndex;
result = parsePdrRegion(pdr, pdrIndex, index);
}
if (result)
return result;
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseGbeRegion(const QByteArray & gbe, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
// Check sanity
if (gbe.isEmpty())
return ERR_EMPTY_REGION;
// Get info
QString name = tr("GbE region");
const GBE_MAC_ADDRESS* mac = (const GBE_MAC_ADDRESS*)gbe.constData();
const GBE_VERSION* version = (const GBE_VERSION*)(gbe.constData() + GBE_VERSION_OFFSET);
QString info = tr("Full size: %1h (%2)\nMAC: %3:%4:%5:%6:%7:%8\nVersion: %9.%10")
.hexarg(gbe.size()).arg(gbe.size())
.hexarg2(mac->vendor[0], 2)
.hexarg2(mac->vendor[1], 2)
.hexarg2(mac->vendor[2], 2)
.hexarg2(mac->device[0], 2)
.hexarg2(mac->device[1], 2)
.hexarg2(mac->device[2], 2)
.arg(version->major)
.arg(version->minor);
// Add tree item
index = model->addItem(Types::Region, ATTR_REGION_TYPE_GBE, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), gbe, parent, mode);
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseMeRegion(const QByteArray & me, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
// Check sanity
if (me.isEmpty())
return ERR_EMPTY_REGION;
// Get info
QString name = tr("ME/TXE region");
QString info = tr("Full size: %1h (%2)").
hexarg(me.size()).arg(me.size());
// Parse region
bool versionFound = true;
bool emptyRegion = false;
// Check for empty region
if (me.count() == me.count('\xFF') || me.count() == me.count('\x00')) {
// Further parsing not needed
emptyRegion = true;
info += tr("\nState: empty");
}
else {
// Search for new signature
INT32 versionOffset = me.indexOf(ME_VERSION_SIGNATURE2);
if (versionOffset < 0){ // New signature not found
// Search for old signature
versionOffset = me.indexOf(ME_VERSION_SIGNATURE);
if (versionOffset < 0){
info += tr("\nVersion: unknown");
versionFound = false;
}
}
// Add version information
if (versionFound) {
const ME_VERSION* version = (const ME_VERSION*)(me.constData() + versionOffset);
info += tr("\nVersion: %1.%2.%3.%4")
.arg(version->major)
.arg(version->minor)
.arg(version->bugfix)
.arg(version->build);
}
}
// Fill attributes
UINT32 attr = 0;
REGION_ATTRIBUTES* attributes = (REGION_ATTRIBUTES*)&attr;
attributes->Type = ATTR_REGION_TYPE_ME;
attributes->Empty = emptyRegion;
// Add tree item
index = model->addItem(Types::Region, attr, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), me, parent, mode);
// Show messages
if (emptyRegion) {
msg(tr("parseRegion: ME/TXE region is empty"), index);
}
else if (!versionFound) {
msg(tr("parseRegion: ME/TXE region version is unknown, it can be damaged"), index);
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::parsePdrRegion(const QByteArray & pdr, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
// Check sanity
if (pdr.isEmpty())
return ERR_EMPTY_REGION;
// Get info
QString name = tr("PDR region");
QString info = tr("Full size: %1h (%2)").
hexarg(pdr.size()).arg(pdr.size());
// Add tree item
index = model->addItem(Types::Region, ATTR_REGION_TYPE_PDR, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), pdr, parent, mode);
// Parse PDR region as BIOS space
UINT8 result = parseBios(pdr, index);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseBiosRegion(const QByteArray & bios, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
if (bios.isEmpty())
return ERR_EMPTY_REGION;
// Get info
QString name = tr("BIOS region");
QString info = tr("Full size: %1h (%2)").
hexarg(bios.size()).arg(bios.size());
// Add tree item
index = model->addItem(Types::Region, ATTR_REGION_TYPE_BIOS, COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), bios, parent, mode);
return parseBios(bios, index);
}
UINT32 FfsEngine::getPaddingType(const QByteArray & padding)
{
if (padding.count('\x00') == padding.count())
return ATTR_PADDING_ZERO_EMPTY;
if (padding.count('\xFF') == padding.count())
return ATTR_PADDING_ONE_EMPTY;
return ATTR_PADDING_DATA;
}
UINT8 FfsEngine::parseBios(const QByteArray & bios, const QModelIndex & parent)
{
// Search for first volume
UINT32 prevVolumeOffset;
UINT8 result;
result = findNextVolume(bios, 0, prevVolumeOffset);
if (result)
return result;
// First volume is not at the beginning of BIOS space
QString name;
QString info;
if (prevVolumeOffset > 0) {
// Get info
QByteArray padding = bios.left(prevVolumeOffset);
name = tr("Padding");
info = tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), padding, parent);
}
// Search for and parse all volumes
UINT32 volumeOffset = prevVolumeOffset;
UINT32 prevVolumeSize = 0;
UINT32 volumeSize = 0;
UINT32 bmVolumeSize = 0;
while (true)
{
bool msgAlignmentBitsSet = false;
bool msgUnaligned = false;
bool msgUnknownRevision = false;
bool msgSizeMismach = false;
// Padding between volumes
if (volumeOffset > prevVolumeOffset + prevVolumeSize) {
UINT32 paddingSize = volumeOffset - prevVolumeOffset - prevVolumeSize;
QByteArray padding = bios.mid(prevVolumeOffset + prevVolumeSize, paddingSize);
// Get info
name = tr("Padding");
info = tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), padding, parent);
}
// Get volume size
result = getVolumeSize(bios, volumeOffset, volumeSize, bmVolumeSize);
if (result)
return result;
// Check reported size
if (volumeSize != bmVolumeSize)
msgSizeMismach = true;
//!TODO: now we trust header size, sometimes it's the bmVolumeSize that is OK, need to implement some settings for it
//Check that volume is fully present in input
if (volumeOffset + volumeSize > (UINT32)bios.size()) {
msg(tr("parseBios: One of volumes inside overlaps the end of data"), parent);
return ERR_INVALID_VOLUME;
}
// Check volume revision and alignment
const EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (const EFI_FIRMWARE_VOLUME_HEADER*)(bios.constData() + volumeOffset);
UINT32 alignment;
if (volumeHeader->Revision == 1) {
// Acquire alignment capability bit
bool alignmentCap = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_CAP;
if (!alignmentCap) {
if (volumeHeader->Attributes & 0xFFFF0000)
msgAlignmentBitsSet = true;
}
}
else if (volumeHeader->Revision == 2) {
// Acquire alignment
alignment = (UINT32)pow(2.0, (int)(volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16);
// Check alignment
if (volumeOffset % alignment)
msgUnaligned = true;
}
else
msgUnknownRevision = true;
// Parse volume
QModelIndex index;
UINT8 result = parseVolume(bios.mid(volumeOffset, volumeSize), index, parent);
if (result)
msg(tr("parseBios: Volume parsing failed with error \"%1\"").arg(errorMessage(result)), parent);
// Show messages
if (msgAlignmentBitsSet)
msg("parseBios: Alignment bits set on volume without alignment capability", index);
if (msgUnaligned)
msg(tr("parseBios: Unaligned revision 2 volume"), index);
if (msgUnknownRevision)
msg(tr("parseBios: Unknown volume revision %1").arg(volumeHeader->Revision), index);
if (msgSizeMismach)
msg(tr("parseBios: Volume size stored in header %1h differs from calculated using block map %2h")
.hexarg(volumeSize)
.hexarg(bmVolumeSize), index);
// Go to next volume
prevVolumeOffset = volumeOffset;
prevVolumeSize = volumeSize;
result = findNextVolume(bios, volumeOffset + prevVolumeSize, volumeOffset);
if (result) {
UINT32 endPaddingSize = bios.size() - prevVolumeOffset - prevVolumeSize;
// Padding at the end of BIOS space
if (endPaddingSize > 0) {
QByteArray padding = bios.right(endPaddingSize);
// Get info
name = tr("Padding");
info = tr("Full size: %1h (%2)")
.hexarg(padding.size()).arg(padding.size());
// Add tree item
model->addItem(Types::Padding, getPaddingType(padding), COMPRESSION_ALGORITHM_NONE, name, "", info, QByteArray(), padding, parent);
}
break;
}
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::findNextVolume(const QByteArray & bios, UINT32 volumeOffset, UINT32 & nextVolumeOffset)
{
int nextIndex = bios.indexOf(EFI_FV_SIGNATURE, volumeOffset);
if (nextIndex < EFI_FV_SIGNATURE_OFFSET) {
return ERR_VOLUMES_NOT_FOUND;
}
nextVolumeOffset = nextIndex - EFI_FV_SIGNATURE_OFFSET;
return ERR_SUCCESS;
}
UINT8 FfsEngine::getVolumeSize(const QByteArray & bios, UINT32 volumeOffset, UINT32 & volumeSize, UINT32 & bmVolumeSize)
{
// Populate volume header
const EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (const EFI_FIRMWARE_VOLUME_HEADER*)(bios.constData() + volumeOffset);
// Check volume signature
if (QByteArray((const char*)&volumeHeader->Signature, sizeof(volumeHeader->Signature)) != EFI_FV_SIGNATURE)
return ERR_INVALID_VOLUME;
// Calculate volume size using BlockMap
const EFI_FV_BLOCK_MAP_ENTRY* entry = (const EFI_FV_BLOCK_MAP_ENTRY*)(bios.constData() + volumeOffset + sizeof(EFI_FIRMWARE_VOLUME_HEADER));
UINT32 calcVolumeSize = 0;
while (entry->NumBlocks != 0 && entry->Length != 0) {
if ((void*)entry > bios.constData() + bios.size())
return ERR_INVALID_VOLUME;
calcVolumeSize += entry->NumBlocks * entry->Length;
entry += 1;
}
volumeSize = volumeHeader->FvLength;
bmVolumeSize = calcVolumeSize;
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
// Populate volume header
const EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (const EFI_FIRMWARE_VOLUME_HEADER*)(volume.constData());
// Calculate volume header size
UINT32 headerSize;
if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) {
const EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (const EFI_FIRMWARE_VOLUME_EXT_HEADER*)(volume.constData() + volumeHeader->ExtHeaderOffset);
headerSize = volumeHeader->ExtHeaderOffset + extendedHeader->ExtHeaderSize;
}
else
headerSize = volumeHeader->HeaderLength;
// Sanity check after some new crazy MSI images
headerSize = ALIGN8(headerSize);
// Check for volume structure to be known
UINT32 attr = 0;
VOLUME_ATTRIBUTES* attributes = (VOLUME_ATTRIBUTES*)&attr;
attributes->Unknown = true;
// Check for FFS v2 volume
if (FFSv2Volumes.contains(QByteArray::fromRawData((const char*)volumeHeader->FileSystemGuid.Data, sizeof(EFI_GUID)))) {
attributes->Unknown = false;
attributes->FsVersion = 2;
}
//!TODO:Check for FFS v3 volume
// Check attributes
// Determine value of empty byte
char empty = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00';
// Get volume size
UINT32 volumeSize;
UINT32 bmVolumeSize;
UINT8 result = getVolumeSize(volume, 0, volumeSize, bmVolumeSize);
if (result)
return result;
// Check for Apple CRC32 in ZeroVector
UINT32 crc32FromZeroVector = *(UINT32*)(volume.constData() + 8);
if (crc32FromZeroVector != 0) {
// Calculate CRC32 of the volume body
UINT32 crc = crc32(0, (const UINT8*)(volume.constData() + volumeHeader->HeaderLength), volumeSize - volumeHeader->HeaderLength);
if (crc == crc32FromZeroVector) {
attributes->ZeroVectorCrc = true;
}
}
// Check header checksum by recalculating it
bool msgInvalidChecksum = false;
if (!attributes->Unknown && calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength))
msgInvalidChecksum = true;
// Get info
QString name = guidToQString(volumeHeader->FileSystemGuid);
QString info = tr("ZeroVector:\n%1 %2 %3 %4 %5 %6 %7 %8\n%9 %10 %11 %12 %13 %14 %15 %16\nFileSystem GUID: %17\nFull size: %18h (%19)\n"
"Header size: %20h (%21)\nBody size: %22h (%23)\nRevision: %24\nAttributes: %25h\nErase polarity: %26")
.hexarg2(volumeHeader->ZeroVector[0], 2).hexarg2(volumeHeader->ZeroVector[1], 2).hexarg2(volumeHeader->ZeroVector[2], 2).hexarg2(volumeHeader->ZeroVector[3], 2)
.hexarg2(volumeHeader->ZeroVector[4], 2).hexarg2(volumeHeader->ZeroVector[5], 2).hexarg2(volumeHeader->ZeroVector[6], 2).hexarg2(volumeHeader->ZeroVector[7], 2)
.hexarg2(volumeHeader->ZeroVector[8], 2).hexarg2(volumeHeader->ZeroVector[9], 2).hexarg2(volumeHeader->ZeroVector[10], 2).hexarg2(volumeHeader->ZeroVector[11], 2)
.hexarg2(volumeHeader->ZeroVector[12], 2).hexarg2(volumeHeader->ZeroVector[13], 2).hexarg2(volumeHeader->ZeroVector[14], 2).hexarg2(volumeHeader->ZeroVector[15], 2)
.arg(guidToQString(volumeHeader->FileSystemGuid))
.hexarg(volumeSize).arg(volumeSize)
.hexarg(headerSize).arg(headerSize)
.hexarg(volumeSize - headerSize).arg(volumeSize - headerSize)
.arg(volumeHeader->Revision)
.hexarg2(volumeHeader->Attributes, 8)
.arg(empty ? "1" : "0");
// Apple CRC32 volume
if (attributes->ZeroVectorCrc) {
info += tr("\nCRC32 in ZeroVector: valid");
}
// Extended header present
if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) {
const EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (const EFI_FIRMWARE_VOLUME_EXT_HEADER*)(volume.constData() + volumeHeader->ExtHeaderOffset);
info += tr("\nExtended header size: %1h (%2)\nVolume GUID: %3")
.hexarg(extendedHeader->ExtHeaderSize).arg(extendedHeader->ExtHeaderSize)
.arg(guidToQString(extendedHeader->FvName));
}
// Add tree item
QByteArray header = volume.left(headerSize);
QByteArray body = volume.mid(headerSize, volumeSize - headerSize);
index = model->addItem(Types::Volume, attr, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Show messages
if (attributes->Unknown) {
msg(tr("parseVolume: Unknown file system %1").arg(guidToQString(volumeHeader->FileSystemGuid)), index);
// Do not parse unknown volumes
return ERR_SUCCESS;
}
if (msgInvalidChecksum) {
msg(tr("parseVolume: Volume header checksum is invalid"), index);
}
// Search for and parse all files
UINT32 fileOffset = headerSize;
UINT32 fileSize;
QQueue<QByteArray> files;
while (fileOffset < volumeSize) {
bool msgUnalignedFile = false;
bool msgDuplicateGuid = false;
result = getFileSize(volume, fileOffset, fileSize);
if (result)
return result;
// Check file size to be at least size of EFI_FFS_FILE_HEADER
if (fileSize < sizeof(EFI_FFS_FILE_HEADER)) {
msg(tr("parseVolume: Volume has FFS file with invalid size"), index);
return ERR_INVALID_FILE;
}
QByteArray file = volume.mid(fileOffset, fileSize);
QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER));
// If we are at empty space in the end of volume
if (header.count(empty) == header.size()) {
// Check free space to be actually free
QByteArray freeSpace = volume.right(volumeSize - fileOffset);
if (freeSpace.count(empty) != freeSpace.count())
msg(tr("parseVolume: Non-UEFI data found in volume's free space will be destroyed after volume modification"), index);
break; // Exit from loop
}
// Check file alignment
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)header.constData();
UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3];
UINT32 alignment = (UINT32)pow(2.0, alignmentPower);
if ((fileOffset + sizeof(EFI_FFS_FILE_HEADER)) % alignment)
msgUnalignedFile = true;
// Check file GUID
if (fileHeader->Type != EFI_FV_FILETYPE_PAD && files.indexOf(header.left(sizeof(EFI_GUID))) != -1)
msgDuplicateGuid = true;
// Add file GUID to queue
files.enqueue(header.left(sizeof(EFI_GUID)));
// Parse file
QModelIndex fileIndex;
result = parseFile(file, fileIndex, empty == '\xFF' ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
msg(tr("parseVolume: FFS file parsing failed with error \"%1\"").arg(errorMessage(result)), index);
// Show messages
if (msgUnalignedFile)
msg(tr("parseVolume: Unaligned file %1").arg(guidToQString(fileHeader->Name)), fileIndex);
if (msgDuplicateGuid)
msg(tr("parseVolume: File with duplicate GUID %1").arg(guidToQString(fileHeader->Name)), fileIndex);
// Move to next file
fileOffset += fileSize;
fileOffset = ALIGN8(fileOffset);
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::getFileSize(const QByteArray & volume, const UINT32 fileOffset, UINT32 & fileSize)
{
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)(volume.constData() + fileOffset);
fileSize = uint24ToUint32(fileHeader->Size);
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const UINT8 erasePolarity, const QModelIndex & parent, const UINT8 mode)
{
bool msgInvalidHeaderChecksum = false;
bool msgInvalidDataChecksum = false;
bool msgInvalidTailValue = false;
bool msgInvalidType = false;
bool msgNonEmptyPadFile = false;
// Populate file header
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)file.constData();
// Check file state
// Construct empty byte for this file
char empty = erasePolarity ? '\xFF' : '\x00';
// Check header checksum
QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER));
QByteArray tempHeader = header;
EFI_FFS_FILE_HEADER* tempFileHeader = (EFI_FFS_FILE_HEADER*)(tempHeader.data());
tempFileHeader->IntegrityCheck.Checksum.Header = 0;
tempFileHeader->IntegrityCheck.Checksum.File = 0;
UINT8 calculated = calculateChecksum8((const UINT8*)tempFileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1);
if (fileHeader->IntegrityCheck.Checksum.Header != calculated)
msgInvalidHeaderChecksum = true;
// Check data checksum
// Data checksum must be calculated
if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) {
UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER);
// Exclude file tail from data checksum calculation
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)
bufferSize -= sizeof(UINT16);
calculated = calculateChecksum8((const UINT8*)(file.constData() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize);
if (fileHeader->IntegrityCheck.Checksum.File != calculated)
msgInvalidDataChecksum = true;
}
// Data checksum must be one of predefined values
else if (fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM && fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2)
msgInvalidDataChecksum = true;
// Get file body
QByteArray body = file.right(file.size() - sizeof(EFI_FFS_FILE_HEADER));
// Check for file tail presence
QByteArray tail;
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)
{
//Check file tail;
tail = body.right(sizeof(UINT16));
UINT16 tailValue = *(UINT16*)tail.constData();
if (fileHeader->IntegrityCheck.TailReference != (UINT16)~tailValue)
msgInvalidTailValue = true;
// Remove tail from file body
body = body.left(body.size() - sizeof(UINT16));
}
// Parse current file by default
bool parseCurrentFile = true;
bool parseAsBios = false;
// Check file type
switch (fileHeader->Type)
{
case EFI_FV_FILETYPE_ALL:
parseAsBios = true;
break;
case EFI_FV_FILETYPE_RAW:
parseAsBios = true;
break;
case EFI_FV_FILETYPE_FREEFORM:
break;
case EFI_FV_FILETYPE_SECURITY_CORE:
break;
case EFI_FV_FILETYPE_PEI_CORE:
break;
case EFI_FV_FILETYPE_DXE_CORE:
break;
case EFI_FV_FILETYPE_PEIM:
break;
case EFI_FV_FILETYPE_DRIVER:
break;
case EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER:
break;
case EFI_FV_FILETYPE_APPLICATION:
break;
case EFI_FV_FILETYPE_SMM:
break;
case EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE:
break;
case EFI_FV_FILETYPE_COMBINED_SMM_DXE:
break;
case EFI_FV_FILETYPE_SMM_CORE:
break;
case EFI_FV_FILETYPE_PAD:
parseCurrentFile = false;
break;
default:
msgInvalidType = true;
parseCurrentFile = false;
};
// Check for empty file
if (body.count(empty) == body.size()) {
// No need to parse empty files
parseCurrentFile = false;
}
// Check for non-empty pad file
else if (fileHeader->Type == EFI_FV_FILETYPE_PAD) {
msgNonEmptyPadFile = true;
}
// Get info
QString name;
QString info;
if (fileHeader->Type != EFI_FV_FILETYPE_PAD)
name = guidToQString(fileHeader->Name);
else
name = tr("Pad-file");
info = tr("File GUID: %1\nType: %2h\nAttributes: %3h\nFull size: %4h (%5)\nHeader size: %6h (%7)\nBody size: %8h (%9)\nState: %10h")
.arg(guidToQString(fileHeader->Name))
.hexarg2(fileHeader->Type, 2)
.hexarg2(fileHeader->Attributes, 2)
.hexarg(header.size() + body.size()).arg(header.size() + body.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.hexarg2(fileHeader->State, 2);
// Add tree item
index = model->addItem(Types::File, fileHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Show messages
if (msgInvalidHeaderChecksum)
msg(tr("parseFile: Invalid header checksum"), index);
if (msgInvalidDataChecksum)
msg(tr("parseFile: Invalid data checksum"), index);
if (msgInvalidTailValue)
msg(tr("parseFile: Invalid tail value"), index);
if (msgInvalidType)
msg(tr("parseFile: Unknown file type %1h").arg(fileHeader->Type, 2), index);
if (msgNonEmptyPadFile)
msg(tr("parseFile: Non-empty pad file contents will be destroyed after volume modification"), index);
if (!parseCurrentFile)
return ERR_SUCCESS;
// Parse file as BIOS space
UINT8 result;
if (parseAsBios) {
result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
msg(tr("parseFile: Parsing file as BIOS failed with error \"%1\"").arg(errorMessage(result)), index);
return result;
}
// Parse sections
result = parseSections(body, index);
if (result)
return result;
return ERR_SUCCESS;
}
UINT8 FfsEngine::getSectionSize(const QByteArray & file, const UINT32 sectionOffset, UINT32 & sectionSize)
{
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(file.constData() + sectionOffset);
sectionSize = uint24ToUint32(sectionHeader->Size);
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseSections(const QByteArray & body, const QModelIndex & parent)
{
// Search for and parse all sections
UINT32 sectionOffset = 0;
UINT32 sectionSize;
UINT32 bodySize = body.size();
UINT8 result;
while (true) {
// Get section size
result = getSectionSize(body, sectionOffset, sectionSize);
if (result)
return result;
// Parse section
QModelIndex sectionIndex;
result = parseSection(body.mid(sectionOffset, sectionSize), sectionIndex, parent);
if (result)
return result;
// Move to next section
sectionOffset += sectionSize;
sectionOffset = ALIGN4(sectionOffset);
// Exit from loop if no sections left
if (sectionOffset >= bodySize)
break;
}
return ERR_SUCCESS;
}
void FfsEngine::parseAprioriRawSection(const QByteArray & body, QString & parsed)
{
parsed.clear();
UINT32 count = body.size() / sizeof(EFI_GUID);
if (count > 0) {
for (UINT32 i = 0; i < count; i++) {
const EFI_GUID* guid = (const EFI_GUID*)body.constData() + i;
parsed += tr("\n%1").arg(guidToQString(*guid));
}
}
}
UINT8 FfsEngine::parseDepexSection(const QByteArray & body, QString & parsed)
{
parsed.clear();
// Check data to be present
if (!body.size())
return ERR_INVALID_PARAMETER;
const EFI_GUID * guid;
const UINT8* current = (const UINT8*)body.constData();
// Special cases of first opcode
switch (*current) {
case EFI_DEP_BEFORE:
if (body.size() != 2*EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID))
return ERR_DEPEX_PARSE_FAILED;
guid = (const EFI_GUID*)(current + EFI_DEP_OPCODE_SIZE);
parsed += tr("\nBEFORE %1").arg(guidToQString(*guid));
current += EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID);
if (*current != EFI_DEP_END)
return ERR_DEPEX_PARSE_FAILED;
return ERR_SUCCESS;
case EFI_DEP_AFTER:
if (body.size() != 2 * EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID))
return ERR_DEPEX_PARSE_FAILED;
guid = (const EFI_GUID*)(current + EFI_DEP_OPCODE_SIZE);
parsed += tr("\nAFTER %1").arg(guidToQString(*guid));
current += EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID);
if (*current != EFI_DEP_END)
return ERR_DEPEX_PARSE_FAILED;
return ERR_SUCCESS;
case EFI_DEP_SOR:
if (body.size() <= 2 * EFI_DEP_OPCODE_SIZE) {
return ERR_DEPEX_PARSE_FAILED;
}
parsed += tr("\nSOR");
current += EFI_DEP_OPCODE_SIZE;
break;
default:
break;
}
// Parse the rest of depex
while (current - (const UINT8*)body.constData() < body.size()) {
switch (*current) {
case EFI_DEP_BEFORE:
case EFI_DEP_AFTER:
case EFI_DEP_SOR:
return ERR_DEPEX_PARSE_FAILED;
case EFI_DEP_PUSH:
// Check that the rest of depex has correct size
if ((UINT32)body.size() - (UINT32)(current - (const UINT8*)body.constData()) <= EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID)) {
parsed.clear();
return ERR_DEPEX_PARSE_FAILED;
}
guid = (const EFI_GUID*)(current + EFI_DEP_OPCODE_SIZE);
parsed += tr("\nPUSH %1").arg(guidToQString(*guid));
current += EFI_DEP_OPCODE_SIZE + sizeof(EFI_GUID);
break;
case EFI_DEP_AND:
parsed += tr("\nAND");
current += EFI_DEP_OPCODE_SIZE;
break;
case EFI_DEP_OR:
parsed += tr("\nOR");
current += EFI_DEP_OPCODE_SIZE;
break;
case EFI_DEP_NOT:
parsed += tr("\nNOT");
current += EFI_DEP_OPCODE_SIZE;
break;
case EFI_DEP_TRUE:
parsed += tr("\nTRUE");
current += EFI_DEP_OPCODE_SIZE;
break;
case EFI_DEP_FALSE:
parsed += tr("\nFALSE");
current += EFI_DEP_OPCODE_SIZE;
break;
case EFI_DEP_END:
parsed += tr("\nEND");
current += EFI_DEP_OPCODE_SIZE;
// Check that END is the last opcode
if (current - (const UINT8*)body.constData() < body.size()) {
parsed.clear();
return ERR_DEPEX_PARSE_FAILED;
}
break;
}
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::parseSection(const QByteArray & section, QModelIndex & index, const QModelIndex & parent, const UINT8 mode)
{
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(section.constData());
UINT32 sectionSize = uint24ToUint32(sectionHeader->Size);
QString name = sectionTypeToQString(sectionHeader->Type) + tr(" section");
QString info;
QByteArray header;
QByteArray body;
UINT32 headerSize;
UINT8 result;
switch (sectionHeader->Type) {
// Encapsulated sections
case EFI_SECTION_COMPRESSION:
{
bool parseCurrentSection = true;
QByteArray decompressed;
UINT8 algorithm;
const EFI_COMPRESSION_SECTION* compressedSectionHeader = (const EFI_COMPRESSION_SECTION*)sectionHeader;
header = section.left(sizeof(EFI_COMPRESSION_SECTION));
body = section.mid(sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION));
algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
// Decompress section
result = decompress(body, compressedSectionHeader->CompressionType, decompressed, &algorithm);
if (result)
parseCurrentSection = false;
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nCompression type: %8\nDecompressed size: %9h (%10)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.arg(compressionTypeToQString(algorithm))
.hexarg(compressedSectionHeader->UncompressedLength).arg(compressedSectionHeader->UncompressedLength);
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, algorithm, name, "", info, header, body, parent, mode);
// Show message
if (!parseCurrentSection)
msg(tr("parseSection: Decompression failed with error \"%1\"").arg(errorMessage(result)), index);
else { // Parse decompressed data
result = parseSections(decompressed, index);
if (result)
return result;
}
} break;
case EFI_SECTION_GUID_DEFINED:
{
bool parseCurrentSection = true;
bool msgUnknownGuid = false;
bool msgInvalidCrc = false;
bool msgUnknownAuth = false;
const EFI_GUID_DEFINED_SECTION* guidDefinedSectionHeader;
header = section.left(sizeof(EFI_GUID_DEFINED_SECTION));
guidDefinedSectionHeader = (const EFI_GUID_DEFINED_SECTION*)(header.constData());
header = section.left(guidDefinedSectionHeader->DataOffset);
guidDefinedSectionHeader = (const EFI_GUID_DEFINED_SECTION*)(header.constData());
body = section.mid(guidDefinedSectionHeader->DataOffset, sectionSize - guidDefinedSectionHeader->DataOffset);
QByteArray processed = body;
QByteArray signature;
// Get info
name = guidToQString(guidDefinedSectionHeader->SectionDefinitionGuid);
info = tr("Section GUID: %1h\nType %2h\nFull size: %3h (%4)\nHeader size: %5h (%6)\nBody size: %7h (%8)\nData offset: %9h\nAttributes: %10h")
.arg(name)
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.hexarg(guidDefinedSectionHeader->DataOffset)
.hexarg2(guidDefinedSectionHeader->Attributes, 4);
UINT8 algorithm = COMPRESSION_ALGORITHM_NONE;
// Check if section requires processing
if (guidDefinedSectionHeader->Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED) {
// Tiano compressed section
if (QByteArray((const char*)&guidDefinedSectionHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_TIANO) {
algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
result = decompress(body, EFI_STANDARD_COMPRESSION, processed, &algorithm);
if (result)
parseCurrentSection = false;
if (algorithm == COMPRESSION_ALGORITHM_TIANO) {
info += tr("\nCompression type: Tiano");
info += tr("\nDecompressed size: %1h (%2)").hexarg(processed.length()).arg(processed.length());
}
else if (algorithm == COMPRESSION_ALGORITHM_EFI11) {
info += tr("\nCompression type: EFI 1.1");
info += tr("\nDecompressed size: %1h (%2)").hexarg(processed.length()).arg(processed.length());
}
else
info += tr("\nCompression type: unknown");
}
// LZMA compressed section
else if (QByteArray((const char*)&guidDefinedSectionHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_LZMA) {
algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
result = decompress(body, EFI_CUSTOMIZED_COMPRESSION, processed, &algorithm);
if (result)
parseCurrentSection = false;
if (algorithm == COMPRESSION_ALGORITHM_LZMA) {
info += tr("\nCompression type: LZMA");
info += tr("\nDecompressed size: %1h (%2)").hexarg(processed.length()).arg(processed.length());
}
else
info += tr("\nCompression type: unknown");
}
// Intel signed section
else if (QByteArray((const char*)&guidDefinedSectionHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_INTEL_SIGNED) {
UINT32 signatureSize = *(const UINT32*)body.constData();
signature = body.left(signatureSize);
processed = body.mid(signatureSize);
}
// Unknown GUIDed section
else {
msgUnknownGuid = true;
parseCurrentSection = false;
}
}
// Check if section requires checksum calculation
else if (guidDefinedSectionHeader->Attributes & EFI_GUIDED_SECTION_AUTH_STATUS_VALID)
{
// CRC32 section
if (QByteArray((const char*)&guidDefinedSectionHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_CRC32) {
info += tr("\nChecksum type: CRC32");
// Calculate CRC32 of section data
UINT32 crc = crc32(0, (const UINT8*)body.constData(), body.size());
// Check stored CRC32
if (crc == *(const UINT32*)(header.constData() + sizeof(EFI_GUID_DEFINED_SECTION))) {
info += tr("\nChecksum: valid");
}
else {
info += tr("\nChecksum: invalid");
msgInvalidCrc = true;
}
}
else
msgUnknownAuth = true;
}
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, algorithm, name, "", info, header, body, parent, mode);
// Show messages
if (msgUnknownGuid)
msg(tr("parseSection: GUID defined section with unknown processing method"), index);
if (msgUnknownAuth)
msg(tr("parseSection: GUID defined section with unknown authentication method"), index);
if (msgInvalidCrc)
msg(tr("parseSection: GUID defined section with invalid CRC32"), index);
if (!parseCurrentSection) {
msg(tr("parseSection: GUID defined section can not be processed"), index);
}
else { // Parse processed data
if (!signature.isEmpty()) {
// Add Intel signature padding to the tree
QModelIndex signatureIndex = model->addItem(Types::Padding, ATTR_PADDING_DATA, COMPRESSION_ALGORITHM_NONE, tr("Intel signature"), "", tr("Full size: %1h (%2)").hexarg(signature.size()).arg(signature.size()), QByteArray(), signature, index, mode);
// Show message
msg(tr("parseSection: Intel signature may become invalid after any modification of the following sections"), signatureIndex);
}
result = parseSections(processed, index);
if (result)
return result;
}
} break;
case EFI_SECTION_DISPOSABLE:
{
header = section.left(sizeof(EFI_DISPOSABLE_SECTION));
body = section.mid(sizeof(EFI_DISPOSABLE_SECTION), sectionSize - sizeof(EFI_DISPOSABLE_SECTION));
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Parse section body
result = parseSections(body, index);
if (result)
return result;
} break;
// Leaf sections
case EFI_SECTION_DXE_DEPEX:
case EFI_SECTION_PEI_DEPEX:
case EFI_SECTION_SMM_DEPEX: {
bool msgDepexParseFailed = false;
headerSize = sizeOfSectionHeader(sectionHeader);
header = section.left(headerSize);
body = section.mid(headerSize, sectionSize - headerSize);
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Parse dependency expression
QString str;
result = parseDepexSection(body, str);
if (result)
msgDepexParseFailed = true;
else if (str.count())
info += tr("\nParsed expression:%1").arg(str);
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Show messages
if (msgDepexParseFailed)
msg(tr("parseSection: dependency expression parsing failed"), index);
} break;
case EFI_SECTION_TE: {
headerSize = sizeOfSectionHeader(sectionHeader);
header = section.left(headerSize);
body = section.mid(headerSize, sectionSize - headerSize);
// Get standard info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Get TE info
bool msgInvalidSignature = false;
const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)body.constData();
UINT32 teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
if (teHeader->Signature != EFI_IMAGE_TE_SIGNATURE) {
info += tr("\nSignature: %1h, invalid").hexarg2(teHeader->Signature, 4);
msgInvalidSignature = true;
}
else {
info += tr("\nSignature: %1h\nMachine type: %2h\nNumber of sections: %3\nSubsystem: %4h\nStrippedSize: %5h (%6)\nBaseOfCode: %7h\nRelativeEntryPoint: %8h\nImageBase: %9h\nEntryPoint: %10h")
.hexarg2(teHeader->Signature, 4)
.hexarg2(teHeader->Machine, 2)
.arg(teHeader->NumberOfSections)
.hexarg2(teHeader->Subsystem, 2)
.hexarg(teHeader->StrippedSize).arg(teHeader->StrippedSize)
.hexarg(teHeader->BaseOfCode)
.hexarg(teHeader->AddressOfEntryPoint)
.hexarg(teHeader->ImageBase)
.hexarg(teHeader->ImageBase + teHeader->AddressOfEntryPoint - teFixup);
}
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Show messages
if (msgInvalidSignature) {
msg("parseSection: TE image with invalid TE signature", index);
}
// Special case of PEI Core
QModelIndex core = model->findParentOfType(index, Types::File);
if (core.isValid() && model->attributes(core) == EFI_FV_FILETYPE_PEI_CORE
&& oldPeiCoreEntryPoint == 0) {
result = getEntryPoint(model->body(index), oldPeiCoreEntryPoint);
if (result)
msg(tr("parseSection: Can't get original PEI core entry point"), index);
}
} break;
case EFI_SECTION_PE32:
case EFI_SECTION_PIC: {
headerSize = sizeOfSectionHeader(sectionHeader);
header = section.left(headerSize);
body = section.mid(headerSize, sectionSize - headerSize);
// Get standard info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Get PE32(+) info
bool msgInvalidDosSignature = false;
bool msgInvalidPeSignature = false;
bool msgUnknownOptionalHeaderSignature = false;
bool renameToPE32 = false;
bool renameToPE32Plus = false;
const EFI_IMAGE_DOS_HEADER* dosHeader = (const EFI_IMAGE_DOS_HEADER*)body.constData();
if (dosHeader->e_magic != EFI_IMAGE_DOS_SIGNATURE) {
info += tr("\nDOS signature: %1h, invalid").hexarg2(dosHeader->e_magic, 4);
msgInvalidDosSignature = true;
}
else {
const EFI_IMAGE_PE_HEADER* peHeader = (EFI_IMAGE_PE_HEADER*)(body.constData() + dosHeader->e_lfanew);
if (peHeader->Signature != EFI_IMAGE_PE_SIGNATURE) {
info += tr("\nPE signature: %1h, invalid").hexarg2(peHeader->Signature, 8);
msgInvalidPeSignature = true;
}
else {
const EFI_IMAGE_FILE_HEADER* imageFileHeader = (const EFI_IMAGE_FILE_HEADER*)(peHeader + 1);
info += tr("\nDOS signature: %1h\nPE signature: %2h\nMachine type: %3h\nNumber of sections: %4\nCharacteristics: %5h")
.hexarg2(dosHeader->e_magic, 4)
.hexarg2(peHeader->Signature, 8)
.hexarg2(imageFileHeader->Machine, 4)
.arg(imageFileHeader->NumberOfSections)
.hexarg2(imageFileHeader->Characteristics, 4);
EFI_IMAGE_OPTIONAL_HEADER_POINTERS_UNION optionalHeader;
optionalHeader.H32 = (const EFI_IMAGE_OPTIONAL_HEADER32*)(imageFileHeader + 1);
if (optionalHeader.H32->Magic == EFI_IMAGE_PE_OPTIONAL_HDR32_MAGIC) {
renameToPE32 = true;
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nRelativeEntryPoint: %3h\nBaseOfCode: %4h\nImageBase: %5h\nEntryPoint: %6h")
.hexarg2(optionalHeader.H32->Magic, 4)
.hexarg2(optionalHeader.H32->Subsystem, 4)
.hexarg(optionalHeader.H32->AddressOfEntryPoint)
.hexarg(optionalHeader.H32->BaseOfCode)
.hexarg(optionalHeader.H32->ImageBase)
.hexarg(optionalHeader.H32->ImageBase + optionalHeader.H32->AddressOfEntryPoint);
}
else if (optionalHeader.H32->Magic == EFI_IMAGE_PE_OPTIONAL_HDR64_MAGIC) {
renameToPE32Plus = true;
info += tr("\nOptional header signature: %1h\nSubsystem: %2h\nRelativeEntryPoint: %3h\nBaseOfCode: %4h\nImageBase: %5h\nEntryPoint: %6h")
.hexarg2(optionalHeader.H64->Magic, 4)
.hexarg2(optionalHeader.H64->Subsystem, 4)
.hexarg(optionalHeader.H64->AddressOfEntryPoint)
.hexarg(optionalHeader.H64->BaseOfCode)
.hexarg(optionalHeader.H64->ImageBase)
.hexarg(optionalHeader.H64->ImageBase + optionalHeader.H64->AddressOfEntryPoint);
}
else {
info += tr("\nOptional header signature: %1h, unknown").hexarg2(optionalHeader.H64->Magic, 4);
msgUnknownOptionalHeaderSignature = true;
}
}
}
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Show messages
if (msgInvalidDosSignature) {
msg("parseSection: PE32 image with invalid DOS signature", index);
}
if (msgInvalidDosSignature) {
msg("parseSection: PE32 image with invalid PE signature", index);
}
if (msgUnknownOptionalHeaderSignature) {
msg("parseSection: PE32 image with unknown optional header signature", index);
}
// Rename section
if (renameToPE32)
model->setName(index, tr("PE32 image"));
if (renameToPE32Plus)
model->setName(index, tr("PE32+ image"));
// Special case of PEI Core
QModelIndex core = model->findParentOfType(index, Types::File);
if (core.isValid() && model->attributes(core) == EFI_FV_FILETYPE_PEI_CORE
&& oldPeiCoreEntryPoint == 0) {
result = getEntryPoint(model->body(index), oldPeiCoreEntryPoint);
if (result)
msg(tr("parseSection: Can't get original PEI core entry point"), index);
}
} break;
case EFI_SECTION_COMPATIBILITY16: {
headerSize = sizeOfSectionHeader(sectionHeader);
header = section.left(headerSize);
body = section.mid(headerSize, sectionSize - headerSize);
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
} break;
case EFI_SECTION_FREEFORM_SUBTYPE_GUID: {
header = section.left(sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION));
body = section.mid(sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION), sectionSize - sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION));
const EFI_FREEFORM_SUBTYPE_GUID_SECTION* fsgHeader = (const EFI_FREEFORM_SUBTYPE_GUID_SECTION*)sectionHeader;
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nSubtype GUID: %8")
.hexarg2(fsgHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.arg(guidToQString(fsgHeader->SubTypeGuid));
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Rename section
model->setName(index, guidToQString(fsgHeader->SubTypeGuid));
} break;
case EFI_SECTION_VERSION: {
header = section.left(sizeof(EFI_VERSION_SECTION));
body = section.mid(sizeof(EFI_VERSION_SECTION), sectionSize - sizeof(EFI_VERSION_SECTION));
const EFI_VERSION_SECTION* versionHeader = (const EFI_VERSION_SECTION*)sectionHeader;
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nBuild number: %8\nVersion string: %9")
.hexarg2(versionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.arg(versionHeader->BuildNumber)
.arg(QString::fromUtf16((const ushort*)body.constData()));
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
} break;
case EFI_SECTION_USER_INTERFACE: {
header = section.left(sizeof(EFI_USER_INTERFACE_SECTION));
body = section.mid(sizeof(EFI_USER_INTERFACE_SECTION), sectionSize - sizeof(EFI_USER_INTERFACE_SECTION));
QString text = QString::fromUtf16((const ushort*)body.constData());
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nText: %8")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.arg(text);
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Rename parent file
model->setText(model->findParentOfType(parent, Types::File), text);
} break;
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE: {
header = section.left(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
body = section.mid(sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Parse section body as BIOS space
result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME) {
msg(tr("parseSection: Parsing firmware volume image section as BIOS failed with error \"%1\"").arg(errorMessage(result)), index);
return result;
}
} break;
case EFI_SECTION_RAW: {
bool parsed = false;
header = section.left(sizeof(EFI_RAW_SECTION));
body = section.mid(sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION));
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Check for apriori file
QModelIndex parentFile = model->findParentOfType(parent, Types::File);
QByteArray parentFileGuid = model->header(parentFile).left(sizeof(EFI_GUID));
if (parentFileGuid == EFI_PEI_APRIORI_FILE_GUID) {
// Mark file as parsed
parsed = true;
// Parse apriori file list
QString str;
parseAprioriRawSection(body, str);
if (str.count())
info += tr("\nFile list:%1").arg(str);
// Set parent file text
model->setText(parentFile, tr("PEI apriori file"));
}
else if (parentFileGuid == EFI_DXE_APRIORI_FILE_GUID) {
// Mark file as parsed
parsed = true;
// Parse apriori file list
QString str;
parseAprioriRawSection(body, str);
if (str.count())
info += tr("\nFile list:%1").arg(str);
// Set parent file text
model->setText(parentFile, tr("DXE apriori file"));
}
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
// Parse section body as BIOS space
if (!parsed) {
result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME) {
msg(tr("parseSection: Parsing raw section as BIOS failed with error \"%1\"").arg(errorMessage(result)), index);
return result;
}
}
} break;
case SCT_SECTION_POSTCODE:
case HP_SECTION_POSTCODE: {
header = section.left(sizeof(POSTCODE_SECTION));
body = section.mid(sizeof(POSTCODE_SECTION), sectionSize - sizeof(POSTCODE_SECTION));
const POSTCODE_SECTION* postcodeHeader = (const POSTCODE_SECTION*)sectionHeader;
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)\nPostcode: %8h")
.hexarg2(postcodeHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size())
.hexarg(postcodeHeader->Postcode);
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
} break;
default:
header = section.left(sizeof(EFI_COMMON_SECTION_HEADER));
body = section.mid(sizeof(EFI_COMMON_SECTION_HEADER), sectionSize - sizeof(EFI_COMMON_SECTION_HEADER));
// Get info
info = tr("Type: %1h\nFull size: %2h (%3)\nHeader size: %4h (%5)\nBody size: %6h (%7)")
.hexarg2(sectionHeader->Type, 2)
.hexarg(section.size()).arg(section.size())
.hexarg(header.size()).arg(header.size())
.hexarg(body.size()).arg(body.size());
// Add tree item
index = model->addItem(Types::Section, sectionHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode);
msg(tr("parseSection: Section with unknown type %1h").hexarg2(sectionHeader->Type, 2), index);
}
return ERR_SUCCESS;
}
// Operations on tree items
UINT8 FfsEngine::create(const QModelIndex & index, const UINT8 type, const QByteArray & header, const QByteArray & body, const UINT8 mode, const UINT8 action, const UINT8 algorithm)
{
QByteArray created;
UINT8 result;
QModelIndex fileIndex;
if (!index.isValid() || !index.parent().isValid())
return ERR_INVALID_PARAMETER;
QModelIndex parent;
if (mode == CREATE_MODE_BEFORE || mode == CREATE_MODE_AFTER)
parent = index.parent();
else
parent = index;
// Create item
if (type == Types::Region) {
UINT32 attr = model->attributes(index);
REGION_ATTRIBUTES* attributes = (REGION_ATTRIBUTES*)&attr;
switch (attributes->Type) {
case ATTR_REGION_TYPE_BIOS:
result = parseBiosRegion(body, fileIndex, index, mode);
break;
case ATTR_REGION_TYPE_ME:
result = parseMeRegion(body, fileIndex, index, mode);
break;
case ATTR_REGION_TYPE_GBE:
result = parseGbeRegion(body, fileIndex, index, mode);
break;
case ATTR_REGION_TYPE_PDR:
result = parsePdrRegion(body, fileIndex, index, mode);
break;
default:
return ERR_NOT_IMPLEMENTED;
}
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
// Set action
model->setAction(fileIndex, action);
}
else if (type == Types::Volume) {
QByteArray volume;
if (header.isEmpty()) // Whole volume
volume.append(body);
else { // Body only
volume.append(model->header(index)).append(body);
INT32 sizeDiff = model->body(index).size() - body.size();
if (sizeDiff > 0) {
const EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (const EFI_FIRMWARE_VOLUME_HEADER*)model->header(index).constData();
bool erasePolarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY;
volume.append(QByteArray(sizeDiff, erasePolarity ? '\xFF' : '\x00'));
}
}
result = parseVolume(volume, fileIndex, index, mode);
if (result)
return result;
// Set action
model->setAction(fileIndex, action);
}
else if (type == Types::File) {
if (model->type(parent) != Types::Volume)
return ERR_INVALID_FILE;
const EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (const EFI_FIRMWARE_VOLUME_HEADER*)model->header(parent).constData();
UINT8 revision = volumeHeader->Revision;
bool erasePolarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY;
if (header.size() != sizeof(EFI_FFS_FILE_HEADER))
return ERR_INVALID_FILE;
QByteArray newHeader = header;
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)newHeader.data();
// Correct file size
UINT8 tailSize = fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT ? sizeof(UINT16) : 0;
uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + body.size() + tailSize, fileHeader->Size);
// Recalculate header checksum
fileHeader->IntegrityCheck.Checksum.Header = 0;
fileHeader->IntegrityCheck.Checksum.File = 0;
fileHeader->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)fileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1);
// Recalculate data checksum, if needed
if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM)
fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((const UINT8*)body.constData(), body.size());
else if (revision == 1)
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
else
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2;
// Append body
created.append(body);
// Append tail, if needed
if (tailSize) {
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
created.append(ht).append(ft);
}
// Set file state
UINT8 state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION;
if (erasePolarity)
state = ~state;
fileHeader->State = state;
// Prepend header
created.prepend(newHeader);
// Parse file
result = parseFile(created, fileIndex, erasePolarity ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index, mode);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
// Set action
model->setAction(fileIndex, action);
// Rebase all PEI-files that follow
rebasePeiFiles(fileIndex);
}
else if (type == Types::Section) {
if (model->type(parent) != Types::File && model->type(parent) != Types::Section)
return ERR_INVALID_SECTION;
if (header.size() < (int) sizeof(EFI_COMMON_SECTION_HEADER))
return ERR_INVALID_SECTION;
QByteArray newHeader = header;
EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)newHeader.data();
switch (commonHeader->Type)
{
case EFI_SECTION_COMPRESSION: {
EFI_COMPRESSION_SECTION* sectionHeader = (EFI_COMPRESSION_SECTION*)newHeader.data();
// Correct uncompressed size
sectionHeader->UncompressedLength = body.size();
// Set compression type
if (algorithm == COMPRESSION_ALGORITHM_NONE)
sectionHeader->CompressionType = EFI_NOT_COMPRESSED;
else if (algorithm == COMPRESSION_ALGORITHM_EFI11 || algorithm == COMPRESSION_ALGORITHM_TIANO)
sectionHeader->CompressionType = EFI_STANDARD_COMPRESSION;
else if (algorithm == COMPRESSION_ALGORITHM_LZMA || algorithm == COMPRESSION_ALGORITHM_IMLZMA)
sectionHeader->CompressionType = EFI_CUSTOMIZED_COMPRESSION;
else
return ERR_UNKNOWN_COMPRESSION_ALGORITHM;
// Compress body
QByteArray compressed;
result = compress(body, algorithm, compressed);
if (result)
return result;
// Correct section size
uint32ToUint24(header.size() + compressed.size(), commonHeader->Size);
// Append header and body
created.append(newHeader).append(compressed);
// Parse section
QModelIndex sectionIndex;
result = parseSection(created, sectionIndex, index, mode);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
// Set create action
model->setAction(sectionIndex, action);
// Find parent file for rebase
fileIndex = model->findParentOfType(parent, Types::File);
}
break;
case EFI_SECTION_GUID_DEFINED:{
// Compress body
QByteArray compressed;
result = compress(body, algorithm, compressed);
if (result)
return result;
// Correct section size
uint32ToUint24(header.size() + compressed.size(), commonHeader->Size);
// Append header and body
created.append(newHeader).append(compressed);
// Parse section
QModelIndex sectionIndex;
result = parseSection(created, sectionIndex, index, mode);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
// Set create action
model->setAction(sectionIndex, action);
// Find parent file for rebase
fileIndex = model->findParentOfType(parent, Types::File);
}
break;
default:
// Correct section size
uint32ToUint24(header.size() + body.size(), commonHeader->Size);
// Append header and body
created.append(newHeader).append(body);
// Parse section
QModelIndex sectionIndex;
result = parseSection(created, sectionIndex, index, mode);
if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME)
return result;
// Set create action
model->setAction(sectionIndex, action);
// Find parent file for rebase
fileIndex = model->findParentOfType(parent, Types::File);
}
// Rebase all PEI-files that follow
rebasePeiFiles(fileIndex);
}
else
return ERR_NOT_IMPLEMENTED;
return ERR_SUCCESS;
}
void FfsEngine::rebasePeiFiles(const QModelIndex & index)
{
// Rebase all PE32 and TE sections in PEI-files after modified file
for (int i = index.row(); i < model->rowCount(index.parent()); i++) {
// PEI-file
QModelIndex currentFileIndex = index.parent().child(i, 0);
if (model->attributes(currentFileIndex) == EFI_FV_FILETYPE_PEI_CORE ||
model->attributes(currentFileIndex) == EFI_FV_FILETYPE_PEIM ||
model->attributes(currentFileIndex) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER) {
for (int j = 0; j < model->rowCount(currentFileIndex); j++) {
// Section in that file
QModelIndex currentSectionIndex = currentFileIndex.child(j, 0);
// If section stores PE32 or TE image
if (model->attributes(currentSectionIndex) == EFI_SECTION_PE32 || model->attributes(currentSectionIndex) == EFI_SECTION_TE)
// Set rebase action
model->setAction(currentSectionIndex, Actions::Rebase);
}
}
}
}
UINT8 FfsEngine::insert(const QModelIndex & index, const QByteArray & object, const UINT8 mode)
{
if (!index.isValid() || !index.parent().isValid())
return ERR_INVALID_PARAMETER;
QModelIndex parent;
if (mode == CREATE_MODE_BEFORE || mode == CREATE_MODE_AFTER)
parent = index.parent();
else
parent = index;
// Determine type of item to insert
UINT8 type;
UINT32 headerSize;
if (model->type(parent) == Types::Volume) {
type = Types::File;
headerSize = sizeof(EFI_FFS_FILE_HEADER);
}
else if (model->type(parent) == Types::File) {
type = Types::Section;
const EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData();
headerSize = sizeOfSectionHeader(commonHeader);
}
else if (model->type(parent) == Types::Section) {
type = Types::Section;
const EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData();
headerSize = sizeOfSectionHeader(commonHeader);
}
else
return ERR_NOT_IMPLEMENTED;
return create(index, type, object.left(headerSize), object.right(object.size() - headerSize), mode, Actions::Insert);
}
UINT8 FfsEngine::replace(const QModelIndex & index, const QByteArray & object, const UINT8 mode)
{
if (!index.isValid())
return ERR_INVALID_PARAMETER;
// Determine type of item to replace
UINT32 headerSize;
UINT8 result;
if (model->type(index) == Types::Region) {
if (mode == REPLACE_MODE_AS_IS)
result = create(index, Types::Region, QByteArray(), object, CREATE_MODE_AFTER, Actions::Replace);
else
return ERR_NOT_IMPLEMENTED;
}
else if (model->type(index) == Types::Volume) {
if (mode == REPLACE_MODE_AS_IS) {
result = create(index, Types::Volume, QByteArray(), object, CREATE_MODE_AFTER, Actions::Replace);
}
else if (mode == REPLACE_MODE_BODY) {
result = create(index, Types::Volume, model->header(index), object, CREATE_MODE_AFTER, Actions::Replace);
}
else
return ERR_NOT_IMPLEMENTED;
}
else if (model->type(index) == Types::File) {
if (mode == REPLACE_MODE_AS_IS) {
headerSize = sizeof(EFI_FFS_FILE_HEADER);
result = create(index, Types::File, object.left(headerSize), object.right(object.size() - headerSize), CREATE_MODE_AFTER, Actions::Replace);
}
else if (mode == REPLACE_MODE_BODY)
result = create(index, Types::File, model->header(index), object, CREATE_MODE_AFTER, Actions::Replace);
else
return ERR_NOT_IMPLEMENTED;
}
else if (model->type(index) == Types::Section) {
if (mode == REPLACE_MODE_AS_IS) {
const EFI_COMMON_SECTION_HEADER* commonHeader = (const EFI_COMMON_SECTION_HEADER*)object.constData();
headerSize = sizeOfSectionHeader(commonHeader);
result = create(index, Types::Section, object.left(headerSize), object.right(object.size() - headerSize), CREATE_MODE_AFTER, Actions::Replace);
}
else if (mode == REPLACE_MODE_BODY) {
result = create(index, Types::Section, model->header(index), object, CREATE_MODE_AFTER, Actions::Replace, model->compression(index));
}
else
return ERR_NOT_IMPLEMENTED;
}
else
return ERR_NOT_IMPLEMENTED;
// Check create result
if (result)
return result;
// Set remove action to replaced item
model->setAction(index, Actions::Remove);
return ERR_SUCCESS;
}
UINT8 FfsEngine::extract(const QModelIndex & index, QByteArray & extracted, const UINT8 mode)
{
if (!index.isValid())
return ERR_INVALID_PARAMETER;
if (mode == EXTRACT_MODE_AS_IS) {
// Extract as is, with header and body
extracted.clear();
extracted.append(model->header(index));
extracted.append(model->body(index));
if (model->type(index) == Types::File) {
//!TODO: add volume revision check, maybe?
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)model->header(index).constData();
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) {
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
extracted.append(ht).append(ft);
}
}
}
else if (mode == EXTRACT_MODE_BODY) {
// Extract without header and tail
extracted.clear();
// Special case of compressed bodies
if (model->type(index) == Types::Section) {
QByteArray decompressed;
UINT8 result;
if (model->attributes(index) == EFI_SECTION_COMPRESSION) {
const EFI_COMPRESSION_SECTION* compressedHeader = (const EFI_COMPRESSION_SECTION*)model->header(index).constData();
result = decompress(model->body(index), compressedHeader->CompressionType, decompressed);
if (result)
return result;
extracted.append(decompressed);
return ERR_SUCCESS;
}
else if (model->attributes(index) == EFI_SECTION_GUID_DEFINED) {
QByteArray decompressed;
// Check if section requires processing
const EFI_GUID_DEFINED_SECTION* guidDefinedSectionHeader = (const EFI_GUID_DEFINED_SECTION*)model->header(index).constData();
if (guidDefinedSectionHeader->Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED) {
// Try to decompress section body using both known compression algorithms
result = decompress(model->body(index), EFI_STANDARD_COMPRESSION, decompressed);
if (result) {
result = decompress(model->body(index), EFI_CUSTOMIZED_COMPRESSION, decompressed);
if (result)
return result;
}
extracted.append(decompressed);
return ERR_SUCCESS;
}
}
}
extracted.append(model->body(index));
}
else
return ERR_UNKNOWN_EXTRACT_MODE;
return ERR_SUCCESS;
}
UINT8 FfsEngine::remove(const QModelIndex & index)
{
if (!index.isValid())
return ERR_INVALID_PARAMETER;
// Set action for the item
model->setAction(index, Actions::Remove);
QModelIndex fileIndex;
if (model->type(index) == Types::Volume && model->rowCount(index) > 0)
fileIndex = index.child(0, 0);
else if (model->type(index) == Types::File)
fileIndex = index;
else if (model->type(index) == Types::Section)
fileIndex = model->findParentOfType(index, Types::File);
else
return ERR_SUCCESS;
// Rebase all PEI-files that follow
rebasePeiFiles(fileIndex);
return ERR_SUCCESS;
}
UINT8 FfsEngine::rebuild(const QModelIndex & index)
{
if (!index.isValid())
return ERR_INVALID_PARAMETER;
// Set action for the item
model->setAction(index, Actions::Rebuild);
QModelIndex fileIndex;
if (model->type(index) == Types::Volume && model->rowCount(index) > 0)
fileIndex = index.child(0, 0);
else if (model->type(index) == Types::File)
fileIndex = index;
else if (model->type(index) == Types::Section)
fileIndex = model->findParentOfType(index, Types::File);
else
return ERR_SUCCESS;
// Rebase all PEI-files that follow
rebasePeiFiles(fileIndex);
return ERR_SUCCESS;
}
// Compression routines
UINT8 FfsEngine::decompress(const QByteArray & compressedData, const UINT8 compressionType, QByteArray & decompressedData, UINT8 * algorithm)
{
const UINT8* data;
UINT32 dataSize;
UINT8* decompressed;
UINT32 decompressedSize = 0;
UINT8* scratch;
UINT32 scratchSize = 0;
const EFI_TIANO_HEADER* header;
switch (compressionType)
{
case EFI_NOT_COMPRESSED:
decompressedData = compressedData;
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_NONE;
return ERR_SUCCESS;
case EFI_STANDARD_COMPRESSION:
// Get buffer sizes
data = (UINT8*)compressedData.data();
dataSize = compressedData.size();
// Check header to be valid
header = (const EFI_TIANO_HEADER*)data;
if (header->CompSize + sizeof(EFI_TIANO_HEADER) != dataSize)
return ERR_STANDARD_DECOMPRESSION_FAILED;
// Get info function is the same for both algorithms
if (ERR_SUCCESS != EfiTianoGetInfo(data, dataSize, &decompressedSize, &scratchSize))
return ERR_STANDARD_DECOMPRESSION_FAILED;
// Allocate memory
decompressed = new UINT8[decompressedSize];
scratch = new UINT8[scratchSize];
// Decompress section data
//TODO: separate EFI1.1 from Tiano another way
// Try Tiano decompression first
if (ERR_SUCCESS != TianoDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize)) {
// Not Tiano, try EFI 1.1
if (ERR_SUCCESS != EfiDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize)) {
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
delete[] decompressed;
delete[] scratch;
return ERR_STANDARD_DECOMPRESSION_FAILED;
}
else if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_EFI11;
}
else if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_TIANO;
decompressedData = QByteArray((const char*)decompressed, decompressedSize);
delete[] decompressed;
delete[] scratch;
return ERR_SUCCESS;
case EFI_CUSTOMIZED_COMPRESSION:
// Get buffer sizes
data = (const UINT8*)compressedData.constData();
dataSize = compressedData.size();
// Get info
if (ERR_SUCCESS != LzmaGetInfo(data, dataSize, &decompressedSize))
return ERR_CUSTOMIZED_DECOMPRESSION_FAILED;
// Allocate memory
decompressed = new UINT8[decompressedSize];
// Decompress section data
if (ERR_SUCCESS != LzmaDecompress(data, dataSize, decompressed)) {
// Intel modified LZMA workaround
EFI_COMMON_SECTION_HEADER* shittySectionHeader;
UINT32 shittySectionSize;
// Shitty compressed section with a section header between COMPRESSED_SECTION_HEADER and LZMA_HEADER
// We must determine section header size by checking it's type before we can unpack that non-standard compressed section
shittySectionHeader = (EFI_COMMON_SECTION_HEADER*)data;
shittySectionSize = sizeOfSectionHeader(shittySectionHeader);
// Decompress section data once again
data += shittySectionSize;
// Get info again
if (ERR_SUCCESS != LzmaGetInfo(data, dataSize, &decompressedSize)) {
delete[] decompressed;
return ERR_CUSTOMIZED_DECOMPRESSION_FAILED;
}
// Decompress section data again
if (ERR_SUCCESS != LzmaDecompress(data, dataSize, decompressed)) {
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
delete[] decompressed;
return ERR_CUSTOMIZED_DECOMPRESSION_FAILED;
}
else {
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_IMLZMA;
decompressedData = QByteArray((const char*)decompressed, decompressedSize);
}
}
else {
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_LZMA;
decompressedData = QByteArray((const char*)decompressed, decompressedSize);
}
delete[] decompressed;
return ERR_SUCCESS;
default:
msg(tr("decompress: Unknown compression type %1").arg(compressionType));
if (algorithm)
*algorithm = COMPRESSION_ALGORITHM_UNKNOWN;
return ERR_UNKNOWN_COMPRESSION_ALGORITHM;
}
}
UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteArray & compressedData)
{
UINT8* compressed;
switch (algorithm) {
case COMPRESSION_ALGORITHM_NONE:
{
compressedData = data;
return ERR_SUCCESS;
}
break;
case COMPRESSION_ALGORITHM_EFI11:
{
// Try legacy function first
UINT32 compressedSize = 0;
if (EfiCompressLegacy(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (EfiCompressLegacy(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
compressedData = QByteArray((const char*)compressed, compressedSize);
// Check that compressed data can be decompressed normally
QByteArray decompressed;
if (decompress(compressedData, EFI_STANDARD_COMPRESSION, decompressed, NULL) == ERR_SUCCESS
&& decompressed == data) {
delete[] compressed;
return ERR_SUCCESS;
}
delete[] compressed;
// Legacy function failed, use current one
compressedSize = 0;
if (EfiCompress(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (EfiCompress(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
compressedData = QByteArray((const char*)compressed, compressedSize);
// New functions will be trusted here, because another check will reduce performance
delete[] compressed;
return ERR_SUCCESS;
}
break;
case COMPRESSION_ALGORITHM_TIANO:
{
// Try legacy function first
UINT32 compressedSize = 0;
if (TianoCompressLegacy(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (TianoCompressLegacy(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
compressedData = QByteArray((const char*)compressed, compressedSize);
// Check that compressed data can be decompressed normally
QByteArray decompressed;
if (decompress(compressedData, EFI_STANDARD_COMPRESSION, decompressed, NULL) == ERR_SUCCESS
&& decompressed == data) {
delete[] compressed;
return ERR_SUCCESS;
}
delete[] compressed;
// Legacy function failed, use current one
compressedSize = 0;
if (TianoCompress(data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_STANDARD_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (TianoCompress(data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_STANDARD_COMPRESSION_FAILED;
}
compressedData = QByteArray((const char*)compressed, compressedSize);
// New functions will be trusted here, because another check will reduce performance
delete[] compressed;
return ERR_SUCCESS;
}
break;
case COMPRESSION_ALGORITHM_LZMA:
{
UINT32 compressedSize = 0;
if (LzmaCompress((const UINT8*)data.constData(), data.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_CUSTOMIZED_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (LzmaCompress((const UINT8*)data.constData(), data.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_CUSTOMIZED_COMPRESSION_FAILED;
}
compressedData = QByteArray((const char*)compressed, compressedSize);
delete[] compressed;
return ERR_SUCCESS;
}
break;
case COMPRESSION_ALGORITHM_IMLZMA:
{
UINT32 compressedSize = 0;
QByteArray header = data.left(sizeof(EFI_COMMON_SECTION_HEADER));
const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)header.constData();
UINT32 headerSize = sizeOfSectionHeader(sectionHeader);
header = data.left(headerSize);
QByteArray newData = data.mid(headerSize);
if (LzmaCompress((const UINT8*)newData.constData(), newData.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL)
return ERR_CUSTOMIZED_COMPRESSION_FAILED;
compressed = new UINT8[compressedSize];
if (LzmaCompress((const UINT8*)newData.constData(), newData.size(), compressed, &compressedSize) != ERR_SUCCESS) {
delete[] compressed;
return ERR_CUSTOMIZED_COMPRESSION_FAILED;
}
compressedData = header.append(QByteArray((const char*)compressed, compressedSize));
delete[] compressed;
return ERR_SUCCESS;
}
break;
default:
msg(tr("compress: Unknown compression algorithm %1").arg(algorithm));
return ERR_UNKNOWN_COMPRESSION_ALGORITHM;
}
}
// Construction routines
UINT8 FfsEngine::constructPadFile(const QByteArray &guid, const UINT32 size, const UINT8 revision, const UINT8 erasePolarity, QByteArray & pad)
{
if (size < sizeof(EFI_FFS_FILE_HEADER) || erasePolarity == ERASE_POLARITY_UNKNOWN)
return ERR_INVALID_PARAMETER;
pad = QByteArray(size - guid.size(), erasePolarity == ERASE_POLARITY_TRUE ? '\xFF' : '\x00');
pad.prepend(guid);
EFI_FFS_FILE_HEADER* header = (EFI_FFS_FILE_HEADER*)pad.data();
uint32ToUint24(size, header->Size);
header->Attributes = 0x00;
header->Type = EFI_FV_FILETYPE_PAD;
header->State = EFI_FILE_HEADER_CONSTRUCTION | EFI_FILE_HEADER_VALID | EFI_FILE_DATA_VALID;
// Invert state bits if erase polarity is true
if (erasePolarity == ERASE_POLARITY_TRUE)
header->State = ~header->State;
// Calculate header checksum
header->IntegrityCheck.Checksum.Header = 0;
header->IntegrityCheck.Checksum.File = 0;
header->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)header, sizeof(EFI_FFS_FILE_HEADER) - 1);
// Set data checksum
if (revision == 1)
header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
else
header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2;
return ERR_SUCCESS;
}
UINT8 FfsEngine::reconstructIntelImage(const QModelIndex& index, QByteArray& reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
// No action
if (model->action(index) == Actions::NoAction) {
reconstructed = model->header(index).append(model->body(index));
return ERR_SUCCESS;
}
// Other supported actions
else if (model->action(index) == Actions::Rebuild) {
reconstructed.clear();
// First child will always be descriptor for this type of image
QByteArray descriptor;
result = reconstructRegion(index.child(0, 0), descriptor);
if (result)
return result;
reconstructed.append(descriptor);
const FLASH_DESCRIPTOR_MAP* descriptorMap = (const FLASH_DESCRIPTOR_MAP*)(descriptor.constData() + sizeof(FLASH_DESCRIPTOR_HEADER));
const FLASH_DESCRIPTOR_REGION_SECTION* regionSection = (const FLASH_DESCRIPTOR_REGION_SECTION*)calculateAddress8((const UINT8*)descriptor.constData(), descriptorMap->RegionBase);
QByteArray gbe;
UINT32 gbeBegin = calculateRegionOffset(regionSection->GbeBase);
UINT32 gbeEnd = gbeBegin + calculateRegionSize(regionSection->GbeBase, regionSection->GbeLimit);
QByteArray me;
UINT32 meBegin = calculateRegionOffset(regionSection->MeBase);
UINT32 meEnd = meBegin + calculateRegionSize(regionSection->MeBase, regionSection->MeLimit);
QByteArray bios;
UINT32 biosBegin = calculateRegionOffset(regionSection->BiosBase);
UINT32 biosEnd = biosBegin + calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit);
QByteArray pdr;
UINT32 pdrBegin = calculateRegionOffset(regionSection->PdrBase);
UINT32 pdrEnd = pdrBegin + calculateRegionSize(regionSection->PdrBase, regionSection->PdrLimit);
UINT32 offset = descriptor.size();
// Reconstruct other regions
char empty = '\xFF';
for (int i = 1; i < model->rowCount(index); i++) {
QByteArray region;
result = reconstructRegion(index.child(i, 0), region);
if (result)
return result;
UINT32 attr = model->attributes(index.child(i, 0));
REGION_ATTRIBUTES* attributes = (REGION_ATTRIBUTES*)&attr;
switch (attributes->Type)
{
case ATTR_REGION_TYPE_GBE:
gbe = region;
if (gbeBegin > offset)
reconstructed.append(QByteArray(gbeBegin - offset, empty));
reconstructed.append(gbe);
offset = gbeEnd;
break;
case ATTR_REGION_TYPE_ME:
me = region;
if (meBegin > offset)
reconstructed.append(QByteArray(meBegin - offset, empty));
reconstructed.append(me);
offset = meEnd;
break;
case ATTR_REGION_TYPE_BIOS:
bios = region;
if (biosBegin > offset)
reconstructed.append(QByteArray(biosBegin - offset, empty));
reconstructed.append(bios);
offset = biosEnd;
break;
case ATTR_REGION_TYPE_PDR:
pdr = region;
if (pdrBegin > offset)
reconstructed.append(QByteArray(pdrBegin - offset, empty));
reconstructed.append(pdr);
offset = pdrEnd;
break;
default:
msg(tr("reconstructIntelImage: unknown region type found"), index);
return ERR_INVALID_REGION;
}
}
if ((UINT32)model->body(index).size() > offset)
reconstructed.append(QByteArray((UINT32)model->body(index).size() - offset, empty));
// Check size of reconstructed image, it must be same
if (reconstructed.size() > model->body(index).size()) {
msg(tr("reconstructIntelImage: reconstructed body size %1h (%2) is bigger then original %3h (%4) ")
.hexarg(reconstructed.size()).arg(reconstructed.size())
.hexarg(model->body(index).size()).arg(model->body(index).size()),
index);
return ERR_INVALID_PARAMETER;
}
else if (reconstructed.size() < model->body(index).size()) {
msg(tr("reconstructIntelImage: reconstructed body size %1h (%2) is smaller then original %3h (%4) ")
.hexarg(reconstructed.size()).arg(reconstructed.size())
.hexarg(model->body(index).size()).arg(model->body(index).size()),
index);
return ERR_INVALID_PARAMETER;
}
// Reconstruction successful
return ERR_SUCCESS;
}
// All other actions are not supported
return ERR_NOT_IMPLEMENTED;
}
UINT8 FfsEngine::reconstructRegion(const QModelIndex& index, QByteArray& reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
// No action
if (model->action(index) == Actions::NoAction) {
reconstructed = model->header(index).append(model->body(index));
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Remove) {
reconstructed.clear();
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Rebuild ||
model->action(index) == Actions::Replace) {
if (model->rowCount(index)) {
reconstructed.clear();
// Reconstruct children
for (int i = 0; i < model->rowCount(index); i++) {
QByteArray child;
result = reconstruct(index.child(i, 0), child);
if (result)
return result;
reconstructed.append(child);
}
}
// Use stored item body
else
reconstructed = model->body(index);
// Check size of reconstructed region, it must be same
if (reconstructed.size() > model->body(index).size()) {
msg(tr("reconstructRegion: reconstructed region size %1h (%2) is bigger then original %3h (%4)")
.hexarg(reconstructed.size()).arg(reconstructed.size())
.hexarg(model->body(index).size()).arg(reconstructed.size()),
index);
return ERR_INVALID_PARAMETER;
}
else if (reconstructed.size() < model->body(index).size()) {
msg(tr("reconstructRegion: reconstructed region size %1h (%2) is smaller then original %3h (%4)")
.hexarg(reconstructed.size()).arg(reconstructed.size())
.hexarg(model->body(index).size()).arg(reconstructed.size()),
index);
return ERR_INVALID_PARAMETER;
}
// Reconstruction successful
reconstructed = model->header(index).append(reconstructed);
return ERR_SUCCESS;
}
// All other actions are not supported
return ERR_NOT_IMPLEMENTED;
}
UINT8 FfsEngine::reconstructVolume(const QModelIndex & index, QByteArray & reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
// No action
if (model->action(index) == Actions::NoAction) {
reconstructed = model->header(index).append(model->body(index));
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Remove) {
reconstructed.clear();
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Replace ||
model->action(index) == Actions::Rebuild) {
//!TODO: add check for weak aligned volume
QByteArray header = model->header(index);
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data();
// Recalculate volume header checksum
volumeHeader->Checksum = 0;
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
// Get volume size
UINT32 volumeSize;
UINT32 bmVolumeSize;
result = getVolumeSize(header, 0, volumeSize, bmVolumeSize);
if (result)
return result;
//!TODO: now we trust header size, sometimes it's the bmVolumeSize that is OK, need to implement some settings for it
// Reconstruct volume body
if (model->rowCount(index)) {
reconstructed.clear();
UINT8 polarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE;
char empty = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00';
// Calculate volume base for volume
UINT32 volumeBase;
QByteArray file;
bool baseFound = false;
// Search for VTF
for (int i = 0; i < model->rowCount(index); i++) {
file = model->header(index.child(i, 0));
// VTF found
if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) {
baseFound = true;
volumeBase = (UINT32)(0x100000000 - volumeSize);
break;
}
}
// Determine if volume is inside compressed item
if (!baseFound) {
// Iterate up to the root, checking for compression type to be other then none
for (QModelIndex parentIndex = index.parent(); model->type(parentIndex) != Types::Root; parentIndex = parentIndex.parent())
if (model->compression(parentIndex) != COMPRESSION_ALGORITHM_NONE) {
// No rebase needed for compressed PEI files
baseFound = true;
volumeBase = 0;
break;
}
}
// Find volume base address using first PEI file in it
if (!baseFound) {
// Search for first PEI-file and use it as base source
UINT32 fileOffset = header.size();
for (int i = 0; i < model->rowCount(index); i++) {
if ((model->attributes(index.child(i, 0)) == EFI_FV_FILETYPE_PEI_CORE ||
model->attributes(index.child(i, 0)) == EFI_FV_FILETYPE_PEIM ||
model->attributes(index.child(i, 0)) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)){
QModelIndex peiFile = index.child(i, 0);
UINT32 sectionOffset = sizeof(EFI_FFS_FILE_HEADER);
// Search for PE32 or TE section
for (int j = 0; j < model->rowCount(peiFile); j++) {
if (model->attributes(peiFile.child(j, 0)) == EFI_SECTION_PE32 ||
model->attributes(peiFile.child(j, 0)) == EFI_SECTION_TE) {
QModelIndex image = peiFile.child(j, 0);
// Check for correct action
if (model->action(image) == Actions::Remove || model->action(image) == Actions::Insert)
continue;
// Calculate relative base address
UINT32 relbase = fileOffset + sectionOffset + model->header(image).size();
// Calculate offset of image relative to file base
UINT32 imagebase;
result = getBase(model->body(image), imagebase);
if (!result) {
// Calculate volume base
volumeBase = imagebase - relbase;
baseFound = true;
goto out;
}
}
sectionOffset += model->header(peiFile.child(j, 0)).size() + model->body(peiFile.child(j, 0)).size();
sectionOffset = ALIGN4(sectionOffset);
}
}
fileOffset += model->header(index.child(i, 0)).size() + model->body(index.child(i, 0)).size();
fileOffset = ALIGN8(fileOffset);
}
}
out:
// Do not set volume base
if (!baseFound)
volumeBase = 0;
// Reconstruct files in volume
UINT32 offset = 0;
QByteArray padFileGuid = EFI_FFS_PAD_FILE_GUID;
QByteArray vtf;
QModelIndex vtfIndex;
for (int i = 0; i < model->rowCount(index); i++) {
// Align to 8 byte boundary
UINT32 alignment = offset % 8;
if (alignment) {
alignment = 8 - alignment;
offset += alignment;
reconstructed.append(QByteArray(alignment, empty));
}
// Calculate file base
UINT32 fileBase = volumeBase ? volumeBase + header.size() + offset : 0;
// Reconstruct file
result = reconstructFile(index.child(i, 0), volumeHeader->Revision, polarity, fileBase, file);
if (result)
return result;
// Empty file
if (file.isEmpty())
continue;
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)file.data();
// Pad file
if (fileHeader->Type == EFI_FV_FILETYPE_PAD) {
padFileGuid = file.left(sizeof(EFI_GUID));
continue;
}
// Volume Top File
if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) {
vtf = file;
vtfIndex = index.child(i, 0);
continue;
}
// Normal file
// Ensure correct alignment
UINT8 alignmentPower;
UINT32 alignmentBase;
alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3];
alignment = (UINT32)pow(2.0, alignmentPower);
alignmentBase = header.size() + offset + sizeof(EFI_FFS_FILE_HEADER);
if (alignmentBase % alignment) {
// File will be unaligned if added as is, so we must add pad file before it
// Determine pad file size
UINT32 size = alignment - (alignmentBase % alignment);
// Required padding is smaller then minimal pad file size
while (size < sizeof(EFI_FFS_FILE_HEADER)) {
size += alignment;
}
// Construct pad file
QByteArray pad;
result = constructPadFile(padFileGuid, size, volumeHeader->Revision, polarity, pad);
if (result)
return result;
// Append constructed pad file to volume body
reconstructed.append(pad);
offset += size;
}
// Append current file to new volume body
reconstructed.append(file);
// Change current file offset
offset += file.size();
}
// Insert VTF to it's correct place
if (!vtf.isEmpty()) {
// Determine correct VTF offset
UINT32 vtfOffset = volumeSize - header.size() - vtf.size();
if (vtfOffset % 8) {
msg(tr("reconstructVolume: wrong size of the Volume Top File"), index);
return ERR_INVALID_FILE;
}
// Insert pad file to fill the gap
if (vtfOffset > offset) {
// Determine pad file size
UINT32 size = vtfOffset - offset;
// Construct pad file
QByteArray pad;
result = constructPadFile(padFileGuid, size, volumeHeader->Revision, polarity, pad);
if (result)
return result;
// Append constructed pad file to volume body
reconstructed.append(pad);
}
// No more space left in volume
else if (vtfOffset < offset) {
msg(tr("reconstructVolume: volume has no free space left"), index);
return ERR_INVALID_VOLUME;
}
// Calculate VTF base
UINT32 vtfBase = volumeBase ? volumeBase + vtfOffset : 0;
// Reconstruct VTF again
result = reconstructFile(vtfIndex, volumeHeader->Revision, polarity, vtfBase, vtf);
if (result)
return result;
// Patch PEI core entry point in VTF
result = patchVtf(vtf);
if (result)
return result;
// Append VTF
reconstructed.append(vtf);
}
else {
// Fill the rest of volume space with empty char
UINT32 volumeBodySize = volumeSize - header.size();
if (volumeBodySize > (UINT32)reconstructed.size()) {
// Fill volume end with empty char
reconstructed.append(QByteArray(volumeBodySize - reconstructed.size(), empty));
}
else if (volumeBodySize < (UINT32)reconstructed.size()) {
// Check if volume can be grown
// Root volume can't be grown yet
UINT8 parentType = model->type(index.parent());
if (parentType != Types::File && parentType != Types::Section) {
msg(tr("reconstructVolume: root volume can't be grown"), index);
return ERR_INVALID_VOLUME;
}
// Grow volume to fit new body
UINT32 newSize = header.size() + reconstructed.size();
result = growVolume(header, volumeSize, newSize);
if (result)
return result;
// Fill volume end with empty char
reconstructed.append(QByteArray(newSize - header.size() - reconstructed.size(), empty));
volumeSize = newSize;
}
}
// Check new volume size
if ((UINT32)(header.size() + reconstructed.size()) > volumeSize)
{
msg(tr("reconstructVolume: volume grow failed"), index);
return ERR_INVALID_VOLUME;
}
}
// Use current volume body
else
reconstructed = model->body(index);
// Reconstruction successful
reconstructed = header.append(reconstructed);
// Recalculate CRC32 in ZeroVector, if needed
UINT32 attr = model->attributes(index);
VOLUME_ATTRIBUTES* attributes = (VOLUME_ATTRIBUTES*)&attr;
if (attributes->ZeroVectorCrc) {
// Get current CRC32 value from volume header
const UINT32 current = *(const UINT32*)(reconstructed.constData() + 8);
// Calculate new value
UINT32 crc = crc32(0, (const UINT8*)reconstructed.constData() + volumeHeader->HeaderLength, reconstructed.size() - volumeHeader->HeaderLength);
// Update the value
if (current != crc) {
*(UINT32*)(reconstructed.data() + 8) = crc;
// Recalculate header checksum again
volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)reconstructed.data();
volumeHeader->Checksum = 0;
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
}
}
return ERR_SUCCESS;
}
// All other actions are not supported
return ERR_NOT_IMPLEMENTED;
}
UINT8 FfsEngine::reconstructFile(const QModelIndex& index, const UINT8 revision, const UINT8 erasePolarity, const UINT32 base, QByteArray& reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
// No action
if (model->action(index) == Actions::NoAction) {
reconstructed = model->header(index).append(model->body(index));
const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)model->header(index).constData();
// Append tail, if needed
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) {
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
reconstructed.append(ht).append(ft);
}
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Remove) {
reconstructed.clear();
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Insert ||
model->action(index) == Actions::Replace ||
model->action(index) == Actions::Rebuild) {
QByteArray header = model->header(index);
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)header.data();
// Check erase polarity
if (erasePolarity == ERASE_POLARITY_UNKNOWN) {
msg(tr("reconstructFile: unknown erase polarity"), index);
return ERR_INVALID_PARAMETER;
}
// Check file state
// Invert it first if erase polarity is true
UINT8 state = fileHeader->State;
if (erasePolarity == ERASE_POLARITY_TRUE)
state = ~state;
// Order of this checks must be preserved
// Check file to have valid state, or delete it otherwise
if (state & EFI_FILE_HEADER_INVALID) {
// File marked to have invalid header and must be deleted
// Do not add anything to queue
msg(tr("reconstructFile: file is HEADER_INVALID state, and will be removed from reconstructed image"), index);
return ERR_SUCCESS;
}
else if (state & EFI_FILE_DELETED) {
// File marked to have been deleted form and must be deleted
// Do not add anything to queue
msg(tr("reconstructFile: file is in DELETED state, and will be removed from reconstructed image"), index);
return ERR_SUCCESS;
}
else if (state & EFI_FILE_MARKED_FOR_UPDATE) {
// File is marked for update, the mark must be removed
msg(tr("reconstructFile: file's MARKED_FOR_UPDATE state cleared"), index);
}
else if (state & EFI_FILE_DATA_VALID) {
// File is in good condition, reconstruct it
}
else if (state & EFI_FILE_HEADER_VALID) {
// Header is valid, but data is not, so file must be deleted
msg(tr("reconstructFile: file is in HEADER_VALID (but not in DATA_VALID) state, and will be removed from reconstructed image"), index);
return ERR_SUCCESS;
}
else if (state & EFI_FILE_HEADER_CONSTRUCTION) {
// Header construction not finished, so file must be deleted
msg(tr("reconstructFile: file is in HEADER_CONSTRUCTION (but not in DATA_VALID) state, and will be removed from reconstructed image"), index);
return ERR_SUCCESS;
}
// Reconstruct file body
if (model->rowCount(index)) {
reconstructed.clear();
// Construct new file body
// File contains raw data, must be parsed as region
if (model->attributes(index) == EFI_FV_FILETYPE_ALL || model->attributes(index) == EFI_FV_FILETYPE_RAW) {
result = reconstructRegion(index, reconstructed);
if (result)
return result;
}
// File contains sections
else {
UINT32 offset = 0;
for (int i = 0; i < model->rowCount(index); i++) {
// Align to 4 byte boundary
UINT8 alignment = offset % 4;
if (alignment) {
alignment = 4 - alignment;
offset += alignment;
reconstructed.append(QByteArray(alignment, '\x00'));
}
// Calculate section base
UINT32 sectionBase = base ? base + sizeof(EFI_FFS_FILE_HEADER) + offset : 0;
// Reconstruct section
QByteArray section;
result = reconstructSection(index.child(i, 0), sectionBase, section);
if (result)
return result;
// Check for empty section
if (section.isEmpty())
continue;
// Append current section to new file body
reconstructed.append(section);
// Change current file offset
offset += section.size();
}
}
// Correct file size
UINT8 tailSize = (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) ? sizeof(UINT16) : 0;
uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize, fileHeader->Size);
// Recalculate header checksum
fileHeader->IntegrityCheck.Checksum.Header = 0;
fileHeader->IntegrityCheck.Checksum.File = 0;
fileHeader->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)fileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1);
}
// Use current file body
else
reconstructed = model->body(index);
// Recalculate data checksum, if needed
if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) {
fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((const UINT8*)reconstructed.constData(), reconstructed.size());
}
else if (revision == 1)
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
else
fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2;
// Append tail, if needed
if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) {
UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header;
UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File;
reconstructed.append(ht).append(ft);
}
// Set file state
state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION;
if (erasePolarity == ERASE_POLARITY_TRUE)
state = ~state;
fileHeader->State = state;
// Reconstruction successful
reconstructed = header.append(reconstructed);
return ERR_SUCCESS;
}
// All other actions are not supported
return ERR_NOT_IMPLEMENTED;
}
UINT8 FfsEngine::reconstructSection(const QModelIndex& index, const UINT32 base, QByteArray& reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
// No action
if (model->action(index) == Actions::NoAction) {
reconstructed = model->header(index).append(model->body(index));
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Remove) {
reconstructed.clear();
return ERR_SUCCESS;
}
else if (model->action(index) == Actions::Insert ||
model->action(index) == Actions::Replace ||
model->action(index) == Actions::Rebuild ||
model->action(index) == Actions::Rebase) {
QByteArray header = model->header(index);
EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)header.data();
// Reconstruct section with children
if (model->rowCount(index)) {
reconstructed.clear();
// Construct new section body
UINT32 offset = 0;
// Reconstruct section body
for (int i = 0; i < model->rowCount(index); i++) {
// Align to 4 byte boundary
UINT8 alignment = offset % 4;
if (alignment) {
alignment = 4 - alignment;
offset += alignment;
reconstructed.append(QByteArray(alignment, '\x00'));
}
// Reconstruct subsections
QByteArray section;
result = reconstruct(index.child(i, 0), section);
if (result)
return result;
// Check for empty queue
if (section.isEmpty())
continue;
// Append current subsection to new section body
reconstructed.append(section);
// Change current file offset
offset += section.size();
}
// Only this 2 sections can have compressed body
if (model->attributes(index) == EFI_SECTION_COMPRESSION) {
EFI_COMPRESSION_SECTION* compessionHeader = (EFI_COMPRESSION_SECTION*)header.data();
// Set new uncompressed size
compessionHeader->UncompressedLength = reconstructed.size();
// Compress new section body
QByteArray compressed;
result = compress(reconstructed, model->compression(index), compressed);
if (result)
return result;
// Correct compression type
if (model->compression(index) == COMPRESSION_ALGORITHM_NONE)
compessionHeader->CompressionType = EFI_NOT_COMPRESSED;
else if (model->compression(index) == COMPRESSION_ALGORITHM_LZMA || model->compression(index) == COMPRESSION_ALGORITHM_IMLZMA)
compessionHeader->CompressionType = EFI_CUSTOMIZED_COMPRESSION;
else if (model->compression(index) == COMPRESSION_ALGORITHM_EFI11 || model->compression(index) == COMPRESSION_ALGORITHM_TIANO)
compessionHeader->CompressionType = EFI_STANDARD_COMPRESSION;
else
return ERR_UNKNOWN_COMPRESSION_ALGORITHM;
// Replace new section body
reconstructed = compressed;
}
else if (model->attributes(index) == EFI_SECTION_GUID_DEFINED) {
EFI_GUID_DEFINED_SECTION* guidDefinedHeader = (EFI_GUID_DEFINED_SECTION*)header.data();
// Compress new section body
QByteArray compressed;
result = compress(reconstructed, model->compression(index), compressed);
if (result)
return result;
// Check for authentication status valid attribute
if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_AUTH_STATUS_VALID) {
// CRC32 section
if (QByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_CRC32) {
// Calculate CRC32 of section data
UINT32 crc = crc32(0, (const UINT8*)compressed.constData(), compressed.size());
// Store new CRC32
*(UINT32*)(header.data() + sizeof(EFI_GUID_DEFINED_SECTION)) = crc;
}
else {
msg(tr("reconstructSection: GUID defined section authentication info can become invalid")
.arg(guidToQString(guidDefinedHeader->SectionDefinitionGuid)), index);
}
}
// Check for Intel signed section
if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED
&& QByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_INTEL_SIGNED) {
msg(tr("reconstructSection: GUID defined section signature can become invalid")
.arg(guidToQString(guidDefinedHeader->SectionDefinitionGuid)), index);
}
// Replace new section body
reconstructed = compressed;
}
else if (model->compression(index) != COMPRESSION_ALGORITHM_NONE) {
msg(tr("reconstructSection: incorrectly required compression for section of type %1")
.arg(model->attributes(index)), index);
return ERR_INVALID_SECTION;
}
// Correct section size
uint32ToUint24(header.size() + reconstructed.size(), commonHeader->Size);
}
// Leaf section
else
reconstructed = model->body(index);
// Rebase PE32 or TE image, if needed
if ((model->attributes(index) == EFI_SECTION_PE32 || model->attributes(index) == EFI_SECTION_TE) &&
(model->attributes(index.parent()) == EFI_FV_FILETYPE_PEI_CORE ||
model->attributes(index.parent()) == EFI_FV_FILETYPE_PEIM ||
model->attributes(index.parent()) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)) {
UINT16 teFixup = 0;
//TODO: add proper handling
/*if (model->attributes(index) == EFI_SECTION_TE) {
const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)model->body(index).constData();
teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
}*/
if (base) {
result = rebase(reconstructed, base - teFixup + header.size());
if (result) {
msg(tr("reconstructSection: executable section rebase failed"), index);
return result;
}
// Special case of PEI Core rebase
if (model->attributes(index.parent()) == EFI_FV_FILETYPE_PEI_CORE) {
result = getEntryPoint(reconstructed, newPeiCoreEntryPoint);
if (result)
msg(tr("reconstructSection: can't get entry point of PEI core"), index);
}
}
}
// Reconstruction successful
reconstructed = header.append(reconstructed);
return ERR_SUCCESS;
}
// All other actions are not supported
return ERR_NOT_IMPLEMENTED;
}
UINT8 FfsEngine::reconstruct(const QModelIndex &index, QByteArray& reconstructed)
{
if (!index.isValid())
return ERR_SUCCESS;
UINT8 result;
switch (model->type(index)) {
case Types::Image:
if (model->attributes(index) == ATTR_IMAGE_TYPE_DESCRIPTOR) {
result = reconstructIntelImage(index, reconstructed);
if (result)
return result;
}
else {
//Other images types can be reconstructed like regions
result = reconstructRegion(index, reconstructed);
if (result)
return result;
}
break;
case Types::Capsule:
// Capsules can be reconstructed like regions
result = reconstructRegion(index, reconstructed);
if (result)
return result;
break;
case Types::Region:
result = reconstructRegion(index, reconstructed);
if (result)
return result;
break;
case Types::Padding:
// No reconstruction needed
reconstructed = model->header(index).append(model->body(index));
return ERR_SUCCESS;
break;
case Types::Volume:
result = reconstructVolume(index, reconstructed);
if (result)
return result;
break;
case Types::File: //Must not be called that way
msg(tr("reconstruct: call of generic function is not supported for files").arg(model->type(index)), index);
return ERR_GENERIC_CALL_NOT_SUPPORTED;
break;
case Types::Section:
result = reconstructSection(index, 0, reconstructed);
if (result)
return result;
break;
default:
msg(tr("reconstruct: unknown item type %1").arg(model->type(index)), index);
return ERR_UNKNOWN_ITEM_TYPE;
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::growVolume(QByteArray & header, const UINT32 size, UINT32 & newSize)
{
// Adjust new size to be representable by current FvBlockMap
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data();
EFI_FV_BLOCK_MAP_ENTRY* blockMap = (EFI_FV_BLOCK_MAP_ENTRY*)(header.data() + sizeof(EFI_FIRMWARE_VOLUME_HEADER));
// Get block map size
UINT32 blockMapSize = volumeHeader->HeaderLength - sizeof(EFI_FIRMWARE_VOLUME_HEADER);
if (blockMapSize % sizeof(EFI_FV_BLOCK_MAP_ENTRY))
return ERR_INVALID_VOLUME;
UINT32 blockMapCount = blockMapSize / sizeof(EFI_FV_BLOCK_MAP_ENTRY);
// Check blockMap validity
if (blockMap[blockMapCount - 1].NumBlocks != 0 || blockMap[blockMapCount - 1].Length != 0)
return ERR_INVALID_VOLUME;
// Case of complex blockMap
if (blockMapCount > 2)
return ERR_COMPLEX_BLOCK_MAP;
// Calculate new size
if (newSize <= size)
return ERR_INVALID_PARAMETER;
newSize += blockMap[0].Length - newSize % blockMap[0].Length;
// Recalculate number of blocks
blockMap[0].NumBlocks = newSize / blockMap[0].Length;
// Set new volume size
volumeHeader->FvLength = 0;
for (UINT8 i = 0; i < blockMapCount; i++) {
volumeHeader->FvLength += blockMap[i].NumBlocks * blockMap[i].Length;
}
// Recalculate volume header checksum
volumeHeader->Checksum = 0;
volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength);
return ERR_SUCCESS;
}
UINT8 FfsEngine::reconstructImageFile(QByteArray & reconstructed)
{
return reconstruct(model->index(0, 0), reconstructed);
}
// Search routines
UINT8 FfsEngine::findHexPattern(const QModelIndex & index, const QByteArray & hexPattern, const UINT8 mode)
{
if (!index.isValid())
return ERR_SUCCESS;
if (hexPattern.isEmpty())
return ERR_INVALID_PARAMETER;
// Check for "all substrings" pattern
if (hexPattern.count('.') == hexPattern.length())
return ERR_SUCCESS;
bool hasChildren = (model->rowCount(index) > 0);
for (int i = 0; i < model->rowCount(index); i++) {
findHexPattern(index.child(i, index.column()), hexPattern, mode);
}
QByteArray data;
if (hasChildren) {
if (mode != SEARCH_MODE_BODY)
data = model->header(index);
}
else {
if (mode == SEARCH_MODE_HEADER)
data.append(model->header(index));
else if (mode == SEARCH_MODE_BODY)
data.append(model->body(index));
else
data.append(model->header(index)).append(model->body(index));
}
QString hexBody = QString(data.toHex());
QRegExp regexp = QRegExp(QString(hexPattern), Qt::CaseInsensitive);
INT32 offset = regexp.indexIn(hexBody);
while (offset >= 0) {
if (offset % 2 == 0) {
msg(tr("Hex pattern \"%1\" found as \"%2\" in %3 at %4-offset %5h")
.arg(QString(hexPattern))
.arg(hexBody.mid(offset, hexPattern.length()).toUpper())
.arg(model->name(index))
.arg(mode == SEARCH_MODE_BODY ? tr("body") : tr("header"))
.hexarg(offset / 2),
index);
}
offset = regexp.indexIn(hexBody, offset + 1);
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::findGuidPattern(const QModelIndex & index, const QByteArray & guidPattern, const UINT8 mode)
{
if (guidPattern.isEmpty())
return ERR_INVALID_PARAMETER;
if (!index.isValid())
return ERR_SUCCESS;
bool hasChildren = (model->rowCount(index) > 0);
for (int i = 0; i < model->rowCount(index); i++) {
findGuidPattern(index.child(i, index.column()), guidPattern, mode);
}
QByteArray data;
if (hasChildren) {
if (mode != SEARCH_MODE_BODY)
data = model->header(index);
}
else {
if (mode == SEARCH_MODE_HEADER)
data.append(model->header(index));
else if (mode == SEARCH_MODE_BODY)
data.append(model->body(index));
else
data.append(model->header(index)).append(model->body(index));
}
QString hexBody = QString(data.toHex());
QList<QByteArray> list = guidPattern.split('-');
if (list.count() != 5)
return ERR_INVALID_PARAMETER;
QByteArray hexPattern;
// Reverse first GUID block
hexPattern.append(list.at(0).mid(6, 2));
hexPattern.append(list.at(0).mid(4, 2));
hexPattern.append(list.at(0).mid(2, 2));
hexPattern.append(list.at(0).mid(0, 2));
// Reverse second GUID block
hexPattern.append(list.at(1).mid(2, 2));
hexPattern.append(list.at(1).mid(0, 2));
// Reverse third GUID block
hexPattern.append(list.at(2).mid(2, 2));
hexPattern.append(list.at(2).mid(0, 2));
// Append fourth and fifth GUID blocks as is
hexPattern.append(list.at(3)).append(list.at(4));
// Check for "all substrings" pattern
if (hexPattern.count('.') == hexPattern.length())
return ERR_SUCCESS;
QRegExp regexp = QRegExp(QString(hexPattern), Qt::CaseInsensitive);
INT32 offset = regexp.indexIn(hexBody);
while (offset >= 0) {
if (offset % 2 == 0) {
msg(tr("GUID pattern \"%1\" found as \"%2\" in %3 at %4-offset %5h")
.arg(QString(guidPattern))
.arg(hexBody.mid(offset, hexPattern.length()).toUpper())
.arg(model->name(index))
.arg(mode == SEARCH_MODE_BODY ? tr("body") : tr("header"))
.hexarg(offset / 2),
index);
}
offset = regexp.indexIn(hexBody, offset + 1);
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::findTextPattern(const QModelIndex & index, const QString & pattern, const bool unicode, const Qt::CaseSensitivity caseSensitive)
{
if (pattern.isEmpty())
return ERR_INVALID_PARAMETER;
if (!index.isValid())
return ERR_SUCCESS;
bool hasChildren = (model->rowCount(index) > 0);
for (int i = 0; i < model->rowCount(index); i++) {
findTextPattern(index.child(i, index.column()), pattern, unicode, caseSensitive);
}
if (hasChildren)
return ERR_SUCCESS;
QString data;
if (unicode)
data = QString::fromUtf16((const ushort*)model->body(index).data(), model->body(index).length() / 2);
else
data = QString::fromLatin1((const char*)model->body(index).data(), model->body(index).length());
int offset = -1;
while ((offset = data.indexOf(pattern, offset + 1, caseSensitive)) >= 0) {
msg(tr("%1 text \"%2\" found in %3 at offset %4h")
.arg(unicode ? "Unicode" : "ASCII")
.arg(pattern)
.arg(model->name(index))
.hexarg(unicode ? offset * 2 : offset),
index);
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::rebase(QByteArray &executable, const UINT32 base)
{
UINT32 delta; // Difference between old and new base addresses
UINT32 relocOffset; // Offset of relocation region
UINT32 relocSize; // Size of relocation region
UINT32 teFixup = 0; // Bytes removed form PE header for TE images
// Copy input data to local storage
QByteArray file = executable;
// Populate DOS header
EFI_IMAGE_DOS_HEADER* dosHeader = (EFI_IMAGE_DOS_HEADER*)file.data();
// Check signature
if (dosHeader->e_magic == EFI_IMAGE_DOS_SIGNATURE){
UINT32 offset = dosHeader->e_lfanew;
EFI_IMAGE_PE_HEADER* peHeader = (EFI_IMAGE_PE_HEADER*)(file.data() + offset);
if (peHeader->Signature != EFI_IMAGE_PE_SIGNATURE)
return ERR_UNKNOWN_IMAGE_TYPE;
offset += sizeof(EFI_IMAGE_PE_HEADER);
// Skip file header
offset += sizeof(EFI_IMAGE_FILE_HEADER);
// Check optional header magic
UINT16 magic = *(UINT16*)(file.data() + offset);
if (magic == EFI_IMAGE_PE_OPTIONAL_HDR32_MAGIC) {
EFI_IMAGE_OPTIONAL_HEADER32* optHeader = (EFI_IMAGE_OPTIONAL_HEADER32*)(file.data() + offset);
delta = base - optHeader->ImageBase;
if (!delta)
// No need to rebase
return ERR_SUCCESS;
relocOffset = optHeader->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
relocSize = optHeader->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
// Set new base
optHeader->ImageBase = base;
}
else if (magic == EFI_IMAGE_PE_OPTIONAL_HDR64_MAGIC) {
EFI_IMAGE_OPTIONAL_HEADER64* optHeader = (EFI_IMAGE_OPTIONAL_HEADER64*)(file.data() + offset);
delta = base - optHeader->ImageBase;
if (!delta)
// No need to rebase
return ERR_SUCCESS;
relocOffset = optHeader->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
relocSize = optHeader->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
// Set new base
optHeader->ImageBase = base;
}
else
return ERR_UNKNOWN_PE_OPTIONAL_HEADER_TYPE;
}
else if (dosHeader->e_magic == EFI_IMAGE_TE_SIGNATURE){
// Populate TE header
EFI_IMAGE_TE_HEADER* teHeader = (EFI_IMAGE_TE_HEADER*)file.data();
delta = base - teHeader->ImageBase;
if (!delta)
// No need to rebase
return ERR_SUCCESS;
relocOffset = teHeader->DataDirectory[EFI_IMAGE_TE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
relocSize = teHeader->DataDirectory[EFI_IMAGE_TE_DIRECTORY_ENTRY_BASERELOC].Size;
// Set new base
teHeader->ImageBase = base;
}
else
return ERR_UNKNOWN_IMAGE_TYPE;
// No relocations
if (relocOffset == 0) {
// No need to fix relocations
executable = file;
return ERR_SUCCESS;
}
EFI_IMAGE_BASE_RELOCATION *RelocBase;
EFI_IMAGE_BASE_RELOCATION *RelocBaseEnd;
UINT16 *Reloc;
UINT16 *RelocEnd;
UINT16 *F16;
UINT32 *F32;
UINT64 *F64;
// Run the whole relocation block
RelocBase = (EFI_IMAGE_BASE_RELOCATION*)(file.data() + relocOffset - teFixup);
RelocBaseEnd = (EFI_IMAGE_BASE_RELOCATION*)(file.data() + relocOffset - teFixup + relocSize);
while (RelocBase < RelocBaseEnd) {
Reloc = (UINT16*)((UINT8*)RelocBase + sizeof(EFI_IMAGE_BASE_RELOCATION));
RelocEnd = (UINT16*)((UINT8*)RelocBase + RelocBase->SizeOfBlock);
// Run this relocation record
while (Reloc < RelocEnd) {
UINT8* data = (UINT8*)(file.data() + RelocBase->VirtualAddress - teFixup + (*Reloc & 0x0FFF));
switch ((*Reloc) >> 12) {
case EFI_IMAGE_REL_BASED_ABSOLUTE:
// Do nothing
break;
case EFI_IMAGE_REL_BASED_HIGH:
// Add second 16 bits of delta
F16 = (UINT16*)data;
*F16 = (UINT16)(*F16 + (UINT16)(((UINT32)delta) >> 16));
break;
case EFI_IMAGE_REL_BASED_LOW:
// Add first 16 bits of delta
F16 = (UINT16*)data;
*F16 = (UINT16)(*F16 + (UINT16)delta);
break;
case EFI_IMAGE_REL_BASED_HIGHLOW:
// Add first 32 bits of delta
F32 = (UINT32*)data;
*F32 = *F32 + (UINT32)delta;
break;
case EFI_IMAGE_REL_BASED_DIR64:
// Add all 64 bits of delta
F64 = (UINT64*)data;
*F64 = *F64 + (UINT64)delta;
break;
default:
return ERR_UNKNOWN_RELOCATION_TYPE;
}
// Next relocation record
Reloc += 1;
}
// Next relocation block
RelocBase = (EFI_IMAGE_BASE_RELOCATION*)RelocEnd;
}
executable = file;
return ERR_SUCCESS;
}
UINT8 FfsEngine::patchVtf(QByteArray &vtf)
{
if (!oldPeiCoreEntryPoint) {
msg(tr("PEI Core entry point can't be determined. VTF can't be patched."));
return ERR_PEI_CORE_ENTRY_POINT_NOT_FOUND;
}
if (!newPeiCoreEntryPoint || oldPeiCoreEntryPoint == newPeiCoreEntryPoint)
// No need to patch anything
return ERR_SUCCESS;
// Replace last occurrence of oldPeiCoreEntryPoint with newPeiCoreEntryPoint
QByteArray old((char*)&oldPeiCoreEntryPoint, sizeof(oldPeiCoreEntryPoint));
int i = vtf.lastIndexOf(old);
if (i == -1) {
msg(tr("PEI Core entry point can't be found in VTF. VTF not patched."));
return ERR_SUCCESS;
}
UINT32* data = (UINT32*)(vtf.data() + i);
*data = newPeiCoreEntryPoint;
return ERR_SUCCESS;
}
UINT8 FfsEngine::getEntryPoint(const QByteArray &file, UINT32& entryPoint)
{
if (file.isEmpty())
return ERR_INVALID_FILE;
// Populate DOS header
const EFI_IMAGE_DOS_HEADER* dosHeader = (const EFI_IMAGE_DOS_HEADER*)file.constData();
// Check signature
if (dosHeader->e_magic == EFI_IMAGE_DOS_SIGNATURE){
UINT32 offset = dosHeader->e_lfanew;
const EFI_IMAGE_PE_HEADER* peHeader = (const EFI_IMAGE_PE_HEADER*)(file.constData() + offset);
if (peHeader->Signature != EFI_IMAGE_PE_SIGNATURE)
return ERR_UNKNOWN_IMAGE_TYPE;
offset += sizeof(EFI_IMAGE_PE_HEADER);
// Skip file header
offset += sizeof(EFI_IMAGE_FILE_HEADER);
// Check optional header magic
const UINT16 magic = *(const UINT16*)(file.constData() + offset);
if (magic == EFI_IMAGE_PE_OPTIONAL_HDR32_MAGIC) {
const EFI_IMAGE_OPTIONAL_HEADER32* optHeader = (const EFI_IMAGE_OPTIONAL_HEADER32*)(file.constData() + offset);
entryPoint = optHeader->ImageBase + optHeader->AddressOfEntryPoint;
}
else if (magic == EFI_IMAGE_PE_OPTIONAL_HDR64_MAGIC) {
const EFI_IMAGE_OPTIONAL_HEADER64* optHeader = (const EFI_IMAGE_OPTIONAL_HEADER64*)(file.constData() + offset);
entryPoint = optHeader->ImageBase + optHeader->AddressOfEntryPoint;
}
else
return ERR_UNKNOWN_PE_OPTIONAL_HEADER_TYPE;
}
else if (dosHeader->e_magic == EFI_IMAGE_TE_SIGNATURE){
// Populate TE header
const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)file.constData();
UINT32 teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER);
entryPoint = teHeader->ImageBase + teHeader->AddressOfEntryPoint - teFixup;
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::getBase(const QByteArray& file, UINT32& base)
{
if (file.isEmpty())
return ERR_INVALID_FILE;
// Populate DOS header
const EFI_IMAGE_DOS_HEADER* dosHeader = (const EFI_IMAGE_DOS_HEADER*)file.constData();
// Check signature
if (dosHeader->e_magic == EFI_IMAGE_DOS_SIGNATURE){
UINT32 offset = dosHeader->e_lfanew;
const EFI_IMAGE_PE_HEADER* peHeader = (const EFI_IMAGE_PE_HEADER*)(file.constData() + offset);
if (peHeader->Signature != EFI_IMAGE_PE_SIGNATURE)
return ERR_UNKNOWN_IMAGE_TYPE;
offset += sizeof(EFI_IMAGE_PE_HEADER);
// Skip file header
offset += sizeof(EFI_IMAGE_FILE_HEADER);
// Check optional header magic
const UINT16 magic = *(const UINT16*)(file.constData() + offset);
if (magic == EFI_IMAGE_PE_OPTIONAL_HDR32_MAGIC) {
const EFI_IMAGE_OPTIONAL_HEADER32* optHeader = (const EFI_IMAGE_OPTIONAL_HEADER32*)(file.constData() + offset);
base = optHeader->ImageBase;
}
else if (magic == EFI_IMAGE_PE_OPTIONAL_HDR64_MAGIC) {
const EFI_IMAGE_OPTIONAL_HEADER64* optHeader = (const EFI_IMAGE_OPTIONAL_HEADER64*)(file.constData() + offset);
base = optHeader->ImageBase;
}
else
return ERR_UNKNOWN_PE_OPTIONAL_HEADER_TYPE;
}
else if (dosHeader->e_magic == EFI_IMAGE_TE_SIGNATURE){
// Populate TE header
const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)file.constData();
//!TODO: add handling
base = teHeader->ImageBase;
}
return ERR_SUCCESS;
}
UINT32 FfsEngine::crc32(UINT32 initial, const UINT8* buffer, UINT32 length)
{
static const UINT32 crcTable[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D };
UINT32 crc32;
UINT32 i;
// Accumulate crc32 for buffer
crc32 = initial ^ 0xFFFFFFFF;
for (i = 0; i < length; i++) {
crc32 = (crc32 >> 8) ^ crcTable[(crc32 ^ buffer[i]) & 0xFF];
}
return(crc32 ^ 0xFFFFFFFF);
}
UINT8 FfsEngine::dump(const QModelIndex & index, const QString & path, const QString & guid)
{
dumped = false;
UINT8 result = recursiveDump(index, path, guid);
if (result)
return result;
else if (!dumped)
return ERR_ITEM_NOT_FOUND;
return ERR_SUCCESS;
}
UINT8 FfsEngine::recursiveDump(const QModelIndex & index, const QString & path, const QString & guid)
{
if (!index.isValid())
return ERR_INVALID_PARAMETER;
QDir dir;
if (guid.isEmpty() ||
guidToQString(*(const EFI_GUID*)model->header(index).constData()) == guid ||
guidToQString(*(const EFI_GUID*)model->header(model->findParentOfType(index, Types::File)).constData()) == guid) {
if (dir.cd(path))
return ERR_DIR_ALREADY_EXIST;
if (!dir.mkpath(path))
return ERR_DIR_CREATE;
QFile file;
if (!model->header(index).isEmpty()) {
file.setFileName(tr("%1/header.bin").arg(path));
if (!file.open(QFile::WriteOnly))
return ERR_FILE_OPEN;
file.write(model->header(index));
file.close();
}
if (!model->body(index).isEmpty()) {
file.setFileName(tr("%1/body.bin").arg(path));
if (!file.open(QFile::WriteOnly))
return ERR_FILE_OPEN;
file.write(model->body(index));
file.close();
}
QString info = tr("Type: %1\nSubtype: %2\n%3%4")
.arg(itemTypeToQString(model->type(index)))
.arg(itemAttributesToQString(model->type(index), model->attributes(index)))
.arg(model->text(index).isEmpty() ? "" : tr("Text: %1\n").arg(model->text(index)))
.arg(model->info(index));
file.setFileName(tr("%1/info.txt").arg(path));
if (!file.open(QFile::Text | QFile::WriteOnly))
return ERR_FILE_OPEN;
file.write(info.toLatin1());
file.close();
dumped = true;
}
UINT8 result;
for (int i = 0; i < model->rowCount(index); i++) {
QModelIndex childIndex = index.child(i, 0);
QString childPath = QString("%1/%2 %3").arg(path).arg(i).arg(model->text(childIndex).isEmpty() ? model->name(childIndex) : model->text(childIndex));
result = recursiveDump(childIndex, childPath, guid);
if (result)
return result;
}
return ERR_SUCCESS;
}
UINT8 FfsEngine::patch(const QModelIndex & index, const QVector<PatchData> & patches)
{
if (!index.isValid() || patches.isEmpty() || model->rowCount(index))
return ERR_INVALID_PARAMETER;
// Skip removed items
if (model->action(index) == Actions::Remove)
return ERR_NOTHING_TO_PATCH;
UINT8 result;
// Apply patches to item's body
QByteArray body = model->body(index);
PatchData current;
Q_FOREACH(current, patches)
{
if (current.type == PATCH_TYPE_OFFSET) {
result = patchViaOffset(body, current.offset, current.hexReplacePattern);
if (result)
return result;
}
else if (current.type == PATCH_TYPE_PATTERN) {
result = patchViaPattern(body, current.hexFindPattern, current.hexReplacePattern);
if (result)
return result;
}
else
return ERR_UNKNOWN_PATCH_TYPE;
}
if (body != model->body(index)) {
QByteArray patched = model->header(index);
patched.append(body);
return replace(index, patched, REPLACE_MODE_AS_IS);
}
return ERR_NOTHING_TO_PATCH;
}
UINT8 FfsEngine::patchViaOffset(QByteArray & data, const UINT32 offset, const QByteArray & hexReplacePattern)
{
QByteArray body = data;
// Skip patterns with odd length
if (hexReplacePattern.length() % 2 > 0)
return ERR_INVALID_PARAMETER;
// Check offset bounds
if (offset > (UINT32)(body.length() - hexReplacePattern.length() / 2))
return ERR_PATCH_OFFSET_OUT_OF_BOUNDS;
// Parse replace pattern
QByteArray replacePattern;
bool converted;
for (int i = 0; i < hexReplacePattern.length() / 2; i++) {
QByteArray hex = hexReplacePattern.mid(2 * i, 2);
UINT8 value = 0;
if (!hex.contains('.')) { // Normal byte pattern
value = (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else { // Placeholder byte pattern
if (hex[0] == '.' && hex[1] == '.') { // Full byte placeholder
value = body.at(offset + i);
}
else if (hex[0] == '.') {// Upper byte part placeholder
hex[0] = '0';
value = (UINT8)(body.at(offset + i) & 0xF0);
value += (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else if (hex[1] == '.') { // Lower byte part placeholder
hex[1] = '0';
value = (UINT8)(body.at(offset + i) & 0x0F);
value += (UINT8)hex.toUShort(&converted, 16);
if (!converted)
return ERR_INVALID_SYMBOL;
}
else
return ERR_INVALID_SYMBOL;
}
// Append calculated value to real pattern
replacePattern.append(value);
}
body.replace(offset, replacePattern.length(), replacePattern);
msg(tr("patch: replaced %1 bytes at offset %2h %3 -> %4")
.arg(replacePattern.length())
.hexarg(offset)
.arg(QString(data.mid(offset, replacePattern.length()).toHex()).toUpper())
.arg(QString(replacePattern.toHex()).toUpper()));
data = body;
return ERR_SUCCESS;
}
UINT8 FfsEngine::patchViaPattern(QByteArray & data, const QByteArray & hexFindPattern, const QByteArray & hexReplacePattern)
{
QByteArray body = data;
// Skip patterns with odd length
if (hexFindPattern.length() % 2 > 0 || hexReplacePattern.length() % 2 > 0)
return ERR_INVALID_PARAMETER;
// Convert file body to hex;
QString hexBody = QString(body.toHex());
QRegExp regexp = QRegExp(QString(hexFindPattern), Qt::CaseInsensitive);
INT32 offset = regexp.indexIn(hexBody);
while (offset >= 0) {
if (offset % 2 == 0) {
UINT8 result = patchViaOffset(body, offset / 2, hexReplacePattern);
if (result)
return result;
}
offset = regexp.indexIn(hexBody, offset + 1);
}
data = body;
return ERR_SUCCESS;
}