From 7eb565d788d0ca115b1448224e8d51c5a52f2fc1 Mon Sep 17 00:00:00 2001 From: Nikolaj Schlej Date: Sun, 19 Feb 2023 12:24:20 -0800 Subject: [PATCH] Switch AMI NVAR parser to Kaitai --- UEFIExtract/CMakeLists.txt | 1 + UEFIFind/CMakeLists.txt | 1 + UEFITool/CMakeLists.txt | 1 + UEFITool/uefitool.cpp | 32 +- UEFITool/uefitool.pro | 3 + common/basetypes.h | 2 + common/fitparser.cpp | 51 +-- common/generated/ami_nvar.cpp | 442 ++++++++++++++++++++++++++ common/generated/ami_nvar.h | 398 ++++++++++++++++++++++++ common/ksy/ami_nvar.ksy | 162 ++++++++++ common/nvramparser.cpp | 562 ++++++++++++++-------------------- common/types.cpp | 1 + common/types.h | 1 + common/umemstream.h | 58 ++++ common/ustring.cpp | 12 +- common/utility.cpp | 11 +- fuzzing/CMakeLists.txt | 3 +- 17 files changed, 1330 insertions(+), 411 deletions(-) create mode 100644 common/generated/ami_nvar.cpp create mode 100644 common/generated/ami_nvar.h create mode 100644 common/ksy/ami_nvar.ksy create mode 100644 common/umemstream.h diff --git a/UEFIExtract/CMakeLists.txt b/UEFIExtract/CMakeLists.txt index 9077669..4db5457 100644 --- a/UEFIExtract/CMakeLists.txt +++ b/UEFIExtract/CMakeLists.txt @@ -33,6 +33,7 @@ SET(PROJECT_SOURCES ../common/ustring.cpp ../common/bstrlib/bstrlib.c ../common/bstrlib/bstrwrap.cpp + ../common/generated/ami_nvar.cpp ../common/generated/intel_acbp_v1.cpp ../common/generated/intel_acbp_v2.cpp ../common/generated/intel_keym_v1.cpp diff --git a/UEFIFind/CMakeLists.txt b/UEFIFind/CMakeLists.txt index b1b39e2..b508422 100644 --- a/UEFIFind/CMakeLists.txt +++ b/UEFIFind/CMakeLists.txt @@ -30,6 +30,7 @@ SET(PROJECT_SOURCES ../common/ustring.cpp ../common/bstrlib/bstrlib.c ../common/bstrlib/bstrwrap.cpp + ../common/generated/ami_nvar.cpp ../common/generated/intel_acbp_v1.cpp ../common/generated/intel_acbp_v2.cpp ../common/generated/intel_keym_v1.cpp diff --git a/UEFITool/CMakeLists.txt b/UEFITool/CMakeLists.txt index 9c2cfd0..8df2aa3 100644 --- a/UEFITool/CMakeLists.txt +++ b/UEFITool/CMakeLists.txt @@ -70,6 +70,7 @@ SET(PROJECT_SOURCES ../common/digest/sha256.c ../common/digest/sha512.c ../common/digest/sm3.c + ../common/generated/ami_nvar.cpp ../common/generated/intel_acbp_v1.cpp ../common/generated/intel_acbp_v2.cpp ../common/generated/intel_keym_v1.cpp diff --git a/UEFITool/uefitool.cpp b/UEFITool/uefitool.cpp index e335596..3ede8ee 100644 --- a/UEFITool/uefitool.cpp +++ b/UEFITool/uefitool.cpp @@ -243,6 +243,7 @@ void UEFITool::populateUi(const QModelIndex ¤t) || type == Types::EvsaStore || type == Types::FtwStore || type == Types::FlashMapStore + || type == Types::NvarGuidStore || type == Types::CmdbStore || type == Types::FptStore || type == Types::BpdtStore @@ -407,9 +408,8 @@ void UEFITool::goToData() UByteArray rdata = model->parsingData(index); const NVAR_ENTRY_PARSING_DATA* pdata = (const NVAR_ENTRY_PARSING_DATA*)rdata.constData(); - UINT32 lastVariableFlag = pdata->emptyByte ? 0xFFFFFF : 0; UINT32 offset = model->offset(index); - if (pdata->next == lastVariableFlag) { + if (pdata->next == 0xFFFFFF) { ui->structureTreeView->scrollTo(index, QAbstractItemView::PositionAtCenter); ui->structureTreeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows | QItemSelectionModel::Clear); } @@ -783,13 +783,7 @@ void UEFITool::showParserMessages() std::vector > messages = ffsParser->getMessages(); -#if QT_VERSION_MAJOR < 6 - std::pair msg; - foreach (msg, messages) -#else - for (const auto &msg : messages) -#endif - { + for (const auto &msg : messages) { QListWidgetItem* item = new QListWidgetItem(msg.first, NULL, 0); item->setData(Qt::UserRole, QByteArray((const char*)&msg.second, sizeof(msg.second))); ui->parserMessagesListWidget->addItem(item); @@ -807,13 +801,7 @@ void UEFITool::showFinderMessages() std::vector > messages = ffsFinder->getMessages(); -#if QT_VERSION_MAJOR < 6 - std::pair msg; - foreach (msg, messages) -#else - for (const auto &msg : messages) -#endif - { + for (const auto &msg : messages) { QListWidgetItem* item = new QListWidgetItem(msg.first, NULL, 0); item->setData(Qt::UserRole, QByteArray((const char*)&msg.second, sizeof(msg.second)));; ui->finderMessagesListWidget->addItem(item); @@ -832,13 +820,7 @@ void UEFITool::showBuilderMessages() std::vector > messages = ffsBuilder->getMessages(); -#if QT_VERSION_MAJOR < 6 - std::pair msg; - foreach (msg, messages) -#else - for (const auto &msg : messages) -#endif - { + for (const auto &msg : messages) { QListWidgetItem* item = new QListWidgetItem(msg.first, NULL, 0); item->setData(Qt::UserRole, QByteArray((const char*)&msg.second, sizeof(msg.second))); ui->builderMessagesListWidget->addItem(item); @@ -891,8 +873,7 @@ void UEFITool::contextMenuEvent(QContextMenuEvent* event) return; } - switch (model->type(index)) - { + switch (model->type(index)) { case Types::Capsule: ui->menuCapsuleActions->exec(event->globalPos()); break; case Types::Image: ui->menuImageActions->exec(event->globalPos()); break; case Types::Region: ui->menuRegionActions->exec(event->globalPos()); break; @@ -907,6 +888,7 @@ void UEFITool::contextMenuEvent(QContextMenuEvent* event) case Types::EvsaStore: case Types::FtwStore: case Types::FlashMapStore: + case Types::NvarGuidStore: case Types::CmdbStore: case Types::FptStore: case Types::CpdStore: diff --git a/UEFITool/uefitool.pro b/UEFITool/uefitool.pro index 2dc54a4..cd890c6 100644 --- a/UEFITool/uefitool.pro +++ b/UEFITool/uefitool.pro @@ -46,9 +46,11 @@ HEADERS += uefitool.h \ ../common/Tiano/EfiTianoCompress.h \ ../common/ustring.h \ ../common/ubytearray.h \ + ../common/umemstream.h \ ../common/digest/sha1.h \ ../common/digest/sha2.h \ ../common/digest/sm3.h \ + ../common/generated/ami_nvar.h \ ../common/generated/intel_acbp_v1.h \ ../common/generated/intel_acbp_v2.h \ ../common/generated/intel_keym_v1.h \ @@ -103,6 +105,7 @@ SOURCES += uefitool_main.cpp \ ../common/digest/sha256.c \ ../common/digest/sha512.c \ ../common/digest/sm3.c \ + ../common/generated/ami_nvar.cpp \ ../common/generated/intel_acbp_v1.cpp \ ../common/generated/intel_acbp_v2.cpp \ ../common/generated/intel_keym_v1.cpp \ diff --git a/common/basetypes.h b/common/basetypes.h index 9e93f27..99bbb0e 100644 --- a/common/basetypes.h +++ b/common/basetypes.h @@ -18,6 +18,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #include +// TODO: improve typedef size_t USTATUS; #define U_SUCCESS 0 #define U_INVALID_PARAMETER 1 @@ -76,6 +77,7 @@ typedef size_t USTATUS; #define U_PATCH_OFFSET_OUT_OF_BOUNDS 54 #define U_INVALID_SYMBOL 55 #define U_ZLIB_DECOMPRESSION_FAILED 56 +#define U_INVALID_STORE 57 #define U_INVALID_MANIFEST 251 #define U_UNKNOWN_MANIFEST_HEADER_VERSION 252 diff --git a/common/fitparser.cpp b/common/fitparser.cpp index 8852b5e..322a381 100644 --- a/common/fitparser.cpp +++ b/common/fitparser.cpp @@ -21,7 +21,7 @@ #include "utility.h" #include "digest/sha2.h" -#include +#include "umemstream.h" #include "kaitai/kaitaistream.h" #include "generated/intel_acbp_v1.h" #include "generated/intel_acbp_v2.h" @@ -29,45 +29,6 @@ #include "generated/intel_keym_v2.h" #include "generated/intel_acm.h" -// TODO: put into separate H/CPP when we start using Kaitai for other parsers -// TODO: this implementation is certainly not a valid replacement to std::stringstream -// TODO: because it only supports getting through the buffer once -// TODO: however, we already do it this way, so it's enough for practical purposes of this file -class membuf : public std::streambuf { -public: - membuf(const char *p, size_t l) { - setg((char*)p, (char*)p, (char*)p + l); - } - - pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override - { - (void)which; - if (dir == std::ios_base::cur) - gbump((int)off); - else if (dir == std::ios_base::end) - setg(eback(), egptr() + off, egptr()); - else if (dir == std::ios_base::beg) - setg(eback(), eback() + off, egptr()); - return gptr() - eback(); - } - - pos_type seekpos(pos_type sp, std::ios_base::openmode which) override - { - return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which); - } -}; - -class memstream : public std::istream { -public: - memstream(const char *p, size_t l) : std::istream(&buffer_), - buffer_(p, l) { - rdbuf(&buffer_); - } - -private: - membuf buffer_; -}; - USTATUS FitParser::parseFit(const UModelIndex & index) { // Reset parser state @@ -318,7 +279,7 @@ USTATUS FitParser::parseFitEntryMicrocode(const UByteArray & microcode, const UI USTATUS FitParser::parseFitEntryAcm(const UByteArray & acm, const UINT32 localOffset, const UModelIndex & parent, UString & info, UINT32 &realSize) { try { - memstream is(acm.constData(), acm.size()); + umemstream is(acm.constData(), acm.size()); is.seekg(localOffset, is.beg); kaitai::kstream ks(&is); intel_acm_t parsed(&ks); @@ -436,7 +397,7 @@ USTATUS FitParser::parseFitEntryBootGuardKeyManifest(const UByteArray & keyManif // v1 try { - memstream is(keyManifest.constData(), keyManifest.size()); + umemstream is(keyManifest.constData(), keyManifest.size()); is.seekg(localOffset, is.beg); kaitai::kstream ks(&is); intel_keym_v1_t parsed(&ks); @@ -539,7 +500,7 @@ USTATUS FitParser::parseFitEntryBootGuardKeyManifest(const UByteArray & keyManif // v2 try { - memstream is(keyManifest.constData(), keyManifest.size()); + umemstream is(keyManifest.constData(), keyManifest.size()); is.seekg(localOffset, is.beg); kaitai::kstream ks(&is); intel_keym_v2_t parsed(&ks); @@ -671,7 +632,7 @@ USTATUS FitParser::parseFitEntryBootGuardBootPolicy(const UByteArray & bootPolic // v1 try { - memstream is(bootPolicy.constData(), bootPolicy.size()); + umemstream is(bootPolicy.constData(), bootPolicy.size()); is.seekg(localOffset, is.beg); kaitai::kstream ks(&is); intel_acbp_v1_t parsed(&ks); @@ -935,7 +896,7 @@ USTATUS FitParser::parseFitEntryBootGuardBootPolicy(const UByteArray & bootPolic // v2 try { - memstream is(bootPolicy.constData(), bootPolicy.size()); + umemstream is(bootPolicy.constData(), bootPolicy.size()); is.seekg(localOffset, is.beg); kaitai::kstream ks(&is); intel_acbp_v2_t parsed(&ks); // This already verified the version to be >= 0x20 diff --git a/common/generated/ami_nvar.cpp b/common/generated/ami_nvar.cpp new file mode 100644 index 0000000..28441a2 --- /dev/null +++ b/common/generated/ami_nvar.cpp @@ -0,0 +1,442 @@ +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "ami_nvar.h" +#include "../kaitai/exceptions.h" + +ami_nvar_t::ami_nvar_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = this; (void)p__root; + m_entries = 0; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::_read() { + m_entries = new std::vector(); + { + int i = 0; + nvar_entry_t* _; + do { + _ = new nvar_entry_t(m__io, this, m__root); + m_entries->push_back(_); + i++; + } while (!( ((_->signature_first() != 78) || (_io()->is_eof())) )); + } +} + +ami_nvar_t::~ami_nvar_t() { + _clean_up(); +} + +void ami_nvar_t::_clean_up() { + if (m_entries) { + for (std::vector::iterator it = m_entries->begin(); it != m_entries->end(); ++it) { + delete *it; + } + delete m_entries; m_entries = 0; + } +} + +ami_nvar_t::nvar_attributes_t::nvar_attributes_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_t* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::nvar_attributes_t::_read() { + m_valid = m__io->read_bits_int_be(1); + m_auth_write = m__io->read_bits_int_be(1); + m_hw_error_record = m__io->read_bits_int_be(1); + m_extended_header = m__io->read_bits_int_be(1); + m_data_only = m__io->read_bits_int_be(1); + m_local_guid = m__io->read_bits_int_be(1); + m_ascii_name = m__io->read_bits_int_be(1); + m_runtime = m__io->read_bits_int_be(1); +} + +ami_nvar_t::nvar_attributes_t::~nvar_attributes_t() { + _clean_up(); +} + +void ami_nvar_t::nvar_attributes_t::_clean_up() { +} + +ami_nvar_t::ucs2_string_t::ucs2_string_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_body_t* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_ucs2_chars = 0; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::ucs2_string_t::_read() { + m_ucs2_chars = new std::vector(); + { + int i = 0; + uint16_t _; + do { + _ = m__io->read_u2le(); + m_ucs2_chars->push_back(_); + i++; + } while (!(_ == 0)); + } +} + +ami_nvar_t::ucs2_string_t::~ucs2_string_t() { + _clean_up(); +} + +void ami_nvar_t::ucs2_string_t::_clean_up() { + if (m_ucs2_chars) { + delete m_ucs2_chars; m_ucs2_chars = 0; + } +} + +ami_nvar_t::nvar_extended_attributes_t::nvar_extended_attributes_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_body_t* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::nvar_extended_attributes_t::_read() { + m_reserved_high = m__io->read_bits_int_be(2); + m_time_based_auth = m__io->read_bits_int_be(1); + m_auth_write = m__io->read_bits_int_be(1); + m_reserved_low = m__io->read_bits_int_be(3); + m_checksum = m__io->read_bits_int_be(1); +} + +ami_nvar_t::nvar_extended_attributes_t::~nvar_extended_attributes_t() { + _clean_up(); +} + +void ami_nvar_t::nvar_extended_attributes_t::_clean_up() { +} + +ami_nvar_t::nvar_entry_t::nvar_entry_t(kaitai::kstream* p__io, ami_nvar_t* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_attributes = 0; + m_body = 0; + m__io__raw_body = 0; + f_offset = false; + f_end_offset = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::nvar_entry_t::_read() { + n_invoke_offset = true; + if (offset() >= 0) { + n_invoke_offset = false; + m_invoke_offset = m__io->read_bytes(0); + } + m_signature_first = m__io->read_u1(); + n_signature_rest = true; + if (signature_first() == 78) { + n_signature_rest = false; + m_signature_rest = m__io->read_bytes(3); + if (!(signature_rest() == std::string("\x56\x41\x52", 3))) { + throw kaitai::validation_not_equal_error(std::string("\x56\x41\x52", 3), signature_rest(), _io(), std::string("/types/nvar_entry/seq/2")); + } + } + n_size = true; + if (signature_first() == 78) { + n_size = false; + m_size = m__io->read_u2le(); + { + uint16_t _ = size(); + if (!(_ > ((4 + 2) + 4))) { + throw kaitai::validation_expr_error(size(), _io(), std::string("/types/nvar_entry/seq/3")); + } + } + } + n_next = true; + if (signature_first() == 78) { + n_next = false; + m_next = m__io->read_bits_int_le(24); + } + m__io->align_to_byte(); + n_attributes = true; + if (signature_first() == 78) { + n_attributes = false; + m_attributes = new nvar_attributes_t(m__io, this, m__root); + } + n_body = true; + if (signature_first() == 78) { + n_body = false; + m__raw_body = m__io->read_bytes((size() - ((4 + 2) + 4))); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new nvar_entry_body_t(m__io__raw_body, this, m__root); + } + n_invoke_end_offset = true; + if ( ((signature_first() == 78) && (end_offset() >= 0)) ) { + n_invoke_end_offset = false; + m_invoke_end_offset = m__io->read_bytes(0); + } +} + +ami_nvar_t::nvar_entry_t::~nvar_entry_t() { + _clean_up(); +} + +void ami_nvar_t::nvar_entry_t::_clean_up() { + if (!n_invoke_offset) { + } + if (!n_signature_rest) { + } + if (!n_size) { + } + if (!n_next) { + } + if (!n_attributes) { + if (m_attributes) { + delete m_attributes; m_attributes = 0; + } + } + if (!n_body) { + if (m__io__raw_body) { + delete m__io__raw_body; m__io__raw_body = 0; + } + if (m_body) { + delete m_body; m_body = 0; + } + } + if (!n_invoke_end_offset) { + } +} + +int32_t ami_nvar_t::nvar_entry_t::offset() { + if (f_offset) + return m_offset; + m_offset = _io()->pos(); + f_offset = true; + return m_offset; +} + +int32_t ami_nvar_t::nvar_entry_t::end_offset() { + if (f_end_offset) + return m_end_offset; + m_end_offset = _io()->pos(); + f_end_offset = true; + return m_end_offset; +} + +ami_nvar_t::nvar_entry_body_t::nvar_entry_body_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_t* p__parent, ami_nvar_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_ucs2_name = 0; + m_extended_header_attributes = 0; + f_extended_header_attributes = false; + f_data_start_offset = false; + f_extended_header_size_field = false; + f_extended_header_timestamp = false; + f_data_size = false; + f_extended_header_checksum = false; + f_data_end_offset = false; + f_extended_header_size = false; + f_extended_header_hash = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ami_nvar_t::nvar_entry_body_t::_read() { + n_guid_index = true; + if ( ((!(_parent()->attributes()->local_guid())) && (!(_parent()->attributes()->data_only())) && (_parent()->attributes()->valid())) ) { + n_guid_index = false; + m_guid_index = m__io->read_u1(); + } + n_guid = true; + if ( ((_parent()->attributes()->local_guid()) && (!(_parent()->attributes()->data_only())) && (_parent()->attributes()->valid())) ) { + n_guid = false; + m_guid = m__io->read_bytes(16); + } + n_ascii_name = true; + if ( ((_parent()->attributes()->ascii_name()) && (!(_parent()->attributes()->data_only())) && (_parent()->attributes()->valid())) ) { + n_ascii_name = false; + m_ascii_name = kaitai::kstream::bytes_to_str(m__io->read_bytes_term(0, false, true, true), std::string("ASCII")); + } + n_ucs2_name = true; + if ( ((!(_parent()->attributes()->ascii_name())) && (!(_parent()->attributes()->data_only())) && (_parent()->attributes()->valid())) ) { + n_ucs2_name = false; + m_ucs2_name = new ucs2_string_t(m__io, this, m__root); + } + n_invoke_data_start = true; + if (data_start_offset() >= 0) { + n_invoke_data_start = false; + m_invoke_data_start = m__io->read_bytes(0); + } + m_data = m__io->read_bytes_full(); +} + +ami_nvar_t::nvar_entry_body_t::~nvar_entry_body_t() { + _clean_up(); +} + +void ami_nvar_t::nvar_entry_body_t::_clean_up() { + if (!n_guid_index) { + } + if (!n_guid) { + } + if (!n_ascii_name) { + } + if (!n_ucs2_name) { + if (m_ucs2_name) { + delete m_ucs2_name; m_ucs2_name = 0; + } + } + if (!n_invoke_data_start) { + } + if (f_extended_header_attributes && !n_extended_header_attributes) { + if (m_extended_header_attributes) { + delete m_extended_header_attributes; m_extended_header_attributes = 0; + } + } + if (f_extended_header_size_field && !n_extended_header_size_field) { + } + if (f_extended_header_timestamp && !n_extended_header_timestamp) { + } + if (f_extended_header_checksum && !n_extended_header_checksum) { + } + if (f_extended_header_hash && !n_extended_header_hash) { + } +} + +ami_nvar_t::nvar_extended_attributes_t* ami_nvar_t::nvar_entry_body_t::extended_header_attributes() { + if (f_extended_header_attributes) + return m_extended_header_attributes; + n_extended_header_attributes = true; + if ( ((_parent()->attributes()->valid()) && (_parent()->attributes()->extended_header()) && (extended_header_size() >= (1 + 2))) ) { + n_extended_header_attributes = false; + std::streampos _pos = m__io->pos(); + m__io->seek((_io()->pos() - extended_header_size())); + m_extended_header_attributes = new nvar_extended_attributes_t(m__io, this, m__root); + m__io->seek(_pos); + f_extended_header_attributes = true; + } + return m_extended_header_attributes; +} + +int32_t ami_nvar_t::nvar_entry_body_t::data_start_offset() { + if (f_data_start_offset) + return m_data_start_offset; + m_data_start_offset = _io()->pos(); + f_data_start_offset = true; + return m_data_start_offset; +} + +uint16_t ami_nvar_t::nvar_entry_body_t::extended_header_size_field() { + if (f_extended_header_size_field) + return m_extended_header_size_field; + n_extended_header_size_field = true; + if ( ((_parent()->attributes()->valid()) && (_parent()->attributes()->extended_header()) && (_parent()->size() > (((4 + 2) + 4) + 2))) ) { + n_extended_header_size_field = false; + std::streampos _pos = m__io->pos(); + m__io->seek((_io()->pos() - 2)); + m_extended_header_size_field = m__io->read_u2le(); + m__io->seek(_pos); + f_extended_header_size_field = true; + } + return m_extended_header_size_field; +} + +uint64_t ami_nvar_t::nvar_entry_body_t::extended_header_timestamp() { + if (f_extended_header_timestamp) + return m_extended_header_timestamp; + n_extended_header_timestamp = true; + if ( ((_parent()->attributes()->valid()) && (_parent()->attributes()->extended_header()) && (extended_header_size() >= ((1 + 8) + 2)) && (extended_header_attributes()->time_based_auth())) ) { + n_extended_header_timestamp = false; + std::streampos _pos = m__io->pos(); + m__io->seek(((_io()->pos() - extended_header_size()) + 1)); + m_extended_header_timestamp = m__io->read_u8le(); + m__io->seek(_pos); + f_extended_header_timestamp = true; + } + return m_extended_header_timestamp; +} + +int32_t ami_nvar_t::nvar_entry_body_t::data_size() { + if (f_data_size) + return m_data_size; + m_data_size = ((data_end_offset() - data_start_offset()) - extended_header_size()); + f_data_size = true; + return m_data_size; +} + +uint8_t ami_nvar_t::nvar_entry_body_t::extended_header_checksum() { + if (f_extended_header_checksum) + return m_extended_header_checksum; + n_extended_header_checksum = true; + if ( ((_parent()->attributes()->valid()) && (_parent()->attributes()->extended_header()) && (extended_header_size() >= ((1 + 1) + 2)) && (extended_header_attributes()->checksum())) ) { + n_extended_header_checksum = false; + std::streampos _pos = m__io->pos(); + m__io->seek(((_io()->pos() - 2) - 1)); + m_extended_header_checksum = m__io->read_u1(); + m__io->seek(_pos); + f_extended_header_checksum = true; + } + return m_extended_header_checksum; +} + +int32_t ami_nvar_t::nvar_entry_body_t::data_end_offset() { + if (f_data_end_offset) + return m_data_end_offset; + m_data_end_offset = _io()->pos(); + f_data_end_offset = true; + return m_data_end_offset; +} + +uint16_t ami_nvar_t::nvar_entry_body_t::extended_header_size() { + if (f_extended_header_size) + return m_extended_header_size; + m_extended_header_size = ((_parent()->attributes()->extended_header()) ? (((extended_header_size_field() >= (1 + 2)) ? (extended_header_size_field()) : (0))) : (0)); + f_extended_header_size = true; + return m_extended_header_size; +} + +std::string ami_nvar_t::nvar_entry_body_t::extended_header_hash() { + if (f_extended_header_hash) + return m_extended_header_hash; + n_extended_header_hash = true; + if ( ((_parent()->attributes()->valid()) && (_parent()->attributes()->extended_header()) && (extended_header_size() >= (((1 + 8) + 32) + 2)) && (extended_header_attributes()->time_based_auth()) && (!(_parent()->attributes()->data_only()))) ) { + n_extended_header_hash = false; + std::streampos _pos = m__io->pos(); + m__io->seek((((_io()->pos() - extended_header_size()) + 1) + 8)); + m_extended_header_hash = m__io->read_bytes(32); + m__io->seek(_pos); + f_extended_header_hash = true; + } + return m_extended_header_hash; +} diff --git a/common/generated/ami_nvar.h b/common/generated/ami_nvar.h new file mode 100644 index 0000000..b372835 --- /dev/null +++ b/common/generated/ami_nvar.h @@ -0,0 +1,398 @@ +#ifndef AMI_NVAR_H_ +#define AMI_NVAR_H_ + +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "../kaitai/kaitaistruct.h" +#include +#include + +#if KAITAI_STRUCT_VERSION < 9000L +#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required" +#endif + +class ami_nvar_t : public kaitai::kstruct { + +public: + class nvar_attributes_t; + class ucs2_string_t; + class nvar_extended_attributes_t; + class nvar_entry_t; + class nvar_entry_body_t; + + ami_nvar_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, ami_nvar_t* p__root = 0); + +private: + void _read(); + void _clean_up(); + +public: + ~ami_nvar_t(); + + class nvar_attributes_t : public kaitai::kstruct { + + public: + + nvar_attributes_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_t* p__parent = 0, ami_nvar_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nvar_attributes_t(); + + private: + bool m_valid; + bool m_auth_write; + bool m_hw_error_record; + bool m_extended_header; + bool m_data_only; + bool m_local_guid; + bool m_ascii_name; + bool m_runtime; + ami_nvar_t* m__root; + ami_nvar_t::nvar_entry_t* m__parent; + + public: + bool valid() const { return m_valid; } + bool auth_write() const { return m_auth_write; } + bool hw_error_record() const { return m_hw_error_record; } + bool extended_header() const { return m_extended_header; } + bool data_only() const { return m_data_only; } + bool local_guid() const { return m_local_guid; } + bool ascii_name() const { return m_ascii_name; } + bool runtime() const { return m_runtime; } + ami_nvar_t* _root() const { return m__root; } + ami_nvar_t::nvar_entry_t* _parent() const { return m__parent; } + }; + + class ucs2_string_t : public kaitai::kstruct { + + public: + + ucs2_string_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_body_t* p__parent = 0, ami_nvar_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~ucs2_string_t(); + + private: + std::vector* m_ucs2_chars; + ami_nvar_t* m__root; + ami_nvar_t::nvar_entry_body_t* m__parent; + + public: + std::vector* ucs2_chars() const { return m_ucs2_chars; } + ami_nvar_t* _root() const { return m__root; } + ami_nvar_t::nvar_entry_body_t* _parent() const { return m__parent; } + }; + + class nvar_extended_attributes_t : public kaitai::kstruct { + + public: + + nvar_extended_attributes_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_body_t* p__parent = 0, ami_nvar_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nvar_extended_attributes_t(); + + private: + uint64_t m_reserved_high; + bool m_time_based_auth; + bool m_auth_write; + uint64_t m_reserved_low; + bool m_checksum; + ami_nvar_t* m__root; + ami_nvar_t::nvar_entry_body_t* m__parent; + + public: + uint64_t reserved_high() const { return m_reserved_high; } + bool time_based_auth() const { return m_time_based_auth; } + bool auth_write() const { return m_auth_write; } + uint64_t reserved_low() const { return m_reserved_low; } + bool checksum() const { return m_checksum; } + ami_nvar_t* _root() const { return m__root; } + ami_nvar_t::nvar_entry_body_t* _parent() const { return m__parent; } + }; + + class nvar_entry_t : public kaitai::kstruct { + + public: + + nvar_entry_t(kaitai::kstream* p__io, ami_nvar_t* p__parent = 0, ami_nvar_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nvar_entry_t(); + + private: + bool f_offset; + int32_t m_offset; + + public: + int32_t offset(); + + private: + bool f_end_offset; + int32_t m_end_offset; + + public: + int32_t end_offset(); + + private: + std::string m_invoke_offset; + bool n_invoke_offset; + + public: + bool _is_null_invoke_offset() { invoke_offset(); return n_invoke_offset; }; + + private: + uint8_t m_signature_first; + std::string m_signature_rest; + bool n_signature_rest; + + public: + bool _is_null_signature_rest() { signature_rest(); return n_signature_rest; }; + + private: + uint16_t m_size; + bool n_size; + + public: + bool _is_null_size() { size(); return n_size; }; + + private: + uint64_t m_next; + bool n_next; + + public: + bool _is_null_next() { next(); return n_next; }; + + private: + nvar_attributes_t* m_attributes; + bool n_attributes; + + public: + bool _is_null_attributes() { attributes(); return n_attributes; }; + + private: + nvar_entry_body_t* m_body; + bool n_body; + + public: + bool _is_null_body() { body(); return n_body; }; + + private: + std::string m_invoke_end_offset; + bool n_invoke_end_offset; + + public: + bool _is_null_invoke_end_offset() { invoke_end_offset(); return n_invoke_end_offset; }; + + private: + ami_nvar_t* m__root; + ami_nvar_t* m__parent; + std::string m__raw_body; + bool n__raw_body; + + public: + bool _is_null__raw_body() { _raw_body(); return n__raw_body; }; + + private: + kaitai::kstream* m__io__raw_body; + + public: + std::string invoke_offset() const { return m_invoke_offset; } + uint8_t signature_first() const { return m_signature_first; } + std::string signature_rest() const { return m_signature_rest; } + uint16_t size() const { return m_size; } + uint64_t next() const { return m_next; } + nvar_attributes_t* attributes() const { return m_attributes; } + nvar_entry_body_t* body() const { return m_body; } + std::string invoke_end_offset() const { return m_invoke_end_offset; } + ami_nvar_t* _root() const { return m__root; } + ami_nvar_t* _parent() const { return m__parent; } + std::string _raw_body() const { return m__raw_body; } + kaitai::kstream* _io__raw_body() const { return m__io__raw_body; } + }; + + class nvar_entry_body_t : public kaitai::kstruct { + + public: + + nvar_entry_body_t(kaitai::kstream* p__io, ami_nvar_t::nvar_entry_t* p__parent = 0, ami_nvar_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nvar_entry_body_t(); + + private: + bool f_extended_header_attributes; + nvar_extended_attributes_t* m_extended_header_attributes; + bool n_extended_header_attributes; + + public: + bool _is_null_extended_header_attributes() { extended_header_attributes(); return n_extended_header_attributes; }; + + private: + + public: + nvar_extended_attributes_t* extended_header_attributes(); + + private: + bool f_data_start_offset; + int32_t m_data_start_offset; + + public: + int32_t data_start_offset(); + + private: + bool f_extended_header_size_field; + uint16_t m_extended_header_size_field; + bool n_extended_header_size_field; + + public: + bool _is_null_extended_header_size_field() { extended_header_size_field(); return n_extended_header_size_field; }; + + private: + + public: + uint16_t extended_header_size_field(); + + private: + bool f_extended_header_timestamp; + uint64_t m_extended_header_timestamp; + bool n_extended_header_timestamp; + + public: + bool _is_null_extended_header_timestamp() { extended_header_timestamp(); return n_extended_header_timestamp; }; + + private: + + public: + uint64_t extended_header_timestamp(); + + private: + bool f_data_size; + int32_t m_data_size; + + public: + int32_t data_size(); + + private: + bool f_extended_header_checksum; + uint8_t m_extended_header_checksum; + bool n_extended_header_checksum; + + public: + bool _is_null_extended_header_checksum() { extended_header_checksum(); return n_extended_header_checksum; }; + + private: + + public: + uint8_t extended_header_checksum(); + + private: + bool f_data_end_offset; + int32_t m_data_end_offset; + + public: + int32_t data_end_offset(); + + private: + bool f_extended_header_size; + uint16_t m_extended_header_size; + + public: + uint16_t extended_header_size(); + + private: + bool f_extended_header_hash; + std::string m_extended_header_hash; + bool n_extended_header_hash; + + public: + bool _is_null_extended_header_hash() { extended_header_hash(); return n_extended_header_hash; }; + + private: + + public: + std::string extended_header_hash(); + + private: + uint8_t m_guid_index; + bool n_guid_index; + + public: + bool _is_null_guid_index() { guid_index(); return n_guid_index; }; + + private: + std::string m_guid; + bool n_guid; + + public: + bool _is_null_guid() { guid(); return n_guid; }; + + private: + std::string m_ascii_name; + bool n_ascii_name; + + public: + bool _is_null_ascii_name() { ascii_name(); return n_ascii_name; }; + + private: + ucs2_string_t* m_ucs2_name; + bool n_ucs2_name; + + public: + bool _is_null_ucs2_name() { ucs2_name(); return n_ucs2_name; }; + + private: + std::string m_invoke_data_start; + bool n_invoke_data_start; + + public: + bool _is_null_invoke_data_start() { invoke_data_start(); return n_invoke_data_start; }; + + private: + std::string m_data; + ami_nvar_t* m__root; + ami_nvar_t::nvar_entry_t* m__parent; + + public: + uint8_t guid_index() const { return m_guid_index; } + std::string guid() const { return m_guid; } + std::string ascii_name() const { return m_ascii_name; } + ucs2_string_t* ucs2_name() const { return m_ucs2_name; } + std::string invoke_data_start() const { return m_invoke_data_start; } + std::string data() const { return m_data; } + ami_nvar_t* _root() const { return m__root; } + ami_nvar_t::nvar_entry_t* _parent() const { return m__parent; } + }; + +private: + std::vector* m_entries; + ami_nvar_t* m__root; + kaitai::kstruct* m__parent; + +public: + std::vector* entries() const { return m_entries; } + ami_nvar_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } +}; + +#endif // AMI_NVAR_H_ diff --git a/common/ksy/ami_nvar.ksy b/common/ksy/ami_nvar.ksy new file mode 100644 index 0000000..25e63de --- /dev/null +++ b/common/ksy/ami_nvar.ksy @@ -0,0 +1,162 @@ +meta: + id: ami_nvar + title: AMI Aptio NVRAM Storage + application: AMI Aptio-based UEFI firmware + file-extension: nvar + tags: + - firmware + license: CC0-1.0 + ks-version: 0.9 + endian: le + +seq: +- id: entries + type: nvar_entry + repeat: until + repeat-until: _.signature_first != 0x4e or _io.eof + +types: + nvar_entry: + seq: + - id: invoke_offset + size: 0 + if: offset >= 0 + - id: signature_first + type: u1 + - id: signature_rest + contents: [VAR] + if: signature_first == 0x4e + - id: size + type: u2 + valid: + expr: _ > sizeof + sizeof + sizeof + if: signature_first == 0x4e + - id: next + type: b24le + if: signature_first == 0x4e + - id: attributes + type: nvar_attributes + if: signature_first == 0x4e + - id: body + type: nvar_entry_body + size: size - (sizeof + sizeof + sizeof) + if: signature_first == 0x4e + - id: invoke_end_offset + size: 0 + if: signature_first == 0x4e and end_offset >= 0 + instances: + offset: + value: _io.pos + end_offset: + value: _io.pos + + nvar_attributes: + seq: + - id: valid + type: b1 + - id: auth_write + type: b1 + - id: hw_error_record + type: b1 + - id: extended_header + type: b1 + - id: data_only + type: b1 + - id: local_guid + type: b1 + - id: ascii_name + type: b1 + - id: runtime + type: b1 + + nvar_extended_attributes: + seq: + - id: reserved_high + type: b2 + - id: time_based_auth + type: b1 + - id: auth_write + type: b1 + - id: reserved_low + type: b3 + - id: checksum + type: b1 + + ucs2_string: + seq: + - id: ucs2_chars + type: u2 + repeat: until + repeat-until: _ == 0 + + nvar_entry_body: + seq: + - id: guid_index + type: u1 + if: (not _parent.attributes.local_guid) + and (not _parent.attributes.data_only) + and (_parent.attributes.valid) + - id: guid + size: 16 + if: (_parent.attributes.local_guid) + and (not _parent.attributes.data_only) + and (_parent.attributes.valid) + - id: ascii_name + type: strz + encoding: ASCII + if: (_parent.attributes.ascii_name) + and (not _parent.attributes.data_only) + and (_parent.attributes.valid) + - id: ucs2_name + type: ucs2_string + if: (not _parent.attributes.ascii_name) + and (not _parent.attributes.data_only) + and (_parent.attributes.valid) + - id: invoke_data_start + size: 0 + if: data_start_offset >= 0 + - id: data + size-eos: true + instances: + extended_header_size_field: + pos: _io.pos - sizeof + type: u2 + if: _parent.attributes.valid + and _parent.attributes.extended_header + and _parent.size > sizeof + sizeof + sizeof + sizeof + extended_header_size: + value: '_parent.attributes.extended_header ? (extended_header_size_field >= sizeof + sizeof ? extended_header_size_field : 0) : 0' + extended_header_attributes: + pos: _io.pos - extended_header_size + type: nvar_extended_attributes + if: _parent.attributes.valid + and _parent.attributes.extended_header + and (extended_header_size >= sizeof + sizeof) + extended_header_timestamp: + pos: _io.pos - extended_header_size + sizeof + type: u8 + if: _parent.attributes.valid + and _parent.attributes.extended_header + and (extended_header_size >= sizeof + sizeof + sizeof) + and extended_header_attributes.time_based_auth + extended_header_hash: + pos: _io.pos - extended_header_size + sizeof + sizeof + size: 32 + if: _parent.attributes.valid + and _parent.attributes.extended_header + and (extended_header_size >= sizeof + sizeof + 32 + sizeof) + and extended_header_attributes.time_based_auth + and (not _parent.attributes.data_only) + extended_header_checksum: + pos: _io.pos - sizeof - sizeof + type: u1 + if: _parent.attributes.valid + and _parent.attributes.extended_header + and (extended_header_size >= sizeof + sizeof + sizeof) + and extended_header_attributes.checksum + data_start_offset: + value: _io.pos + data_end_offset: + value: _io.pos + data_size: + value: (data_end_offset - data_start_offset) - extended_header_size diff --git a/common/nvramparser.cpp b/common/nvramparser.cpp index 404e2a2..4fd3377 100755 --- a/common/nvramparser.cpp +++ b/common/nvramparser.cpp @@ -12,358 +12,272 @@ */ +#ifdef U_ENABLE_NVRAM_PARSING_SUPPORT #include #include "nvramparser.h" #include "parsingdata.h" +#include "ustring.h" #include "utility.h" #include "nvram.h" #include "ffs.h" #include "intel_microcode.h" -#ifdef U_ENABLE_NVRAM_PARSING_SUPPORT +#include "umemstream.h" +#include "kaitai/kaitaistream.h" +#include "generated/ami_nvar.h" + USTATUS NvramParser::parseNvarStore(const UModelIndex & index) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; - - // Obtain required information from parent file - UINT8 emptyByte = 0xFF; - UModelIndex parentFileIndex = model->findParentOfType(index, Types::File); - if (parentFileIndex.isValid() && model->hasEmptyParsingData(parentFileIndex) == false) { - UByteArray data = model->parsingData(parentFileIndex); - const FILE_PARSING_DATA* pdata = (const FILE_PARSING_DATA*)data.constData(); - emptyByte = readUnaligned(pdata).emptyByte; - } - - // Get local offset - UINT32 localOffset = (UINT32)model->header(index).size(); - - // Get item data - const UByteArray data = model->body(index); - - // Parse all entries - UINT32 offset = 0; - UINT32 guidsInStore = 0; - while (1) { - bool msgUnknownExtDataFormat = false; - bool msgExtHeaderTooLong = false; - bool msgExtDataTooShort = false; - - bool isInvalid = false; - bool isInvalidLink = false; - bool hasExtendedHeader = false; - bool hasChecksum = false; - bool hasTimestamp = false; - bool hasHash = false; - bool hasGuidIndex = false; - - UINT32 guidIndex = 0; - UINT8 storedChecksum = 0; - UINT8 calculatedChecksum = 0; - UINT32 extendedHeaderSize = 0; - UINT8 extendedAttributes = 0; - UINT64 timestamp = 0; - UByteArray hash; - - UINT8 subtype = Subtypes::FullNvarEntry; - UString name; - UString guid; - UString text; - UByteArray header; - UByteArray body; - UByteArray tail; - - UINT32 guidAreaSize = guidsInStore * sizeof(EFI_GUID); - UINT32 unparsedSize = (UINT32)data.size() - offset - guidAreaSize; - - // Get entry header - const NVAR_ENTRY_HEADER* entryHeader = (const NVAR_ENTRY_HEADER*)(data.constData() + offset); - - // Check header size and signature - if (unparsedSize < sizeof(NVAR_ENTRY_HEADER) || - entryHeader->Signature != NVRAM_NVAR_ENTRY_SIGNATURE || - unparsedSize < entryHeader->Size) { - // Check if the data left is a free space or a padding - UByteArray padding = data.mid(offset, unparsedSize); - - // Get info - UString info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size()); - - if ((UINT32)padding.count(emptyByte) == unparsedSize) { // Free space + + UByteArray nvar = model->body(index); + + // Nothing to parse in an empty store + if (nvar.isEmpty()) + return U_SUCCESS; + + try { + const UINT32 localOffset = (UINT32)model->header(index).size(); + umemstream is(nvar.constData(), nvar.size()); + kaitai::kstream ks(&is); + ami_nvar_t parsed(&ks); + + UINT16 guidsInStore = 0; + UINT32 currentEntryIndex = 0; + for (const auto & entry : *parsed.entries()) { + UINT8 subtype = Subtypes::FullNvarEntry; + UString name; + UString text; + UString info; + UString guid; + UByteArray header; + UByteArray body; + UByteArray tail; + + // This is a terminating entry, needs special processing + if (entry->_is_null_signature_rest()) { + UINT32 guidAreaSize = guidsInStore * sizeof(EFI_GUID); + UINT32 unparsedSize = (UINT32)nvar.size() - entry->offset() - guidAreaSize; + + // Check if the data left is a free space or a padding + UByteArray padding = nvar.mid(entry->offset(), unparsedSize); + + // Get info + UString info = usprintf("Full size: %Xh (%u)", (UINT32)padding.size(), (UINT32)padding.size()); + + if ((UINT32)padding.count(0xFF) == unparsedSize) { // Free space + // Add tree item + model->addItem(localOffset + entry->offset(), Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index); + } + else { + // Nothing is parsed yet, but the file is not empty + if (entry->offset() == 0) { + msg(usprintf("%s: file can't be parsed as NVAR variable store", __FUNCTION__), index); + return U_SUCCESS; + } + + // Add tree item + model->addItem(localOffset + entry->offset(), Types::Padding, getPaddingType(padding), UString("Padding"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index); + } + + // Add GUID store area + UByteArray guidArea = nvar.right(guidAreaSize); + // Get info + name = UString("GUID store"); + info = usprintf("Full size: %Xh (%u)\nGUIDs in store: %u", + (UINT32)guidArea.size(), (UINT32)guidArea.size(), + guidsInStore); // Add tree item - model->addItem(localOffset + offset, Types::FreeSpace, 0, UString("Free space"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index); + model->addItem((UINT32)(localOffset + entry->offset() + padding.size()), Types::NvarGuidStore, 0, name, UString(), info, UByteArray(), guidArea, UByteArray(), Fixed, index); + + return U_SUCCESS; } - else { - // Nothing is parsed yet, but the file is not empty - if (!offset) { - msg(usprintf("%s: file can't be parsed as NVAR variables store", __FUNCTION__), index); - return U_SUCCESS; - } - - // Add tree item - model->addItem(localOffset + offset, Types::Padding, getPaddingType(padding), UString("Padding"), UString(), info, UByteArray(), padding, UByteArray(), Fixed, index); + + // This is a normal entry + const auto entry_body = entry->body(); + + // Set default next to predefined last value + NVAR_ENTRY_PARSING_DATA pdata = {}; + pdata.emptyByte = 0xFF; + pdata.next = 0xFFFFFF; + pdata.isValid = TRUE; + + // Check for invalid entry + if (!entry->attributes()->valid()) { + subtype = Subtypes::InvalidNvarEntry; + name = UString("Invalid"); + pdata.isValid = FALSE; + goto processing_done; } - - // Add GUID store area - UByteArray guidArea = data.right(guidAreaSize); - // Get info - name = UString("GUID store"); - info = usprintf("Full size: %Xh (%u)\nGUIDs in store: %u", - (UINT32)guidArea.size(), (UINT32)guidArea.size(), - guidsInStore); - // Add tree item - model->addItem((UINT32)(localOffset + offset + padding.size()), Types::Padding, getPaddingType(guidArea), name, UString(), info, UByteArray(), guidArea, UByteArray(), Fixed, index); - - return U_SUCCESS; - } - - // Contruct generic header and body - header = data.mid(offset, sizeof(NVAR_ENTRY_HEADER)); - body = data.mid(offset + sizeof(NVAR_ENTRY_HEADER), entryHeader->Size - sizeof(NVAR_ENTRY_HEADER)); - - UINT32 lastVariableFlag = emptyByte ? 0xFFFFFF : 0; - - // Set default next to predefined last value - NVAR_ENTRY_PARSING_DATA pdata = {}; - pdata.emptyByte = emptyByte; - pdata.next = lastVariableFlag; - - // Entry is marked as invalid - if ((entryHeader->Attributes & NVRAM_NVAR_ENTRY_VALID) == 0) { // Valid attribute is not set - isInvalid = true; - // Do not parse further - goto parsing_done; - } - - // Add next node information to parsing data - if (entryHeader->Next != lastVariableFlag) { - subtype = Subtypes::LinkNvarEntry; - pdata.next = entryHeader->Next; - } - - // Entry with extended header - if (entryHeader->Attributes & NVRAM_NVAR_ENTRY_EXT_HEADER) { - hasExtendedHeader = true; - msgUnknownExtDataFormat = true; - - extendedHeaderSize = readUnaligned((UINT16*)(body.constData() + body.size() - sizeof(UINT16))); - if (extendedHeaderSize > (UINT32)body.size()) { - msgExtHeaderTooLong = true; - isInvalid = true; - // Do not parse further - goto parsing_done; + + // Check for link entry + if (entry->next() != 0xFFFFFF) { + subtype = Subtypes::LinkNvarEntry; + pdata.next = (UINT32)entry->next(); } - - extendedAttributes = *(UINT8*)(body.constData() + body.size() - extendedHeaderSize); - - // Variable with checksum - if (extendedAttributes & NVRAM_NVAR_ENTRY_EXT_CHECKSUM) { - // Get stored checksum - storedChecksum = *(UINT8*)(body.constData() + body.size() - sizeof(UINT16) - sizeof(UINT8)); - - // Recalculate checksum for the variable - calculatedChecksum = 0; - // Include entry data - UINT8* start = (UINT8*)(entryHeader + 1); - for (UINT8* p = start; p < start + entryHeader->Size - sizeof(NVAR_ENTRY_HEADER); p++) { - calculatedChecksum += *p; - } - // Include entry size and flags - start = (UINT8*)&entryHeader->Size; - for (UINT8*p = start; p < start + sizeof(UINT16); p++) { - calculatedChecksum += *p; - } - // Include entry attributes - calculatedChecksum += entryHeader->Attributes; - - hasChecksum = true; - msgUnknownExtDataFormat = false; - } - - tail = body.mid(body.size() - extendedHeaderSize); - body = body.left(body.size() - extendedHeaderSize); - - // Entry with authenticated write (for SecureBoot) - if (entryHeader->Attributes & NVRAM_NVAR_ENTRY_AUTH_WRITE) { - if ((entryHeader->Attributes & NVRAM_NVAR_ENTRY_DATA_ONLY)) {// Data only auth. variables has no hash - if ((UINT32)tail.size() < sizeof(UINT64)) { - msgExtDataTooShort = true; - isInvalid = true; - // Do not parse further - goto parsing_done; - } - - timestamp = readUnaligned(tail.constData() + sizeof(UINT8)); - hasTimestamp = true; - msgUnknownExtDataFormat = false; - } - else { // Full or link variable have hash - if ((UINT32)tail.size() < sizeof(UINT64) + SHA256_HASH_SIZE) { - msgExtDataTooShort = true; - isInvalid = true; - // Do not parse further - goto parsing_done; - } - - timestamp = readUnaligned((UINT64*)(tail.constData() + sizeof(UINT8))); - hash = tail.mid(sizeof(UINT64) + sizeof(UINT8), SHA256_HASH_SIZE); - hasTimestamp = true; - hasHash = true; - msgUnknownExtDataFormat = false; - } - } - } - - // Entry is data-only (nameless and GUIDless entry or link) - if (entryHeader->Attributes & NVRAM_NVAR_ENTRY_DATA_ONLY) { // Data-only attribute is set - isInvalidLink = true; - UModelIndex nvarIndex; - // Search previously added entries for a link to this variable - // WARNING: O(n^2), may be very slow - for (int i = model->rowCount(index) - 1; i >= 0; i--) { - nvarIndex = index.model()->index(i, 0, index); - - if (model->hasEmptyParsingData(nvarIndex) == false) { - UByteArray nvarData = model->parsingData(nvarIndex); - const NVAR_ENTRY_PARSING_DATA nvarPdata = readUnaligned((const NVAR_ENTRY_PARSING_DATA*)nvarData.constData()); - if (nvarPdata.isValid && nvarPdata.next + model->offset(nvarIndex) - localOffset == offset) { // Previous link is present and valid - isInvalidLink = false; - break; + + // Check for data-only entry (nameless and GUIDless entry or link) + if (entry->attributes()->data_only()) { + // Search backwards for a previous entry with a link to this variable + UModelIndex prevEntryIndex; + if (currentEntryIndex > 0) { + for (UINT32 i = currentEntryIndex - 1; i > 0; i--) { + const auto previousEntry = parsed.entries()->at(i); + + if (previousEntry == entry) + break; + + if (previousEntry->next() + previousEntry->offset() == entry->offset()) { // Previous link is present and valid + prevEntryIndex = index.model()->index(i, 0, index); + // Make sure that we are linking to a valid entry + NVAR_ENTRY_PARSING_DATA pd = readUnaligned((NVAR_ENTRY_PARSING_DATA*)model->parsingData(prevEntryIndex).constData()); + if (!pd.isValid) { + prevEntryIndex = UModelIndex(); + } + break; + } } } + // Check if the link is valid + if (prevEntryIndex.isValid()) { + // Use the name and text of the previous entry + name = model->name(prevEntryIndex); + text = model->text(prevEntryIndex); + + if (entry->next() == 0xFFFFFF) + subtype = Subtypes::DataNvarEntry; + } + else { + subtype = Subtypes::InvalidLinkNvarEntry; + name = UString("InvalidLink"); + pdata.isValid = FALSE; + } + goto processing_done; } - // Check if the link is valid - if (!isInvalidLink) { - // Use the name and text of the previous link - name = model->name(nvarIndex); - text = model->text(nvarIndex); - - if (entryHeader->Next == lastVariableFlag) - subtype = Subtypes::DataNvarEntry; + + // Obtain text + if (!entry_body->_is_null_ascii_name()) { + text = entry_body->ascii_name().c_str(); } - - // Do not parse further - goto parsing_done; - } - - // Get entry name - { - UINT32 nameOffset = (entryHeader->Attributes & NVRAM_NVAR_ENTRY_GUID) ? sizeof(EFI_GUID) : sizeof(UINT8); // GUID can be stored with the variable or in a separate store, so there will only be an index of it - CHAR8* namePtr = (CHAR8*)(entryHeader + 1) + nameOffset; - UINT32 nameSize = 0; - if (entryHeader->Attributes & NVRAM_NVAR_ENTRY_ASCII_NAME) { // Name is stored as ASCII string of CHAR8s - text = UString(namePtr); - nameSize = (UINT32)(text.length() + 1); + else if (!entry_body->_is_null_ucs2_name()) { + UByteArray temp; + for (const auto & ch : *entry_body->ucs2_name()->ucs2_chars()) { + temp += UByteArray((const char*)&ch, sizeof(ch)); + } + text = uFromUcs2(temp.constData()); } - else { // Name is stored as UCS2 string of CHAR16s - text = uFromUcs2(namePtr); - nameSize = (UINT32)((text.length() + 1) * 2); + + // Obtain GUID + if (!entry_body->_is_null_guid()) { // GUID is stored in the entry itself + const EFI_GUID g = readUnaligned((EFI_GUID*)entry_body->guid().c_str()); + name = guidToUString(g); + guid = guidToUString(g, false); } - - // Get entry GUID - if (entryHeader->Attributes & NVRAM_NVAR_ENTRY_GUID) { // GUID is strored in the variable itself - name = guidToUString(readUnaligned((EFI_GUID*)(entryHeader + 1))); - guid = guidToUString(readUnaligned((EFI_GUID*)(entryHeader + 1)), false); - } - // GUID is stored in GUID list at the end of the store - else { - guidIndex = *(UINT8*)(entryHeader + 1); - if (guidsInStore < guidIndex + 1) - guidsInStore = guidIndex + 1; - + else { // GUID is stored in GUID store at the end of the NVAR store + // Grow the GUID store if needed + if (guidsInStore < entry_body->guid_index() + 1) + guidsInStore = entry_body->guid_index() + 1; + // The list begins at the end of the store and goes backwards - const EFI_GUID* guidPtr = (const EFI_GUID*)(data.constData() + data.size()) - 1 - guidIndex; - name = guidToUString(readUnaligned(guidPtr)); - guid = guidToUString(readUnaligned(guidPtr), false); - hasGuidIndex = true; + const EFI_GUID g = readUnaligned((EFI_GUID*)(nvar.constData() + nvar.size()) - (entry_body->guid_index() + 1)); + name = guidToUString(g); + guid = guidToUString(g, false); } - - // Include name and GUID into the header and remove them from body - header = data.mid(offset, sizeof(NVAR_ENTRY_HEADER) + nameOffset + nameSize); - body = body.mid(nameOffset + nameSize); + +processing_done: + // This feels hacky, but I haven't found a way to ask Kaitai for raw bytes + header = nvar.mid(entry->offset(), sizeof(NVAR_ENTRY_HEADER) + entry_body->data_start_offset()); + body = nvar.mid(entry->offset() + sizeof(NVAR_ENTRY_HEADER) + entry_body->data_start_offset(), entry_body->data_size()); + tail = nvar.mid(entry->end_offset() - entry_body->extended_header_size(), entry_body->extended_header_size()); + + // Add GUID info for valid entries + if (!guid.isEmpty()) + info += UString("Variable GUID: ") + guid + "\n"; + + // Add GUID index information + if (!entry_body->_is_null_guid_index()) + info += usprintf("GUID index: %u\n", entry_body->guid_index()); + + // Add header, body and extended data info + info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nTail size: %Xh (%u)", + entry->size(), entry->size(), + (UINT32)header.size(), (UINT32)header.size(), + (UINT32)body.size(), (UINT32)body.size(), + (UINT32)tail.size(), (UINT32)tail.size()); + + // Add attributes info + const NVAR_ENTRY_HEADER entryHeader = readUnaligned((NVAR_ENTRY_HEADER*)header.constData()); + info += usprintf("\nAttributes: %02Xh", entryHeader.Attributes); + + // Translate attributes to text + if (entryHeader.Attributes != 0x00 && entryHeader.Attributes != 0xFF) + info += UString(" (") + nvarAttributesToUString(entryHeader.Attributes) + UString(")"); + + // Add next node info + if (entry->next() != 0xFFFFFF) + info += usprintf("\nNext node at offset: %Xh", localOffset + entry->offset() + (UINT32)entry->next()); + + // Add extended header info + if (entry_body->extended_header_size() > 0) { + info += usprintf("\nExtended header size: %Xh (%u)", + entry_body->extended_header_size(), entry_body->extended_header_size()); + + const UINT8 extendedAttributes = *tail.constData(); + info += usprintf("\nExtended attributes: %02Xh (", extendedAttributes) + nvarExtendedAttributesToUString(extendedAttributes) + UString(")"); + + // Add checksum + if (!entry_body->_is_null_extended_header_checksum()) { + UINT8 calculatedChecksum = 0; + UByteArray wholeBody = body + tail; + + // Include entry body + UINT8* start = (UINT8*)wholeBody.constData(); + for (UINT8* p = start; p < start + wholeBody.size(); p++) { + calculatedChecksum += *p; + } + // Include entry size and flags + start = (UINT8*)&entryHeader.Size; + for (UINT8*p = start; p < start + sizeof(UINT16); p++) { + calculatedChecksum += *p; + } + // Include entry attributes + calculatedChecksum += entryHeader.Attributes; + info += usprintf("\nChecksum: %02Xh, ", entry_body->extended_header_checksum()) + + (calculatedChecksum ? usprintf(", invalid, should be %02Xh", 0x100 - calculatedChecksum) : UString(", valid")); + } + + // Add timestamp + if (!entry_body->_is_null_extended_header_timestamp()) + info += usprintf("\nTimestamp: %" PRIX64 "h", entry_body->extended_header_timestamp()); + + // Add hash + if (!entry_body->_is_null_extended_header_hash()) { + UByteArray hash = UByteArray(entry_body->extended_header_hash().c_str(), entry_body->extended_header_hash().size()); + info += UString("\nHash: ") + UString(hash.toHex().constData()); + } + } + + // Add tree item + UModelIndex varIndex = model->addItem(localOffset + entry->offset(), Types::NvarEntry, subtype, name, text, info, header, body, tail, Fixed, index); + currentEntryIndex++; + + // Set parsing data + model->setParsingData(varIndex, UByteArray((const char*)&pdata, sizeof(pdata))); + + // Try parsing the entry data as NVAR storage if it begins with NVAR signature + if ((subtype == Subtypes::DataNvarEntry || subtype == Subtypes::FullNvarEntry) + && body.size() >= 4 && readUnaligned((const UINT32*)body.constData()) == NVRAM_NVAR_ENTRY_SIGNATURE) + (void)parseNvarStore(varIndex); } - parsing_done: - UString info; - - // Rename invalid entries according to their types - pdata.isValid = TRUE; - if (isInvalid) { - name = UString("Invalid"); - subtype = Subtypes::InvalidNvarEntry; - pdata.isValid = FALSE; - } - else if (isInvalidLink) { - name = UString("Invalid link"); - subtype = Subtypes::InvalidLinkNvarEntry; - pdata.isValid = FALSE; - } - else // Add GUID info for valid entries - info += UString("Variable GUID: ") + guid + "\n"; - - // Add GUID index information - if (hasGuidIndex) - info += usprintf("GUID index: %u\n", guidIndex); - - // Add header, body and extended data info - info += usprintf("Full size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)", - entryHeader->Size, entryHeader->Size, - (UINT32)header.size(), (UINT32)header.size(), - (UINT32)body.size(), (UINT32)body.size()); - - // Add attributes info - info += usprintf("\nAttributes: %02Xh", entryHeader->Attributes); - // Translate attributes to text - if (entryHeader->Attributes && entryHeader->Attributes != 0xFF) - info += UString(" (") + nvarAttributesToUString(entryHeader->Attributes) + UString(")"); - - // Add next node info - if (!isInvalid && entryHeader->Next != lastVariableFlag) - info += usprintf("\nNext node at offset: %Xh", localOffset + offset + entryHeader->Next); - - // Add extended header info - if (hasExtendedHeader) { - info += usprintf("\nExtended header size: %Xh (%u)\nExtended attributes: %Xh (", - extendedHeaderSize, extendedHeaderSize, - extendedAttributes) + nvarExtendedAttributesToUString(extendedAttributes) + UString(")"); - - // Add checksum - if (hasChecksum) - info += usprintf("\nChecksum: %02Xh", storedChecksum) + - (calculatedChecksum ? usprintf(", invalid, should be %02Xh", 0x100 - calculatedChecksum) : UString(", valid")); - - // Add timestamp - if (hasTimestamp) - info += usprintf("\nTimestamp: %" PRIX64 "h", timestamp); - - // Add hash - if (hasHash) - info += UString("\nHash: ") + UString(hash.toHex().constData()); - } - - // Add tree item - UModelIndex varIndex = model->addItem(localOffset + offset, Types::NvarEntry, subtype, name, text, info, header, body, tail, Fixed, index); - - // Set parsing data for created entry - model->setParsingData(varIndex, UByteArray((const char*)&pdata, sizeof(pdata))); - - // Show messages - if (msgUnknownExtDataFormat) msg(usprintf("%s: unknown extended data format", __FUNCTION__), varIndex); - if (msgExtHeaderTooLong) msg(usprintf("%s: extended header size (%Xh) is greater than body size (%Xh)", __FUNCTION__, - extendedHeaderSize, (UINT32)body.size()), varIndex); - if (msgExtDataTooShort) msg(usprintf("%s: extended header size (%Xh) is too small for timestamp and hash", __FUNCTION__, - (UINT32)tail.size()), varIndex); - - // Try parsing the entry data as NVAR storage if it begins with NVAR signature - if ((subtype == Subtypes::DataNvarEntry || subtype == Subtypes::FullNvarEntry) - && body.size() >= 4 && readUnaligned((const UINT32*)body.constData()) == NVRAM_NVAR_ENTRY_SIGNATURE) - parseNvarStore(varIndex); - - // Move to next exntry - offset += entryHeader->Size; } - + catch (...) { + msg(usprintf("%s: unable to parse AMI NVAR storage", __FUNCTION__), index); + return U_INVALID_STORE; + } + return U_SUCCESS; } diff --git a/common/types.cpp b/common/types.cpp index f397316..32e3791 100755 --- a/common/types.cpp +++ b/common/types.cpp @@ -59,6 +59,7 @@ UString itemTypeToUString(const UINT8 type) case Types::EvsaStore: return UString("EVSA store"); case Types::CmdbStore: return UString("CMDB store"); case Types::FlashMapStore: return UString("FlashMap store"); + case Types::NvarGuidStore: return UString("NVAR GUID store"); case Types::NvarEntry: return UString("NVAR entry"); case Types::VssEntry: return UString("VSS entry"); case Types::FsysEntry: return UString("Fsys entry"); diff --git a/common/types.h b/common/types.h index 62c8929..5785a96 100755 --- a/common/types.h +++ b/common/types.h @@ -51,6 +51,7 @@ namespace Types { EvsaStore, FlashMapStore, CmdbStore, + NvarGuidStore, NvarEntry, VssEntry, FsysEntry, diff --git a/common/umemstream.h b/common/umemstream.h new file mode 100644 index 0000000..243b5c2 --- /dev/null +++ b/common/umemstream.h @@ -0,0 +1,58 @@ +/* umemstream.h + + Copyright (c) 2023, 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 UMEMSTREAM_H +#define UMEMSTREAM_H + +#include + +// NOTE: this implementation is certainly not a valid replacement to std::stringstream +// NOTE: because it only supports getting through the buffer once +// NOTE: however, we already do it this way, so it's enough for practical purposes + +class umembuf : public std::streambuf { +public: + umembuf(const char *p, size_t l) { + setg((char*)p, (char*)p, (char*)p + l); + } + + pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override + { + (void)which; + if (dir == std::ios_base::cur) + gbump((int)off); + else if (dir == std::ios_base::end) + setg(eback(), egptr() + off, egptr()); + else if (dir == std::ios_base::beg) + setg(eback(), eback() + off, egptr()); + return gptr() - eback(); + } + + pos_type seekpos(pos_type sp, std::ios_base::openmode which) override + { + return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which); + } +}; + +class umemstream : public std::istream { +public: + umemstream(const char *p, size_t l) : std::istream(&buffer_), + buffer_(p, l) { + rdbuf(&buffer_); + } + +private: + umembuf buffer_; +}; + +#endif // UMEMSTREAM_H diff --git a/common/ustring.cpp b/common/ustring.cpp index 1786a60..2493a41 100644 --- a/common/ustring.cpp +++ b/common/ustring.cpp @@ -113,12 +113,10 @@ UString uFromUcs2(const char* str, size_t max_len) UString msg; const char *str8 = str; size_t rest = (max_len == 0) ? SIZE_MAX : max_len; - if (max_len == 0) { - while (str8[0] && rest) { - msg += str8[0]; - str8 += 2; - rest--; - } + while (str8[0] && rest) { + msg += str8[0]; + str8 += 2; + rest--; } return msg; -} \ No newline at end of file +} diff --git a/common/utility.cpp b/common/utility.cpp index fcd37a7..336b7c7 100755 --- a/common/utility.cpp +++ b/common/utility.cpp @@ -146,6 +146,7 @@ void fixFileName(UString &name, bool replaceSpaces) // Returns text representation of error code UString errorCodeToUString(USTATUS errorCode) { + // TODO: improve switch (errorCode) { case U_SUCCESS: return UString("Success"); case U_NOT_IMPLEMENTED: return UString("Not implemented"); @@ -165,7 +166,6 @@ UString errorCodeToUString(USTATUS errorCode) case U_VOLUMES_NOT_FOUND: return UString("UEFI volumes not found"); case U_INVALID_VOLUME: return UString("Invalid UEFI volume"); case U_VOLUME_REVISION_NOT_SUPPORTED: return UString("Volume revision not supported"); - //case U_VOLUME_GROW_FAILED: return UString("Volume grow failed"); case U_UNKNOWN_FFS: return UString("Unknown file system"); case U_INVALID_FILE: return UString("Invalid file"); case U_INVALID_SECTION: return UString("Invalid section"); @@ -177,26 +177,19 @@ UString errorCodeToUString(USTATUS errorCode) case U_UNKNOWN_COMPRESSION_TYPE: return UString("Unknown compression type"); case U_UNKNOWN_EXTRACT_MODE: return UString("Unknown extract mode"); case U_UNKNOWN_REPLACE_MODE: return UString("Unknown replace mode"); - //case U_UNKNOWN_INSERT_MODE: return UString("Unknown insert mode"); case U_UNKNOWN_IMAGE_TYPE: return UString("Unknown executable image type"); case U_UNKNOWN_PE_OPTIONAL_HEADER_TYPE: return UString("Unknown PE optional header type"); case U_UNKNOWN_RELOCATION_TYPE: return UString("Unknown relocation type"); - //case U_GENERIC_CALL_NOT_SUPPORTED: return UString("Generic call not supported"); - //case U_VOLUME_BASE_NOT_FOUND: return UString("Volume base address not found"); - //case U_PEI_CORE_ENTRY_POINT_NOT_FOUND: return UString("PEI core entry point not found"); case U_COMPLEX_BLOCK_MAP: return UString("Block map structure too complex for correct analysis"); case U_DIR_ALREADY_EXIST: return UString("Directory already exists"); case U_DIR_CREATE: return UString("Directory can't be created"); case U_DIR_CHANGE: return UString("Change directory failed"); - //case U_UNKNOWN_PATCH_TYPE: return UString("Unknown patch type"); - //case U_PATCH_OFFSET_OUT_OF_BOUNDS: return UString("Patch offset out of bounds"); - //case U_INVALID_SYMBOL: return UString("Invalid symbol"); - //case U_NOTHING_TO_PATCH: return UString("Nothing to patch"); case U_DEPEX_PARSE_FAILED: return UString("Dependency expression parsing failed"); case U_TRUNCATED_IMAGE: return UString("Image is truncated"); case U_INVALID_CAPSULE: return UString("Invalid capsule"); case U_STORES_NOT_FOUND: return UString("Stores not found"); case U_INVALID_STORE_SIZE: return UString("Invalid store size"); + case U_INVALID_STORE: return UString("Invalid store"); default: return usprintf("Unknown error %02lX", errorCode); } } diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index eb99ad3..5a9f9a4 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -31,6 +31,7 @@ SET(PROJECT_SOURCES ../common/LZMA/SDK/C/LzmaDec.c ../common/Tiano/EfiTianoDecompress.c ../common/ustring.cpp + ../common/generated/ami_nvar.cpp ../common/generated/intel_acbp_v1.cpp ../common/generated/intel_acbp_v2.cpp ../common/generated/intel_keym_v1.cpp @@ -86,7 +87,7 @@ ADD_DEFINITIONS( ADD_EXECUTABLE(ffsparser_fuzzer ${PROJECT_SOURCES}) -IF(USE_AFL_DRIVER) +IF(NOT USE_AFL_DRIVER) TARGET_COMPILE_OPTIONS(ffsparser_fuzzer PRIVATE -O1 -fno-omit-frame-pointer -g -ggdb3 -fsanitize=fuzzer,address,undefined -fsanitize-address-use-after-scope -fno-sanitize-recover=undefined) TARGET_LINK_LIBRARIES(ffsparser_fuzzer PRIVATE -fsanitize=fuzzer,address,undefined) ELSE()