UEFITool/uefitool.cpp
Nikolaj Schlej 4afe74850d Version 0.2.0
Initial public commit
2013-10-08 09:07:03 +02:00

961 lines
42 KiB
C++

/* uefitool.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,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*/
#include "uefitool.h"
#include "ui_uefitool.h"
UEFITool::UEFITool(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::UEFITool)
{
ui->setupUi(this);
treeModel = NULL;
// Signal-slot connections
connect(ui->fromFileButton, SIGNAL(clicked()), this, SLOT(openImageFile()));
connect(ui->exportAllButton, SIGNAL(clicked()), this, SLOT(saveAll()));
connect(ui->exportBodyButton, SIGNAL(clicked()), this, SLOT(saveBody()));
// Enable Drag-and-Drop actions
this->setAcceptDrops(true);
// Initialise non-persistent data
init();
}
UEFITool::~UEFITool()
{
delete ui;
delete treeModel;
}
void UEFITool::init()
{
// Clear UI components
ui->debugEdit->clear();
ui->infoEdit->clear();
ui->exportAllButton->setDisabled(true);
ui->exportBodyButton->setDisabled(true);
// Make new tree model
TreeModel * newModel = new TreeModel(this);
ui->structureTreeView->setModel(newModel);
if (treeModel)
delete treeModel;
treeModel = newModel;
// Show info after selection the item in tree view
connect(ui->structureTreeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
this, SLOT(populateUi(const QModelIndex &)));
//connect(ui->structureTreeView, SIGNAL(collapsed(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void)));
connect(ui->structureTreeView, SIGNAL(expanded(const QModelIndex &)), this, SLOT(resizeTreeViewColums(void)));
resizeTreeViewColums();
}
void UEFITool::populateUi(const QModelIndex &current/*, const QModelIndex &previous*/)
{
//!TODO: make widget
currentIndex = current;
ui->infoEdit->setPlainText(current.data(Qt::UserRole).toString());
ui->exportAllButton->setDisabled(treeModel->hasEmptyBody(current) && treeModel->hasEmptyHeader(current));
ui->exportBodyButton->setDisabled(treeModel->hasEmptyHeader(current));
}
void UEFITool::resizeTreeViewColums(/*const QModelIndex &index*/)
{
int count = treeModel->columnCount();
for(int i = 0; i < count; i++)
ui->structureTreeView->resizeColumnToContents(i);
}
void UEFITool::openImageFile()
{
QString path = QFileDialog::getOpenFileName(this, tr("Open BIOS image file"),".","BIOS image file (*.rom *.bin *.cap *.bio *.fd *.wph *.efi);;All files (*.*)");
openImageFile(path);
}
void UEFITool::openImageFile(QString path)
{
QFileInfo fileInfo = QFileInfo(path);
if (!fileInfo.exists())
{
ui->statusBar->showMessage(tr("Please select existing BIOS image file."));
return;
}
QFile inputFile;
inputFile.setFileName(path);
if (!inputFile.open(QFile::ReadOnly))
{
ui->statusBar->showMessage(tr("Can't open file for reading. Check file permissions."));
return;
}
QByteArray buffer = inputFile.readAll();
inputFile.close();
init();
UINT8 result = parseInputFile(buffer);
if (result)
debug(tr("Opened file can't be parsed as UEFI image (%1)").arg(result));
else
ui->statusBar->showMessage(tr("Opened: %1").arg(fileInfo.fileName()));
resizeTreeViewColums();
}
void UEFITool::saveAll()
{
QString path = QFileDialog::getSaveFileName(this, tr("Save header to binary file"),".","Binary files (*.bin);;All files (*.*)");
QFile outputFile;
outputFile.setFileName(path);
if (!outputFile.open(QFile::WriteOnly))
{
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
return;
}
outputFile.write(treeModel->header(currentIndex) + treeModel->body(currentIndex));
outputFile.close();
}
void UEFITool::saveBody()
{
QString path = QFileDialog::getSaveFileName(this, tr("Save body to binary file"),".","Binary files (*.bin);;All files (*.*)");
QFile outputFile;
outputFile.setFileName(path);
if (!outputFile.open(QFile::WriteOnly))
{
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
return;
}
outputFile.write(treeModel->body(currentIndex));
outputFile.close();
}
/*void UEFITool::saveImageFile()
{
QString path = QFileDialog::getSaveFileName(this, tr("Save BIOS image file"),".","BIOS image file (*.rom *.bin *.cap *.fd *.fwh);;All files (*.*)");
QFileInfo fileInfo = QFileInfo(path);
if (!fileInfo.exists())
{
ui->statusBar->showMessage(tr("Please select existing BIOS image file."));
return;
}
QFile outputFile;
outputFile.setFileName(path);
if (!outputFile.open(QFile::ReadWrite))
{
ui->statusBar->showMessage(tr("Can't open file for writing. Check file permissions."));
return;
}
}*/
void UEFITool::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("text/uri-list"))
event->acceptProposedAction();
}
void UEFITool::dropEvent(QDropEvent* event)
{
QString path = event->mimeData()->urls().at(0).toLocalFile();
openImageFile(path);
}
void UEFITool::debug(const QString & text)
{
//!TODO: log to separate debug window
ui->debugEdit->appendPlainText(text);
}
QModelIndex UEFITool::addTreeItem(UINT8 type, UINT8 subtype,
const QByteArray & header, const QByteArray & body, const QModelIndex & parent)
{
return treeModel->addItem(type, subtype, header, body, parent);
}
UINT8 UEFITool::parseInputFile(const QByteArray & buffer)
{
UINT32 capsuleHeaderSize = 0;
FLASH_DESCRIPTOR_HEADER* descriptorHeader = NULL;
QByteArray flashImage;
QByteArray bios;
QModelIndex index;
// 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(ItemTypes::CapsuleItem, CapsuleSubtypes::UefiCapsule, 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(ItemTypes::CapsuleItem, CapsuleSubtypes::AptioCapsule, 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) {
debug(tr("Input file is smaller then mininum descriptor size of 4KB"));
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(ItemTypes::DescriptorItem, 0, header, body, index);
// Parse region section
QModelIndex gbeIndex(index);
QModelIndex meIndex(index);
QModelIndex biosIndex(index);
QModelIndex pdrIndex(index);
gbeRegion = parseRegion(flashImage, RegionSubtypes::GbeRegion, regionSection->GbeBase, regionSection->GbeLimit, gbeIndex);
meRegion = parseRegion(flashImage, RegionSubtypes::MeRegion, regionSection->MeBase, regionSection->MeLimit, meIndex);
biosRegion = parseRegion(flashImage, RegionSubtypes::BiosRegion, regionSection->BiosBase, regionSection->BiosLimit, biosIndex);
pdrRegion = parseRegion(flashImage, RegionSubtypes::PdrRegion, regionSection->PdrBase, regionSection->PdrLimit, pdrIndex);
// Parse complete
//!TODO: show some info about GbE, ME and PDR regions if found
// Exit if no bios region found
if (!biosRegion) {
debug(tr("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* UEFITool::parseRegion(const QByteArray & flashImage, UINT8 regionSubtype, const UINT16 regionBase, const UINT16 regionLimit, QModelIndex & index)
{
// Check for empty region
if (!regionLimit)
return NULL;
// Calculate region size
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 RegionSubtypes::GbeRegion:
regionName = "GbE";
break;
case RegionSubtypes::MeRegion:
regionName = "ME";
break;
case RegionSubtypes::BiosRegion:
regionName = "Bios";
break;
case RegionSubtypes::PdrRegion:
regionName = "PDR";
break;
default:
regionName = "Unknown";
debug(tr("Unknown region type"));
};
// Check region base to be in buffer
if (regionBase * 0x1000 >= flashImage.size())
{
debug(tr("%1 region stored in descriptor not found").arg(regionName));
if (twoChips)
debug(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
debug(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump"));
debug(tr("Absence of %1 region assumed").arg(regionName));
return NULL;
}
// Check region to be fully present in buffer
else if (regionBase * 0x1000 + regionSize > flashImage.size())
{
debug(tr("%s region stored in descriptor overlaps the end of opened file").arg(regionName));
if (twoChips)
debug(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
debug(tr("One flash chip installed, so it is an error caused by damaged or incomplete dump"));
debug(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(regionBase * 0x1000, regionSize);
index = addTreeItem(ItemTypes::RegionItem, regionSubtype, QByteArray(), body, index);
return region;
}
UINT8 UEFITool::parseBios(const QByteArray & bios, QModelIndex & parent)
{
// Search for first volume
INT32 prevVolumeIndex = getNextVolumeIndex(bios);
// No volumes found
if (prevVolumeIndex < 0) {
return ERR_VOLUMES_NOT_FOUND;
}
// First volume is not at the beginning of bios space
if (prevVolumeIndex > 0) {
QByteArray padding = bios.left(prevVolumeIndex);
addTreeItem(ItemTypes::PaddingItem, 0, QByteArray(), padding, parent);
}
// Search for and parse all volumes
INT32 volumeIndex;
UINT32 prevVolumeSize;
for (volumeIndex = prevVolumeIndex, prevVolumeSize = 0;
volumeIndex >= 0;
prevVolumeIndex = volumeIndex, prevVolumeSize = getVolumeSize(bios, volumeIndex), volumeIndex = getNextVolumeIndex(bios, volumeIndex + prevVolumeSize))
{
// Padding between volumes
if (volumeIndex > prevVolumeIndex + prevVolumeSize) {
UINT32 size = volumeIndex - prevVolumeIndex - prevVolumeSize;
QByteArray padding = bios.mid(prevVolumeIndex + prevVolumeSize, size);
addTreeItem(ItemTypes::PaddingItem, 0, QByteArray(), padding, parent);
}
// Populate volume header
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeIndex);
//Check that volume is fully present in input
if (volumeIndex + volumeHeader->FvLength > bios.size()) {
debug(tr("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))
debug("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 (volumeIndex % alignment) {
debug(tr("Unaligned revision 1 volume"));
}
}
else if (volumeHeader->Revision == 2) {
// Aquire alignment
alignment = pow(2, (volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16);
// Check alignment
if (volumeIndex % alignment) {
debug(tr("Unaligned revision 2 volume"));
}
}
else
debug(tr("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"));
debug(tr("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)) {
debug(tr("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(volumeIndex, headerSize);
QByteArray body = bios.mid(volumeIndex + headerSize, volumeHeader->FvLength - headerSize);
QModelIndex index = addTreeItem(ItemTypes::VolumeItem, 0, header, body, parent);
// Parse volume
if (parseCurrentVolume) {
UINT32 result = parseVolume(bios.mid(volumeIndex + headerSize, volumeHeader->FvLength - headerSize), headerSize, volumeHeader->Revision, erasePolarity, index);
if (result)
debug(tr("Volume parsing failed (%1)").arg(result));
}
}
return ERR_SUCCESS;
}
INT32 UEFITool::getNextVolumeIndex(const QByteArray & bios, INT32 volumeIndex)
{
if (volumeIndex < 0)
return -1;
INT32 nextIndex = bios.indexOf(EFI_FV_SIGNATURE, volumeIndex);
if (nextIndex < EFI_FV_SIGNATURE_OFFSET) {
return -1;
}
return nextIndex - EFI_FV_SIGNATURE_OFFSET;
}
UINT32 UEFITool::getVolumeSize(const QByteArray & bios, INT32 volumeIndex)
{
// Populate volume header
EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*) (bios.constData() + volumeIndex);
// Check volume signature
if (QByteArray((const char*) &volumeHeader->Signature, sizeof(volumeHeader->Signature)) != EFI_FV_SIGNATURE)
return 0;
return volumeHeader->FvLength;
}
UINT8 UEFITool::parseVolume(const QByteArray & volume, UINT32 volumeBase, UINT8 revision, bool erasePolarity, 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 fileIndex = 0;
while (fileIndex >= 0) {
EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*) (volume.constData() + fileIndex);
QByteArray file = volume.mid(fileIndex, uint24ToUint32(fileHeader->Size));
QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER));
// We are at empty space in the end of volume
if (header.count(empty) == header.size()) {
QByteArray body = volume.right(volume.size() - fileIndex);
addTreeItem(ItemTypes::PaddingItem, 0, 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)
{
debug(tr("%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) {
debug(tr("%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) {
debug(tr("%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 + fileIndex + sizeof(EFI_FFS_FILE_HEADER)) % alignment) {
debug(tr("%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)
debug(tr("%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;
debug(tr("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(ItemTypes::FileItem, fileHeader->Type, header, body, parent);
// Parse file
if (parseCurrentFile) {
if (parseAsBios) {
UINT32 result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND)
debug(tr("Parse file as BIOS failed (%1)").arg(result));
}
else {
UINT32 result = parseFile(body, revision, erasePolarity, index);
if (result)
debug(tr("Parse file as FFS failed (%1)").arg(result));
}
}
// Move to next file
fileIndex += file.size();
fileIndex = ALIGN8(fileIndex);
// Exit from loop if no files left
if (fileIndex >= volume.size())
fileIndex = -1;
}
return ERR_SUCCESS;
}
UINT8 UEFITool::parseFile(const QByteArray & file, UINT8 revision, bool erasePolarity, QModelIndex & parent)
{
// Search for and parse all sections
INT32 sectionIndex = 0;
while(sectionIndex >= 0)
{
EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*) (file.constData() + sectionIndex);
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;
bool lzmaHeaderFound;
switch (sectionHeader->Type)
{
// Encapsulated sections
case EFI_SECTION_COMPRESSION:
compressedSectionHeader = (EFI_COMPRESSION_SECTION*) sectionHeader;
header = file.mid(sectionIndex, sizeof(EFI_COMPRESSION_SECTION));
// Try to decompress this section
switch (compressedSectionHeader->CompressionType)
{
case EFI_NOT_COMPRESSED:
body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), compressedSectionHeader->UncompressedLength);
index = addTreeItem(ItemTypes::SectionItem, SectionSubtypes::CompressionSection, header, body, parent);
// Parse stored file
result = parseFile(body, revision, erasePolarity, index);
if (result)
debug(tr("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(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
// Get buffer sizes
data = (VOID*) (file.constData() + sectionIndex + sizeof(EFI_COMPRESSION_SECTION));
dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION);
if (TianoGetInfo(data, dataSize, &decompressedSize, &scratchSize) != ERR_SUCCESS
|| decompressedSize != compressedSectionHeader->UncompressedLength)
debug(tr("TianoGetInfo failed"));
else {
decompressed = new UINT8[decompressedSize];
scratch = new UINT8[scratchSize];
// Decompress section data
if (TianoDecompress(data, dataSize, decompressed, decompressedSize, scratch, scratchSize) != ERR_SUCCESS)
debug(tr("TianoDecompress failed"));
else
{
body = QByteArray::fromRawData((const char*) decompressed, decompressedSize);
// Parse stored file
result = parseFile(body, revision, erasePolarity, index);
if (result)
debug(tr("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(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
// Get buffer sizes
data = (VOID*) (file.constData() + sectionIndex + 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 some data between COMPRESSED_SECTION_HEADER and LZMA_HEADER
// Search for LZMA header in body
// Header structure: UINT8 LzmaProperties | UINT32 DictionarySize | UINT64 DecompressedSize
// We can't determine first two fiels here, but Decompressed size is known, so search for it
INT32 lzmaHeaderIndex = body.indexOf(QByteArray((const char*)&compressedSectionHeader->UncompressedLength,
sizeof(compressedSectionHeader->UncompressedLength)).append('\x00').append('\x00').append('\x00').append('\x00'));
lzmaHeaderIndex -= LZMA_PROPS_SIZE;
// LZMA header not found
if (lzmaHeaderIndex < 0)
{
lzmaHeaderFound = false;
debug(tr("Lzma header not found"));
}
// LZMA header found
else if (lzmaHeaderIndex) {
data = (VOID*) (file.constData() + sectionIndex + sizeof(EFI_COMPRESSION_SECTION) + lzmaHeaderIndex);
dataSize = uint24ToUint32(sectionHeader->Size) - sizeof(EFI_COMPRESSION_SECTION) - lzmaHeaderIndex;
// Get buffer sizes again
if (LzmaGetInfo(data, dataSize, &decompressedSize) != ERR_SUCCESS
|| decompressedSize != compressedSectionHeader->UncompressedLength)
{
debug(tr("LzmaGetInfo failed on LZMA header"));
lzmaHeaderFound = false;
}
else
debug(tr("Padding of size %1 between CompressedSectionHeader and LzmaHeader")
.arg(lzmaHeaderIndex));
}
lzmaHeaderFound = true;
}
else
lzmaHeaderFound = true;
// Decompress if header is found
if (lzmaHeaderFound) {
decompressed = new UINT8[decompressedSize];
// Decompress section data
if (LzmaDecompress(data, dataSize, decompressed) != ERR_SUCCESS)
debug(tr("LzmaDecompress failed"));
else
{
body = QByteArray::fromRawData((const char*) decompressed, decompressedSize);
// Parse stored file
result = parseFile(body, revision, erasePolarity, index);
if (result)
debug(tr("Compressed section with LZMA compression can not be parsed as file (%1)").arg(result));
}
delete[] decompressed;
}
break;
default:
body = file.mid(sectionIndex + sizeof(EFI_COMPRESSION_SECTION), sectionSize - sizeof(EFI_COMPRESSION_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
debug(tr("Compressed section with unknown compression type found (%1)").arg(compressedSectionHeader->CompressionType));
}
break;
case EFI_SECTION_GUID_DEFINED:
header = file.mid(sectionIndex, sizeof(EFI_GUID_DEFINED_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_GUID_DEFINED_SECTION), sectionSize - sizeof(EFI_GUID_DEFINED_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
// Parse section body as file
guidDefinedSectionHeader = (EFI_GUID_DEFINED_SECTION*) (header.constData());
body = file.mid(sectionIndex + guidDefinedSectionHeader->DataOffset, sectionSize - guidDefinedSectionHeader->DataOffset);
result = parseFile(body, revision, erasePolarity, index);
if (result)
debug(tr("GUID defined section body can not be parsed as file (%1)").arg(result));
break;
case EFI_SECTION_DISPOSABLE:
header = file.mid(sectionIndex, sizeof(EFI_DISPOSABLE_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_DISPOSABLE_SECTION), sectionSize - sizeof(EFI_DISPOSABLE_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
// Leaf sections
case EFI_SECTION_PE32:
header = file.mid(sectionIndex, sizeof(EFI_PE32_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_PE32_SECTION), sectionSize - sizeof(EFI_PE32_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_PIC:
header = file.mid(sectionIndex, sizeof(EFI_PIC_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_PIC_SECTION), sectionSize - sizeof(EFI_PIC_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_TE:
header = file.mid(sectionIndex, sizeof(EFI_TE_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_TE_SECTION), sectionSize - sizeof(EFI_TE_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_VERSION:
header = file.mid(sectionIndex, sizeof(EFI_VERSION_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_VERSION_SECTION), sectionSize - sizeof(EFI_VERSION_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_USER_INTERFACE:
header = file.mid(sectionIndex, sizeof(EFI_USER_INTERFACE_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_USER_INTERFACE_SECTION), sectionSize - sizeof(EFI_USER_INTERFACE_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_COMPATIBILITY16:
header = file.mid(sectionIndex, sizeof(EFI_COMPATIBILITY16_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_COMPATIBILITY16_SECTION), sectionSize - sizeof(EFI_COMPATIBILITY16_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
header = file.mid(sectionIndex, sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION), sectionSize - sizeof(EFI_FIRMWARE_VOLUME_IMAGE_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
// Parse section body as BIOS space
result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND)
debug(tr("Firmware volume image can not be parsed (%1)").arg(result));
break;
case EFI_SECTION_FREEFORM_SUBTYPE_GUID:
header = file.mid(sectionIndex, sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION), sectionSize - sizeof(EFI_FREEFORM_SUBTYPE_GUID_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_RAW:
header = file.mid(sectionIndex, sizeof(EFI_RAW_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_RAW_SECTION), sectionSize - sizeof(EFI_RAW_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
// Parse section body as BIOS space
result = parseBios(body, index);
if (result && result != ERR_VOLUMES_NOT_FOUND)
debug(tr("Raw section can not be parsed as BIOS (%1)").arg(result));
break;
break;
case EFI_SECTION_DXE_DEPEX:
header = file.mid(sectionIndex, sizeof(EFI_DXE_DEPEX_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_DXE_DEPEX_SECTION), sectionSize - sizeof(EFI_DXE_DEPEX_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_PEI_DEPEX:
header = file.mid(sectionIndex, sizeof(EFI_PEI_DEPEX_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_PEI_DEPEX_SECTION), sectionSize - sizeof(EFI_PEI_DEPEX_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
case EFI_SECTION_SMM_DEPEX:
header = file.mid(sectionIndex, sizeof(EFI_SMM_DEPEX_SECTION));
body = file.mid(sectionIndex + sizeof(EFI_SMM_DEPEX_SECTION), sectionSize - sizeof(EFI_SMM_DEPEX_SECTION));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
break;
default:
debug(tr("Section with unknown type (%1)").arg(sectionHeader->Type, 2, 16, QChar('0')));
header = file.mid(sectionIndex, sizeof(EFI_COMMON_SECTION_HEADER));
body = file.mid(sectionIndex + sizeof(EFI_COMMON_SECTION_HEADER), sectionSize - sizeof(EFI_COMMON_SECTION_HEADER));
index = addTreeItem(ItemTypes::SectionItem, sectionHeader->Type, header, body, parent);
}
// Move to next section
sectionIndex += uint24ToUint32(sectionHeader->Size);
sectionIndex = ALIGN4(sectionIndex);
// Exit from loop if no sections left
if (sectionIndex >= file.size())
sectionIndex = -1;
}
return ERR_SUCCESS;
}