From 1c34c1bf84c283e0f4c7f0fc56a22722a9c8ae71 Mon Sep 17 00:00:00 2001 From: Nikolaj Schlej Date: Thu, 19 Jun 2014 05:45:20 +0200 Subject: [PATCH] UEFIPatch 0.1.0 - new command-line utility to patch files inside UEFI image - corrected bug with wrong header size calculation for GUID_DEFINED_SECTION with PROCESSING_REQUIRED attribute set - patch routine implemented in ffsEngine, will be added to UEFITool soon --- UEFIPatch/patches.txt | 17 ++++ UEFIPatch/uefipatch.cpp | 165 +++++++++++++++++++++++++++++++++++ UEFIPatch/uefipatch.h | 45 ++++++++++ UEFIPatch/uefipatch.pro | 39 +++++++++ UEFIPatch/uefipatch_main.cpp | 73 ++++++++++++++++ basetypes.h | 7 ++ ffs.cpp | 13 ++- ffs.h | 2 +- ffsengine.cpp | 57 ++++++++++-- ffsengine.h | 1 + 10 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 UEFIPatch/patches.txt create mode 100644 UEFIPatch/uefipatch.cpp create mode 100644 UEFIPatch/uefipatch.h create mode 100644 UEFIPatch/uefipatch.pro create mode 100644 UEFIPatch/uefipatch_main.cpp diff --git a/UEFIPatch/patches.txt b/UEFIPatch/patches.txt new file mode 100644 index 0000000..613fbb4 --- /dev/null +++ b/UEFIPatch/patches.txt @@ -0,0 +1,17 @@ +# PowerMgmtDxe | Haswell +F7731B4C-58A2-4DF4-8980-5645D39ECE58 75080FBAE80F89442430 EB080FBAE80F89442430 +# PowerMgmtDxe | Haswell-E +F7731B4C-58A2-4DF4-8980-5645D39ECE58 0FBA6C24380F 0FBA7424380F + +# PowerManagement | Sandy Bridge with ME 8.xx, Ivy Bridge +8C783970-F02A-4A4D-AF09-8797A51EEC8D 75080FBAE80F89442430 EB080FBAE80F89442430 +# PowerManagement | New SB-E/IB-E +8C783970-F02A-4A4D-AF09-8797A51EEC8D 0FBA6C24380F 0FBA7424380F + +# CpuPei | Sandy Bridge with ME 7.xx, old SB-E/IB-E +2BB5AFA9-FF33-417B-8497-CB773C2B93BF 800018EB050D0080 000018EB050D0000 + +# You can add your own patch here +# Format is simple: "file_GUID search_pattern replace_pattern" +# String beginned with '#' symbol is a comment and will be ignored by the program + diff --git a/UEFIPatch/uefipatch.cpp b/UEFIPatch/uefipatch.cpp new file mode 100644 index 0000000..fc0b711 --- /dev/null +++ b/UEFIPatch/uefipatch.cpp @@ -0,0 +1,165 @@ +/* uefipatch.cpp + +Copyright (c) 2014, 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 "uefipatch.h" + +UEFIPatch::UEFIPatch(QObject *parent) : + QObject(parent) +{ + ffsEngine = new FfsEngine(this); + model = ffsEngine->treeModel(); +} + +UEFIPatch::~UEFIPatch() +{ + delete ffsEngine; +} + +UINT8 UEFIPatch::patchFromFile(QString path) +{ + QFileInfo patchInfo = QFileInfo("patches.txt"); + + if (!patchInfo.exists()) + return ERR_INVALID_FILE; + + QFile file; + file.setFileName("patches.txt"); + + if (!file.open(QFile::ReadOnly | QFile::Text)) + return ERR_INVALID_FILE; + + QFileInfo fileInfo = QFileInfo(path); + + if (!fileInfo.exists()) + return ERR_FILE_OPEN; + + QFile inputFile; + inputFile.setFileName(path); + + if (!inputFile.open(QFile::ReadOnly)) + return ERR_FILE_READ; + + QByteArray buffer = inputFile.readAll(); + inputFile.close(); + + UINT8 result = ffsEngine->parseImageFile(buffer); + if (result) + return result; + + while (!file.atEnd()) { + QByteArray line = file.readLine(); + // Use sharp sign as commentary + if (line.count() == 0 || line[0] == '#') + continue; + + QList list = line.split(' '); + if (list.count() < 3) + continue; + + QUuid uuid = QUuid(list.at(0)); + QByteArray guid = QByteArray::fromRawData((const char*)&uuid.data1, sizeof(EFI_GUID)); + result = patchFile(model->index(0, 0), guid, QByteArray::fromHex(list.at(1)), QByteArray::fromHex(list.at(2))); + if (result) + return result; + } + + QByteArray reconstructed; + result = ffsEngine->reconstructImageFile(reconstructed); + if (result) + return result; + + if (reconstructed == buffer) { + return ERR_ITEM_NOT_FOUND; + } + + QFile outputFile; + outputFile.setFileName(path.append(".patched")); + if (!outputFile.open(QFile::WriteOnly)) + return ERR_FILE_WRITE; + + outputFile.resize(0); + outputFile.write(reconstructed); + outputFile.close(); + + return ERR_SUCCESS; +} + +UINT8 UEFIPatch::patch(QString path, QString fileGuid, QString findPattern, QString replacePattern) +{ + QFileInfo fileInfo = QFileInfo(path); + + if (!fileInfo.exists()) + return ERR_FILE_OPEN; + + QFile inputFile; + inputFile.setFileName(path); + + if (!inputFile.open(QFile::ReadOnly)) + return ERR_FILE_READ; + + QByteArray buffer = inputFile.readAll(); + inputFile.close(); + + UINT8 result = ffsEngine->parseImageFile(buffer); + if (result) + return result; + + + QUuid uuid = QUuid(fileGuid); + QByteArray guid = QByteArray::fromRawData((const char*)&uuid.data1, sizeof(EFI_GUID)); + result = patchFile(model->index(0, 0), guid, QByteArray::fromHex(findPattern.toLatin1()), QByteArray::fromHex(replacePattern.toLatin1())); + if (result) + return result; + + QByteArray reconstructed; + result = ffsEngine->reconstructImageFile(reconstructed); + if (result) + return result; + + if (reconstructed == buffer) { + return ERR_ITEM_NOT_FOUND; + } + + QFile outputFile; + outputFile.setFileName(path.append(".patched")); + if (!outputFile.open(QFile::WriteOnly)) + return ERR_FILE_WRITE; + + outputFile.resize(0); + outputFile.write(reconstructed); + outputFile.close(); + + return ERR_SUCCESS; +} + +UINT8 UEFIPatch::patchFile(const QModelIndex & index, const QByteArray & fileGuid, const QByteArray & findPattern, const QByteArray & replacePattern) +{ + if (!model || !index.isValid()) + return ERR_INVALID_PARAMETER; + + if (model->type(index) == Types::File && model->header(index).left(sizeof(EFI_GUID)) == fileGuid) { + return ffsEngine->patch(index, findPattern, replacePattern, PATCH_MODE_BODY); + } + + int childCount = model->rowCount(index); + if (childCount > 0) { + UINT8 result; + for (int i = 0; i < childCount; i++) { + result = patchFile(index.child(i, 0), fileGuid, findPattern, replacePattern); + if (result) + return result; + } + } + + return ERR_SUCCESS; +} \ No newline at end of file diff --git a/UEFIPatch/uefipatch.h b/UEFIPatch/uefipatch.h new file mode 100644 index 0000000..44c4562 --- /dev/null +++ b/UEFIPatch/uefipatch.h @@ -0,0 +1,45 @@ +/* uefipatch.h + +Copyright (c) 2014, 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. + +*/ + +#ifndef __UEFIPATCH_H__ +#define __UEFIPATCH_H__ + +#include +#include +#include +#include +#include +#include + +#include "../basetypes.h" +#include "../ffs.h" +#include "../ffsengine.h" + +class UEFIPatch : public QObject +{ + Q_OBJECT + +public: + explicit UEFIPatch(QObject *parent = 0); + ~UEFIPatch(); + + UINT8 patchFromFile(QString path); + UINT8 patch(QString path, QString fileGuid, QString findPattern, QString replacePattern); + +private: + UINT8 patchFile(const QModelIndex & index, const QByteArray & fileGuid, const QByteArray & findPattern, const QByteArray & replacePattern); + FfsEngine* ffsEngine; + TreeModel* model; +}; + +#endif diff --git a/UEFIPatch/uefipatch.pro b/UEFIPatch/uefipatch.pro new file mode 100644 index 0000000..2345707 --- /dev/null +++ b/UEFIPatch/uefipatch.pro @@ -0,0 +1,39 @@ +QT += core +QT -= gui + +TARGET = UEFIPatch +TEMPLATE = app +CONFIG += console + +SOURCES += uefipatch_main.cpp \ + uefipatch.cpp \ + ../types.cpp \ + ../descriptor.cpp \ + ../ffs.cpp \ + ../ffsengine.cpp \ + ../treeitem.cpp \ + ../treemodel.cpp \ + ../LZMA/LzmaCompress.c \ + ../LZMA/LzmaDecompress.c \ + ../LZMA/SDK/C/LzFind.c \ + ../LZMA/SDK/C/LzmaDec.c \ + ../LZMA/SDK/C/LzmaEnc.c \ + ../Tiano/EfiTianoDecompress.c \ + ../Tiano/EfiTianoCompress.c + +HEADERS += uefipatch.h \ + ../basetypes.h \ + ../descriptor.h \ + ../gbe.h \ + ../me.h \ + ../ffs.h \ + ../peimage.h \ + ../types.h \ + ../ffsengine.h \ + ../treeitem.h \ + ../treemodel.h \ + ../LZMA/LzmaCompress.h \ + ../LZMA/LzmaDecompress.h \ + ../Tiano/EfiTianoDecompress.h \ + ../Tiano/EfiTianoCompress.h + \ No newline at end of file diff --git a/UEFIPatch/uefipatch_main.cpp b/UEFIPatch/uefipatch_main.cpp new file mode 100644 index 0000000..f15f79d --- /dev/null +++ b/UEFIPatch/uefipatch_main.cpp @@ -0,0 +1,73 @@ +/* uefipatch_main.cpp + +Copyright (c) 2014, 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 +#include +#include +#include +#include "uefipatch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + a.setOrganizationName("CodeRush"); + a.setOrganizationDomain("coderush.me"); + a.setApplicationName("UEFIExtract"); + + UEFIPatch w; + UINT8 result = ERR_SUCCESS; + UINT32 argumentsCount = a.arguments().length(); + + + if (argumentsCount == 2) { + result = w.patchFromFile(a.arguments().at(1)); + } + else if (argumentsCount == 5) { + result = w.patch(a.arguments().at(1), a.arguments().at(2), a.arguments().at(3), a.arguments().at(4)); + } + else + result = ERR_INVALID_PARAMETER; + + switch (result) { + case ERR_INVALID_PARAMETER: + std::cout << "UEFIPatch 0.1.0 - UEFI image file patching utility" << std::endl << std::endl << + "Usage: UEFIPatch image_file [ffs_file_guid search_pattern replace_pattern]" << std::endl << std::endl << + "image_file - full or relative path to UEFI image file" << std::endl << + "ffs_file_guid - GUID of FFS file to be patched" << std::endl << + "search_pattern - pattern to search" << std::endl << + "replace_pattern - pattern to replace" << std::endl << std::endl << + "If only image_file parameter is specified, patches will be read from patches.txt file"; + break; + case ERR_SUCCESS: + std::cout << "Image patched" << std::endl; + break; + case ERR_ITEM_NOT_FOUND: + std::cout << "FFS file or search pattern not found in input file" << std::endl; + break; + case ERR_INVALID_FILE: + std::cout << "patches.txt file not found or can't be read" << std::endl; + break; + case ERR_FILE_OPEN: + std::cout << "Input file not found" << std::endl; + break; + case ERR_FILE_READ: + std::cout << "Input file can't be read" << std::endl; + break; + case ERR_FILE_WRITE: + std::cout << "Output file can't be written" << std::endl; + break; + default: + std::cout << "Error " << result << std::endl; + } + + return result; +} \ No newline at end of file diff --git a/basetypes.h b/basetypes.h index 13e09fe..c910adb 100644 --- a/basetypes.h +++ b/basetypes.h @@ -85,6 +85,7 @@ typedef uint16_t CHAR16; #define ERR_COMPLEX_BLOCK_MAP 35 #define ERR_DIR_ALREADY_EXIST 36 #define ERR_DIR_CREATE 37 +#define ERR_UNKNOWN_PATCH_MODE 38 #define ERR_NOT_IMPLEMENTED 0xFF // Compression algorithms @@ -109,6 +110,10 @@ typedef uint16_t CHAR16; #define REPLACE_MODE_AS_IS 0 #define REPLACE_MODE_BODY 1 +// Item patch modes +#define PATCH_MODE_HEADER 0 +#define PATCH_MODE_BODY 1 + // Erase polarity types #define ERASE_POLARITY_FALSE 0 #define ERASE_POLARITY_TRUE 1 @@ -119,6 +124,8 @@ typedef uint16_t CHAR16; #define SEARCH_MODE_BODY 2 #define SEARCH_MODE_ALL 3 + + // EFI GUID typedef struct { UINT8 Data[16]; diff --git a/ffs.cpp b/ffs.cpp index a47966b..477b27e 100644 --- a/ffs.cpp +++ b/ffs.cpp @@ -165,14 +165,19 @@ QString sectionTypeToQString(const UINT8 type) } } -UINT32 sizeOfSectionHeaderOfType(const UINT8 type) +UINT32 sizeOfSectionHeader(EFI_COMMON_SECTION_HEADER* header) { - switch (type) + if (!header) + return 0; + + switch (header->Type) { case EFI_SECTION_COMPRESSION: return sizeof(EFI_COMMON_SECTION_HEADER); - case EFI_SECTION_GUID_DEFINED: - return sizeof(EFI_GUID_DEFINED_SECTION); + case EFI_SECTION_GUID_DEFINED: { + EFI_GUID_DEFINED_SECTION* gdsHeader = (EFI_GUID_DEFINED_SECTION*)header; + return gdsHeader->DataOffset; + } case EFI_SECTION_DISPOSABLE: return sizeof(EFI_DISPOSABLE_SECTION); case EFI_SECTION_PE32: diff --git a/ffs.h b/ffs.h index 885b23a..fbe7c21 100644 --- a/ffs.h +++ b/ffs.h @@ -416,7 +416,7 @@ typedef EFI_COMMON_SECTION_HEADER EFI_FIRMWARE_VOLUME_IMAGE_SECTION; typedef EFI_COMMON_SECTION_HEADER EFI_USER_INTERFACE_SECTION; //Section routines -extern UINT32 sizeOfSectionHeaderOfType(const UINT8 type); +extern UINT32 sizeOfSectionHeader(EFI_COMMON_SECTION_HEADER* header); // Restore previous packing rules #pragma pack(pop) diff --git a/ffsengine.cpp b/ffsengine.cpp index a3fa9b9..b11cd6c 100644 --- a/ffsengine.cpp +++ b/ffsengine.cpp @@ -1132,7 +1132,7 @@ UINT8 FfsEngine::parseSection(const QByteArray & section, QModelIndex & index, c case EFI_SECTION_PEI_DEPEX: case EFI_SECTION_SMM_DEPEX: case EFI_SECTION_COMPATIBILITY16: { - headerSize = sizeOfSectionHeaderOfType(sectionHeader->Type); + headerSize = sizeOfSectionHeader(sectionHeader); header = section.left(headerSize); body = section.mid(headerSize, sectionSize - headerSize); @@ -1509,12 +1509,12 @@ UINT8 FfsEngine::insert(const QModelIndex & index, const QByteArray & object, co else if (model->type(parent) == Types::File) { type = Types::Section; EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData(); - headerSize = sizeOfSectionHeaderOfType(commonHeader->Type); + headerSize = sizeOfSectionHeader(commonHeader); } else if (model->type(parent) == Types::Section) { type = Types::Section; EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData(); - headerSize = sizeOfSectionHeaderOfType(commonHeader->Type); + headerSize = sizeOfSectionHeader(commonHeader); } else return ERR_NOT_IMPLEMENTED; @@ -1549,7 +1549,7 @@ UINT8 FfsEngine::replace(const QModelIndex & index, const QByteArray & object, c else if (model->type(index) == Types::Section) { if (mode == REPLACE_MODE_AS_IS) { EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData(); - headerSize = sizeOfSectionHeaderOfType(commonHeader->Type); + 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) { @@ -1754,7 +1754,7 @@ UINT8 FfsEngine::decompress(const QByteArray & compressedData, const UINT8 compr // 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 = sizeOfSectionHeaderOfType(shittySectionHeader->Type); + shittySectionSize = sizeOfSectionHeader(shittySectionHeader); // Decompress section data once again data += shittySectionSize; @@ -1852,7 +1852,7 @@ UINT8 FfsEngine::compress(const QByteArray & data, const UINT8 algorithm, QByteA { QByteArray header = data.left(sizeof(EFI_COMMON_SECTION_HEADER)); EFI_COMMON_SECTION_HEADER* sectionHeader = (EFI_COMMON_SECTION_HEADER*)header.constData(); - UINT32 headerSize = sizeOfSectionHeaderOfType(sectionHeader->Type); + UINT32 headerSize = sizeOfSectionHeader(sectionHeader); header = data.left(headerSize); QByteArray newData = data.mid(headerSize); if (LzmaCompress((UINT8*)newData.constData(), newData.size(), NULL, &compressedSize) != ERR_BUFFER_TOO_SMALL) @@ -3198,3 +3198,48 @@ UINT8 FfsEngine::dump(const QModelIndex & index, const QString path) return ERR_SUCCESS; } +UINT8 FfsEngine::patch(const QModelIndex & index, const QByteArray & findPattern, const QByteArray & replacePattern, const UINT8 mode) +{ + if (!index.isValid() || findPattern.isEmpty()) + return ERR_INVALID_PARAMETER; + + // Skip removed files + if (model->action(index) == Actions::Remove) + return ERR_SUCCESS; + + // Patch header + if (mode == PATCH_MODE_HEADER && model->header(index).contains(findPattern)) { + QByteArray patched = model->header(index); + patched.replace(findPattern, replacePattern).append(model->body(index)); + msg(tr("Header of %1 patched, %2 -> %3") + .arg(model->nameString(index)) + .arg(QString(findPattern.toHex())) + .arg(QString(replacePattern.toHex())), index); + return replace(index, patched, REPLACE_MODE_AS_IS); + } + // Patch body + else if (mode == PATCH_MODE_BODY) { + if (model->rowCount(index)) { + UINT8 result; + for (int i = 0; i < model->rowCount(index); i++) { + result = patch(index.child(i, 0), findPattern, replacePattern, PATCH_MODE_BODY); + if (result) + return result; + } + } + else if (model->body(index).contains(findPattern)){ + QByteArray patched = model->body(index); + patched.replace(findPattern, replacePattern); + patched.prepend(model->header(index)); + msg(tr("Body of %1 patched, %2 -> %3") + .arg(model->nameString(index)) + .arg(QString(findPattern.toHex())) + .arg(QString(replacePattern.toHex())), index); + return replace(index, patched, REPLACE_MODE_AS_IS); + } + } + else + return ERR_UNKNOWN_PATCH_MODE; + + return ERR_SUCCESS; +} \ No newline at end of file diff --git a/ffsengine.h b/ffsengine.h index 7fd1d1a..de8f2d6 100644 --- a/ffsengine.h +++ b/ffsengine.h @@ -86,6 +86,7 @@ public: UINT8 remove(const QModelIndex & index); UINT8 rebuild(const QModelIndex & index); UINT8 dump(const QModelIndex & index, const QString path); + UINT8 patch(const QModelIndex & index, const QByteArray & findPattern, const QByteArray & replacePattern, const UINT8 mode); // Search routines UINT8 findHexPattern(const QByteArray & pattern, const UINT8 mode);