diff options
| author | Brandon Miller <brandon@vector35.com> | 2024-11-22 08:20:14 -0500 |
|---|---|---|
| committer | Brandon Miller <brandon@vector35.com> | 2024-11-22 08:20:53 -0500 |
| commit | c74fa0d5f974efa4ccd569a821701956063a31e9 (patch) | |
| tree | ed20c1e13e22df9c5d3867294654a28931f4fbe7 | |
| parent | 3617086969462676272786827089388047502883 (diff) | |
C++ and Python API for Firmware Ninja
| -rw-r--r-- | binaryninjaapi.h | 123 | ||||
| -rw-r--r-- | binaryninjacore.h | 66 | ||||
| -rw-r--r-- | firmwareninja.cpp | 292 | ||||
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/firmwareninja.py | 468 | ||||
| -rw-r--r-- | python/variable.py | 4 |
6 files changed, 921 insertions, 33 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 2de2aa63..762cd275 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -18369,6 +18369,129 @@ namespace BinaryNinja { bool IsAborted(); }; + + /*! + \ingroup firmwareninja + */ + struct FirmwareNinjaDevice + { + std::string name; + uint64_t start; + uint64_t end; + std::string info; + }; + + /*! + \ingroup firmwareninja + */ + struct FirmwareNinjaFunctionMemoryAccesses + { + uint64_t start; + size_t count; + std::vector<BNFirmwareNinjaMemoryAccess> accesses; + }; + + /*! + \ingroup firmwareninja + */ + struct FirmwareNinjaDeviceAccesses + { + std::string name; + size_t total; + size_t unique; + }; + + /*! FirmwareNinja is a class containing features specific to embedded firmware analysis. This class is only + available in the Ultimate Edition of Binary Ninja. + + \ingroup firmwareninja + */ + class FirmwareNinja + { + Ref<BinaryView> m_view; + BNFirmwareNinja* m_object; + + public: + FirmwareNinja(Ref<BinaryView> view); + ~FirmwareNinja(); + + /*! Store a user-defined Firmware Ninja device to the binary view metadata + + \param device Hardware device information + \return true on success, false otherwise + */ + bool StoreCustomDevice(FirmwareNinjaDevice& device); + + /*! Remove a user-defined Firmware Ninja device from the binary view metadata + + \param name Name of the device to remove + \return true on success, false otherwise + */ + bool RemoveCustomDevice(const std::string& name); + + /*! Query all user-defined Firmware Ninja devices from the binary view metadata + + \return Vector of user-defined Firmware Ninja devices + */ + std::vector<FirmwareNinjaDevice> QueryCustomDevices(); + + /*! Query names of all boards that are compatable with the current binary view and contain bundled device + definitions + + \return Vector of board names + */ + std::vector<std::string> QueryBoardNames(); + + /*! Query Firmware Ninja device definitions for the specified board + + \param board Name of the board to query devices for + \return Vector of Firmware Ninja device definitions + */ + std::vector<FirmwareNinjaDevice> QueryDevicesForBoard(const std::string& board); + + /*! Find sections in the binary with Firmware Ninja heuristics and entropy analysis + + \param board highCodeEntropyThreshold High threshold for code entropy value range + \param board lowCodeEntropyThreshold Low threshold for code entropy value range + \param blockSize Size of blocks to analyze + \param mode Analysis mode of operation + \return Vector of Firmware Ninja section information + */ + std::vector<BNFirmwareNinjaSection> FindSections(float highCodeEntropyThreshold, float lowCodeEntropyThreshold, + size_t blockSize, BNFirmwareNinjaSectionAnalysisMode mode); + + /*! Find functions that access memory-mapped I/O and other non-file backed memory regions + + \param progress Progress callback function + \param progressContext Progress context + \return Vector of Firmware Ninja function memory accesses information + */ + std::vector<FirmwareNinjaFunctionMemoryAccesses> GetFunctionMemoryAccesses(BNProgressFunction progress, + void* progressContext); + + /*! Store Firmware Ninja function memory accesses information in the binary view metadata + + \param fma Vector of Firmware Ninja function memory accesses information + */ + void StoreFunctionMemoryAccesses(const std::vector<FirmwareNinjaFunctionMemoryAccesses>& fma); + + /*! Query cached Firmware Ninja function memory accesses information from the binary view metadata + + \return Vector of Firmware Ninja memory analyis information + */ + std::vector<FirmwareNinjaFunctionMemoryAccesses> QueryFunctionMemoryAccesses(); + + /*! Compute number of accesses mad to memory-mapped hardware devices for each board that is compatible with the + current architecture + + \param fma Vector of Firmware Ninja function memory accesses information + \return Vector of Firmware Ninja device accesses information for each board + */ + std::vector<FirmwareNinjaDeviceAccesses> GetBoardDeviceAccesses( + const std::vector<FirmwareNinjaFunctionMemoryAccesses>& fma); + }; + + /*! \ingroup demangler */ diff --git a/binaryninjacore.h b/binaryninjacore.h index ff1f3241..bb60b931 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -37,14 +37,14 @@ // Current ABI version for linking to the core. This is incremented any time // there are changes to the API that affect linking, including new functions, // new types, or modifications to existing functions or types. -#define BN_CURRENT_CORE_ABI_VERSION 83 +#define BN_CURRENT_CORE_ABI_VERSION 84 // Minimum ABI version that is supported for loading of plugins. Plugins that // are linked to an ABI version less than this will not be able to load and // will require rebuilding. The minimum version is increased when there are // incompatible changes that break binary compatibility, such as changes to // existing types or functions. -#define BN_MINIMUM_CORE_ABI_VERSION 81 +#define BN_MINIMUM_CORE_ABI_VERSION 82 #ifdef __GNUC__ #ifdef BINARYNINJACORE_LIBRARY @@ -3497,37 +3497,38 @@ extern "C" float entropy; } BNFirmwareNinjaSection; - typedef enum BNFirmwareNinjaMMIOHeuristic + typedef enum BNFirmwareNinjaMemoryHeuristic { - NoMMIOHeuristic, - HasReadBarrierMMIOHeuristic, - HasWriteBarrierMMIOHeuristic, - StoreToOOBMemoryMMIOHeuristic, - LoadFromOOBMemoryMMIOHeuristic, - RepeatLoadStoreMMIOHeuristic, - } BNFirmwareNinjaMMIOHeuristic; + NoMemoryHeuristic, + HasReadBarrierMemoryHeuristic, + HasWriteBarrierMemoryHeuristic, + StoreToOOBMemoryMemoryHeuristic, + LoadFromOOBMemoryMemoryHeuristic, + RepeatLoadStoreMemoryHeuristic, + } BNFirmwareNinjaMemoryHeuristic; - typedef enum BNFirmwareNinjaMMIOAccessType + typedef enum BNFirmwareNinjaMemoryAccessType { - NoMMIOAccessType, - ReadMMIOAccessType, - WriteMMIOAccessType, - } BNFirmwareNinjaMMIOAccessType; + NoMemoryAccessType, + ReadMemoryAccessType, + WriteMemoryAccessType, + } BNFirmwareNinjaMemoryAccessType; - typedef struct BNFirmwareNinjaMMIOAccess + typedef struct BNFirmwareNinjaMemoryAccess { uint64_t instrAddress; - BNRegisterValue reg; - BNFirmwareNinjaMMIOHeuristic heuristic; - BNFirmwareNinjaMMIOAccessType type; - } BNFirmwareNinjaMMIOAccess; + BNRegisterValue memAddress; + BNFirmwareNinjaMemoryHeuristic heuristic; + BNFirmwareNinjaMemoryAccessType type; + BNRegisterValue value; + } BNFirmwareNinjaMemoryAccess; - typedef struct BNFirmwareNinjaFunctionMMIOInfo + typedef struct BNFirmwareNinjaFunctionMemoryAccesses { uint64_t start; size_t count; - BNFirmwareNinjaMMIOAccess** accesses; - } BNFirmwareNinjaFunctionMMIOInfo; + BNFirmwareNinjaMemoryAccess** accesses; + } BNFirmwareNinjaFunctionMemoryAccesses; typedef struct BNFirmwareNinjaDeviceAccesses { @@ -8025,22 +8026,21 @@ extern "C" // FirmwareNinja BINARYNINJACOREAPI BNFirmwareNinja* BNCreateFirmwareNinja(BNBinaryView *view); - BINARYNINJACOREAPI bool BNFirmwareNinjaAddCustomDevice(BNFirmwareNinja* fn, const char* name, uint64_t start, uint64_t end, const char* info); - BINARYNINJACOREAPI bool BNFirmwareNinjaDeleteCustomDevice(BNFirmwareNinja* fn, const char* name); + BINARYNINJACOREAPI void BNFreeFirmwareNinja(BNFirmwareNinja* fn); + BINARYNINJACOREAPI bool BNFirmwareNinjaStoreCustomDevice(BNFirmwareNinja* fn, const char* name, uint64_t start, uint64_t end, const char* info); + BINARYNINJACOREAPI bool BNFirmwareNinjaRemoveCustomDevice(BNFirmwareNinja* fn, const char* name); BINARYNINJACOREAPI int BNFirmwareNinjaQueryCustomDevices(BNFirmwareNinja* fn, BNFirmwareNinjaDevice** devices); BINARYNINJACOREAPI void BNFirmwareNinjaFreeDevices(BNFirmwareNinjaDevice *devices, int size); BINARYNINJACOREAPI int BNFirmwareNinjaQueryBoardNamesForArchitecture(BNFirmwareNinja* fn, BNArchitecture* arch, char ***boards); BINARYNINJACOREAPI void BNFirmwareNinjaFreeBoardNames(char **boards, int size); BINARYNINJACOREAPI int BNFirmwareNinjaQueryBoardDevices(BNFirmwareNinja* fn, BNArchitecture* arch, const char* board, BNFirmwareNinjaDevice** devices); - BINARYNINJACOREAPI int BNFirmwareNinjaFindSectionsWithEntropy(BNFirmwareNinja* fn, BNFirmwareNinjaSection** sections, - float highCodeEntropyThreshold, float lowCodeEntropyThreshold, size_t blockSize, BNFirmwareNinjaSectionAnalysisMode mode); + BINARYNINJACOREAPI int BNFirmwareNinjaFindSectionsWithEntropy(BNFirmwareNinja* fn, BNFirmwareNinjaSection** sections, float highCodeEntropyThreshold, float lowCodeEntropyThreshold, size_t blockSize, BNFirmwareNinjaSectionAnalysisMode mode); BINARYNINJACOREAPI void BNFirmwareNinjaFreeSections(BNFirmwareNinjaSection *sections, int size); - BINARYNINJACOREAPI int BNFirmwareNinjaFindMMIOAccesses(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMMIOInfo*** mmio, BNProgressFunction progress, void* progressContext); - BINARYNINJACOREAPI void BNFirmwareNinjaFreeMMIOAccesses(BNFirmwareNinjaFunctionMMIOInfo **mmio, int size); - BINARYNINJACOREAPI void BNFirmwareNinjaSaveMMIOAccessesToMetadata(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMMIOInfo** mmio, int size); - BINARYNINJACOREAPI int BNFirmwareNinjaLoadMMIOAccessesFromMetadata(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMMIOInfo*** mmio); - BINARYNINJACOREAPI int BNFirmwareNinjaGetBoardDeviceAccesses(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMMIOInfo** mmio, int size, - BNFirmwareNinjaDeviceAccesses** accesses, BNArchitecture* arch); + BINARYNINJACOREAPI int BNFirmwareNinjaGetFunctionMemoryAccesses(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMemoryAccesses*** mmio, BNProgressFunction progress, void* progressContext); + BINARYNINJACOREAPI void BNFirmwareNinjaFreeFunctionMemoryAccesses(BNFirmwareNinjaFunctionMemoryAccesses **mmio, int size); + BINARYNINJACOREAPI void BNFirmwareNinjaStoreFunctionMemoryAccessesToMetadata(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMemoryAccesses** mmio, int size); + BINARYNINJACOREAPI int BNFirmwareNinjaQueryFunctionMemoryAccessesFromMetadata(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMemoryAccesses*** mmio); + BINARYNINJACOREAPI int BNFirmwareNinjaGetBoardDeviceAccesses(BNFirmwareNinja* fn, BNFirmwareNinjaFunctionMemoryAccesses** mmio, int size, BNFirmwareNinjaDeviceAccesses** accesses, BNArchitecture* arch); BINARYNINJACOREAPI void BNFirmwareNinjaFreeBoardDeviceAccesses(BNFirmwareNinjaDeviceAccesses *accesses, int size); #ifdef __cplusplus } diff --git a/firmwareninja.cpp b/firmwareninja.cpp new file mode 100644 index 00000000..5bf623bd --- /dev/null +++ b/firmwareninja.cpp @@ -0,0 +1,292 @@ +// Copyright (c) 2015-2024 Vector 35 Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "binaryninjaapi.h" +#include "binaryninjacore.h" + +using namespace BinaryNinja; + + +static BNFirmwareNinjaFunctionMemoryAccesses** MemoryInfoVectorToArray( + const std::vector<FirmwareNinjaFunctionMemoryAccesses>& fma) +{ + BNFirmwareNinjaFunctionMemoryAccesses** result = new BNFirmwareNinjaFunctionMemoryAccesses*[fma.size()]; + for (size_t i = 0; i < fma.size(); i++) + { + result[i] = new BNFirmwareNinjaFunctionMemoryAccesses; + result[i]->start = fma[i].start; + result[i]->count = fma[i].count; + result[i]->accesses = new BNFirmwareNinjaMemoryAccess*[fma[i].count]; + for (size_t j = 0; j < fma[i].count; j++) + { + result[i]->accesses[j] = new BNFirmwareNinjaMemoryAccess; + std::memcpy(result[i]->accesses[j], &fma[i].accesses[j], sizeof(BNFirmwareNinjaMemoryAccess)); + } + } + + return result; +} + + +static void FreeMemoryInfoArray(BNFirmwareNinjaFunctionMemoryAccesses** fma, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + for (size_t j = 0; j < fma[i]->count; j++) + delete fma[i]->accesses[j]; + + delete[] fma[i]->accesses; + delete fma[i]; + } +} + + +FirmwareNinja::FirmwareNinja(Ref<BinaryView> view) +{ + m_view = view; + m_object = BNCreateFirmwareNinja(view->GetObject()); +} + + +FirmwareNinja::~FirmwareNinja() +{ + BNFreeFirmwareNinja(m_object); +} + + +bool FirmwareNinja::StoreCustomDevice(FirmwareNinjaDevice& device) +{ + return BNFirmwareNinjaStoreCustomDevice(m_object, device.name.c_str(), + device.start, device.end, device.info.c_str()); +} + + +bool FirmwareNinja::RemoveCustomDevice(const std::string& name) +{ + return BNFirmwareNinjaRemoveCustomDevice(m_object, name.c_str()); +} + + +std::vector<FirmwareNinjaDevice> FirmwareNinja::QueryCustomDevices() +{ + std::vector<FirmwareNinjaDevice> result; + BNFirmwareNinjaDevice* devices; + int count = BNFirmwareNinjaQueryCustomDevices(m_object, &devices); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back({ + devices[i].name, + devices[i].start, + devices[i].end, + devices[i].info + }); + + BNFirmwareNinjaFreeDevices(devices, count); + return result; +} + + +std::vector<std::string> FirmwareNinja::QueryBoardNames() +{ + std::vector<std::string> result; + char** boards; + auto platform = m_view->GetDefaultPlatform(); + if (!platform) + return result; + + auto arch = platform->GetArchitecture(); + if (!arch) + return result; + + int count = BNFirmwareNinjaQueryBoardNamesForArchitecture(m_object, arch->GetObject(), &boards); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back(boards[i]); + + BNFirmwareNinjaFreeBoardNames(boards, count); + sort(result.begin(), result.end()); + return result; +} + + +std::vector<FirmwareNinjaDevice> FirmwareNinja::QueryDevicesForBoard(const std::string& board) +{ + std::vector<FirmwareNinjaDevice> result; + BNFirmwareNinjaDevice* devices; + auto platform = m_view->GetDefaultPlatform(); + if (!platform) + return result; + + auto arch = platform->GetArchitecture(); + if (!arch) + return result; + + int count = BNFirmwareNinjaQueryBoardDevices(m_object, arch->GetObject(), board.c_str(), &devices); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back({ + devices[i].name, + devices[i].start, + devices[i].end, + devices[i].info + }); + + BNFirmwareNinjaFreeDevices(devices, count); + return result; +} + + +std::vector<BNFirmwareNinjaSection> FirmwareNinja::FindSections(float highCodeEntropyThreshold, + float lowCodeEntropyThreshold, size_t blockSize, BNFirmwareNinjaSectionAnalysisMode mode) +{ + std::vector<BNFirmwareNinjaSection> result; + BNFirmwareNinjaSection* sections; + int count = BNFirmwareNinjaFindSectionsWithEntropy(m_object, §ions, highCodeEntropyThreshold, + lowCodeEntropyThreshold, blockSize, mode); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back(sections[i]); + + BNFirmwareNinjaFreeSections(sections, count); + return result; +} + + +std::vector<FirmwareNinjaFunctionMemoryAccesses> FirmwareNinja::GetFunctionMemoryAccesses(BNProgressFunction progress, + void* progressContext) +{ + std::vector<FirmwareNinjaFunctionMemoryAccesses> result; + BNFirmwareNinjaFunctionMemoryAccesses** fma; + int count = BNFirmwareNinjaGetFunctionMemoryAccesses(m_object, &fma, progress, progressContext); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + FirmwareNinjaFunctionMemoryAccesses info; + info.start = fma[i]->start; + info.count = fma[i]->count; + for (size_t j = 0; j < info.count; j++) + { + BNFirmwareNinjaMemoryAccess access; + std::memcpy(&access, fma[i]->accesses[j], sizeof(BNFirmwareNinjaMemoryAccess)); + info.accesses.push_back(access); + } + + result.push_back(info); + } + + BNFirmwareNinjaFreeFunctionMemoryAccesses(fma, count); + std::sort(result.begin(), result.end(), [](const FirmwareNinjaFunctionMemoryAccesses& a, + const FirmwareNinjaFunctionMemoryAccesses& b) { + return a.count > b.count; + }); + + return result; +} + + +void FirmwareNinja::StoreFunctionMemoryAccesses(const std::vector<FirmwareNinjaFunctionMemoryAccesses>& fma) +{ + BNFirmwareNinjaFunctionMemoryAccesses** fmaArray = MemoryInfoVectorToArray(fma); + BNFirmwareNinjaStoreFunctionMemoryAccessesToMetadata(m_object, fmaArray, fma.size()); + FreeMemoryInfoArray(fmaArray, fma.size()); +} + + +std::vector<FirmwareNinjaFunctionMemoryAccesses> FirmwareNinja::QueryFunctionMemoryAccesses() +{ + std::vector<FirmwareNinjaFunctionMemoryAccesses> result; + BNFirmwareNinjaFunctionMemoryAccesses** fma; + int count = BNFirmwareNinjaQueryFunctionMemoryAccessesFromMetadata(m_object, &fma); + if (count <= 0) + return result; + + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + FirmwareNinjaFunctionMemoryAccesses info; + info.start = fma[i]->start; + info.count = fma[i]->count; + for (size_t j = 0; j < info.count; j++) + { + BNFirmwareNinjaMemoryAccess access; + std::memcpy(&access, fma[i]->accesses[j], sizeof(BNFirmwareNinjaMemoryAccess)); + info.accesses.push_back(access); + } + + result.push_back(info); + } + + BNFirmwareNinjaFreeFunctionMemoryAccesses(fma, count); + std::sort(result.begin(), result.end(), [](const FirmwareNinjaFunctionMemoryAccesses& a, + const FirmwareNinjaFunctionMemoryAccesses& b) { + return a.count > b.count; + }); + + return result; +} + + +std::vector<FirmwareNinjaDeviceAccesses> FirmwareNinja::GetBoardDeviceAccesses( + const std::vector<FirmwareNinjaFunctionMemoryAccesses>& fma) +{ + std::vector<FirmwareNinjaDeviceAccesses> result; + auto platform = m_view->GetDefaultPlatform(); + if (!platform) + return result; + + auto arch = platform->GetArchitecture(); + if (!arch) + return result; + + BNFirmwareNinjaFunctionMemoryAccesses** fmaArray = MemoryInfoVectorToArray(fma); + BNFirmwareNinjaDeviceAccesses* accesses; + int count = BNFirmwareNinjaGetBoardDeviceAccesses(m_object, fmaArray, fma.size(), &accesses, arch->GetObject()); + if (count <= 0) + { + FreeMemoryInfoArray(fmaArray, fma.size()); + return result; + } + + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back({accesses[i].name, accesses[i].total, accesses[i].unique}); + + BNFirmwareNinjaFreeBoardDeviceAccesses(accesses, count); + sort(result.begin(), result.end(), [](const FirmwareNinjaDeviceAccesses& a, const FirmwareNinjaDeviceAccesses& b) { + return a.total > b.total; + }); + + return result; +} diff --git a/python/__init__.py b/python/__init__.py index b6a3de97..9b5a394e 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -96,6 +96,7 @@ warnings.filterwarnings('once', '', DeprecatedWarning) # Only load Enterprise Client support on Ultimate builds if core.BNGetProduct() == "Binary Ninja Enterprise Client" or core.BNGetProduct() == "Binary Ninja Ultimate": from .enterprise import * + from .firmwareninja import * def shutdown(): diff --git a/python/firmwareninja.py b/python/firmwareninja.py new file mode 100644 index 00000000..b000ecc9 --- /dev/null +++ b/python/firmwareninja.py @@ -0,0 +1,468 @@ +# coding=utf-8 +# Copyright (c) 2015-2024 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import ctypes +from dataclasses import dataclass +from typing import Callable +from .binaryview import BinaryView +from .variable import RegisterValue +from .enums import ( + FirmwareNinjaMemoryHeuristic, + FirmwareNinjaMemoryAccessType, + FirmwareNinjaSectionAnalysisMode, + FirmwareNinjaSectionType, +) +from .function import Function +from . import _binaryninjacore as core + + +@dataclass +class FirmwareNinjaDevice: + """ + ``class FirmwareNinjaDevice`` is a class that stores information about a hardware device, including the device + name, start address, size, and information about the device. + """ + + name: str + start: int + size: int + info: str + + +@dataclass +class FirmwareNinjaSection: + """ + ``class FirmwareNinjaSection`` is a class that stores information about a section identified with Firmware Ninja + analysis, including the section type, start address, size, and entropy of the section. + """ + + type: FirmwareNinjaSectionType + start: int + size: int + entropy: float + + +@dataclass +class FirmwareNinjaMemoryAccess: + """ + ``class FirmwareNinjaMemoryAccess`` is a class that stores information on instructions that access regions of + memory that are not file-backed, such as memory-mapped I/O and RAM. + """ + + instr_address: int + mem_address: RegisterValue + heuristic: FirmwareNinjaMemoryHeuristic + type: FirmwareNinjaMemoryAccessType + value: RegisterValue + + @classmethod + def from_BNFirmwareNinjaMemoryAccess(cls, access: core.BNFirmwareNinjaMemoryAccess) -> "FirmwareNinjaMemoryAccess": + return cls( + instr_address=access.instrAddress, + mem_address=RegisterValue.from_BNRegisterValue(access.memAddress), + heuristic=FirmwareNinjaMemoryHeuristic(access.heuristic), + type=FirmwareNinjaMemoryAccessType(access.type), + value=RegisterValue.from_BNRegisterValue(access.value), + ) + + @classmethod + def to_BNFirmwareNinjaMemoryAccess(cls, access: "FirmwareNinjaMemoryAccess") -> core.BNFirmwareNinjaMemoryAccess: + return core.BNFirmwareNinjaMemoryAccess( + instrAddress=access.instr_address, + memAddress=RegisterValue.to_BNRegisterValue(access.mem_address), + heuristic=access.heuristic, + type=access.type, + value=RegisterValue.to_BNRegisterValue(access.value), + ) + + +@dataclass +class FirmwareNinjaFunctionMemoryAccesses: + """ + ``class FirmwareNinjaFunctionMemoryAccesses`` is a class that stores information on accesses made by a function + to memory regions that are not file-backed, such as memory-mapped I/O and RAM. + """ + + function: Function + accesses: list[FirmwareNinjaMemoryAccess] + + @classmethod + def from_BNFirmwareNinjaFunctionMemoryAccesses( + cls, + info: core.BNFirmwareNinjaFunctionMemoryAccesses, + view: BinaryView, + ) -> "FirmwareNinjaFunctionMemoryAccesses": + accesses = [] + for i in range(info.count): + access = info.accesses[i] + accesses.append(FirmwareNinjaMemoryAccess.from_BNFirmwareNinjaMemoryAccess(access.contents)) + + return cls( + function=view.get_function_at(info.start), + accesses=accesses, + ) + + +@dataclass +class FirmwareNinjaDeviceAccesses: + """ + ``class FirmwareNinjaDeviceAccesses`` is a class that stores information on the number of accesses to hardware + devices for each board that is compatible with the current architecture. This information can be used to identify + a board based on the number of accesses to hardware devices. + """ + + board_name: str + total: int + unique: int + + +class FirmwareNinja: + """ + ``class FirmwareNinja`` is a class that aids in analysis of embedded firmware images. This class is only available + in the Ultimate Edition of Binary Ninja. + + :Example: + + >>> from binaryninja import * + >>> view = load("path/to/firmware.bin", options={"loader.imageBase": 0x100000}) + >>> fwn = FirmwareNinja(view) + >>> fwn.get_function_memory_accesses()[0].accesses[0].mem_address + <const ptr 0x40090028> + """ + + def __init__(self, view: BinaryView) -> None: + self._view = view + self._handle = core.BNCreateFirmwareNinja(view.handle) + + def __del__(self): + if core is not None: + core.BNFreeFirmwareNinja(self._handle) + + def store_custom_device(self, name: str, start: int, size: int, info: str) -> bool: + """ + ``store_custom_device`` store a user-defined Firmware Ninja device in the binary view metadata + + :param str name: Name of the device + :param int start: Start address of the device + :param int size: Size of the device memory region + :param str info: Information about the device + :return: True on success, False on failure + :rtype: bool + """ + + return core.BNFirmwareNinjaStoreCustomDevice(self._handle, name, start, start + size, info) + + def remove_custom_device(self, name: str) -> bool: + """ + ``remove_custom_device`` removes a user-defined Firmware Ninja device from the binary view metadata by device + name + + :param str name: Name of the device + :return: True on success, False on failure + :rtype: bool + """ + + return core.BNFirmwareNinjaRemoveCustomDevice(self._handle, name) + + def query_custom_devices(self) -> list[FirmwareNinjaDevice]: + """ + ``query_custom_devices`` queries user-defined Firmware Ninja devices from the binary view metadata + + :return: List of Firmware Ninja device objects + :rtype: list[FirmwareNinjaDevice] + """ + + devices = ctypes.POINTER(core.BNFirmwareNinjaDevice)() + count = core.BNFirmwareNinjaQueryCustomDevices(self._handle, ctypes.byref(devices)) + if count == -1: + raise RuntimeError("BNFirmwareNinjaQueryCustomDevices") + + try: + device_list = [] + for i in range(count): + device_list.append( + FirmwareNinjaDevice( + name=devices[i].name, + start=devices[i].start, + size=devices[i].end - devices[i].start, + info=devices[i].info, + ) + ) + + return device_list + finally: + core.BNFirmwareNinjaFreeDevices(devices, count) + + def query_board_names(self) -> list[str]: + """ + ``query_board_names`` queries the name of all boards that are compatible with the current architecture + + :return: List of board names + :rtype: list[str] + """ + + boards = ctypes.POINTER(ctypes.c_char_p)() + count = core.BNFirmwareNinjaQueryBoardNamesForArchitecture( + self._handle, self._view.arch.handle, ctypes.byref(boards) + ) + if count == -1: + raise RuntimeError("BNFirmwareNinjaQueryBoardNamesForArchitecture") + + try: + board_list = [] + for i in range(count): + board_list.append(boards[i].decode("utf-8")) + + return board_list + finally: + core.BNFirmwareNinjaFreeBoardNames(boards, count) + + def query_devices_by_board_name(self, name: str) -> list[FirmwareNinjaDevice]: + """ + ``query_devices_by_board_name`` queries the hardware device information for a specific board + + :Example: + + >>> fwn = FirmwareNinja(bv) + >>> fwn.query_devices_by_board_name(fwn.query_board_names()[0])[0] + FirmwareNinjaDevice(name='nand@12f', start=303, size=1024, info='marvell,orion-nand') + + :param str name: Name of the board + :return: List of Firmware Ninja device objects + :rtype: list[FirmwareNinjaDevice] + """ + + devices = ctypes.POINTER(core.BNFirmwareNinjaDevice)() + count = core.BNFirmwareNinjaQueryBoardDevices(self._handle, self._view.arch.handle, name, ctypes.byref(devices)) + if count == -1: + raise RuntimeError("BNFirmwareNinjaQueryBoardDevices") + + try: + device_list = [] + for i in range(count): + device_list.append( + FirmwareNinjaDevice( + name=devices[i].name, + start=devices[i].start, + size=devices[i].end - devices[i].start, + info=devices[i].info, + ) + ) + + return device_list + finally: + core.BNFirmwareNinjaFreeDevices(devices, count) + + def find_sections( + self, + high_code_entropy_threshold: float = 0.910, + low_code_entropy_threshold: float = 0.500, + block_size: int = 4096, + mode: FirmwareNinjaSectionAnalysisMode = FirmwareNinjaSectionAnalysisMode.DetectStringsSectionAnalysisMode, + ) -> list[FirmwareNinjaSection]: + """ + ``find_sections`` finds sections with Firmware Ninja entropy analysis and heuristics + + :Example: + + >>> fwn = FirmwareNinja(bv) + >>> fwn.find_sections(block_size=2048)[0].entropy + 0.48716872930526733 + >>> fwn.find_sections(block_size=2048)[0].type + <FirmwareNinjaSectionType.DataSectionType: 1> + + :param float high_code_entropy_threshold: High code entropy threshold + :param float low_code_entropy_threshold: Low code entropy threshold + :param int block_size: Block size + :param str mode: Analysis mode + :return: List of sections + :rtype: list[FirmwareNinjaSection] + """ + + sections = ctypes.POINTER(core.BNFirmwareNinjaSection)() + count = core.BNFirmwareNinjaFindSectionsWithEntropy( + self._handle, + ctypes.byref(sections), + high_code_entropy_threshold, + low_code_entropy_threshold, + block_size, + mode, + ) + if count == -1: + raise RuntimeError("BNFirmwareNinjaFindSectionsWithEntropy") + + try: + section_list = [] + for i in range(count): + section_list.append( + FirmwareNinjaSection( + type=FirmwareNinjaSectionType(sections[i].type), + start=sections[i].start, + size=sections[i].end - sections[i].start, + entropy=sections[i].entropy, + ) + ) + + return section_list + finally: + core.BNFirmwareNinjaFreeSections(sections, count) + + def get_function_memory_accesses(self, progress_func: Callable = None) -> list[FirmwareNinjaFunctionMemoryAccesses]: + """ + ``get_function_memory_accesses`` runs analysis to find accesses to memory regions that are not file-backed, such + as memory-mapped I/O and RAM. + + :param callback progress_func: optional function to be called with the current progress and total count. + :return: List of function memory accesses objects + :rtype: list[FirmwareNinjaFunctionMemoryAccesses] + """ + + fma_info = ctypes.POINTER((ctypes.POINTER(core.BNFirmwareNinjaFunctionMemoryAccesses)))() + if progress_func is None: + progress_cfunc = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_ulonglong)( + lambda ctxt, cur, total: True + ) + else: + progress_cfunc = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_ulonglong)( + lambda ctxt, cur, total: progress_func(cur, total) + ) + + count = core.BNFirmwareNinjaGetFunctionMemoryAccesses( + self._handle, ctypes.byref(fma_info), progress_cfunc, None + ) + if count == -1: + raise RuntimeError("BNFirmwareNinjaGetFunctionMemoryAccesses") + + try: + fma_info_list = [] + for i in range(count): + fma_info_list.append( + FirmwareNinjaFunctionMemoryAccesses.from_BNFirmwareNinjaFunctionMemoryAccesses( + fma_info[i].contents, self._view + ) + ) + + return fma_info_list + finally: + core.BNFirmwareNinjaFreeFunctionMemoryAccesses(fma_info, count) + + def _fma_info_list_to_array(self, fma: list[FirmwareNinjaFunctionMemoryAccesses]) -> ctypes.POINTER: + fma_info_ptr_array = (ctypes.POINTER(core.BNFirmwareNinjaFunctionMemoryAccesses) * len(fma))() + for i, info in enumerate(fma): + accesses_ptr_array = (ctypes.POINTER(core.BNFirmwareNinjaMemoryAccess) * len(info.accesses))() + for j, access in enumerate(info.accesses): + accesses_ptr_array[j] = ctypes.pointer(FirmwareNinjaMemoryAccess.to_BNFirmwareNinjaMemoryAccess(access)) + + fma_info_struct = core.BNFirmwareNinjaFunctionMemoryAccesses( + function=info.function.handle, + accesses=accesses_ptr_array, + count=len(info.accesses), + ) + + fma_info_ptr_array[i] = ctypes.pointer(fma_info_struct) + + return fma_info_ptr_array + + def store_function_memory_accesses(self, fma: list[FirmwareNinjaFunctionMemoryAccesses]) -> None: + """ + ``store_function_memory_accesses`` saves information on function memory accesses to binary view metadata + + :Example: + + >>> fwn = FirmwareNinja(bv) + >>> fma = fwn.get_function_memory_accesses() + >>> fwn.store_function_memory_accesses(fma) + + :param list[FirmwareNinjaFunctionMemoryAccesses] fma: List of function memory accesses objects + :return: None + :rtype: None + """ + + fma_info_ptr_array = self._fma_info_list_to_array(fma) + core.BNFirmwareNinjaStoreFunctionMemoryAccessesToMetadata(self._handle, fma_info_ptr_array, len(fma)) + + def query_function_memory_accesses(self) -> list[FirmwareNinjaFunctionMemoryAccesses]: + """ + ``query_function_memory_accesses`` queries information on function memory accesses from binary view metadata + + :return: List of function memory accesses objects + :rtype: list[FirmwareNinjaFunctionMemoryAccesses] + """ + + fma = ctypes.POINTER((ctypes.POINTER(core.BNFirmwareNinjaFunctionMemoryAccesses)))() + count = core.BNFirmwareNinjaQueryFunctionMemoryAccessesFromMetadata(self._handle, ctypes.byref(fma)) + if count == -1: + return None + + try: + fma_info_list = [] + for i in range(count): + fma_info_list.append( + FirmwareNinjaFunctionMemoryAccesses.from_BNFirmwareNinjaFunctionMemoryAccesses( + fma[i].contents, self._view + ) + ) + + return fma_info_list + finally: + core.BNFirmwareNinjaFreeFunctionMemoryAccesses(fma, count) + + def get_board_device_accesses( + self, fma: list[FirmwareNinjaFunctionMemoryAccesses] + ) -> list[FirmwareNinjaDeviceAccesses]: + """ + ``get_board_device_accesses`` counts accesses made to memory-mapped hardware devices for each board that is + compatible with the current architecture. This function can be used to help identify a board. + + :Example: + + >>> fwn = FirmwareNinja(bv) + >>> fma = fwn.get_function_memory_accesses() + >>> fwn.get_board_device_accesses(fma)[0] + FirmwareNinjaDeviceAccesses(board_name='stm32mp157c-dhcom-picoitx', total=414, unique=2) + + :param list[FirmwareNinjaFunctionMemoryAccesses] fma: List of function memory accesses objects + :return: List of device accesses objects + :rtype: list[FirmwareNinjaDeviceAccesses] + """ + + fma_info_ptr_array = self._fma_info_list_to_array(fma) + device_accesses = ctypes.POINTER(core.BNFirmwareNinjaDeviceAccesses)() + count = core.BNFirmwareNinjaGetBoardDeviceAccesses( + self._handle, fma_info_ptr_array, len(fma), ctypes.byref(device_accesses), self._view.arch.handle + ) + if count == -1: + raise RuntimeError("BNFirmwareNinjaGetBoardDeviceAccesses") + + try: + device_accesses_list = [] + for i in range(count): + device_accesses_list.append( + FirmwareNinjaDeviceAccesses( + board_name=device_accesses[i].name, + total=device_accesses[i].total, + unique=device_accesses[i].unique, + ) + ) + + return device_accesses_list + finally: + core.BNFirmwareNinjaFreeBoardDeviceAccesses(device_accesses, count) diff --git a/python/variable.py b/python/variable.py index 7b4121bf..ec8f050d 100644 --- a/python/variable.py +++ b/python/variable.py @@ -114,6 +114,10 @@ class RegisterValue: return ConstantDataRegisterValue(reg_value.value, 0, RegisterValueType(reg_value.state), confidence=confidence, size=reg_value.size) assert False, f"RegisterValueType {reg_value.state} not handled" + @classmethod + def to_BNRegisterValue(cls, reg_value: 'RegisterValue') -> core.BNRegisterValue: + return reg_value._to_core_struct() + @dataclass(frozen=True, eq=False) class Undetermined(RegisterValue): |
