/* ffsengine.cpp Copyright (c) 2013, 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 #include "ffsengine.h" #include "treeitem.h" #include "treemodel.h" #include "treeitemtypes.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" FfsEngine::FfsEngine(QObject *parent) : QObject(parent) { rootItem = new TreeItem(RootItem, 0, 0, tr("Object"), tr("Type"), tr("Subtype"), tr("Text")); treeModel = new TreeModel(rootItem); } FfsEngine::~FfsEngine(void) { delete treeModel; delete rootItem; } TreeModel* FfsEngine::model() const { return treeModel; } QString FfsEngine::message() const { return text; } void FfsEngine::msg(const QString & message) { text.append(message).append("\n"); } UINT8 FfsEngine::parseInputFile(const QByteArray & buffer) { UINT32 capsuleHeaderSize = 0; FLASH_DESCRIPTOR_HEADER* descriptorHeader = NULL; QByteArray flashImage; QByteArray bios; QModelIndex index; // Check buffer size to be more or equal then sizeof(EFI_CAPSULE_HEADER) if (buffer.size() <= sizeof(EFI_CAPSULE_HEADER)) { msg(tr("parseInputFile: Input file is smaller then mininum size of %1 bytes").arg(sizeof(EFI_CAPSULE_HEADER))); return ERR_INVALID_PARAMETER; } // Check buffer for being normal EFI capsule header if (buffer.startsWith(EFI_CAPSULE_GUID)) { EFI_CAPSULE_HEADER* capsuleHeader = (EFI_CAPSULE_HEADER*) buffer.constData(); capsuleHeaderSize = capsuleHeader->HeaderSize; QByteArray header = buffer.left(capsuleHeaderSize); QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); index = addTreeItem(CapsuleItem, UefiCapsule, 0, header, body); } // Check buffer for being extended Aptio capsule header else if (buffer.startsWith(APTIO_CAPSULE_GUID)) { APTIO_CAPSULE_HEADER* aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) buffer.constData(); capsuleHeaderSize = aptioCapsuleHeader->RomImageOffset; QByteArray header = buffer.left(capsuleHeaderSize); QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); index = addTreeItem(CapsuleItem, AptioCapsule, 0, header, body); } // Skip capsule header to have flash chip image flashImage = buffer.right(buffer.size() - capsuleHeaderSize); // Check buffer for being Intel flash descriptor descriptorHeader = (FLASH_DESCRIPTOR_HEADER*) flashImage.constData(); // Check descriptor signature if (descriptorHeader->Signature == FLASH_DESCRIPTOR_SIGNATURE) { FLASH_DESCRIPTOR_MAP* descriptorMap; FLASH_DESCRIPTOR_REGION_SECTION* regionSection; // Store the beginning of descriptor as descriptor base address UINT8* descriptor = (UINT8*) flashImage.constData(); UINT8* gbeRegion = NULL; UINT8* meRegion = NULL; UINT8* biosRegion = NULL; UINT8* pdrRegion = NULL; // Check for buffer size to be greater or equal to descriptor region size if (flashImage.size() < FLASH_DESCRIPTOR_SIZE) { msg(tr("parseInputFile: Input file is smaller then mininum descriptor size of 4096 bytes")); return ERR_INVALID_FLASH_DESCRIPTOR; } // Parse descriptor map descriptorMap = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); regionSection = (FLASH_DESCRIPTOR_REGION_SECTION*) calculateAddress8(descriptor, descriptorMap->RegionBase); // Add tree item QByteArray header = flashImage.left(sizeof(FLASH_DESCRIPTOR_HEADER)); QByteArray body = flashImage.mid(sizeof(FLASH_DESCRIPTOR_HEADER), FLASH_DESCRIPTOR_SIZE - sizeof(FLASH_DESCRIPTOR_HEADER)); index = addTreeItem(DescriptorItem, 0, 0, header, body, index); // Parse region section QModelIndex gbeIndex(index); QModelIndex meIndex(index); QModelIndex biosIndex(index); QModelIndex pdrIndex(index); gbeRegion = parseRegion(flashImage, GbeRegion, regionSection->GbeBase, regionSection->GbeLimit, gbeIndex); meRegion = parseRegion(flashImage, MeRegion, regionSection->MeBase, regionSection->MeLimit, meIndex); biosRegion = parseRegion(flashImage, BiosRegion, regionSection->BiosBase, regionSection->BiosLimit, biosIndex); pdrRegion = parseRegion(flashImage, PdrRegion, regionSection->PdrBase, regionSection->PdrLimit, pdrIndex); // Parse complete // Exit if no bios region found if (!biosRegion) { msg(tr("parseInputFile: BIOS region not found")); return ERR_BIOS_REGION_NOT_FOUND; } index = biosIndex; bios = QByteArray::fromRawData((const char*) biosRegion, calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit)); } else { bios = buffer; } // We are in the beginning of BIOS space, where firmware volumes are // Parse BIOS space return parseBios(bios, index); } UINT8* FfsEngine::parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index) { // Check for empty region or flash image if (!regionLimit || flashImage.size() <= 0) return NULL; // Storing flash image size to unsigned variable, because it can't be negative now and all other values are unsigned UINT32 flashImageSize = (UINT32) flashImage.size(); // Calculate region offset and size UINT32 regionOffset = calculateRegionOffset(regionBase); UINT32 regionSize = calculateRegionSize(regionBase, regionLimit); // Populate descriptor map FLASH_DESCRIPTOR_MAP* descriptor_map = (FLASH_DESCRIPTOR_MAP*) (flashImage.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); // Determine presence of 2 flash chips bool twoChips = descriptor_map->NumberOfFlashChips; // construct region name //!TODO: make this to regionTypeToQString(const UINT8 type) in descriptor.cpp QString regionName; switch (regionSubtype) { case GbeRegion: regionName = "GbE"; break; case MeRegion: regionName = "ME"; break; case BiosRegion: regionName = "Bios"; break; case PdrRegion: regionName = "PDR"; break; default: regionName = "Unknown"; msg(tr("parseRegion: Unknown region type")); }; // Check region base to be in buffer if (regionOffset >= flashImageSize) { msg(tr("parseRegion: %1 region stored in descriptor not found").arg(regionName)); if (twoChips) msg(tr("Two flash chips installed, so it could be in another flash chip\n" "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); else msg(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); msg(tr("Absence of %1 region assumed").arg(regionName)); return NULL; } // Check region to be fully present in buffer else if (regionOffset + regionSize > flashImageSize) { msg(tr("parseRegion: %1 region stored in descriptor overlaps the end of opened file").arg(regionName)); if (twoChips) msg(tr("Two flash chips installed, so it could be in another flash chip\n" "Make a dump from another flash chip and open it to view information about %1 region").arg(regionName)); else msg(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump")); msg(tr("Absence of %1 region assumed\n").arg(regionName)); return NULL; } // Calculate region address UINT8* region = calculateAddress16((UINT8*) flashImage.constData(), regionBase); // Add tree item QByteArray body = flashImage.mid(regionOffset, regionSize); index = addTreeItem(RegionItem, regionSubtype, regionOffset, QByteArray(), body, index); return region; } UINT8 FfsEngine::parseBios(const QByteArray & bios, const QModelIndex & parent) { // Search for first volume INT32 prevVolumeOffset = findNextVolume(bios); // No volumes found if (prevVolumeOffset < 0) { return ERR_VOLUMES_NOT_FOUND; } // First volume is not at the beginning of bios space if (prevVolumeOffset > 0) { QByteArray padding = bios.left(prevVolumeOffset); addTreeItem(PaddingItem, 0, 0, QByteArray(), padding, parent); } // Search for and parse all volumes INT32 volumeOffset; UINT32 prevVolumeSize; for (volumeOffset = prevVolumeOffset, prevVolumeSize = 0; volumeOffset >= 0; prevVolumeOffset = volumeOffset, prevVolumeSize = getVolumeSize(bios, volumeOffset), volumeOffset = findNextVolume(bios, volumeOffset + prevVolumeSize)) { // Padding between volumes if ((UINT32) volumeOffset > prevVolumeOffset + prevVolumeSize) { // Conversion to suppress warning, volumeOffset can't be negative here UINT32 size = volumeOffset - prevVolumeOffset - prevVolumeSize; QByteArray padding = bios.mid(prevVolumeOffset + prevVolumeSize, size); addTreeItem(PaddingItem, 0, prevVolumeOffset + prevVolumeSize, QByteArray(), padding, parent); } // Populate volume header EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeOffset); //Check that volume is fully present in input if (volumeOffset + volumeHeader->FvLength > bios.size()) { msg(tr("parseBios: Volume overlaps the end of input buffer")); return ERR_INVALID_VOLUME; } // Check volume revision and alignment UINT32 alignment; if (volumeHeader->Revision == 1) { // Aquire alignment bits bool alignmentCap = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_CAP; bool alignment2 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2; bool alignment4 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4; bool alignment8 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8; bool alignment16 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16; bool alignment32 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32; bool alignment64 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64; bool alignment128 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_128; bool alignment256 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_256; bool alignment512 = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_512; bool alignment1k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_1K; bool alignment2k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_2K; bool alignment4k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_4K; bool alignment8k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_8K; bool alignment16k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_16K; bool alignment32k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_32K; bool alignment64k = volumeHeader->Attributes & EFI_FVB_ALIGNMENT_64K; // Check alignment setup if (!alignmentCap && ( alignment2 || alignment4 || alignment8 || alignment16 || alignment32 || alignment64 || alignment128 || alignment256 || alignment512 || alignment1k || alignment2k || alignment4k || alignment8k || alignment16k || alignment32k || alignment64k)) msg("parseBios: Incompatible revision 1 volume alignment setup"); // Assume that smaller alignment value consumes greater alignment = 0x01; if (alignment2) alignment = 0x02; else if (alignment4) alignment = 0x04; else if (alignment8) alignment = 0x08; else if (alignment16) alignment = 0x10; else if (alignment32) alignment = 0x20; else if (alignment64) alignment = 0x40; else if (alignment128) alignment = 0x80; else if (alignment256) alignment = 0x100; else if (alignment512) alignment = 0x200; else if (alignment1k) alignment = 0x400; else if (alignment2k) alignment = 0x800; else if (alignment4k) alignment = 0x1000; else if (alignment8k) alignment = 0x2000; else if (alignment16k) alignment = 0x4000; else if (alignment32k) alignment = 0x8000; else if (alignment64k) alignment = 0x10000; // Check alignment if (volumeOffset % alignment) { msg(tr("parseBios: Unaligned revision 1 volume")); } } else if (volumeHeader->Revision == 2) { // Aquire alignment alignment = pow(2, (volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16); // Check alignment if (volumeOffset % alignment) { msg(tr("parseBios: Unaligned revision 2 volume")); } } else msg(tr("parseBios: Unknown volume revision (%1)").arg(volumeHeader->Revision)); // Check filesystem GUID to be known // Do not parse volume with unknown FFS, because parsing will fail bool parseCurrentVolume = true; // FFS GUID v1 if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM_GUID) { // Code can be added here } // FFS GUID v2 else if (QByteArray((const char*) &volumeHeader->FileSystemGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_FILE_SYSTEM2_GUID) { // Code can be added here } // Other GUID else { //info = info.append(tr("File system: unknown\n")); msg(tr("parseBios: Unknown file system (%1)").arg(guidToQString(volumeHeader->FileSystemGuid))); parseCurrentVolume = false; } // Check attributes // Determine erase polarity bool erasePolarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY; // Check header checksum by recalculating it if (!calculateChecksum16((UINT8*) volumeHeader, volumeHeader->HeaderLength)) { msg(tr("parseBios: Volume header checksum is invalid")); } // Check for presence of extended header, only if header revision is not 1 UINT32 headerSize; if (volumeHeader->Revision > 1 && volumeHeader->ExtHeaderOffset) { EFI_FIRMWARE_VOLUME_EXT_HEADER* extendedHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER*) ((UINT8*) volumeHeader + volumeHeader->ExtHeaderOffset); headerSize = volumeHeader->ExtHeaderOffset + extendedHeader->ExtHeaderSize; } else { headerSize = volumeHeader->HeaderLength; } // Adding tree item QByteArray header = bios.mid(volumeOffset, headerSize); QByteArray body = bios.mid(volumeOffset + headerSize, volumeHeader->FvLength - headerSize); QModelIndex index = addTreeItem(VolumeItem, 0, volumeOffset, header, body, parent); // Parse volume if (parseCurrentVolume) { UINT32 result = parseVolume(bios.mid(volumeOffset + headerSize, volumeHeader->FvLength - headerSize), headerSize, volumeHeader->Revision, erasePolarity, index); if (result) msg(tr("parseBios: Volume parsing failed (%1)").arg(result)); } } return ERR_SUCCESS; } INT32 FfsEngine::findNextVolume(const QByteArray & bios, INT32 volumeOffset) { if (volumeOffset < 0) return -1; INT32 nextIndex = bios.indexOf(EFI_FV_SIGNATURE, volumeOffset); if (nextIndex < EFI_FV_SIGNATURE_OFFSET) { return -1; } return nextIndex - EFI_FV_SIGNATURE_OFFSET; } UINT32 FfsEngine::getVolumeSize(const QByteArray & bios, INT32 volumeOffset) { // Populate volume header EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeOffset); // Check volume signature if (QByteArray((const char*) &volumeHeader->Signature, sizeof(volumeHeader->Signature)) != EFI_FV_SIGNATURE) return 0; return volumeHeader->FvLength; } UINT8 FfsEngine::parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, const QModelIndex & parent) { // Construct empty byte based on erasePolarity value // Native char type is used because QByteArray.count() takes it char empty = erasePolarity ? '\xFF' : '\x00'; // Search for and parse all files INT32 fileOffset = 0; while (fileOffset >= 0) { EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) (volume.constData() + fileOffset); QByteArray file = volume.mid(fileOffset, uint24ToUint32(fileHeader->Size)); QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER)); // Check file size to at least sizeof(EFI_FFS_FILE_HEADER) if (file.size() < sizeof(EFI_FFS_FILE_HEADER)) { msg(tr("parseVolume: File with invalid size")); return ERR_INVALID_FILE; } // We are at empty space in the end of volume if (header.count(empty) == header.size()) { QByteArray body = volume.right(volume.size() - fileOffset); addTreeItem(PaddingItem, 0, fileOffset, QByteArray(), body, parent); break; } // Check header checksum 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((UINT8*) tempFileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); if (fileHeader->IntegrityCheck.Checksum.Header != calculated) { msg(tr("parseVolume: %1, stored header checksum %2 differs from calculated %3") .arg(guidToQString(fileHeader->Name)) .arg(fileHeader->IntegrityCheck.Checksum.Header, 2, 16, QChar('0')) .arg(calculated, 2, 16, QChar('0'))); } // Check data checksum, if no tail was found // 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(revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) bufferSize -= sizeof(UINT16); calculated = calculateChecksum8((UINT8*)(file.constData() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); if (fileHeader->IntegrityCheck.Checksum.File != calculated) { msg(tr("parseVolume: %1, stored data checksum %2 differs from calculated %3") .arg(guidToQString(fileHeader->Name)) .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0')) .arg(calculated, 2, 16, QChar('0'))); } } // Data checksum must be one of predefined values else { if (fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM &&fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2) { msg(tr("parseVolume: %1, stored data checksum %2 differs from standard value") .arg(guidToQString(fileHeader->Name)) .arg(fileHeader->IntegrityCheck.Checksum.File, 2, 16, QChar('0'))); } } // Check file alignment UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; UINT32 alignment = pow(2, alignmentPower); if ((volumeBase + fileOffset + sizeof(EFI_FFS_FILE_HEADER)) % alignment) { msg(tr("parseVolume: %1, unaligned file").arg(guidToQString(fileHeader->Name))); } // Get file body QByteArray body = file.right(file.size() - sizeof(EFI_FFS_FILE_HEADER)); // For files in Revision 1 volumes, check for file tail presence if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { //Check file tail; UINT16* tail = (UINT16*) body.right(sizeof(UINT16)).constData(); if (!fileHeader->IntegrityCheck.TailReference == *tail) msg(tr("parseVolume: %1, file tail value %2 is not a bitwise not of %3 stored in file header") .arg(guidToQString(fileHeader->Name)) .arg(*tail, 4, 16, QChar('0')) .arg(fileHeader->IntegrityCheck.TailReference, 4, 16, QChar('0'))); // Remove tail from file body body = body.left(body.size() - sizeof(UINT16)); } // Parse current file by default bool parseCurrentFile = true; // Raw files can hide volumes inside them // So try to parse them as bios space bool parseAsBios = false; // Check file type //!TODO: add more file specific checks 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: parseCurrentFile = false; msg(tr("parseVolume: Unknown file type (%1)").arg(fileHeader->Type, 2, 16, QChar('0'))); }; // Check for empty file if (body.count(empty) == body.size()) { // No need to parse empty files parseCurrentFile = false; } // Add tree item QModelIndex index = addTreeItem(FileItem, fileHeader->Type, fileOffset, header, body, parent); // Parse file if (parseCurrentFile) { if (parseAsBios) { UINT32 result = parseBios(body, index); if (result && result != ERR_VOLUMES_NOT_FOUND) msg(tr("parseVolume: Parse file as BIOS failed (%1)").arg(result)); } else { UINT32 result = parseFile(body, revision, erasePolarity, index); if (result) msg(tr("parseVolume: Parse file as FFS failed (%1)").arg(result)); } } // Move to next file fileOffset += file.size(); fileOffset = ALIGN8(fileOffset); // Exit from loop if no files left if (fileOffset >= volume.size()) fileOffset = -1; } return ERR_SUCCESS; } UINT8 FfsEngine::parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, const QModelIndex & parent) { // Search for and parse all sections INT32 sectionOffset = 0; while(sectionOffset >= 0) { EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*) (file.constData() + sectionOffset); UINT32 sectionSize = uint24ToUint32(sectionHeader->Size); // This declarations must be here because of the nature of switch statement EFI_COMPRESSION_SECTION* compressedSectionHeader; EFI_GUID_DEFINED_SECTION* guidDefinedSectionHeader; QByteArray header; QByteArray body; UINT32 decompressedSize; UINT32 scratchSize; UINT8* decompressed; UINT8* scratch; VOID* data; UINT32 dataSize; QModelIndex index; UINT32 result; UINT32 shittySectionSize; EFI_COMMON_SECTION_HEADER* shittySectionHeader; switch (sectionHeader->Type) { // Encapsulated sections case EFI_SECTION_COMPRESSION: compressedSectionHeader = (EFI_COMPRESSION_SECTION*) sectionHeader; header = file.mid(sectionOffset, sizeof(EFI_COMPRESSION_SECTION)); // Try to decompress this section switch (compressedSectionHeader->CompressionType) { case EFI_NOT_COMPRESSED: body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), compressedSectionHeader->UncompressedLength); index = addTreeItem(SectionItem, CompressionSection, sectionOffset, header, body, parent); // Parse stored file result = parseFile(body, revision, erasePolarity, index); if (result) msg(tr("parseFile: Stored section can not be parsed as file (%1)").arg(result)); break; case EFI_STANDARD_COMPRESSION: //Must be Tiano for all revisions, needs checking body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); // Get buffer sizes data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION)); dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); if (TianoGetInfo(data, dataSize, &decompressedSize, &scratchSize) != ERR_SUCCESS || decompressedSize != compressedSectionHeader->UncompressedLength) msg(tr("parseFile: TianoGetInfo failed")); else { decompressed = new UINT8[decompressedSize]; scratch = new UINT8[scratchSize]; // Decompress section data if (TianoDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize) != ERR_SUCCESS) msg(tr("parseFile: TianoDecompress failed")); else { body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); // Parse stored file result = parseFile(body, revision, erasePolarity, index); if (result) msg(tr("parseFile: Compressed section with Tiano compression can not be parsed as file (%1)").arg(result)); } delete[] decompressed; delete[] scratch; } break; case EFI_CUSTOMIZED_COMPRESSION: body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); // Get buffer sizes data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION)); dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION); if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS || decompressedSize != compressedSectionHeader->UncompressedLength) { // Shitty file 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 = sizeOfSectionHeaderOfType(shittySectionHeader->Type); data = (VOID*) (file.constData() + sectionOffset + sizeof(EFI_COMPRESSION_SECTION) + shittySectionSize); dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION) - shittySectionSize; if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS) msg(tr("parseFile: LzmaGetInfo failed")); } decompressed = new UINT8[decompressedSize]; // Decompress section data if (LzmaDecompress(data, dataSize, decompressed) != ERR_SUCCESS) msg(tr("parseFile: LzmaDecompress failed")); else { body = QByteArray::fromRawData((const char*) decompressed, decompressedSize); // Parse stored file result = parseFile(body, revision, erasePolarity, index); if (result) msg(tr("parseFile: Compressed section with LZMA compression can not be parsed as file (%1)").arg(result)); } delete[] decompressed; break; default: body = file.mid(sectionOffset + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); msg(tr("parseFile: Compressed section with unknown compression type found (%1)").arg(compressedSectionHeader->CompressionType)); } break; case EFI_SECTION_GUID_DEFINED: header = file.mid(sectionOffset, sizeof(EFI_GUID_DEFINED_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_GUID_DEFINED_SECTION), sectionSize - sizeof(EFI_GUID_DEFINED_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); // Parse section body as file guidDefinedSectionHeader = (EFI_GUID_DEFINED_SECTION*) (header.constData()); body = file.mid(sectionOffset + guidDefinedSectionHeader->DataOffset, sectionSize - guidDefinedSectionHeader->DataOffset); result = parseFile(body, revision, erasePolarity, index); if (result) msg(tr("parseFile: GUID defined section can not be parsed as file (%1)").arg(result)); break; case EFI_SECTION_DISPOSABLE: header = file.mid(sectionOffset, sizeof(EFI_DISPOSABLE_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_DISPOSABLE_SECTION), sectionSize - sizeof(EFI_DISPOSABLE_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; // Leaf sections case EFI_SECTION_PE32: header = file.mid(sectionOffset, sizeof(EFI_PE32_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_PE32_SECTION), sectionSize - sizeof(EFI_PE32_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_PIC: header = file.mid(sectionOffset, sizeof(EFI_PIC_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_PIC_SECTION), sectionSize - sizeof(EFI_PIC_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_TE: header = file.mid(sectionOffset, sizeof(EFI_TE_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_TE_SECTION), sectionSize - sizeof(EFI_TE_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_VERSION: header = file.mid(sectionOffset, sizeof(EFI_VERSION_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_VERSION_SECTION), sectionSize - sizeof(EFI_VERSION_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_USER_INTERFACE: header = file.mid(sectionOffset, sizeof(EFI_USER_INTERFACE_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_USER_INTERFACE_SECTION), sectionSize - sizeof(EFI_USER_INTERFACE_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_COMPATIBILITY16: header = file.mid(sectionOffset, sizeof(EFI_COMPATIBILITY16_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_COMPATIBILITY16_SECTION), sectionSize - sizeof(EFI_COMPATIBILITY16_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_FIRMWARE_VOLUME_IMAGE: header = file.mid(sectionOffset, sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); // Parse section body as BIOS space result = parseBios(body, index); if (result && result != ERR_VOLUMES_NOT_FOUND) msg(tr("parseFile: Firmware volume image can not be parsed (%1)").arg(result)); break; case EFI_SECTION_FREEFORM_SUBTYPE_GUID: header = file.mid(sectionOffset, sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION), sectionSize - sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_RAW: header = file.mid(sectionOffset, sizeof(EFI_RAW_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); // Parse section body as BIOS space result = parseBios(body, index); if (result && result != ERR_VOLUMES_NOT_FOUND) msg(tr("parseFile: Raw section can not be parsed as BIOS (%1)").arg(result)); break; break; case EFI_SECTION_DXE_DEPEX: header = file.mid(sectionOffset, sizeof(EFI_DXE_DEPEX_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_DXE_DEPEX_SECTION), sectionSize - sizeof(EFI_DXE_DEPEX_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_PEI_DEPEX: header = file.mid(sectionOffset, sizeof(EFI_PEI_DEPEX_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_PEI_DEPEX_SECTION), sectionSize - sizeof(EFI_PEI_DEPEX_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; case EFI_SECTION_SMM_DEPEX: header = file.mid(sectionOffset, sizeof(EFI_SMM_DEPEX_SECTION)); body = file.mid(sectionOffset + sizeof(EFI_SMM_DEPEX_SECTION), sectionSize - sizeof(EFI_SMM_DEPEX_SECTION)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); break; default: msg(tr("parseFile: Section with unknown type (%1)").arg(sectionHeader->Type, 2, 16, QChar('0'))); header = file.mid(sectionOffset, sizeof(EFI_COMMON_SECTION_HEADER)); body = file.mid(sectionOffset + sizeof(EFI_COMMON_SECTION_HEADER), sectionSize - sizeof(EFI_COMMON_SECTION_HEADER)); index = addTreeItem(SectionItem, sectionHeader->Type, sectionOffset, header, body, parent); } // Move to next section sectionOffset += uint24ToUint32(sectionHeader->Size); sectionOffset = ALIGN4(sectionOffset); // Exit from loop if no sections left if (sectionOffset >= file.size()) sectionOffset = -1; } return ERR_SUCCESS; } bool FfsEngine::hasEmptyHeader(const QModelIndex& index) const { if (!index.isValid()) return true; TreeItem *item = static_cast(index.internalPointer()); return item->hasEmptyHeader(); } bool FfsEngine::hasEmptyBody(const QModelIndex& index) const { if (!index.isValid()) return true; TreeItem *item = static_cast(index.internalPointer()); return item->hasEmptyBody(); } QModelIndex FfsEngine::addTreeItem(const UINT8 type, const UINT8 subtype, const UINT32 offset, const QByteArray &header, const QByteArray &body, const QModelIndex &parent) { TreeItem *parentItem; int parentColumn = 0; if (!parent.isValid()) parentItem = rootItem; else { parentItem = static_cast(parent.internalPointer()); parentColumn = parent.column(); } // All information extraction must be here QString name, typeName, subtypeName, info; EFI_CAPSULE_HEADER* capsuleHeader; APTIO_CAPSULE_HEADER* aptioCapsuleHeader; FLASH_DESCRIPTOR_MAP* descriptorMap; //FLASH_DESCRIPTOR_COMPONENT_SECTION* componentSection; //FLASH_DESCRIPTOR_REGION_SECTION* regionSection; //FLASH_DESCRIPTOR_MASTER_SECTION* masterSection; GBE_MAC* gbeMac; GBE_VERSION* gbeVersion; ME_VERSION* meVersion; INT32 meVersionOffset; EFI_FIRMWARE_VOLUME_HEADER* volumeHeader; EFI_FFS_FILE_HEADER* fileHeader; //EFI_COMMON_SECTION_HEADER* sectionHeader; switch (type) { case RootItem: // Do not allow to add another root item return QModelIndex(); break; case CapsuleItem: //typeName = tr("Capsule"); switch (subtype) { case AptioCapsule: name = tr("AMI Aptio capsule"); aptioCapsuleHeader = (APTIO_CAPSULE_HEADER*) header.constData(); info = tr("Offset: %1\nHeader size: %2\nFlags: %3\nImage size: %4") .arg(offset, 8, 16, QChar('0')) .arg(aptioCapsuleHeader->RomImageOffset, 4, 16, QChar('0')) .arg(aptioCapsuleHeader->CapsuleHeader.Flags, 8, 16, QChar('0')) .arg(aptioCapsuleHeader->CapsuleHeader.CapsuleImageSize - aptioCapsuleHeader->RomImageOffset, 8, 16, QChar('0')); //!TODO: more info about Aptio capsule break; case UefiCapsule: name = tr("UEFI capsule"); capsuleHeader = (EFI_CAPSULE_HEADER*) header.constData(); info = tr("Offset: %1\nHeader size: %2\nFlags: %3\nImage size: %4") .arg(offset, 8, 16, QChar('0')) .arg(capsuleHeader->HeaderSize, 8, 16, QChar('0')) .arg(capsuleHeader->Flags, 8, 16, QChar('0')) .arg(capsuleHeader->CapsuleImageSize, 8, 16, QChar('0')); break; default: name = tr("Unknown capsule"); info = tr("Offset: %1\nGUID: %2\n") .arg(offset, 8, 16, QChar('0')) .arg(guidToQString(*(EFI_GUID*)header.constData())); break; } break; case DescriptorItem: name = tr("Descriptor"); descriptorMap = (FLASH_DESCRIPTOR_MAP*) body.constData(); info = tr("Flash chips: %1\nRegions: %2\nMasters: %3\nPCH straps:%4\nPROC straps: %5\nICC table entries: %6") .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); //!TODO: more info about descriptor break; case RegionItem: typeName = tr("Region"); info = tr("Offset: %1\nSize: %2") .arg(offset, 8, 16, QChar('0')) .arg(body.size(), 8, 16, QChar('0')); switch (subtype) { case GbeRegion: name = tr("GbE region"); gbeMac = (GBE_MAC*) body.constData(); gbeVersion = (GBE_VERSION*) (body.constData() + GBE_VERSION_OFFSET); info += tr("\nMAC: %1:%2:%3:%4:%5:%6\nVersion: %7.%8") .arg(gbeMac->vendor[0], 2, 16, QChar('0')) .arg(gbeMac->vendor[1], 2, 16, QChar('0')) .arg(gbeMac->vendor[2], 2, 16, QChar('0')) .arg(gbeMac->device[0], 2, 16, QChar('0')) .arg(gbeMac->device[1], 2, 16, QChar('0')) .arg(gbeMac->device[2], 2, 16, QChar('0')) .arg(gbeVersion->major) .arg(gbeVersion->minor); break; case MeRegion: name = tr("ME region"); meVersionOffset = body.indexOf(ME_VERSION_SIGNATURE); if (meVersionOffset < 0){ info += tr("\nVersion: unknown"); msg(tr("addTreeItem: ME region version is unknown, it can be damaged")); } else { meVersion = (ME_VERSION*) (body.constData() + meVersionOffset); info += tr("\nVersion: %1.%2.%3.%4") .arg(meVersion->major) .arg(meVersion->minor) .arg(meVersion->bugfix) .arg(meVersion->build); } break; case BiosRegion: name = tr("BIOS region"); break; case PdrRegion: name = tr("PDR region"); break; default: name = tr("Unknown region"); msg(tr("addTreeItem: Unknown region")); break; } break; case PaddingItem: name = tr("Padding"); info = tr("Offset: %1\nSize: %2") .arg(offset, 8, 16, QChar('0')) .arg(body.size(), 8, 16, QChar('0')); break; case VolumeItem: typeName = tr("Volume"); // Parse volume header to determine its revision and file system volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) header.constData(); name = guidToQString(volumeHeader->FileSystemGuid); subtypeName = tr("Revision %1").arg(volumeHeader->Revision); info = tr("Offset: %1\nSize: %2\nAttributes: %3\nHeader size: %4") .arg(offset, 8, 16, QChar('0')) .arg(volumeHeader->FvLength, 8, 16, QChar('0')) .arg(volumeHeader->Attributes, 8, 16, QChar('0')) .arg(volumeHeader->HeaderLength, 4, 16, QChar('0')); break; case FileItem: typeName = tr("File"); // Parse file header to determine its GUID and type fileHeader = (EFI_FFS_FILE_HEADER*) header.constData(); name = guidToQString(fileHeader->Name); subtypeName = fileTypeToQString(subtype); info = tr("Offset: %1\nType: %2\nAttributes: %3\nSize: %4\nState: %5") .arg(offset, 8, 16, QChar('0')) .arg(fileHeader->Type, 2, 16, QChar('0')) .arg(fileHeader->Attributes, 2, 16, QChar('0')) .arg(uint24ToUint32(fileHeader->Size), 6, 16, QChar('0')) .arg(fileHeader->State, 2, 16, QChar('0')); break; case SectionItem: typeName = tr("Section"); name = sectionTypeToQString(subtype) + tr(" section"); info = tr("Offset: %1\nSize: %2") .arg(offset, 8, 16, QChar('0')) .arg(body.size(), 8, 16, QChar('0')); //!TODO: add more specific info for all section types with uncommon headers // Set name of file if (subtype == UserInterfaceSection) { QString text = QString::fromUtf16((const ushort*)body.constData()); setTreeItemText(text, findParentOfType(FileItem, parent)); } break; default: name = tr("Unknown"); info = tr("Offset: %1").arg(offset, 8, 16, QChar('0')); break; } return treeModel->addItem(type, subtype, offset, name, typeName, subtypeName, "", info, header, body, parent); } bool FfsEngine::setTreeItemName(const QString &data, const QModelIndex &index) { if(!index.isValid()) return false; return treeModel->setItemName(data, index); } bool FfsEngine::setTreeItemText(const QString &data, const QModelIndex &index) { if(!index.isValid()) return false; return treeModel->setItemText(data, index); } bool FfsEngine::removeItem(const QModelIndex &index) { TreeItem *item = static_cast(index.internalPointer()); item->parent()->removeChild(item); delete item; return true; } QModelIndex FfsEngine::findParentOfType(UINT8 type, const QModelIndex& index) const { if(!index.isValid()) return QModelIndex(); TreeItem *item; QModelIndex parent = index; for(item = static_cast(parent.internalPointer()); item != NULL && item != rootItem && item->type() != type; item = static_cast(parent.internalPointer())) parent = parent.parent(); if (item != NULL && item != rootItem) return parent; return QModelIndex(); } QByteArray FfsEngine::header(const QModelIndex& index) const { if (!index.isValid()) return QByteArray(); TreeItem *item = static_cast(index.internalPointer()); return item->header(); } QByteArray FfsEngine::body(const QModelIndex& index) const { if (!index.isValid()) return QByteArray(); TreeItem *item = static_cast(index.internalPointer()); return item->body(); } QByteArray FfsEngine::uncompressFile(const QModelIndex& index) const { if (!index.isValid()) return QByteArray(); // Check index item to be FFS file TreeItem *item = static_cast(index.internalPointer()); if(item->type() != FileItem) return QByteArray(); QByteArray file; UINT32 offset = 0; // Construct new item body for (int i = 0; i < item->childCount(); i++) { // If section is not compressed, add it to new body as is TreeItem* sectionItem = item->child(i); if (sectionItem->subtype() != CompressionSection) { QByteArray section = sectionItem->header().append(sectionItem->body()); UINT32 align = ALIGN4(offset) - offset; file.append(QByteArray(align, '\x00')).append(section); offset += align + section.size(); } else { // Construct new section body by adding all child sections to this new section QByteArray section; UINT32 subOffset = 0; for (int j = 0; j < sectionItem->childCount(); j++) { TreeItem* subSectionItem = sectionItem->child(j); QByteArray subSection = subSectionItem->header().append(subSectionItem->body()); UINT32 align = ALIGN4(subOffset) - subOffset; section.append(QByteArray(align, '\x00')).append(subSection); subOffset += align + subSection.size(); } // Add newly constructed section to file body EFI_COMPRESSION_SECTION sectionHeader; sectionHeader.Type = EFI_SECTION_COMPRESSION; sectionHeader.CompressionType = EFI_NOT_COMPRESSED; sectionHeader.UncompressedLength = section.size(); uint32ToUint24(section.size() + sizeof(EFI_COMPRESSION_SECTION), sectionHeader.Size); UINT32 align = ALIGN4(offset) - offset; file.append(QByteArray(align, '\x00')) .append(QByteArray((const char*) §ionHeader, sizeof(EFI_COMPRESSION_SECTION))) .append(section); offset += align + section.size(); } } QByteArray header = item->header(); EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) header.data(); // Correct file data checksum, if needed if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER); fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((UINT8*)(file.data() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); } // Add file tail, if needed if(fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) file.append(!fileHeader->IntegrityCheck.TailReference); return header.append(file); } bool FfsEngine::isCompressedFile(const QModelIndex& index) const { if (!index.isValid()) return false; TreeItem *item = static_cast(index.internalPointer()); if(item->type() != FileItem) return false; for (int i = 0; i < item->childCount(); i++) { if (item->child(i)->subtype() == CompressionSection) return true; } return false; }