diff options
| author | kat <kat@vector35.com> | 2024-10-23 21:56:29 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2024-10-23 21:58:47 -0400 |
| commit | 3006c68e108a18f9b8782bc937dc9ec7e2cb3b54 (patch) | |
| tree | f589bb38909349142c09b6215244205aef0a57ac /view/sharedcache | |
| parent | 80fcccec8f686e0e5c89626b60af121a97baf856 (diff) | |
Initial commit of the alpha dyld_shared_cache view API Plugin.
This is an early release of our DSC processing plugin. We're still hard at work improving this feature. You should be able to just drop in a dyld_shared_cache and use the 'Shared Cache Triage' view to load and analyze images.
Diffstat (limited to 'view/sharedcache')
40 files changed, 15441 insertions, 0 deletions
diff --git a/view/sharedcache/.clang-format b/view/sharedcache/.clang-format new file mode 100644 index 00000000..ba1ac018 --- /dev/null +++ b/view/sharedcache/.clang-format @@ -0,0 +1,86 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveMacros: 'true' +AlignConsecutiveAssignments: 'false' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: DontAlign +AlignOperands: 'false' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'true' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'true' +BinPackParameters: 'true' +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: 'true' + AfterClass: 'true' + AfterControlStatement: 'true' + AfterEnum: 'true' + AfterFunction: 'true' + AfterNamespace: 'false' + AfterObjCDeclaration: 'true' + AfterStruct: 'true' + AfterUnion: 'true' + AfterExternBlock: 'true' + BeforeCatch: 'true' + BeforeElse: 'true' + IndentBraces: 'false' + SplitEmptyFunction: 'false' + SplitEmptyRecord: 'false' + SplitEmptyNamespace: 'false' +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeTernaryOperators: 'false' +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +ColumnLimit: '120' +CompactNamespaces: 'true' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' +Cpp11BracedListStyle: 'true' +DerivePointerAlignment: 'false' +DisableFormat: 'false' +EmptyLineBeforeAccessModifier: Always +FixNamespaceComments: 'true' +IncludeBlocks: Preserve +IndentCaseLabels: 'false' +IndentPPDirectives: BeforeHash +IndentWrappedFunctionNames: 'true' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +MaxEmptyLinesToKeep: '2' +NamespaceIndentation: All +PenaltyIndentedWhitespace: 100 +PointerAlignment: Left +SortIncludes: 'false' +SortUsingDeclarations: 'true' +SpaceAfterCStyleCast: 'false' +SpaceAfterLogicalNot: 'false' +SpaceAfterTemplateKeyword: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'true' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '2' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +IndentWidth: '4' +TabWidth: '4' +UseTab: Always + +... diff --git a/view/sharedcache/CMakeLists.txt b/view/sharedcache/CMakeLists.txt new file mode 100644 index 00000000..83ff4a57 --- /dev/null +++ b/view/sharedcache/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(sharedcache) + +if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) + set(BN_API_PATH $ENV{BN_API_PATH} CACHE STRING "Path to Binary Ninja API source") + if(NOT BN_API_PATH) + message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") + endif() +endif() + +if (NOT BN_INTERNAL_BUILD) + if(WIN32) + set(MSVC_VERSION msvc2022_64 CACHE STRING "Version of MSVC Qt is built with" ) + endif() + set(QT_VERSION 6.7.2 CACHE STRING "Version of Qt to use") + + if(NOT CMAKE_PREFIX_PATH) + if(APPLE) + set(CMAKE_PREFIX_PATH $ENV{HOME}/Qt/${QT_VERSION}/clang_64/lib/cmake) + elseif(WIN32) + set(CMAKE_PREFIX_PATH $ENV{HOMEDRIVE}$ENV{HOMEPATH}/Qt/${QT_VERSION}/${MSVC_VERSION}/lib/cmake) + else() + set(CMAKE_PREFIX_PATH $ENV{HOME}/Qt/${QT_VERSION}/gcc_64/lib/cmake) + endif() + endif() + message("CMAKE_PREFIX_PATH is: ${CMAKE_PREFIX_PATH}") +endif() + +set(HARD_FAIL_MODE OFF CACHE BOOL "Enable hard fail mode") +set(SLIDEINFO_DEBUG_TAGS OFF CACHE BOOL "Enable debug tags in slideinfo") +set(VIEW_NAME "DSCViewAlpha" CACHE STRING "Name of the view") + +add_subdirectory(core) +add_subdirectory(api) +add_subdirectory(workflow) + +add_library(sharedcache SHARED + HeadlessPlugin.cpp) + + +if(BN_INTERNAL_BUILD) + set_target_properties(sharedcache PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else() + set_target_properties(sharedcache PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + ) +endif() + +set_target_properties(sharedcache PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ) + +target_include_directories(sharedcache PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/core ${CMAKE_CURRENT_SOURCE_DIR}/api ${CMAKE_CURRENT_SOURCE_DIR}/workflow) + +target_link_libraries(sharedcache PUBLIC sharedcacheapi binaryninjaapi sharedcachecore sharedcacheworkflow) + +if (HARD_FAIL_MODE) + target_compile_definitions(sharedcache PRIVATE ABORT_FAILURES) +endif() + +if (SLIDEINFO_DEBUG_TAGS) + target_compile_definitions(sharedcache PRIVATE SLIDEINFO_DEBUG_TAGS) +endif() + +target_compile_definitions(sharedcache PRIVATE VIEW_NAME="${VIEW_NAME}") + +if(NOT HEADLESS) + add_subdirectory(ui) +endif() + +message(" +▓█████▄ ██████ ▄████▄ +▒██▀ ██▌ ▒██ ▒ ▒██▀ ▀█ Shared Cache Plugin +░██ █▌ ░ ▓██▄ ▒▓█ ▄ +░▓█▄ █▌ ▒ ██▒ ▒▓▓▄ ▄██▒ CMake Prefix Path: ${CMAKE_PREFIX_PATH} +░▒████▓ ▒██████▒▒▒ ▓███▀ ░ Qt Version: ${QT_VERSION} + ▒▒▓ ▒ ▒ ▒▓▒ ▒ ░░ ░▒ ▒ ░ Crash on Failure: ${HARD_FAIL_MODE} + ░ ▒ ▒ ░ ░▒ ░ ░ ░ ▒ Slideinfo Debug Tags: ${SLIDEINFO_DEBUG_TAGS} + ░ ░ ░ ░ ░ ░ ░ +")
\ No newline at end of file diff --git a/view/sharedcache/HeadlessPlugin.cpp b/view/sharedcache/HeadlessPlugin.cpp new file mode 100644 index 00000000..c49ace5a --- /dev/null +++ b/view/sharedcache/HeadlessPlugin.cpp @@ -0,0 +1,23 @@ +#include <binaryninjaapi.h> +#include "DSCView.h" +#include "SharedCache.h" + +#ifdef __cplusplus +extern "C" { +#endif + extern void RegisterSharedCacheWorkflow(); +#ifdef __cplusplus +} +#endif + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + + BINARYNINJAPLUGIN bool CorePluginInit() + { + InitDSCViewType(); + RegisterSharedCacheWorkflow(); + return true; + } +}
\ No newline at end of file diff --git a/view/sharedcache/api/CMakeLists.txt b/view/sharedcache/api/CMakeLists.txt new file mode 100644 index 00000000..b09ae321 --- /dev/null +++ b/view/sharedcache/api/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(sharedcacheapi) +file(GLOB BN_MACHO_API_SOURCES *.cpp *.h) +add_library(sharedcacheapi OBJECT ${BN_MACHO_API_SOURCES}) + +if (VIEW_NAME) + target_compile_definitions(sharedcacheapi PRIVATE VIEW_NAME="${VIEW_NAME}") +else() + error("VIEW_NAME must be defined") +endif() + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(binaryninjaapi INCLUDES) + +target_include_directories(sharedcacheapi + PUBLIC ${PROJECT_SOURCE_DIR} ${INCLUDES}) + +set_target_properties(sharedcacheapi PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/out) + +if (NOT DEMO) + add_subdirectory(python) +endif()
\ No newline at end of file diff --git a/view/sharedcache/api/MetadataSerializable.hpp b/view/sharedcache/api/MetadataSerializable.hpp new file mode 100644 index 00000000..dd3951ea --- /dev/null +++ b/view/sharedcache/api/MetadataSerializable.hpp @@ -0,0 +1,500 @@ +// +// Created by kat on 5/31/23. +// + +/* + * Welcome to, this file. + * + * This is a metadata serialization helper. + * + * Have you ever wished turning a complex datastructure into a Metadata object was as easy in C++ as it is in python? + * Do you like macros and templates? + * + * Great news. + * + * Implement these on your `public MetadataSerializable` subclass: + * ``` + void Store() override { + MSS(m_someVariable); + MSS(m_someOtherVariable); + } + void Load() override { + MSL(m_someVariable); + MSL(m_someOtherVariable); + } + ``` + * Then, you can turn your object into a Metadata object with `AsMetadata()`, and load it back with + `LoadFromMetadata()`. + * + * Serialized fields will be automatically repopulated. + * + * Other ser/deser formats (rapidjson objects, strings) also exist. You can use these to achieve nesting, but probably + avoid that. + * */ + +#include "binaryninjaapi.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/prettywriter.h" + +#ifndef SHAREDCACHE_METADATASERIALIZABLE_HPP +#define SHAREDCACHE_METADATASERIALIZABLE_HPP + +#define MSS(name) store(#name, name) +#define MSS_CAST(name, type) store(#name, (type) name) +#define MSS_SUBCLASS(name) Serialize(#name, name) +#define MSL(name) name = load(#name, name) +#define MSL_CAST(name, storedType, type) name = (type)load(#name, (storedType) name) +#define MSL_SUBCLASS(name) Deserialize(#name, name) + +using namespace BinaryNinja; + +class MetadataSerializable +{ +protected: + struct SerialContext + { + rapidjson::Document doc; + rapidjson::Document::AllocatorType allocator; + }; + struct DeserContext + { + rapidjson::Document doc; + }; + + DeserContext m_activeDeserContext; + SerialContext m_activeContext; + +public: + MetadataSerializable() + { + m_activeContext.doc.SetObject(); + m_activeContext.allocator = m_activeContext.doc.GetAllocator(); + } + + // copy constructor + MetadataSerializable(const MetadataSerializable& other) + { + m_activeContext.doc.CopyFrom(other.m_activeContext.doc, m_activeContext.doc.GetAllocator()); + } + + // copy assignment + MetadataSerializable& operator=(const MetadataSerializable& other) + { + m_activeContext.doc.CopyFrom(other.m_activeContext.doc, m_activeContext.doc.GetAllocator()); + return *this; + } + + virtual ~MetadataSerializable() + { + } + + void SetupSerContext(rapidjson::Document::AllocatorType* alloc = nullptr) + { + m_activeContext.doc.SetObject(); + m_activeContext.allocator = m_activeContext.doc.GetAllocator(); + } + void S() + { + // fixme factor out + } + void Serialize(std::string& name, bool b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, bool& b) { b = m_activeDeserContext.doc[name.c_str()].GetBool(); } + + void Serialize(std::string& name, uint8_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint8_t& b) + { + b = static_cast<uint8_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint16_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint16_t& b) + { + b = static_cast<uint16_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint32_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint32_t& b) + { + b = static_cast<uint32_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint64_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint64_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetUint64(); + } + + void Serialize(std::string& name, int8_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int8_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, int16_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int16_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, int32_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int32_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt(); + } + + void Serialize(std::string& name, int64_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int64_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, std::string b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value value(b.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, value, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::string& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetString(); + } + + void Serialize(std::string& name, std::map<uint64_t, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::map<uint64_t, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::unordered_map<uint64_t, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Serialize(std::string& name, std::unordered_map<std::string, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + rapidjson::Value key(i.first.c_str(), m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(key, m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<uint64_t, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::unordered_map<uint64_t, uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + p.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<uint64_t, uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetUint64(); + } + + // std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>> + void Serialize(std::string& name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value classes(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value classArr(rapidjson::kArrayType); + rapidjson::Value classKey(i.first.c_str(), m_activeContext.allocator); + classArr.PushBack(classKey, m_activeContext.allocator); + rapidjson::Value membersArr(rapidjson::kArrayType); + for (auto& j : i.second) + { + rapidjson::Value member(rapidjson::kArrayType); + member.PushBack(j.first, m_activeContext.allocator); + member.PushBack(j.second, m_activeContext.allocator); + membersArr.PushBack(member, m_activeContext.allocator); + } + classArr.PushBack(membersArr, m_activeContext.allocator); + classes.PushBack(classArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, classes, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::string key = i.GetArray()[0].GetString(); + std::unordered_map<uint64_t, uint64_t> memArray; + for (auto& member : i.GetArray()[1].GetArray()) + { + memArray[member.GetArray()[0].GetUint64()] = member.GetArray()[1].GetUint64(); + } + b[key] = memArray; + } + } + + void Deserialize(std::string& name, std::unordered_map<std::string, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::vector<std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (const auto& s : b) + { + rapidjson::Value value(s.c_str(), m_activeContext.allocator); + bArr.PushBack(value, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b.emplace_back(i.GetString()); + } + + void Serialize(std::string& name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value segV(rapidjson::kArrayType); + segV.PushBack(i.first, m_activeContext.allocator); + segV.PushBack(i.second.first, m_activeContext.allocator); + segV.PushBack(i.second.second, m_activeContext.allocator); + bArr.PushBack(segV, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> j; + j.first = i.GetArray()[0].GetUint64(); + j.second.first = i.GetArray()[1].GetUint64(); + j.second.second = i.GetArray()[2].GetUint64(); + b.push_back(j); + } + } + + void Serialize(std::string& name, std::vector<std::pair<uint64_t, bool>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value segV(rapidjson::kArrayType); + segV.PushBack(i.first, m_activeContext.allocator); + segV.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(segV, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::pair<uint64_t, bool>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::pair<uint64_t, bool> j; + j.first = i.GetArray()[0].GetUint64(); + j.second = i.GetArray()[1].GetBool(); + b.push_back(j); + } + } + + void Serialize(std::string& name, std::vector<uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + bArr.PushBack(i, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + b.push_back(i.GetUint64()); + } + } + + // std::unordered_map<std::string, uint64_t> + void Serialize(std::string& name, std::unordered_map<std::string, uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + rapidjson::Value key(i.first.c_str(), m_activeContext.allocator); + p.PushBack(key, m_activeContext.allocator); + p.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<std::string, uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetUint64(); + } + } + + template <typename T> + void store(std::string x, T y) + { + Serialize(x, y); + } + + template <typename T> + T load(std::string x, T y) + { + T val; + Deserialize(x, val); + return val; + } + + rapidjson::Document& GetDoc() + { + S(); + Store(); + return m_activeContext.doc; + } + +public: + virtual void Store() = 0; + virtual void Load() = 0; + + std::string AsString() + { + rapidjson::StringBuffer strbuf; + rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(strbuf); + GetDoc().Accept(writer); + + std::string s = strbuf.GetString(); + return s; + } + rapidjson::Document& AsDocument() { return GetDoc(); } + void LoadFromString(const std::string& s) + { + m_activeDeserContext.doc.Parse(s.c_str()); + Load(); + } + void LoadFromValue(rapidjson::Value& s) + { + m_activeDeserContext.doc.CopyFrom(s, m_activeDeserContext.doc.GetAllocator()); + Load(); + } + Ref<Metadata> AsMetadata() { return new Metadata(AsString()); } + bool LoadFromMetadata(const Ref<Metadata>& meta) + { + if (!meta->IsString()) + return false; + LoadFromString(meta->GetString()); + return true; + } +}; + +#endif // SHAREDCACHE_METADATASERIALIZABLE_HPP diff --git a/view/sharedcache/api/python/CMakeLists.txt b/view/sharedcache/api/python/CMakeLists.txt new file mode 100644 index 00000000..c04a57ff --- /dev/null +++ b/view/sharedcache/api/python/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(sharedcache-python-api) + +file(GLOB PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/*.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/_sharedcachecore.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/enums.py) + +add_executable(sharedcache_generator + ${PROJECT_SOURCE_DIR}/generator.cpp) +target_link_libraries(sharedcache_generator binaryninjaapi) +target_include_directories(sharedcache_generator PUBLIC {PROJECT_SOURCE_DIR}/../../api) + +set_target_properties(sharedcache_generator PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + BUILD_WITH_INSTALL_RPATH OFF + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +if(BN_INTERNAL_BUILD) + set(PYTHON_OUTPUT_DIRECTORY ${BN_RESOURCE_DIR}/python/binaryninja/sharedcache/) +else() + set(PYTHON_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins/sharedcache/) +endif() + +if(WIN32) + if (BN_INTERNAL_BUILD) + add_custom_command(TARGET sharedcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_CORE_OUTPUT_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + else() + add_custom_command(TARGET sharedcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_INSTALL_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + endif() +endif() + +add_custom_target(sharedcache_generator_copy ALL + BYPRODUCTS ${PROJECT_SOURCE_DIR}/_sharedcachecore.py ${PROJECT_SOURCE_DIR}/enums.py + DEPENDS ${PYTHON_SOURCES} ${PROJECT_SOURCE_DIR}/../sharedcachecore.h $<TARGET_FILE:sharedcache_generator> + COMMAND ${CMAKE_COMMAND} -E echo "Copying SharedCache Python Sources" + COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $<TARGET_FILE:sharedcache_generator> + ${PROJECT_SOURCE_DIR}/../sharedcachecore.h + ${PROJECT_SOURCE_DIR}/_sharedcachecore.py + ${PROJECT_SOURCE_DIR}/_sharedcachecore_template.py + ${PROJECT_SOURCE_DIR}/sharedcache_enums.py + + COMMAND ${CMAKE_COMMAND} -E copy ${PYTHON_SOURCES} ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/_sharedcachecore.py ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/sharedcache_enums.py ${PYTHON_OUTPUT_DIRECTORY}) + diff --git a/view/sharedcache/api/python/__init__.py b/view/sharedcache/api/python/__init__.py new file mode 100644 index 00000000..1f65af93 --- /dev/null +++ b/view/sharedcache/api/python/__init__.py @@ -0,0 +1,8 @@ +import os + +from binaryninja._binaryninjacore import BNGetUserPluginDirectory +user_plugin_dir = os.path.realpath(BNGetUserPluginDirectory()) +current_path = os.path.realpath(__file__) + +from .sharedcache import * + diff --git a/view/sharedcache/api/python/_sharedcachecore.py b/view/sharedcache/api/python/_sharedcachecore.py new file mode 100644 index 00000000..93228468 --- /dev/null +++ b/view/sharedcache/api/python/_sharedcachecore.py @@ -0,0 +1,659 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import sharedcache_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +# By the time the debugger is loaded, binaryninja has not fully initialized. +# So we cannot call binaryninja.bundled_plugin_path() +from binaryninja._binaryninjacore import BNGetBundledPluginDirectory, BNFreeString +if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "sharedcache.dll")) +else: + raise Exception("OS not supported") + +def cstr(var) -> Optional[ctypes.c_char_p]: + if var is None: + return None + if isinstance(var, bytes): + return var + return var.encode("utf-8") + +def pyNativeStr(arg): + if isinstance(arg, str): + return arg + else: + return arg.decode('utf8') + +def free_string(value:ctypes.c_char_p) -> None: + BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte))) + +# Type definitions +from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle +class BNDSCBackingCache(ctypes.Structure): + @property + def path(self): + return pyNativeStr(self._path) + @path.setter + def path(self, value): + self._path = cstr(value) +BNDSCBackingCacheHandle = ctypes.POINTER(BNDSCBackingCache) +class BNDSCBackingCacheMapping(ctypes.Structure): + pass +BNDSCBackingCacheMappingHandle = ctypes.POINTER(BNDSCBackingCacheMapping) +class BNDSCImage(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCImageHandle = ctypes.POINTER(BNDSCImage) +class BNDSCImageMemoryMapping(ctypes.Structure): + @property + def filePath(self): + return pyNativeStr(self._filePath) + @filePath.setter + def filePath(self, value): + self._filePath = cstr(value) + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCImageMemoryMappingHandle = ctypes.POINTER(BNDSCImageMemoryMapping) +class BNDSCMappedMemoryRegion(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCMappedMemoryRegionHandle = ctypes.POINTER(BNDSCMappedMemoryRegion) +class BNDSCMemoryUsageInfo(ctypes.Structure): + pass +BNDSCMemoryUsageInfoHandle = ctypes.POINTER(BNDSCMemoryUsageInfo) +class BNDSCSymbolRep(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) + @property + def image(self): + return pyNativeStr(self._image) + @image.setter + def image(self, value): + self._image = cstr(value) +BNDSCSymbolRepHandle = ctypes.POINTER(BNDSCSymbolRep) +DSCViewLoadProgressEnum = ctypes.c_int +DSCViewStateEnum = ctypes.c_int +class BNSharedCache(ctypes.Structure): + pass +BNSharedCacheHandle = ctypes.POINTER(BNSharedCache) + +# Structure definitions +BNDSCBackingCache._fields_ = [ + ("_path", ctypes.c_char_p), + ("isPrimary", ctypes.c_bool), + ("mappings", ctypes.POINTER(BNDSCBackingCacheMapping)), + ("mappingCount", ctypes.c_ulonglong), + ] +BNDSCBackingCacheMapping._fields_ = [ + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("fileOffset", ctypes.c_ulonglong), + ] +BNDSCImage._fields_ = [ + ("_name", ctypes.c_char_p), + ("headerAddress", ctypes.c_ulonglong), + ("mappings", ctypes.POINTER(BNDSCImageMemoryMapping)), + ("mappingCount", ctypes.c_ulonglong), + ] +BNDSCImageMemoryMapping._fields_ = [ + ("_filePath", ctypes.c_char_p), + ("_name", ctypes.c_char_p), + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("loaded", ctypes.c_bool), + ("rawViewOffset", ctypes.c_ulonglong), + ] +BNDSCMappedMemoryRegion._fields_ = [ + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ] +BNDSCMemoryUsageInfo._fields_ = [ + ("sharedCacheRefs", ctypes.c_ulonglong), + ("mmapRefs", ctypes.c_ulonglong), + ] +BNDSCSymbolRep._fields_ = [ + ("address", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ("_image", ctypes.c_char_p), + ] + +# Function definitions +# ------------------------------------------------------- +# _BNDSCFindSymbolAtAddressAndApplyToAddress + +_BNDSCFindSymbolAtAddressAndApplyToAddress = core.BNDSCFindSymbolAtAddressAndApplyToAddress +_BNDSCFindSymbolAtAddressAndApplyToAddress.restype = None +_BNDSCFindSymbolAtAddressAndApplyToAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ctypes.c_ulonglong, + ctypes.c_bool, + ] + + +# noinspection PyPep8Naming +def BNDSCFindSymbolAtAddressAndApplyToAddress( + cache: ctypes.POINTER(BNSharedCache), + symbolLocation: int, + targetLocation: int, + triggerReanalysis: bool + ) -> None: + return _BNDSCFindSymbolAtAddressAndApplyToAddress(cache, symbolLocation, targetLocation, triggerReanalysis) + + +# ------------------------------------------------------- +# _BNDSCViewFastGetBackingCacheCount + +_BNDSCViewFastGetBackingCacheCount = core.BNDSCViewFastGetBackingCacheCount +_BNDSCViewFastGetBackingCacheCount.restype = ctypes.c_ulonglong +_BNDSCViewFastGetBackingCacheCount.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNDSCViewFastGetBackingCacheCount( + view: ctypes.POINTER(BNBinaryView) + ) -> int: + return _BNDSCViewFastGetBackingCacheCount(view) + + +# ------------------------------------------------------- +# _BNDSCViewFreeAllImages + +_BNDSCViewFreeAllImages = core.BNDSCViewFreeAllImages +_BNDSCViewFreeAllImages.restype = None +_BNDSCViewFreeAllImages.argtypes = [ + ctypes.POINTER(BNDSCImage), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeAllImages( + images: ctypes.POINTER(BNDSCImage), + count: int + ) -> None: + return _BNDSCViewFreeAllImages(images, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeBackingCaches + +_BNDSCViewFreeBackingCaches = core.BNDSCViewFreeBackingCaches +_BNDSCViewFreeBackingCaches.restype = None +_BNDSCViewFreeBackingCaches.argtypes = [ + ctypes.POINTER(BNDSCBackingCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeBackingCaches( + caches: ctypes.POINTER(BNDSCBackingCache), + count: int + ) -> None: + return _BNDSCViewFreeBackingCaches(caches, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeLoadedRegions + +_BNDSCViewFreeLoadedRegions = core.BNDSCViewFreeLoadedRegions +_BNDSCViewFreeLoadedRegions.restype = None +_BNDSCViewFreeLoadedRegions.argtypes = [ + ctypes.POINTER(BNDSCMappedMemoryRegion), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeLoadedRegions( + images: ctypes.POINTER(BNDSCMappedMemoryRegion), + count: int + ) -> None: + return _BNDSCViewFreeLoadedRegions(images, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeSymbols + +_BNDSCViewFreeSymbols = core.BNDSCViewFreeSymbols +_BNDSCViewFreeSymbols.restype = None +_BNDSCViewFreeSymbols.argtypes = [ + ctypes.POINTER(BNDSCSymbolRep), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeSymbols( + symbols: ctypes.POINTER(BNDSCSymbolRep), + count: int + ) -> None: + return _BNDSCViewFreeSymbols(symbols, count) + + +# ------------------------------------------------------- +# _BNDSCViewGetAllImages + +_BNDSCViewGetAllImages = core.BNDSCViewGetAllImages +_BNDSCViewGetAllImages.restype = ctypes.POINTER(BNDSCImage) +_BNDSCViewGetAllImages.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetAllImages( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCImage)]: + result = _BNDSCViewGetAllImages(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetBackingCaches + +_BNDSCViewGetBackingCaches = core.BNDSCViewGetBackingCaches +_BNDSCViewGetBackingCaches.restype = ctypes.POINTER(BNDSCBackingCache) +_BNDSCViewGetBackingCaches.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetBackingCaches( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCBackingCache)]: + result = _BNDSCViewGetBackingCaches(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetImageHeaderForAddress + +_BNDSCViewGetImageHeaderForAddress = core.BNDSCViewGetImageHeaderForAddress +_BNDSCViewGetImageHeaderForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageHeaderForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageHeaderForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageHeaderForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetImageHeaderForName + +_BNDSCViewGetImageHeaderForName = core.BNDSCViewGetImageHeaderForName +_BNDSCViewGetImageHeaderForName.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageHeaderForName.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageHeaderForName( + cache: ctypes.POINTER(BNSharedCache), + name: Optional[str] + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageHeaderForName(cache, cstr(name)) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetImageNameForAddress + +_BNDSCViewGetImageNameForAddress = core.BNDSCViewGetImageNameForAddress +_BNDSCViewGetImageNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageNameForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageNameForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetInstallNames + +_BNDSCViewGetInstallNames = core.BNDSCViewGetInstallNames +_BNDSCViewGetInstallNames.restype = ctypes.POINTER(ctypes.c_char_p) +_BNDSCViewGetInstallNames.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetInstallNames( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(ctypes.c_char_p)]: + result = _BNDSCViewGetInstallNames(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetLoadProgress + +_BNDSCViewGetLoadProgress = core.BNDSCViewGetLoadProgress +_BNDSCViewGetLoadProgress.restype = DSCViewLoadProgressEnum +_BNDSCViewGetLoadProgress.argtypes = [ + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetLoadProgress( + sessionID: int + ) -> DSCViewLoadProgressEnum: + return _BNDSCViewGetLoadProgress(sessionID) + + +# ------------------------------------------------------- +# _BNDSCViewGetLoadedRegions + +_BNDSCViewGetLoadedRegions = core.BNDSCViewGetLoadedRegions +_BNDSCViewGetLoadedRegions.restype = ctypes.POINTER(BNDSCMappedMemoryRegion) +_BNDSCViewGetLoadedRegions.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetLoadedRegions( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCMappedMemoryRegion)]: + result = _BNDSCViewGetLoadedRegions(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetMemoryUsageInfo + +_BNDSCViewGetMemoryUsageInfo = core.BNDSCViewGetMemoryUsageInfo +_BNDSCViewGetMemoryUsageInfo.restype = BNDSCMemoryUsageInfo +_BNDSCViewGetMemoryUsageInfo.argtypes = [ + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetMemoryUsageInfo( + ) -> BNDSCMemoryUsageInfo: + return _BNDSCViewGetMemoryUsageInfo() + + +# ------------------------------------------------------- +# _BNDSCViewGetNameForAddress + +_BNDSCViewGetNameForAddress = core.BNDSCViewGetNameForAddress +_BNDSCViewGetNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetNameForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetNameForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetState + +_BNDSCViewGetState = core.BNDSCViewGetState +_BNDSCViewGetState.restype = DSCViewStateEnum +_BNDSCViewGetState.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetState( + cache: ctypes.POINTER(BNSharedCache) + ) -> DSCViewStateEnum: + return _BNDSCViewGetState(cache) + + +# ------------------------------------------------------- +# _BNDSCViewLoadAllSymbolsAndWait + +_BNDSCViewLoadAllSymbolsAndWait = core.BNDSCViewLoadAllSymbolsAndWait +_BNDSCViewLoadAllSymbolsAndWait.restype = ctypes.POINTER(BNDSCSymbolRep) +_BNDSCViewLoadAllSymbolsAndWait.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadAllSymbolsAndWait( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCSymbolRep)]: + result = _BNDSCViewLoadAllSymbolsAndWait(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewLoadImageContainingAddress + +_BNDSCViewLoadImageContainingAddress = core.BNDSCViewLoadImageContainingAddress +_BNDSCViewLoadImageContainingAddress.restype = ctypes.c_bool +_BNDSCViewLoadImageContainingAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadImageContainingAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> bool: + return _BNDSCViewLoadImageContainingAddress(cache, address) + + +# ------------------------------------------------------- +# _BNDSCViewLoadImageWithInstallName + +_BNDSCViewLoadImageWithInstallName = core.BNDSCViewLoadImageWithInstallName +_BNDSCViewLoadImageWithInstallName.restype = ctypes.c_bool +_BNDSCViewLoadImageWithInstallName.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadImageWithInstallName( + cache: ctypes.POINTER(BNSharedCache), + name: Optional[str] + ) -> bool: + return _BNDSCViewLoadImageWithInstallName(cache, cstr(name)) + + +# ------------------------------------------------------- +# _BNDSCViewLoadSectionAtAddress + +_BNDSCViewLoadSectionAtAddress = core.BNDSCViewLoadSectionAtAddress +_BNDSCViewLoadSectionAtAddress.restype = ctypes.c_bool +_BNDSCViewLoadSectionAtAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadSectionAtAddress( + cache: ctypes.POINTER(BNSharedCache), + name: int + ) -> bool: + return _BNDSCViewLoadSectionAtAddress(cache, name) + + +# ------------------------------------------------------- +# _BNDSCViewLoadedImageCount + +_BNDSCViewLoadedImageCount = core.BNDSCViewLoadedImageCount +_BNDSCViewLoadedImageCount.restype = ctypes.c_ulonglong +_BNDSCViewLoadedImageCount.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadedImageCount( + cache: ctypes.POINTER(BNSharedCache) + ) -> int: + return _BNDSCViewLoadedImageCount(cache) + + +# ------------------------------------------------------- +# _BNFreeSharedCacheReference + +_BNFreeSharedCacheReference = core.BNFreeSharedCacheReference +_BNFreeSharedCacheReference.restype = None +_BNFreeSharedCacheReference.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNFreeSharedCacheReference( + cache: ctypes.POINTER(BNSharedCache) + ) -> None: + return _BNFreeSharedCacheReference(cache) + + +# ------------------------------------------------------- +# _BNGetSharedCache + +_BNGetSharedCache = core.BNGetSharedCache +_BNGetSharedCache.restype = ctypes.POINTER(BNSharedCache) +_BNGetSharedCache.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNGetSharedCache( + data: ctypes.POINTER(BNBinaryView) + ) -> Optional[ctypes.POINTER(BNSharedCache)]: + result = _BNGetSharedCache(data) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNNewSharedCacheReference + +_BNNewSharedCacheReference = core.BNNewSharedCacheReference +_BNNewSharedCacheReference.restype = ctypes.POINTER(BNSharedCache) +_BNNewSharedCacheReference.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNNewSharedCacheReference( + cache: ctypes.POINTER(BNSharedCache) + ) -> Optional[ctypes.POINTER(BNSharedCache)]: + result = _BNNewSharedCacheReference(cache) + if not result: + return None + return result + + + +# Helper functions +def handle_of_type(value, handle_type): + if isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p): + return ctypes.cast(value, ctypes.POINTER(handle_type)) + raise ValueError('expected pointer to %s' % str(handle_type)) diff --git a/view/sharedcache/api/python/_sharedcachecore_template.py b/view/sharedcache/api/python/_sharedcachecore_template.py new file mode 100644 index 00000000..6b5b174f --- /dev/null +++ b/view/sharedcache/api/python/_sharedcachecore_template.py @@ -0,0 +1,43 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import sharedcache_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +# By the time the debugger is loaded, binaryninja has not fully initialized. +# So we cannot call binaryninja.bundled_plugin_path() +from binaryninja._binaryninjacore import BNGetBundledPluginDirectory, BNFreeString +if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "sharedcache.dll")) +else: + raise Exception("OS not supported") + +def cstr(var) -> Optional[ctypes.c_char_p]: + if var is None: + return None + if isinstance(var, bytes): + return var + return var.encode("utf-8") + +def pyNativeStr(arg): + if isinstance(arg, str): + return arg + else: + return arg.decode('utf8') + +def free_string(value:ctypes.c_char_p) -> None: + BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte))) + diff --git a/view/sharedcache/api/python/generator.cpp b/view/sharedcache/api/python/generator.cpp new file mode 100644 index 00000000..91ff88c0 --- /dev/null +++ b/view/sharedcache/api/python/generator.cpp @@ -0,0 +1,617 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + + +#include <stdio.h> +#include <inttypes.h> +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace std; + + +map<string, string> g_pythonKeywordReplacements = { + {"False", "False_"}, + {"True", "True_"}, + {"None", "None_"}, + {"and", "and_"}, + {"as", "as_"}, + {"assert", "assert_"}, + {"async", "async_"}, + {"await", "await_"}, + {"break", "break_"}, + {"class", "class_"}, + {"continue", "continue_"}, + {"def", "def_"}, + {"del", "del_"}, + {"elif", "elif_"}, + {"else", "else_"}, + {"except", "except_"}, + {"finally", "finally_"}, + {"for", "for_"}, + {"from", "from_"}, + {"global", "global_"}, + {"if", "if_"}, + {"import", "import_"}, + {"in", "in_"}, + {"is", "is_"}, + {"lambda", "lambda_"}, + {"nonlocal", "nonlocal_"}, + {"not", "not_"}, + {"or", "or_"}, + {"pass", "pass_"}, + {"raise", "raise_"}, + {"return", "return_"}, + {"try", "try_"}, + {"while", "while_"}, + {"with", "with_"}, + {"yield", "yield_"}, +}; + + +void OutputType(FILE* out, Type* type, bool isReturnType = false, bool isCallback = false) +{ + switch (type->GetClass()) + { + case BoolTypeClass: + fprintf(out, "ctypes.c_bool"); + break; + case IntegerTypeClass: + switch (type->GetWidth()) + { + case 1: + if (type->IsSigned()) + fprintf(out, "ctypes.c_byte"); + else + fprintf(out, "ctypes.c_ubyte"); + break; + case 2: + if (type->IsSigned()) + fprintf(out, "ctypes.c_short"); + else + fprintf(out, "ctypes.c_ushort"); + break; + case 4: + if (type->IsSigned()) + fprintf(out, "ctypes.c_int"); + else + fprintf(out, "ctypes.c_uint"); + break; + default: + if (type->IsSigned()) + fprintf(out, "ctypes.c_longlong"); + else + fprintf(out, "ctypes.c_ulonglong"); + break; + } + break; + case FloatTypeClass: + if (type->GetWidth() == 4) + fprintf(out, "ctypes.c_float"); + else + fprintf(out, "ctypes.c_double"); + break; + case NamedTypeReferenceClass: + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + string name = type->GetNamedTypeReference()->GetName().GetString(); + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + name = name.substr(2); + fprintf(out, "%sEnum", name.c_str()); + } + else + { + fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + break; + case PointerTypeClass: + if (isCallback || (type->GetChildType()->GetClass() == VoidTypeClass)) + { + fprintf(out, "ctypes.c_void_p"); + break; + } + else if ((type->GetChildType()->GetClass() == IntegerTypeClass) && + (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned())) + { + if (isReturnType) + fprintf(out, "ctypes.POINTER(ctypes.c_byte)"); + else + fprintf(out, "ctypes.c_char_p"); + break; + } + else if (type->GetChildType()->GetClass() == FunctionTypeClass) + { + fprintf(out, "ctypes.CFUNCTYPE("); + OutputType(out, type->GetChildType()->GetChildType(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType()); + fprintf(out, " * %" PRId64, type->GetElementCount()); + break; + default: + fprintf(out, "None"); + break; + } +} + + +void OutputSwizzledType(FILE* out, Type* type) +{ + switch (type->GetClass()) + { + case BoolTypeClass: + fprintf(out, "bool"); + break; + case IntegerTypeClass: + fprintf(out, "int"); + break; + case FloatTypeClass: + fprintf(out, "float"); + break; + case NamedTypeReferenceClass: + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + string name = type->GetNamedTypeReference()->GetName().GetString(); + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + name = name.substr(2); + fprintf(out, "%sEnum", name.c_str()); + } + else + { + fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + break; + case PointerTypeClass: + if (type->GetChildType()->GetClass() == VoidTypeClass) + { + fprintf(out, "Optional[ctypes.c_void_p]"); + break; + } + else if ((type->GetChildType()->GetClass() == IntegerTypeClass) && + (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned())) + { + fprintf(out, "Optional[str]"); + break; + } + else if (type->GetChildType()->GetClass() == FunctionTypeClass) + { + fprintf(out, "ctypes.CFUNCTYPE("); + OutputType(out, type->GetChildType()->GetChildType(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType()); + fprintf(out, " * %" PRId64, type->GetElementCount()); + break; + default: + fprintf(out, "None"); + break; + } +} + + +int main(int argc, char* argv[]) +{ + if (argc < 5) + { + fprintf(stderr, "Usage: generator <header> <output> <output_template> <output_enum>\n"); + return 1; + } + + // Parse API header to get type and function information + map<QualifiedName, Ref<Type>> types, vars, funcs; + string errors; + auto arch = new CoreArchitecture(BNGetNativeTypeParserArchitecture()); + + // Enable ephemeral settings + Settings::Instance()->LoadSettingsFile(""); + Settings::Instance()->Set("analysis.types.parserName", "ClangTypeParser"); + bool ok = arch->GetStandalonePlatform()->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors); + + if (!ok) + { + fprintf(stderr, "Errors: %s\n", errors.c_str()); + return 1; + } + + FILE* out = fopen(argv[2], "w"); + FILE* out_template = fopen(argv[3], "r"); + FILE* enums = fopen(argv[4], "w"); + + fprintf(enums, "import enum\n"); + + // Copy the content of the template to the output file + int c; + while((c = fgetc(out_template)) != EOF) + fputc(c, out); + + // Create type objects + fprintf(out, "# Type definitions\n"); + for (auto& i : types) + { + string name; + if (i.first.size() != 1) + continue; + name = i.first[0]; + if (name == "BNBinaryView") + { + fprintf(out, "from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle\n"); + continue; + } + if (i.second->GetClass() == StructureTypeClass) + { + fprintf(out, "class %s(ctypes.Structure):\n", name.c_str()); + + // python uses str's, C uses byte-arrays + bool stringField = false; + for (auto& arg : i.second->GetStructure()->GetMembers()) + { + if ((arg.type->GetClass() == PointerTypeClass) && + (arg.type->GetChildType()->GetWidth() == 1) && + (arg.type->GetChildType()->IsSigned())) + { + fprintf(out, "\t@property\n\tdef %s(self):\n\t\treturn pyNativeStr(self._%s)\n", arg.name.c_str(), arg.name.c_str()); + fprintf(out, "\t@%s.setter\n\tdef %s(self, value):\n\t\tself._%s = cstr(value)\n", arg.name.c_str(), arg.name.c_str(), arg.name.c_str()); + stringField = true; + } + } + + if (!stringField) + fprintf(out, "\tpass\n"); + + fprintf(out, "%sHandle = ctypes.POINTER(%s)\n", name.c_str(), name.c_str()); + } + else if (i.second->GetClass() == EnumerationTypeClass) + { + bool isBNAPIEnum = false; + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + { + name = name.substr(2); + isBNAPIEnum = false; + } + else + continue; + + if (isBNAPIEnum) + { + fprintf(out, "from binaryninja._binaryninjacore import %sEnum\n", name.c_str()); + continue; + } + + fprintf(out, "%sEnum = ctypes.c_int\n", name.c_str()); + + fprintf(enums, "\n\nclass %s(enum.IntEnum):\n", name.c_str()); + for (auto& j : i.second->GetEnumeration()->GetMembers()) + { + fprintf(enums, "\t%s = %" PRId64 "\n", j.name.c_str(), j.value); + } + } + else if ((i.second->GetClass() == BoolTypeClass) || (i.second->GetClass() == IntegerTypeClass) || + (i.second->GetClass() == FloatTypeClass) || (i.second->GetClass() == ArrayTypeClass)) + { + fprintf(out, "%s = ", name.c_str()); + OutputType(out, i.second); + fprintf(out, "\n"); + } + } + + + fprintf(out, "\n# Structure definitions\n"); + set<QualifiedName> structsToProcess; + set<QualifiedName> finishedStructs; + for (auto& i : types) + structsToProcess.insert(i.first); + while (structsToProcess.size() != 0) + { + set<QualifiedName> currentStructList = structsToProcess; + structsToProcess.clear(); + bool processedSome = false; + for (auto& i : currentStructList) + { + string name; + if (i.size() != 1) + continue; + Ref<Type> type = types[i]; + name = i[0]; + if ((type->GetClass() == StructureTypeClass) && (type->GetStructure()->GetMembers().size() != 0)) + { + bool requiresDependency = false; + for (auto& j : type->GetStructure()->GetMembers()) + { + if ((j.type->GetClass() == NamedTypeReferenceClass) && + (types[j.type->GetNamedTypeReference()->GetName()]->GetClass() == StructureTypeClass) && + (finishedStructs.count(j.type->GetNamedTypeReference()->GetName()) == 0)) + { + // This structure needs another structure that isn't fully defined yet, need to wait + // for the dependencies to be defined + structsToProcess.insert(i); + requiresDependency = true; + break; + } + } + + if (requiresDependency) + continue; + fprintf(out, "%s._fields_ = [\n", name.c_str()); + for (auto& j : type->GetStructure()->GetMembers()) + { + // To help the python->C wrappers + if ((j.type->GetClass() == PointerTypeClass) && + (j.type->GetChildType()->GetWidth() == 1) && + (j.type->GetChildType()->IsSigned())) + { + fprintf(out, "\t\t(\"_%s\", ", j.name.c_str()); + } + else + fprintf(out, "\t\t(\"%s\", ", j.name.c_str()); + OutputType(out, j.type); + fprintf(out, "),\n"); + } + fprintf(out, "\t]\n"); + finishedStructs.insert(i); + processedSome = true; + } + else if (type->GetClass() == NamedTypeReferenceClass) + { + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == StructNamedTypeClass) + { + fprintf(out, "%s = %s\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str()); + fprintf(out, "%sHandle = %sHandle\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + else if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + fprintf(out, "%s = ctypes.c_int\n", name.c_str()); + } + finishedStructs.insert(i); + processedSome = true; + } + } + + if (!processedSome && structsToProcess.size() != 0) + { + fprintf(stderr, "Detected dependency cycle in structures\n"); + for (auto& i : structsToProcess) + fprintf(stderr, "%s\n", i.GetString().c_str()); + return 1; + } + } + + fprintf(out, "\n# Function definitions\n"); + for (auto& i : funcs) + { + string name; + if (i.first.size() != 1) + continue; + name = i.first[0]; + + // Check for a string result, these will be automatically wrapped to free the string + // memory and return a Python string + bool stringResult = (i.second->GetChildType()->GetClass() == PointerTypeClass) && + (i.second->GetChildType()->GetChildType()->GetWidth() == 1) && + (i.second->GetChildType()->GetChildType()->IsSigned()); + // Pointer returns will be automatically wrapped to return None on null pointer + bool pointerResult = (i.second->GetChildType()->GetClass() == PointerTypeClass); + + // From python -> C python3 requires str -> str.encode('charmap') + bool swizzleArgs = true; + if (name == "BNFreeString") + swizzleArgs = false; + + bool callbackConvention = false; + if (name == "BNAllocString") + { + // Don't perform automatic wrapping of string allocation, and return a void + // pointer so that callback functions (which is the only valid use of BNDebuggerAllocString) + // can properly return the result + stringResult = false; + callbackConvention = true; + swizzleArgs = false; + } + + string funcName = string("_") + name; + + fprintf(out, "# -------------------------------------------------------\n"); + fprintf(out, "# %s\n\n", funcName.c_str()); + fprintf(out, "%s = core.%s\n", funcName.c_str(), name.c_str()); + fprintf(out, "%s.restype = ", funcName.c_str()); + OutputType(out, i.second->GetChildType(), true, callbackConvention); + fprintf(out, "\n"); + if (!i.second->HasVariableArguments()) + { + fprintf(out, "%s.argtypes = [\n", funcName.c_str()); + for (auto& j : i.second->GetParameters()) + { + fprintf(out, "\t\t"); + if (name == "BNFreeString") + { + // BNDebuggerFreeString expects a pointer to a string allocated by the core, so do not use + // a c_char_p here, as that would be allocated by the Python runtime. This can + // be enforced by outputting like a return value. + OutputType(out, j.type, true); + } + else + { + OutputType(out, j.type); + } + fprintf(out, ",\n"); + } + fprintf(out, "\t]"); + } + else + { + // As of writing this, only BNLog's have variable instruction lengths, but in an attempt not to break in the future: + if (funcName.compare(0, 6, "_BNLog") == 0) + { + if (funcName != "_BNLog") + { + fprintf(out, "def %s(*args):\n", name.c_str()); + fprintf(out, "\treturn %s(*[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } + else + { + fprintf(out, "def %s(level, *args):\n", name.c_str()); + fprintf(out, "\treturn %s(level, *[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } + } + } + fprintf(out, "\n\n\n# noinspection PyPep8Naming\n"); + fprintf(out, "def %s(", name.c_str()); + if (!i.second->HasVariableArguments()) + { + size_t argN = 0; + for (auto& arg: i.second->GetParameters()) + { + string argName = arg.name; + if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end()) + argName = g_pythonKeywordReplacements[argName]; + + if (argName.empty()) + argName = "arg" + to_string(argN); + + if (argN > 0) + fprintf(out, ", "); + fprintf(out, "\n\t\t"); + fprintf(out, "%s: ", argName.c_str()); + if (swizzleArgs) + OutputSwizzledType(out, arg.type); + else + OutputType(out, arg.type); + argN ++; + } + } + fprintf(out, "\n\t\t) -> "); + if (swizzleArgs) + { + if (stringResult || pointerResult) + fprintf(out, "Optional["); + OutputSwizzledType(out, i.second->GetChildType()); + if (stringResult || pointerResult) + fprintf(out, "]"); + } + else + { + OutputType(out, i.second->GetChildType()); + } + fprintf(out, ":\n"); + + string stringArgFuncCall = funcName + "("; + size_t argN = 0; + for (auto& arg : i.second->GetParameters()) + { + string argName = arg.name; + if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end()) + argName = g_pythonKeywordReplacements[argName]; + + if (argName.empty()) + argName = "arg" + to_string(argN); + + if (swizzleArgs && (arg.type->GetClass() == PointerTypeClass) && + (arg.type->GetChildType()->GetClass() == IntegerTypeClass) && + (arg.type->GetChildType()->GetWidth() == 1) && + (arg.type->GetChildType()->IsSigned())) + { + stringArgFuncCall += string("cstr(") + argName + "), "; + } + else + { + stringArgFuncCall += argName + ", "; + } + argN++; + } + if (argN > 0) + stringArgFuncCall = stringArgFuncCall.substr(0, stringArgFuncCall.size()-2); + stringArgFuncCall += ")"; + + if (stringResult) + { + // Emit wrapper to get Python string and free native memory + fprintf(out, "\tresult = "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + fprintf(out, "\tif not result:\n"); + fprintf(out, "\t\treturn None\n"); + fprintf(out, "\tstring = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))\n"); + fprintf(out, "\tBNFreeString(result)\n"); + fprintf(out, "\treturn string\n"); + } + else if (pointerResult) + { + // Emit wrapper to return None on null pointer + fprintf(out, "\tresult = "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + fprintf(out, "\tif not result:\n"); + fprintf(out, "\t\treturn None\n"); + fprintf(out, "\treturn result\n"); + } + else + { + fprintf(out, "\treturn "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + } + fprintf(out, "\n\n"); + } + + fprintf(out, "\n# Helper functions\n"); + fprintf(out, "def handle_of_type(value, handle_type):\n"); + fprintf(out, "\tif isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p):\n"); + fprintf(out, "\t\treturn ctypes.cast(value, ctypes.POINTER(handle_type))\n"); + fprintf(out, "\traise ValueError('expected pointer to %%s' %% str(handle_type))\n"); + + fclose(out); + fclose(enums); + return 0; +} diff --git a/view/sharedcache/api/python/sharedcache.py b/view/sharedcache/api/python/sharedcache.py new file mode 100644 index 00000000..ca031e03 --- /dev/null +++ b/view/sharedcache/api/python/sharedcache.py @@ -0,0 +1,260 @@ +import os +import ctypes +import dataclasses +import traceback + +import binaryninja +from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString + +from . import _sharedcachecore as sccore +from .sharedcache_enums import * + + +@dataclasses.dataclass +class DSCMemoryMapping: + filePath: str + name: str + vmAddress: int + rawViewOffset: int + size: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>" + + +@dataclasses.dataclass +class LoadedRegion: + name: str + headerAddress: int + mappings: list[DSCMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<LoadedRegion {self.name} @ {self.headerAddress:x}>" + + +@dataclasses.dataclass +class DSCBackingCacheMapping: + vmAddress: int + size: int + fileOffset: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCBackingCacheMapping {self.vmAddress:x}+{self.size:x} @ {self.fileOffset:x}" + + +@dataclasses.dataclass +class DSCBackingCache: + path: str + isPrimary: bool + mappings: list[DSCBackingCacheMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCBackingCache {self.path} {'Primary' if self.isPrimary else 'Secondary'} | {len(self.mappings)} mappings>" + + +@dataclasses.dataclass +class DSCImageMemoryMapping: + filePath: str + name: str + vmAddress: int + size: int + loaded: bool + rawViewOffset: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCImageMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>" + + +@dataclasses.dataclass +class DSCImage: + name: str + headerAddress: int + mappings: list[DSCImageMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCImage {self.name} @ {self.headerAddress:x}>" + + +@dataclasses.dataclass +class DSCSymbol: + name: str + image: str + address: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCSymbol {self.name} @ {self.address:x} ({self.image}>" + + +class SharedCache: + def __init__(self, view): + self.handle = sccore.BNGetSharedCache(view.handle) + + def load_image_with_install_name(self, installName): + str = BNAllocString(installName.encode('utf-8')) + return sccore.BNDSCViewLoadImageWithInstallName(self.handle, str) + + def load_section_at_address(self, addr): + return sccore.BNDSCViewLoadSectionAtAddress(self.handle, addr) + + def load_image_containing_address(self, addr): + return sccore.BNDSCViewLoadImageContainingAddress(self.handle, addr) + + @property + def caches(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetBackingCaches(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCBackingCacheMapping( + value[i].mappings[j].vmAddress, + value[i].mappings[j].size, + value[i].mappings[j].fileOffset + ) + mappings.append(mapping) + result.append(DSCBackingCache( + value[i].path, + value[i].isPrimary, + mappings + )) + + sccore.BNDSCViewFreeBackingCaches(value, count) + return result + + @property + def images(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetAllImages(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCImageMemoryMapping( + value[i].mappings[j].filePath, + value[i].mappings[j].name, + value[i].mappings[j].vmAddress, + value[i].mappings[j].size, + value[i].mappings[j].loaded, + value[i].mappings[j].rawViewOffset + ) + mappings.append(mapping) + result.append(DSCImage( + value[i].name, + value[i].headerAddress, + mappings + )) + + sccore.BNDSCViewFreeAllImages(value, count) + return result + + @property + def loaded_images(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetLoadedImages(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCMemoryMapping( + value[i].mappings[j].filePath, + value[i].mappings[j].name, + value[i].mappings[j].vmAddress, + value[i].mappings[j].rawViewOffset, + value[i].mappings[j].size + ) + mappings.append(mapping) + result.append(LoadedRegion( + value[i].name, + value[i].headerAddress, + mappings + )) + + sccore.BNDSCViewFreeLoadedImages(value, count) + return result + + def load_all_symbols_and_wait(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewLoadAllSymbolsAndWait(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + sym = DSCSymbol( + value[i].name, + value[i].image, + value[i].address + ) + result.append(sym) + + sccore.BNDSCViewFreeSymbols(value, count) + return result + + @property + def image_names(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetInstallNames(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + result.append(value[i].decode('utf-8')) + + BNFreeStringList(value, count) + return result + + @property + def image_count(self): + return sccore.BNDSCViewLoadedImageCount(self.handle) + + @property + def state(self): + return DSCViewState(sccore.BNDSCViewGetState(self.handle)) + + def get_name_for_address(self, address): + name = sccore.BNDSCViewGetNameForAddress(self.handle, address) + if name is None: + return "" + result = name + return result + + def get_image_name_for_address(self, address): + name = sccore.BNDSCViewGetImageNameForAddress(self.handle, address) + if name is None: + return "" + result = name + return result + + def find_symbol_at_addr_and_apply_to_addr(self, symbolAddress, targetAddress, triggerReanalysis) -> None: + sccore.BNDSCFindSymbolAtAddressAndApplyToAddress(self.handle, symbolAddress, targetAddress, triggerReanalysis) diff --git a/view/sharedcache/api/python/sharedcache_enums.py b/view/sharedcache/api/python/sharedcache_enums.py new file mode 100644 index 00000000..34668424 --- /dev/null +++ b/view/sharedcache/api/python/sharedcache_enums.py @@ -0,0 +1,14 @@ +import enum + + +class DSCViewLoadProgress(enum.IntEnum): + LoadProgressNotStarted = 0 + LoadProgressLoadingCaches = 1 + LoadProgressLoadingImages = 2 + LoadProgressFinished = 3 + + +class DSCViewState(enum.IntEnum): + Unloaded = 0 + Loaded = 1 + LoadedWithImages = 2 diff --git a/view/sharedcache/api/sharedcache.cpp b/view/sharedcache/api/sharedcache.cpp new file mode 100644 index 00000000..5ca7c480 --- /dev/null +++ b/view/sharedcache/api/sharedcache.cpp @@ -0,0 +1,224 @@ +// +// Created by kat on 5/21/23. +// + +#include "sharedcacheapi.h" + +namespace SharedCacheAPI { + + SharedCache::SharedCache(Ref<BinaryView> view) { + m_object = BNGetSharedCache(view->GetObject()); + } + + BNDSCViewLoadProgress SharedCache::GetLoadProgress(Ref<BinaryView> view) + { + return BNDSCViewGetLoadProgress(view->GetFile()->GetSessionId()); + } + + uint64_t SharedCache::FastGetBackingCacheCount(Ref<BinaryView> view) + { + return BNDSCViewFastGetBackingCacheCount(view->GetObject()); + } + + bool SharedCache::LoadImageWithInstallName(std::string installName) + { + char* str = BNAllocString(installName.c_str()); + return BNDSCViewLoadImageWithInstallName(m_object, str); + } + + bool SharedCache::LoadSectionAtAddress(uint64_t addr) + { + return BNDSCViewLoadSectionAtAddress(m_object, addr); + } + + bool SharedCache::LoadImageContainingAddress(uint64_t addr) + { + return BNDSCViewLoadImageContainingAddress(m_object, addr); + } + + std::vector<std::string> SharedCache::GetAvailableImages() + { + size_t count; + char** value = BNDSCViewGetInstallNames(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector<std::string> result; + for (size_t i = 0; i < count; i++) + { + result.push_back(value[i]); + } + + BNFreeStringList(value, count); + return result; + } + + std::vector<DSCMemoryRegion> SharedCache::GetLoadedMemoryRegions() + { + size_t count; + BNDSCMappedMemoryRegion* value = BNDSCViewGetLoadedRegions(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector<DSCMemoryRegion> result; + for (size_t i = 0; i < count; i++) + { + DSCMemoryRegion region; + region.vmAddress = value[i].vmAddress; + region.size = value[i].size; + region.prettyName = value[i].name; + result.push_back(region); + } + + BNDSCViewFreeLoadedRegions(value, count); + return result; + } + std::vector<BackingCache> SharedCache::GetBackingCaches() + { + size_t count; + BNDSCBackingCache* value = BNDSCViewGetBackingCaches(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector<BackingCache> result; + for (size_t i = 0; i < count; i++) + { + BackingCache cache; + cache.path = value[i].path; + cache.isPrimary = value[i].isPrimary; + for (size_t j = 0; j < value[i].mappingCount; j++) + { + BackingCacheMapping mapping; + mapping.vmAddress = value[i].mappings[j].vmAddress; + mapping.size = value[i].mappings[j].size; + mapping.fileOffset = value[i].mappings[j].fileOffset; + cache.mappings.push_back(mapping); + } + result.push_back(cache); + } + + BNDSCViewFreeBackingCaches(value, count); + return result; + } + + std::vector<DSCImage> SharedCache::GetImages() + { + size_t count; + BNDSCImage* value = BNDSCViewGetAllImages(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector<DSCImage> result; + for (size_t i = 0; i < count; i++) + { + DSCImage img; + img.name = value[i].name; + img.headerAddress = value[i].headerAddress; + for (size_t j = 0; j < value[i].mappingCount; j++) + { + DSCImageMemoryMapping mapping; + mapping.filePath = value[i].mappings[j].filePath; + mapping.name = value[i].mappings[j].name; + mapping.vmAddress = value[i].mappings[j].vmAddress; + mapping.rawViewOffset = value[i].mappings[j].rawViewOffset; + mapping.size = value[i].mappings[j].size; + mapping.loaded = value[i].mappings[j].loaded; + img.mappings.push_back(mapping); + } + result.push_back(img); + } + + BNDSCViewFreeAllImages(value, count); + return result; + } + + std::vector<DSCSymbol> SharedCache::LoadAllSymbolsAndWait() + { + size_t count; + BNDSCSymbolRep* value = BNDSCViewLoadAllSymbolsAndWait(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector<DSCSymbol> result; + for (size_t i = 0; i < count; i++) + { + DSCSymbol sym; + sym.address = value[i].address; + sym.name = value[i].name; + sym.image = value[i].image; + result.push_back(sym); + } + + BNDSCViewFreeSymbols(value, count); + return result; + } + + std::string SharedCache::GetNameForAddress(uint64_t address) + { + char* name = BNDSCViewGetNameForAddress(m_object, address); + if (name == nullptr) + return {}; + std::string result = name; + BNFreeString(name); + return result; + } + + std::string SharedCache::GetImageNameForAddress(uint64_t address) + { + char* name = BNDSCViewGetImageNameForAddress(m_object, address); + if (name == nullptr) + return {}; + std::string result = name; + BNFreeString(name); + return result; + } + + std::optional<SharedCacheMachOHeader> SharedCache::GetMachOHeaderForImage(std::string name) + { + char* str = BNAllocString(name.c_str()); + char* outputStr = BNDSCViewGetImageHeaderForName(m_object, str); + if (outputStr == nullptr) + return {}; + std::string output = outputStr; + BNFreeString(outputStr); + if (output.empty()) + return {}; + SharedCacheMachOHeader header; + header.LoadFromString(output); + return header; + } + + std::optional<SharedCacheMachOHeader> SharedCache::GetMachOHeaderForAddress(uint64_t address) + { + char* outputStr = BNDSCViewGetImageHeaderForAddress(m_object, address); + if (outputStr == nullptr) + return {}; + std::string output = outputStr; + BNFreeString(outputStr); + if (output.empty()) + return {}; + SharedCacheMachOHeader header; + header.LoadFromString(output); + return header; + } + + BNDSCViewState SharedCache::GetState() + { + return BNDSCViewGetState(m_object); + } + + void SharedCache::FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) const + { + BNDSCFindSymbolAtAddressAndApplyToAddress(m_object, symbolLocation, targetLocation, triggerReanalysis); + } +} // namespace SharedCacheAPI diff --git a/view/sharedcache/api/sharedcacheapi.h b/view/sharedcache/api/sharedcacheapi.h new file mode 100644 index 00000000..efd38532 --- /dev/null +++ b/view/sharedcache/api/sharedcacheapi.h @@ -0,0 +1,820 @@ +#pragma once + +#include <binaryninjaapi.h> +#include "MetadataSerializable.hpp" +#include "view/macho/machoview.h" +#include "sharedcachecore.h" + +using namespace BinaryNinja; + +namespace SharedCacheAPI { + template<class T> + class SCRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } + + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) + delete this; + } + + public: + std::atomic<int> m_refs; + T *m_object; + + SCRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~SCRefCountObject() {} + + T *GetObject() const { return m_object; } + + static T *GetObject(SCRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } + + void AddRef() { AddRefInternal(); } + + void Release() { ReleaseInternal(); } + + void AddRefForRegistration() { AddRefInternal(); } + }; + + + template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> + class SCCoreRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } + + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) { + if (!m_registeredRef) + delete this; + } + } + + public: + std::atomic<int> m_refs; + bool m_registeredRef = false; + T *m_object; + + SCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~SCCoreRefCountObject() {} + + T *GetObject() const { return m_object; } + + static T *GetObject(SCCoreRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } + + void AddRef() { + if (m_object && (m_refs != 0)) + AddObjectReference(m_object); + AddRefInternal(); + } + + void Release() { + if (m_object) + FreeObjectReference(m_object); + ReleaseInternal(); + } + + void AddRefForRegistration() { m_registeredRef = true; } + + void ReleaseForRegistration() { + m_object = nullptr; + m_registeredRef = false; + if (m_refs == 0) + delete this; + } + }; + + + template<class T> + class SCRef { + T *m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + + public: + SCRef<T>() : m_obj(NULL) {} + + SCRef<T>(T *obj) : m_obj(obj) { + if (m_obj) { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + SCRef<T>(const SCRef<T> &obj) : m_obj(obj.m_obj) { + if (m_obj) { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + SCRef<T>(SCRef<T> &&other) : m_obj(other.m_obj) { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + ~SCRef<T>() { + if (m_obj) { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + } + } + + SCRef<T> &operator=(const Ref<T> &obj) { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T *oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + SCRef<T> &operator=(SCRef<T> &&other) { + if (m_obj) { +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + m_obj->Release(); + } + m_obj = other.m_obj; + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + return *this; + } + + SCRef<T> &operator=(T *obj) { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T *oldObj = m_obj; + m_obj = obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + operator T *() const { + return m_obj; + } + + T *operator->() const { + return m_obj; + } + + T &operator*() const { + return *m_obj; + } + + bool operator!() const { + return m_obj == NULL; + } + + bool operator==(const T *obj) const { + return T::GetObject(m_obj) == T::GetObject(obj); + } + + bool operator==(const SCRef<T> &obj) const { + return T::GetObject(m_obj) == T::GetObject(obj.m_obj); + } + + bool operator!=(const T *obj) const { + return T::GetObject(m_obj) != T::GetObject(obj); + } + + bool operator!=(const SCRef<T> &obj) const { + return T::GetObject(m_obj) != T::GetObject(obj.m_obj); + } + + bool operator<(const T *obj) const { + return T::GetObject(m_obj) < T::GetObject(obj); + } + + bool operator<(const SCRef<T> &obj) const { + return T::GetObject(m_obj) < T::GetObject(obj.m_obj); + } + + T *GetPtr() const { + return m_obj; + } + }; + + struct DSCMemoryRegion { + uint64_t vmAddress; + uint64_t size; + std::string prettyName; + }; + + struct BackingCacheMapping { + uint64_t vmAddress; + uint64_t size; + uint64_t fileOffset; + }; + + struct BackingCache { + std::string path; + bool isPrimary; + std::vector<BackingCacheMapping> mappings; + }; + + struct DSCImageMemoryMapping { + std::string filePath; + std::string name; + uint64_t vmAddress; + uint64_t size; + bool loaded; + uint64_t rawViewOffset; + }; + + struct DSCImage { + std::string name; + uint64_t headerAddress; + std::vector<DSCImageMemoryMapping> mappings; + }; + + struct DSCSymbol { + uint64_t address; + std::string name; + std::string image; + }; + + using namespace BinaryNinja; + struct SharedCacheMachOHeader : public MetadataSerializable { + uint64_t textBase = 0; + uint64_t loadCommandOffset = 0; + mach_header_64 ident; + std::string identifierPrefix; + std::string installName; + + std::vector<std::pair<uint64_t, bool>> entryPoints; + std::vector<uint64_t> m_entryPoints; //list of entrypoints + + symtab_command symtab; + dysymtab_command dysymtab; + dyld_info_command dyldInfo; + routines_command_64 routines64; + function_starts_command functionStarts; + std::vector<section_64> moduleInitSections; + linkedit_data_command exportTrie; + linkedit_data_command chainedFixups {}; + + uint64_t relocationBase; + // Section and program headers, internally use 64-bit form as it is a superset of 32-bit + std::vector<segment_command_64> segments; //only three types of sections __TEXT, __DATA, __IMPORT + segment_command_64 linkeditSegment; + std::vector<section_64> sections; + std::vector<std::string> sectionNames; + + std::vector<section_64> symbolStubSections; + std::vector<section_64> symbolPointerSections; + + std::vector<std::string> dylibs; + + build_version_command buildVersion; + std::vector<build_tool_version> buildToolVersions; + + std::string exportTriePath; + + bool dysymPresent = false; + bool dyldInfoPresent = false; + bool exportTriePresent = false; + bool chainedFixupsPresent = false; + bool routinesPresent = false; + bool functionStartsPresent = false; + bool relocatable = false; + void Serialize(const std::string& name, mach_header_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.magic, m_activeContext.allocator); + bArr.PushBack(b.cputype, m_activeContext.allocator); + bArr.PushBack(b.cpusubtype, m_activeContext.allocator); + bArr.PushBack(b.filetype, m_activeContext.allocator); + bArr.PushBack(b.ncmds, m_activeContext.allocator); + bArr.PushBack(b.sizeofcmds, m_activeContext.allocator); + bArr.PushBack(b.flags, m_activeContext.allocator); + bArr.PushBack(b.reserved, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, mach_header_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.magic = bArr[0].GetInt64(); + b.cputype = bArr[1].GetInt64(); + b.cpusubtype = bArr[2].GetInt64(); + b.filetype = bArr[3].GetInt64(); + b.ncmds = bArr[4].GetInt64(); + b.sizeofcmds = bArr[5].GetInt64(); + b.flags = bArr[6].GetInt64(); + b.reserved = bArr[7].GetInt64(); + } + + void Serialize(const std::string& name, symtab_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.symoff, m_activeContext.allocator); + bArr.PushBack(b.nsyms, m_activeContext.allocator); + bArr.PushBack(b.stroff, m_activeContext.allocator); + bArr.PushBack(b.strsize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, symtab_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.symoff = bArr[2].GetUint(); + b.nsyms = bArr[3].GetUint(); + b.stroff = bArr[4].GetUint(); + b.strsize = bArr[5].GetUint(); + } + + void Serialize(const std::string& name, dysymtab_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.ilocalsym, m_activeContext.allocator); + bArr.PushBack(b.nlocalsym, m_activeContext.allocator); + bArr.PushBack(b.iextdefsym, m_activeContext.allocator); + bArr.PushBack(b.nextdefsym, m_activeContext.allocator); + bArr.PushBack(b.iundefsym, m_activeContext.allocator); + bArr.PushBack(b.nundefsym, m_activeContext.allocator); + bArr.PushBack(b.tocoff, m_activeContext.allocator); + bArr.PushBack(b.ntoc, m_activeContext.allocator); + bArr.PushBack(b.modtaboff, m_activeContext.allocator); + bArr.PushBack(b.nmodtab, m_activeContext.allocator); + bArr.PushBack(b.extrefsymoff, m_activeContext.allocator); + bArr.PushBack(b.nextrefsyms, m_activeContext.allocator); + bArr.PushBack(b.indirectsymoff, m_activeContext.allocator); + bArr.PushBack(b.nindirectsyms, m_activeContext.allocator); + bArr.PushBack(b.extreloff, m_activeContext.allocator); + bArr.PushBack(b.nextrel, m_activeContext.allocator); + bArr.PushBack(b.locreloff, m_activeContext.allocator); + bArr.PushBack(b.nlocrel, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, dysymtab_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.ilocalsym = bArr[2].GetUint(); + b.nlocalsym = bArr[3].GetUint(); + b.iextdefsym = bArr[4].GetUint(); + b.nextdefsym = bArr[5].GetUint(); + b.iundefsym = bArr[6].GetUint(); + b.nundefsym = bArr[7].GetUint(); + b.tocoff = bArr[8].GetUint(); + b.ntoc = bArr[9].GetUint(); + b.modtaboff = bArr[10].GetUint(); + b.nmodtab = bArr[11].GetUint(); + b.extrefsymoff = bArr[12].GetUint(); + b.nextrefsyms = bArr[13].GetUint(); + b.indirectsymoff = bArr[14].GetUint(); + b.nindirectsyms = bArr[15].GetUint(); + b.extreloff = bArr[16].GetUint(); + b.nextrel = bArr[17].GetUint(); + b.locreloff = bArr[18].GetUint(); + b.nlocrel = bArr[19].GetUint(); + } + + void Serialize(const std::string& name, dyld_info_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.rebase_off, m_activeContext.allocator); + bArr.PushBack(b.rebase_size, m_activeContext.allocator); + bArr.PushBack(b.bind_off, m_activeContext.allocator); + bArr.PushBack(b.bind_size, m_activeContext.allocator); + bArr.PushBack(b.weak_bind_off, m_activeContext.allocator); + bArr.PushBack(b.weak_bind_size, m_activeContext.allocator); + bArr.PushBack(b.lazy_bind_off, m_activeContext.allocator); + bArr.PushBack(b.lazy_bind_size, m_activeContext.allocator); + bArr.PushBack(b.export_off, m_activeContext.allocator); + bArr.PushBack(b.export_size, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, dyld_info_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.rebase_off = bArr[2].GetUint(); + b.rebase_size = bArr[3].GetUint(); + b.bind_off = bArr[4].GetUint(); + b.bind_size = bArr[5].GetUint(); + b.weak_bind_off = bArr[6].GetUint(); + b.weak_bind_size = bArr[7].GetUint(); + b.lazy_bind_off = bArr[8].GetUint(); + b.lazy_bind_size = bArr[9].GetUint(); + b.export_off = bArr[10].GetUint(); + b.export_size = bArr[11].GetUint(); + } + + void Serialize(const std::string& name, routines_command_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.init_address, m_activeContext.allocator); + bArr.PushBack(b.init_module, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, routines_command_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.init_address = bArr[2].GetUint(); + b.init_module = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, function_starts_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.funcoff, m_activeContext.allocator); + bArr.PushBack(b.funcsize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, function_starts_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.funcoff = bArr[2].GetUint(); + b.funcsize = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, std::vector<section_64> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + std::string sectNameStr; + char sectName[16]; + memcpy(sectName, s.sectname, 16); + sectName[15] = 0; + sectNameStr = std::string(sectName); + sArr.PushBack(rapidjson::Value(sectNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + std::string segNameStr; + char segName[16]; + memcpy(segName, s.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + sArr.PushBack(rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + sArr.PushBack(s.addr, m_activeContext.allocator); + sArr.PushBack(s.size, m_activeContext.allocator); + sArr.PushBack(s.offset, m_activeContext.allocator); + sArr.PushBack(s.align, m_activeContext.allocator); + sArr.PushBack(s.reloff, m_activeContext.allocator); + sArr.PushBack(s.nreloc, m_activeContext.allocator); + sArr.PushBack(s.flags, m_activeContext.allocator); + sArr.PushBack(s.reserved1, m_activeContext.allocator); + sArr.PushBack(s.reserved2, m_activeContext.allocator); + sArr.PushBack(s.reserved3, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<section_64>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + section_64 sec; + auto s2 = s.GetArray(); + std::string sectNameStr = s2[0].GetString(); + memcpy(sec.sectname, sectNameStr.c_str(), sectNameStr.size()); + std::string segNameStr = s2[1].GetString(); + memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); + sec.addr = s2[2].GetUint64(); + sec.size = s2[3].GetUint64(); + sec.offset = s2[4].GetUint(); + sec.align = s2[5].GetUint(); + sec.reloff = s2[6].GetUint(); + sec.nreloc = s2[7].GetUint(); + sec.flags = s2[8].GetUint(); + sec.reserved1 = s2[9].GetUint(); + sec.reserved2 = s2[10].GetUint(); + sec.reserved3 = s2[11].GetUint(); + b.push_back(sec); + } + } + + void Serialize(const std::string& name, linkedit_data_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.dataoff, m_activeContext.allocator); + bArr.PushBack(b.datasize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, linkedit_data_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.dataoff = bArr[2].GetUint(); + b.datasize = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, segment_command_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + std::string segNameStr; + char segName[16]; + memcpy(segName, b.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + bArr.PushBack(rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + bArr.PushBack(b.vmaddr, m_activeContext.allocator); + bArr.PushBack(b.vmsize, m_activeContext.allocator); + bArr.PushBack(b.fileoff, m_activeContext.allocator); + bArr.PushBack(b.filesize, m_activeContext.allocator); + bArr.PushBack(b.maxprot, m_activeContext.allocator); + bArr.PushBack(b.initprot, m_activeContext.allocator); + bArr.PushBack(b.nsects, m_activeContext.allocator); + bArr.PushBack(b.flags, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, segment_command_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + std::string segNameStr = bArr[0].GetString(); + memcpy(b.segname, segNameStr.c_str(), segNameStr.size()); + b.vmaddr = bArr[1].GetUint64(); + b.vmsize = bArr[2].GetUint64(); + b.fileoff = bArr[3].GetUint64(); + b.filesize = bArr[4].GetUint64(); + b.maxprot = bArr[5].GetUint(); + b.initprot = bArr[6].GetUint(); + b.nsects = bArr[7].GetUint(); + b.flags = bArr[8].GetUint(); + } + + void Serialize(const std::string& name, std::vector<segment_command_64> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + std::string segNameStr; + char segName[16]; + memcpy(segName, s.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + sArr.PushBack(rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + sArr.PushBack(s.vmaddr, m_activeContext.allocator); + sArr.PushBack(s.vmsize, m_activeContext.allocator); + sArr.PushBack(s.fileoff, m_activeContext.allocator); + sArr.PushBack(s.filesize, m_activeContext.allocator); + sArr.PushBack(s.maxprot, m_activeContext.allocator); + sArr.PushBack(s.initprot, m_activeContext.allocator); + sArr.PushBack(s.nsects, m_activeContext.allocator); + sArr.PushBack(s.flags, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<segment_command_64>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + segment_command_64 sec; + auto s2 = s.GetArray(); + std::string segNameStr = s2[0].GetString(); + memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); + sec.vmaddr = s2[1].GetUint64(); + sec.vmsize = s2[2].GetUint64(); + sec.fileoff = s2[3].GetUint64(); + sec.filesize = s2[4].GetUint64(); + sec.maxprot = s2[5].GetUint(); + sec.initprot = s2[6].GetUint(); + sec.nsects = s2[7].GetUint(); + sec.flags = s2[8].GetUint(); + b.push_back(sec); + } + } + + void Serialize(const std::string& name, build_version_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.platform, m_activeContext.allocator); + bArr.PushBack(b.minos, m_activeContext.allocator); + bArr.PushBack(b.sdk, m_activeContext.allocator); + bArr.PushBack(b.ntools, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, build_version_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.platform = bArr[2].GetUint(); + b.minos = bArr[3].GetUint(); + b.sdk = bArr[4].GetUint(); + b.ntools = bArr[5].GetUint(); + } + + void Serialize(const std::string& name, std::vector<build_tool_version> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + sArr.PushBack(s.tool, m_activeContext.allocator); + sArr.PushBack(s.version, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<build_tool_version>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + build_tool_version sec; + auto s2 = s.GetArray(); + sec.tool = s2[0].GetUint(); + sec.version = s2[1].GetUint(); + b.push_back(sec); + } + } + + void Store() override { + MSS(textBase); + MSS(loadCommandOffset); + MSS_SUBCLASS(ident); + MSS(identifierPrefix); + MSS(installName); + MSS(entryPoints); + MSS(m_entryPoints); + MSS_SUBCLASS(symtab); + MSS_SUBCLASS(dysymtab); + MSS_SUBCLASS(dyldInfo); + // MSS_SUBCLASS(routines64); + MSS_SUBCLASS(functionStarts); + MSS_SUBCLASS(moduleInitSections); + MSS_SUBCLASS(exportTrie); + MSS_SUBCLASS(chainedFixups); + MSS(relocationBase); + MSS_SUBCLASS(segments); + MSS_SUBCLASS(linkeditSegment); + MSS_SUBCLASS(sections); + MSS(sectionNames); + MSS_SUBCLASS(symbolStubSections); + MSS_SUBCLASS(symbolPointerSections); + MSS(dylibs); + MSS_SUBCLASS(buildVersion); + MSS_SUBCLASS(buildToolVersions); + MSS(exportTriePath); + MSS(dysymPresent); + MSS(dyldInfoPresent); + MSS(exportTriePresent); + MSS(chainedFixupsPresent); + MSS(routinesPresent); + MSS(functionStartsPresent); + MSS(relocatable); + } + void Load() override { + MSL(textBase); + MSL(loadCommandOffset); + MSL_SUBCLASS(ident); + MSL(identifierPrefix); + MSL(installName); + MSL(entryPoints); + MSL(m_entryPoints); + MSL_SUBCLASS(symtab); + MSL_SUBCLASS(dysymtab); + MSL_SUBCLASS(dyldInfo); + // MSL_SUBCLASS(routines64); // FIXME CRASH but also do we even use this? + MSL_SUBCLASS(functionStarts); + MSL_SUBCLASS(moduleInitSections); + MSL_SUBCLASS(exportTrie); + MSL_SUBCLASS(chainedFixups); + MSL(relocationBase); + MSL_SUBCLASS(segments); + MSL_SUBCLASS(linkeditSegment); + MSL_SUBCLASS(sections); + MSL(sectionNames); + MSL_SUBCLASS(symbolStubSections); + MSL_SUBCLASS(symbolPointerSections); + MSL(dylibs); + MSL_SUBCLASS(buildVersion); + MSL_SUBCLASS(buildToolVersions); + MSL(exportTriePath); + MSL(dysymPresent); + MSL(dyldInfoPresent); + MSL(exportTriePresent); + MSL(chainedFixupsPresent); + // MSL(routinesPresent); + MSL(functionStartsPresent); + MSL(relocatable); + } + }; + + + class SharedCache : public SCCoreRefCountObject<BNSharedCache, BNNewSharedCacheReference, BNFreeSharedCacheReference> { + public: + SharedCache(Ref<BinaryView> view); + + BNDSCViewState GetState(); + static BNDSCViewLoadProgress GetLoadProgress(Ref<BinaryView> view); + static uint64_t FastGetBackingCacheCount(Ref<BinaryView> view); + + bool LoadImageWithInstallName(std::string installName); + bool LoadSectionAtAddress(uint64_t addr); + bool LoadImageContainingAddress(uint64_t addr); + std::vector<std::string> GetAvailableImages(); + + std::vector<DSCSymbol> LoadAllSymbolsAndWait(); + + std::string GetNameForAddress(uint64_t address); + std::string GetImageNameForAddress(uint64_t address); + + std::vector<BackingCache> GetBackingCaches(); + std::vector<DSCImage> GetImages(); + + std::optional<SharedCacheMachOHeader> GetMachOHeaderForImage(std::string name); + std::optional<SharedCacheMachOHeader> GetMachOHeaderForAddress(uint64_t address); + + std::vector<DSCMemoryRegion> GetLoadedMemoryRegions(); + + void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis = true) const; + }; +}
\ No newline at end of file diff --git a/view/sharedcache/api/sharedcachecore.h b/view/sharedcache/api/sharedcachecore.h new file mode 100644 index 00000000..1c382e48 --- /dev/null +++ b/view/sharedcache/api/sharedcachecore.h @@ -0,0 +1,156 @@ +#pragma once + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __GNUC__ + #ifdef SHAREDCACHE_LIBRARY + #define SHAREDCACHE_FFI_API __attribute__((visibility("default"))) + #else // SHAREDCACHE_LIBRARY + #define SHAREDCACHE_FFI_API + #endif // SHAREDCACHE_LIBRARY +#else // __GNUC__ + #ifdef _MSC_VER + #ifndef DEMO_VERSION + #ifdef SHAREDCACHE_LIBRARY + #define SHAREDCACHE_FFI_API __declspec(dllexport) + #else // SHAREDCACHE_LIBRARY + #define SHAREDCACHE_FFI_API __declspec(dllimport) + #endif // SHAREDCACHE_LIBRARY + #else + #define SHAREDCACHE_FFI_API + #endif + #else // _MSC_VER + #define SHAREDCACHE_FFI_API + #endif // _MSC_VER +#endif // __GNUC__C + +#define CORE_ALLOCATED_STRUCT(T) + +#define CORE_ALLOCATED_CLASS(T) \ + public: \ + CORE_ALLOCATED_STRUCT(T) \ + private: + +#define DECLARE_SHAREDCACHE_API_OBJECT_INTERNAL(handle, cls, ns) \ + namespace ns { class cls; } struct handle { ns::cls* object; } + +#define DECLARE_SHAREDCACHE_API_OBJECT(handle, cls) DECLARE_SHAREDCACHE_API_OBJECT_INTERNAL(handle, cls, SharedCacheCore) + +#define IMPLEMENT_SHAREDCACHE_API_OBJECT(handle) \ + CORE_ALLOCATED_CLASS(handle) \ + private: \ + handle m_apiObject; \ + public: \ + typedef handle* APIHandle; \ + handle* GetAPIObject() { return &m_apiObject; } \ + private: +#define INIT_SHAREDCACHE_API_OBJECT() \ + m_apiObject.object = this; + + typedef enum BNDSCViewState { + Unloaded, + Loaded, + LoadedWithImages, + } BNDSCViewState; + + typedef enum BNDSCViewLoadProgress { + LoadProgressNotStarted, + LoadProgressLoadingCaches, + LoadProgressLoadingImages, + LoadProgressFinished, + } BNDSCViewLoadProgress; + + typedef struct BNBinaryView BNBinaryView; + typedef struct BNSharedCache BNSharedCache; + + typedef struct BNDSCImageMemoryMapping { + char* filePath; + char* name; + uint64_t vmAddress; + uint64_t size; + bool loaded; + uint64_t rawViewOffset; + } BNDSCImageMemoryMapping; + + typedef struct BNDSCImage { + char* name; + uint64_t headerAddress; + BNDSCImageMemoryMapping* mappings; + size_t mappingCount; + } BNDSCImage; + + typedef struct BNDSCMappedMemoryRegion { + uint64_t vmAddress; + uint64_t size; + char* name; + } BNDSCMappedMemoryRegion; + + typedef struct BNDSCBackingCacheMapping { + uint64_t vmAddress; + uint64_t size; + uint64_t fileOffset; + } BNDSCBackingCacheMapping; + + typedef struct BNDSCBackingCache { + char* path; + bool isPrimary; + BNDSCBackingCacheMapping* mappings; + size_t mappingCount; + } BNDSCBackingCache; + + typedef struct BNDSCMemoryUsageInfo { + uint64_t sharedCacheRefs; + uint64_t mmapRefs; + } BNDSCMemoryUsageInfo; + + typedef struct BNDSCSymbolRep { + uint64_t address; + char* name; + char* image; + } BNDSCSymbolRep; + + SHAREDCACHE_FFI_API BNSharedCache* BNGetSharedCache(BNBinaryView* data); + + SHAREDCACHE_FFI_API BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache); + SHAREDCACHE_FFI_API void BNFreeSharedCacheReference(BNSharedCache* cache); + + SHAREDCACHE_FFI_API char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache); + + SHAREDCACHE_FFI_API bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name); + SHAREDCACHE_FFI_API bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t name); + SHAREDCACHE_FFI_API bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address); + + SHAREDCACHE_FFI_API char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address); + SHAREDCACHE_FFI_API char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address); + + SHAREDCACHE_FFI_API BNDSCViewState BNDSCViewGetState(BNSharedCache* cache); + SHAREDCACHE_FFI_API BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID); + SHAREDCACHE_FFI_API uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* view); + + SHAREDCACHE_FFI_API BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count); + + SHAREDCACHE_FFI_API BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count); + + SHAREDCACHE_FFI_API BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count); + + SHAREDCACHE_FFI_API BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count); + + SHAREDCACHE_FFI_API void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); + + SHAREDCACHE_FFI_API char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address); + SHAREDCACHE_FFI_API char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name); + + [[maybe_unused]] SHAREDCACHE_FFI_API BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo(); + +#ifdef __cplusplus +} +#endif diff --git a/view/sharedcache/core/CMakeLists.txt b/view/sharedcache/core/CMakeLists.txt new file mode 100644 index 00000000..89e89811 --- /dev/null +++ b/view/sharedcache/core/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(sharedcachecore) + +if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) + set(BN_API_PATH $ENV{BN_API_PATH}) + if(NOT BN_API_PATH) + message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") + endif() +endif() +file(GLOB COMMON_SOURCES + *.cpp + *.h + ) +set(SOURCES ${COMMON_SOURCES}) + +add_library(sharedcachecore OBJECT ${SOURCES}) + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(binaryninjaapi INCLUDES) + + +if (VIEW_NAME) + if (SLIDEINFO_DEBUG_TAGS) + target_compile_definitions(sharedcachecore PRIVATE VIEW_NAME="${VIEW_NAME}" SHAREDCACHE_LIBRARY SLIDEINFO_DEBUG_TAGS) + else() + target_compile_definitions(sharedcachecore PRIVATE VIEW_NAME="${VIEW_NAME}" SHAREDCACHE_LIBRARY) + endif() + message(STATUS "VIEW_NAME: ${VIEW_NAME}") +else() + error("VIEW_NAME must be defined") +endif() + + +target_include_directories(sharedcachecore PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${INCLUDES}) + +set_target_properties(sharedcachecore PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ) + +if(BN_INTERNAL_BUILD) + set(LIBRARY_OUTPUT_DIRECTORY_PATH "${BN_CORE_PLUGIN_DIR}") +else() + set(LIBRARY_OUTPUT_DIRECTORY_PATH "${CMAKE_BINARY_DIR}/out/plugins") +endif() + +set_target_properties(sharedcachecore PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_DIRECTORY_PATH} + RUNTIME_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_DIRECTORY_PATH} + ) + +target_compile_definitions(sharedcachecore PRIVATE SHAREDCACHE_CORE_LIBRARY) + + diff --git a/view/sharedcache/core/DSCView.cpp b/view/sharedcache/core/DSCView.cpp new file mode 100644 index 00000000..b065f1e6 --- /dev/null +++ b/view/sharedcache/core/DSCView.cpp @@ -0,0 +1,872 @@ +// +// Created by kat on 5/23/23. +// + +/* + * If you're here looking for the code to load caches, out of luck. + * + * The VIEW_NAME is essentially a blank slate that only knows how to deserialize and reserialize itself + * based on some metadata encoded in it. + * + * The actual controller logic that does _all_ of the image loading is invoked via API -> SharedCache.cpp + * + * */ + +#include "DSCView.h" +#include "view/macho/machoview.h" +#include "SharedCache.h" + +using namespace BinaryNinja; + +/* + * DSCRawView is a "fake" parent view that the child view actually fills with data on init. + * + * This is the 'magic' that makes this sort of horrible "serialize the file headers and then refill the parent view" + * work. + * + * This throws errors on deser due to undo actions, but it still works. + * */ +DSCRawView::DSCRawView(const std::string& typeName, BinaryView* data, bool parseOnly) : + BinaryView(typeName, data->GetFile(), data) +{ + // This is going to load _only_ the dyld header of the loaded file. + // This written region will be immediately overwritten on image loading by SharedCache.cpp + GetFile()->SetFilename(data->GetFile()->GetOriginalFilename()); + auto reader = new BinaryReader(GetParentView()); + reader->Seek(16); + auto size = reader->Read32() + 0x8; + AddAutoSegment(0, size, 0, size, SegmentReadable); + GetParentView()->WriteBuffer(0, GetParentView()->ReadBuffer(0, size)); +} + +bool DSCRawView::Init() +{ + return true; +} + +DSCRawViewType::DSCRawViewType() : BinaryViewType("DSCRaw", "DSCRaw") {} + +BinaryNinja::Ref<BinaryNinja::BinaryView> DSCRawViewType::Create(BinaryView* data) +{ + return new DSCRawView("DSCRaw", data, false); +} + +BinaryNinja::Ref<BinaryNinja::BinaryView> DSCRawViewType::Parse(BinaryView* data) +{ + return new DSCRawView("DSCRaw", data, true); +} + +bool DSCRawViewType::IsTypeValidForData(BinaryNinja::BinaryView* data) +{ + // Always return false here. + // This view pretty much exists to keep a bunch of internal core logic happy as it expects certain things + // from our view's parent that we need to control, and cannot control on a standard raw view. + + // IIRC an example of this was the need to add more data past the end of the original file. + + // in ios16 caches, the primary file can be like 100kb while a standard image will easily break 1MB + + // I actually should check if the stuff related to non-file-backed-segments changes the need for this, + // but at the time it was created (2022) it was necessary. + return false; +} + + +DSCView::DSCView(const std::string& typeName, BinaryView* data, bool parseOnly) : + BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly) +{ + CreateLogger("SharedCache"); + CreateLogger("SharedCache.ObjC"); +} + +DSCView::~DSCView() +{ + if (!m_parseOnly) + MMappedFileAccessor::CloseAll(GetFile()->GetSessionId()); +} + +enum DSCPlatform { + DSCPlatformMacOS = 1, + DSCPlatformiOS = 2, +}; + +bool DSCView::Init() +{ + uint32_t platform; + GetParentView()->Read(&platform, 0xd8, 4); + char magic[17]; + GetParentView()->Read(&magic, 0, 16); + magic[16] = 0; + std::string os; + if (platform == DSCPlatformMacOS) + { + os = "mac"; + } + else if (platform == DSCPlatformiOS) + { + os = "ios"; + } + else + { + LogError("Unknown platform: %d", platform); + return false; + } + if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e") + { + SetDefaultPlatform(Platform::GetByName(os + "-aarch64")); + SetDefaultArchitecture(Architecture::GetByName("aarch64")); + } + else if (std::string(magic) == "dyld_v1 x86_64") + { + SetDefaultPlatform(Platform::GetByName(os + "-x86_64")); + SetDefaultArchitecture(Architecture::GetByName("x86_64")); + } + else + { + LogError("Unknown magic: %s", magic); + return false; + } + QualifiedNameAndType headerType; + std::string err; + + ParseTypeString("\n" + "\tstruct dyld_cache_header\n" + "\t{\n" + "\t\tchar magic[16];\t\t\t\t\t // e.g. \"dyld_v0 i386\"\n" + "\t\tuint32_t mappingOffset;\t\t\t // file offset to first dyld_cache_mapping_info\n" + "\t\tuint32_t mappingCount;\t\t\t // number of dyld_cache_mapping_info entries\n" + "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing\n" + "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing\n" + "\t\tuint64_t dyldBaseAddress;\t\t // base address of dyld when cache was built\n" + "\t\tuint64_t codeSignatureOffset;\t // file offset of code signature blob\n" + "\t\tuint64_t codeSignatureSize;\t\t // size of code signature blob (zero means to end of file)\n" + "\t\tuint64_t slideInfoOffsetUnused;\t // unused. Used to be file offset of kernel slid info\n" + "\t\tuint64_t slideInfoSizeUnused;\t // unused. Used to be size of kernel slid info\n" + "\t\tuint64_t localSymbolsOffset;\t // file offset of where local symbols are stored\n" + "\t\tuint64_t localSymbolsSize;\t\t // size of local symbols information\n" + "\t\tuint8_t uuid[16];\t\t\t\t // unique value for each shared cache file\n" + "\t\tuint64_t cacheType;\t\t\t\t // 0 for development, 1 for production // Kat: , 2 for iOS 16?\n" + "\t\tuint32_t branchPoolsOffset;\t\t // file offset to table of uint64_t pool addresses\n" + "\t\tuint32_t branchPoolsCount;\t\t // number of uint64_t entries\n" + "\t\tuint64_t accelerateInfoAddr;\t // (unslid) address of optimization info\n" + "\t\tuint64_t accelerateInfoSize;\t // size of optimization info\n" + "\t\tuint64_t imagesTextOffset;\t\t // file offset to first dyld_cache_image_text_info\n" + "\t\tuint64_t imagesTextCount;\t\t // number of dyld_cache_image_text_info entries\n" + "\t\tuint64_t patchInfoAddr;\t\t\t // (unslid) address of dyld_cache_patch_info\n" + "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the dyld_cache_patch_info\n" + "\t\tuint64_t otherImageGroupAddrUnused;\t // unused\n" + "\t\tuint64_t otherImageGroupSizeUnused;\t // unused\n" + "\t\tuint64_t progClosuresAddr;\t\t\t // (unslid) address of list of program launch closures\n" + "\t\tuint64_t progClosuresSize;\t\t\t // size of list of program launch closures\n" + "\t\tuint64_t progClosuresTrieAddr;\t\t // (unslid) address of trie of indexes into program launch closures\n" + "\t\tuint64_t progClosuresTrieSize;\t\t // size of trie of indexes into program launch closures\n" + "\t\tuint32_t platform;\t\t\t\t\t // platform number (macOS=1, etc)\n" + "\t\tuint32_t formatVersion : 8,\t\t\t // dyld3::closure::kFormatVersion\n" + "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid\n" + "\t\t\tsimulator : 1,\t\t\t // for simulator of specified platform\n" + "\t\t\tlocallyBuiltCache : 1,\t // 0 for B&I built cache, 1 for locally built cache\n" + "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables must be used for overrides\n" + "\t\t\tpadding : 20;\t\t\t\t // TBD\n" + "\t\tuint64_t sharedRegionStart;\t\t // base load address of cache if not slid\n" + "\t\tuint64_t sharedRegionSize;\t\t // overall size required to map the cache and all subCaches, if any\n" + "\t\tuint64_t maxSlide;\t\t\t\t // runtime slide of cache can be between zero and this value\n" + "\t\tuint64_t dylibsImageArrayAddr;\t // (unslid) address of ImageArray for dylibs in this cache\n" + "\t\tuint64_t dylibsImageArraySize;\t // size of ImageArray for dylibs in this cache\n" + "\t\tuint64_t dylibsTrieAddr;\t\t // (unslid) address of trie of indexes of all cached dylibs\n" + "\t\tuint64_t dylibsTrieSize;\t\t // size of trie of cached dylib paths\n" + "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherImageArraySize;\t // size of ImageArray for dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherTrieSize;\t // size of trie of dylibs and bundles with dlopen closures\n" + "\t\tuint32_t mappingWithSlideOffset;\t\t // file offset to first dyld_cache_mapping_and_slide_info\n" + "\t\tuint32_t mappingWithSlideCount;\t\t\t // number of dyld_cache_mapping_and_slide_info entries\n" + "\t\tuint64_t dylibsPBLStateArrayAddrUnused;\t // unused\n" + "\t\tuint64_t dylibsPBLSetAddr;\t\t\t\t // (unslid) address of PrebuiltLoaderSet of all cached dylibs\n" + "\t\tuint64_t programsPBLSetPoolAddr;\t\t // (unslid) address of pool of PrebuiltLoaderSet for each program\n" + "\t\tuint64_t programsPBLSetPoolSize;\t\t // size of pool of PrebuiltLoaderSet for each program\n" + "\t\tuint64_t programTrieAddr;\t\t\t\t // (unslid) address of trie mapping program path to PrebuiltLoaderSet\n" + "\t\tuint32_t programTrieSize;\n" + "\t\tuint32_t osVersion;\t\t\t\t// OS Version of dylibs in this cache for the main platform\n" + "\t\tuint32_t altPlatform;\t\t\t// e.g. iOSMac on macOS\n" + "\t\tuint32_t altOsVersion;\t\t\t// e.g. 14.0 for iOSMac\n" + "\t\tuint64_t swiftOptsOffset;\t\t// file offset to Swift optimizations header\n" + "\t\tuint64_t swiftOptsSize;\t\t\t// size of Swift optimizations header\n" + "\t\tuint32_t subCacheArrayOffset;\t// file offset to first dyld_subcache_entry\n" + "\t\tuint32_t subCacheArrayCount;\t// number of subCache entries\n" + "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local symbols\n" + "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add read-only/executable data\n" + "\t\tuint64_t rosettaReadOnlySize;\t// maximum size of the Rosetta read-only/executable region\n" + "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write data\n" + "\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n" + "\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n" + "\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n" + "\t};", headerType, err); + + Ref<Settings> settings = GetLoadSettings(GetTypeName()); + + if (!settings) + { + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("workflows.enable", true, this); + programSettings->Set("workflows.functionWorkflow", "core.function.dsc", this); + } + + // Add Mach-O file header type info + EnumerationBuilder cpuTypeBuilder; + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_VAX", MACHO_CPU_TYPE_VAX); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC680x0", MACHO_CPU_TYPE_MC680x0); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86", MACHO_CPU_TYPE_X86); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86_64", MACHO_CPU_TYPE_X86_64); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MIPS", MACHO_CPU_TYPE_MIPS); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC98000", MACHO_CPU_TYPE_MC98000); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_HPPA", MACHO_CPU_TYPE_HPPA); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM", MACHO_CPU_TYPE_ARM); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64", MACHO_CPU_TYPE_ARM64); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64_32", MACHO_CPU_TYPE_ARM64_32); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC88000", MACHO_CPU_TYPE_MC88000); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_SPARC", MACHO_CPU_TYPE_SPARC); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_I860", MACHO_CPU_TYPE_I860); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ALPHA", MACHO_CPU_TYPE_ALPHA); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC", MACHO_CPU_TYPE_POWERPC); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC64", MACHO_CPU_TYPE_POWERPC64); + Ref<Enumeration> cpuTypeEnum = cpuTypeBuilder.Finalize(); + + Ref<Type> cpuTypeEnumType = Type::EnumerationType(nullptr, cpuTypeEnum, 4, false); + std::string cpuTypeEnumName = "cpu_type_t"; + std::string cpuTypeEnumId = Type::GenerateAutoTypeId("macho", cpuTypeEnumName); + DefineType(cpuTypeEnumId, cpuTypeEnumName, cpuTypeEnumType); + + EnumerationBuilder fileTypeBuilder; + fileTypeBuilder.AddMemberWithValue("MH_OBJECT", MH_OBJECT); + fileTypeBuilder.AddMemberWithValue("MH_EXECUTE", MH_EXECUTE); + fileTypeBuilder.AddMemberWithValue("MH_FVMLIB", MH_FVMLIB); + fileTypeBuilder.AddMemberWithValue("MH_CORE", MH_CORE); + fileTypeBuilder.AddMemberWithValue("MH_PRELOAD", MH_PRELOAD); + fileTypeBuilder.AddMemberWithValue("MH_DYLIB", MH_DYLIB); + fileTypeBuilder.AddMemberWithValue("MH_DYLINKER", MH_DYLINKER); + fileTypeBuilder.AddMemberWithValue("MH_BUNDLE", MH_BUNDLE); + fileTypeBuilder.AddMemberWithValue("MH_DYLIB_STUB", MH_DYLIB_STUB); + fileTypeBuilder.AddMemberWithValue("MH_DSYM", MH_DSYM); + fileTypeBuilder.AddMemberWithValue("MH_KEXT_BUNDLE", MH_KEXT_BUNDLE); + fileTypeBuilder.AddMemberWithValue("MH_FILESET", MH_FILESET); + Ref<Enumeration> fileTypeEnum = fileTypeBuilder.Finalize(); + + Ref<Type> fileTypeEnumType = Type::EnumerationType(nullptr, fileTypeEnum, 4, false); + std::string fileTypeEnumName = "file_type_t"; + std::string fileTypeEnumId = Type::GenerateAutoTypeId("macho", fileTypeEnumName); + DefineType(fileTypeEnumId, fileTypeEnumName, fileTypeEnumType); + + EnumerationBuilder flagsTypeBuilder; + flagsTypeBuilder.AddMemberWithValue("MH_NOUNDEFS", MH_NOUNDEFS); + flagsTypeBuilder.AddMemberWithValue("MH_INCRLINK", MH_INCRLINK); + flagsTypeBuilder.AddMemberWithValue("MH_DYLDLINK", MH_DYLDLINK); + flagsTypeBuilder.AddMemberWithValue("MH_BINDATLOAD", MH_BINDATLOAD); + flagsTypeBuilder.AddMemberWithValue("MH_PREBOUND", MH_PREBOUND); + flagsTypeBuilder.AddMemberWithValue("MH_SPLIT_SEGS", MH_SPLIT_SEGS); + flagsTypeBuilder.AddMemberWithValue("MH_LAZY_INIT", MH_LAZY_INIT); + flagsTypeBuilder.AddMemberWithValue("MH_TWOLEVEL", MH_TWOLEVEL); + flagsTypeBuilder.AddMemberWithValue("MH_FORCE_FLAT", MH_FORCE_FLAT); + flagsTypeBuilder.AddMemberWithValue("MH_NOMULTIDEFS", MH_NOMULTIDEFS); + flagsTypeBuilder.AddMemberWithValue("MH_NOFIXPREBINDING", MH_NOFIXPREBINDING); + flagsTypeBuilder.AddMemberWithValue("MH_PREBINDABLE", MH_PREBINDABLE); + flagsTypeBuilder.AddMemberWithValue("MH_ALLMODSBOUND", MH_ALLMODSBOUND); + flagsTypeBuilder.AddMemberWithValue("MH_SUBSECTIONS_VIA_SYMBOLS", MH_SUBSECTIONS_VIA_SYMBOLS); + flagsTypeBuilder.AddMemberWithValue("MH_CANONICAL", MH_CANONICAL); + flagsTypeBuilder.AddMemberWithValue("MH_WEAK_DEFINES", MH_WEAK_DEFINES); + flagsTypeBuilder.AddMemberWithValue("MH_BINDS_TO_WEAK", MH_BINDS_TO_WEAK); + flagsTypeBuilder.AddMemberWithValue("MH_ALLOW_STACK_EXECUTION", MH_ALLOW_STACK_EXECUTION); + flagsTypeBuilder.AddMemberWithValue("MH_ROOT_SAFE", MH_ROOT_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_SETUID_SAFE", MH_SETUID_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_NO_REEXPORTED_DYLIBS", MH_NO_REEXPORTED_DYLIBS); + flagsTypeBuilder.AddMemberWithValue("MH_PIE", MH_PIE); + flagsTypeBuilder.AddMemberWithValue("MH_DEAD_STRIPPABLE_DYLIB", MH_DEAD_STRIPPABLE_DYLIB); + flagsTypeBuilder.AddMemberWithValue("MH_HAS_TLV_DESCRIPTORS", MH_HAS_TLV_DESCRIPTORS); + flagsTypeBuilder.AddMemberWithValue("MH_NO_HEAP_EXECUTION", MH_NO_HEAP_EXECUTION); + flagsTypeBuilder.AddMemberWithValue("MH_APP_EXTENSION_SAFE", _MH_APP_EXTENSION_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_NLIST_OUTOFSYNC_WITH_DYLDINFO", _MH_NLIST_OUTOFSYNC_WITH_DYLDINFO); + flagsTypeBuilder.AddMemberWithValue("MH_SIM_SUPPORT", _MH_SIM_SUPPORT); + flagsTypeBuilder.AddMemberWithValue("MH_DYLIB_IN_CACHE", _MH_DYLIB_IN_CACHE); + Ref<Enumeration> flagsTypeEnum = flagsTypeBuilder.Finalize(); + + Ref<Type> flagsTypeEnumType = Type::EnumerationType(nullptr, flagsTypeEnum, 4, false); + std::string flagsTypeEnumName = "flags_type_t"; + std::string flagsTypeEnumId = Type::GenerateAutoTypeId("macho", flagsTypeEnumName); + DefineType(flagsTypeEnumId, flagsTypeEnumName, flagsTypeEnumType); + + StructureBuilder machoHeaderBuilder; + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "magic"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("cpu_type_t")), "cputype"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "cpusubtype"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("file_type_t")), "filetype"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "ncmds"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "sizeofcmds"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("flags_type_t")), "flags"); + if (GetAddressSize() == 8) + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + Ref<Structure> machoHeaderStruct = machoHeaderBuilder.Finalize(); + QualifiedName headerName = (GetAddressSize() == 8) ? std::string("mach_header_64") : std::string("mach_header"); + + std::string headerTypeId = Type::GenerateAutoTypeId("macho", headerName); + Ref<Type> machoHeaderType = Type::StructureType(machoHeaderStruct); + DefineType(headerTypeId, headerName, machoHeaderType); + + EnumerationBuilder cmdTypeBuilder; + cmdTypeBuilder.AddMemberWithValue("LC_REQ_DYLD", LC_REQ_DYLD); + cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT", LC_SEGMENT); + cmdTypeBuilder.AddMemberWithValue("LC_SYMTAB", LC_SYMTAB); + cmdTypeBuilder.AddMemberWithValue("LC_SYMSEG", LC_SYMSEG); + cmdTypeBuilder.AddMemberWithValue("LC_THREAD", LC_THREAD); + cmdTypeBuilder.AddMemberWithValue("LC_UNIXTHREAD", LC_UNIXTHREAD); + cmdTypeBuilder.AddMemberWithValue("LC_LOADFVMLIB", LC_LOADFVMLIB); + cmdTypeBuilder.AddMemberWithValue("LC_IDFVMLIB", LC_IDFVMLIB); + cmdTypeBuilder.AddMemberWithValue("LC_IDENT", LC_IDENT); + cmdTypeBuilder.AddMemberWithValue("LC_FVMFILE", LC_FVMFILE); + cmdTypeBuilder.AddMemberWithValue("LC_PREPAGE", LC_PREPAGE); + cmdTypeBuilder.AddMemberWithValue("LC_DYSYMTAB", LC_DYSYMTAB); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLIB", LC_LOAD_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLIB", LC_ID_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLINKER", LC_LOAD_DYLINKER); + cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLINKER", LC_ID_DYLINKER); + cmdTypeBuilder.AddMemberWithValue("LC_PREBOUND_DYLIB", LC_PREBOUND_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES", LC_ROUTINES); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_FRAMEWORK", LC_SUB_FRAMEWORK); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_UMBRELLA", LC_SUB_UMBRELLA); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_CLIENT", LC_SUB_CLIENT); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_LIBRARY", LC_SUB_LIBRARY); + cmdTypeBuilder.AddMemberWithValue("LC_TWOLEVEL_HINTS", LC_TWOLEVEL_HINTS); + cmdTypeBuilder.AddMemberWithValue("LC_PREBIND_CKSUM", LC_PREBIND_CKSUM); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_WEAK_DYLIB", LC_LOAD_WEAK_DYLIB); // (0x18 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_64", LC_SEGMENT_64); + cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES_64", LC_ROUTINES_64); + cmdTypeBuilder.AddMemberWithValue("LC_UUID", LC_UUID); + cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH); // (0x1c | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE); + cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO); + cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB); // (0x23 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX); + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS); + cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT); + cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE); + cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION); + cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS); + cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO_64", _LC_ENCRYPTION_INFO_64); + cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTION", _LC_LINKER_OPTION); + cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTIMIZATION_HINT", _LC_LINKER_OPTIMIZATION_HINT); + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_TVOS", _LC_VERSION_MIN_TVOS); + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_WATCHOS", LC_VERSION_MIN_WATCHOS); + cmdTypeBuilder.AddMemberWithValue("LC_NOTE", LC_NOTE); + cmdTypeBuilder.AddMemberWithValue("LC_BUILD_VERSION", LC_BUILD_VERSION); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_EXPORTS_TRIE", LC_DYLD_EXPORTS_TRIE); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_CHAINED_FIXUPS", LC_DYLD_CHAINED_FIXUPS); + cmdTypeBuilder.AddMemberWithValue("LC_FILESET_ENTRY", LC_FILESET_ENTRY); + Ref<Enumeration> cmdTypeEnum = cmdTypeBuilder.Finalize(); + + Ref<Type> cmdTypeEnumType = Type::EnumerationType(nullptr, cmdTypeEnum, 4, false); + std::string cmdTypeEnumName = "load_command_type_t"; + std::string cmdTypeEnumId = Type::GenerateAutoTypeId("macho", cmdTypeEnumName); + DefineType(cmdTypeEnumId, cmdTypeEnumName, cmdTypeEnumType); + + StructureBuilder loadCommandBuilder; + loadCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + Ref<Structure> loadCommandStruct = loadCommandBuilder.Finalize(); + QualifiedName loadCommandName = std::string("load_command"); + std::string loadCommandTypeId = Type::GenerateAutoTypeId("macho", loadCommandName); + Ref<Type> loadCommandType = Type::StructureType(loadCommandStruct); + DefineType(loadCommandTypeId, loadCommandName, loadCommandType); + + EnumerationBuilder protTypeBuilder; + protTypeBuilder.AddMemberWithValue("VM_PROT_NONE", MACHO_VM_PROT_NONE); + protTypeBuilder.AddMemberWithValue("VM_PROT_READ", MACHO_VM_PROT_READ); + protTypeBuilder.AddMemberWithValue("VM_PROT_WRITE", MACHO_VM_PROT_WRITE); + protTypeBuilder.AddMemberWithValue("VM_PROT_EXECUTE", MACHO_VM_PROT_EXECUTE); + // protTypeBuilder.AddMemberWithValue("VM_PROT_DEFAULT", MACHO_VM_PROT_DEFAULT); + // protTypeBuilder.AddMemberWithValue("VM_PROT_ALL", MACHO_VM_PROT_ALL); + protTypeBuilder.AddMemberWithValue("VM_PROT_NO_CHANGE", MACHO_VM_PROT_NO_CHANGE); + protTypeBuilder.AddMemberWithValue("VM_PROT_COPY_OR_WANTS_COPY", MACHO_VM_PROT_COPY); + // protTypeBuilder.AddMemberWithValue("VM_PROT_WANTS_COPY", MACHO_VM_PROT_WANTS_COPY); + Ref<Enumeration> protTypeEnum = protTypeBuilder.Finalize(); + + Ref<Type> protTypeEnumType = Type::EnumerationType(nullptr, protTypeEnum, 4, false); + std::string protTypeEnumName = "vm_prot_t"; + std::string protTypeEnumId = Type::GenerateAutoTypeId("macho", protTypeEnumName); + DefineType(protTypeEnumId, protTypeEnumName, protTypeEnumType); + + EnumerationBuilder segFlagsTypeBuilder; + segFlagsTypeBuilder.AddMemberWithValue("SG_HIGHVM", SG_HIGHVM); + segFlagsTypeBuilder.AddMemberWithValue("SG_FVMLIB", SG_FVMLIB); + segFlagsTypeBuilder.AddMemberWithValue("SG_NORELOC", SG_NORELOC); + segFlagsTypeBuilder.AddMemberWithValue("SG_PROTECTED_VERSION_1", SG_PROTECTED_VERSION_1); + Ref<Enumeration> segFlagsTypeEnum = segFlagsTypeBuilder.Finalize(); + + Ref<Type> segFlagsTypeEnumType = Type::EnumerationType(nullptr, segFlagsTypeEnum, 4, false); + std::string segFlagsTypeEnumName = "sg_flags_t"; + std::string segFlagsTypeEnumId = Type::GenerateAutoTypeId("macho", segFlagsTypeEnumName); + DefineType(segFlagsTypeEnumId, segFlagsTypeEnumName, segFlagsTypeEnumType); + + StructureBuilder loadSegmentCommandBuilder; + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + loadSegmentCommandBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmaddr"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmsize"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "fileoff"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "filesize"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "nsects"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); + Ref<Structure> loadSegmentCommandStruct = loadSegmentCommandBuilder.Finalize(); + QualifiedName loadSegmentCommandName = std::string("segment_command"); + std::string loadSegmentCommandTypeId = Type::GenerateAutoTypeId("macho", loadSegmentCommandName); + Ref<Type> loadSegmentCommandType = Type::StructureType(loadSegmentCommandStruct); + DefineType(loadSegmentCommandTypeId, loadSegmentCommandName, loadSegmentCommandType); + + StructureBuilder loadSegmentCommand64Builder; + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "cmdsize"); + loadSegmentCommand64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmaddr"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmsize"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "fileoff"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "filesize"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "nsects"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); + Ref<Structure> loadSegmentCommand64Struct = loadSegmentCommand64Builder.Finalize(); + QualifiedName loadSegment64CommandName = std::string("segment_command_64"); + std::string loadSegment64CommandTypeId = Type::GenerateAutoTypeId("macho", loadSegment64CommandName); + Ref<Type> loadSegment64CommandType = Type::StructureType(loadSegmentCommand64Struct); + DefineType(loadSegment64CommandTypeId, loadSegment64CommandName, loadSegment64CommandType); + + StructureBuilder sectionBuilder; + sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); + sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "addr"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "size"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "offset"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "align"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reloff"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "nreloc"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "flags"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved1"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved2"); + Ref<Structure> sectionStruct = sectionBuilder.Finalize(); + QualifiedName sectionName = std::string("section"); + std::string sectionTypeId = Type::GenerateAutoTypeId("macho", sectionName); + Ref<Type> sectionType = Type::StructureType(sectionStruct); + DefineType(sectionTypeId, sectionName, sectionType); + + StructureBuilder section64Builder; + section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); + section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + section64Builder.AddMember(Type::IntegerType(8, false), "addr"); + section64Builder.AddMember(Type::IntegerType(8, false), "size"); + section64Builder.AddMember(Type::IntegerType(4, false), "offset"); + section64Builder.AddMember(Type::IntegerType(4, false), "align"); + section64Builder.AddMember(Type::IntegerType(4, false), "reloff"); + section64Builder.AddMember(Type::IntegerType(4, false), "nreloc"); + section64Builder.AddMember(Type::IntegerType(4, false), "flags"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved1"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved2"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved3"); + Ref<Structure> section64Struct = section64Builder.Finalize(); + QualifiedName section64Name = std::string("section_64"); + std::string section64TypeId = Type::GenerateAutoTypeId("macho", section64Name); + Ref<Type> section64Type = Type::StructureType(section64Struct); + DefineType(section64TypeId, section64Name, section64Type); + + StructureBuilder symtabBuilder; + symtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "symoff"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "nsyms"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "stroff"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "strsize"); + Ref<Structure> symtabStruct = symtabBuilder.Finalize(); + QualifiedName symtabName = std::string("symtab"); + std::string symtabTypeId = Type::GenerateAutoTypeId("macho", symtabName); + Ref<Type> symtabType = Type::StructureType(symtabStruct); + DefineType(symtabTypeId, symtabName, symtabType); + + StructureBuilder dynsymtabBuilder; + dynsymtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ilocalsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocalsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iextdefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextdefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iundefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nundefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "tocoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ntoc"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "modtaboff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nmodtab"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extrefsymoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrefsyms"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "indirectsymoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nindirectsyms"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extreloff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrel"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "locreloff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocrel"); + Ref<Structure> dynsymtabStruct = dynsymtabBuilder.Finalize(); + QualifiedName dynsymtabName = std::string("dynsymtab"); + std::string dynsymtabTypeId = Type::GenerateAutoTypeId("macho", dynsymtabName); + Ref<Type> dynsymtabType = Type::StructureType(dynsymtabStruct); + DefineType(dynsymtabTypeId, dynsymtabName, dynsymtabType); + + StructureBuilder uuidBuilder; + uuidBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + uuidBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + uuidBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, false), 16), "uuid"); + Ref<Structure> uuidStruct = uuidBuilder.Finalize(); + QualifiedName uuidName = std::string("uuid"); + std::string uuidTypeId = Type::GenerateAutoTypeId("macho", uuidName); + Ref<Type> uuidType = Type::StructureType(uuidStruct); + DefineType(uuidTypeId, uuidName, uuidType); + + StructureBuilder linkeditDataBuilder; + linkeditDataBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "dataoff"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "datasize"); + Ref<Structure> linkeditDataStruct = linkeditDataBuilder.Finalize(); + QualifiedName linkeditDataName = std::string("linkedit_data"); + std::string linkeditDataTypeId = Type::GenerateAutoTypeId("macho", linkeditDataName); + Ref<Type> linkeditDataType = Type::StructureType(linkeditDataStruct); + DefineType(linkeditDataTypeId, linkeditDataName, linkeditDataType); + + StructureBuilder encryptionInfoBuilder; + encryptionInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptoff"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptsize"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptid"); + Ref<Structure> encryptionInfoStruct = encryptionInfoBuilder.Finalize(); + QualifiedName encryptionInfoName = std::string("encryption_info"); + std::string encryptionInfoTypeId = Type::GenerateAutoTypeId("macho", encryptionInfoName); + Ref<Type> encryptionInfoType = Type::StructureType(encryptionInfoStruct); + DefineType(encryptionInfoTypeId, encryptionInfoName, encryptionInfoType); + + StructureBuilder versionMinBuilder; + versionMinBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "version"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "sdk"); + Ref<Structure> versionMinStruct = versionMinBuilder.Finalize(); + QualifiedName versionMinName = std::string("version_min"); + std::string versionMinTypeId = Type::GenerateAutoTypeId("macho", versionMinName); + Ref<Type> versionMinType = Type::StructureType(versionMinStruct); + DefineType(versionMinTypeId, versionMinName, versionMinType); + + StructureBuilder dyldInfoBuilder; + dyldInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_size"); + Ref<Structure> dyldInfoStruct = dyldInfoBuilder.Finalize(); + QualifiedName dyldInfoName = std::string("dyld_info"); + std::string dyldInfoTypeId = Type::GenerateAutoTypeId("macho", dyldInfoName); + Ref<Type> dyldInfoType = Type::StructureType(dyldInfoStruct); + DefineType(dyldInfoTypeId, dyldInfoName, dyldInfoType); + + StructureBuilder dylibBuilder; + dylibBuilder.AddMember(Type::IntegerType(4, false), "name"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "timestamp"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "current_version"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "compatibility_version"); + Ref<Structure> dylibStruct = dylibBuilder.Finalize(); + QualifiedName dylibName = std::string("dylib"); + std::string dylibTypeId = Type::GenerateAutoTypeId("macho", dylibName); + Ref<Type> dylibType = Type::StructureType(dylibStruct); + DefineType(dylibTypeId, dylibName, dylibType); + + StructureBuilder dylibCommandBuilder; + dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dylibCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("dylib")), "dylib"); + Ref<Structure> dylibCommandStruct = dylibCommandBuilder.Finalize(); + QualifiedName dylibCommandName = std::string("dylib_command"); + std::string dylibCommandTypeId = Type::GenerateAutoTypeId("macho", dylibCommandName); + Ref<Type> dylibCommandType = Type::StructureType(dylibCommandStruct); + DefineType(dylibCommandTypeId, dylibCommandName, dylibCommandType); + + StructureBuilder filesetEntryCommandBuilder; + filesetEntryCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "vmaddr"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "fileoff"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "entry_id"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + Ref<Structure> filesetEntryCommandStruct = filesetEntryCommandBuilder.Finalize(); + QualifiedName filesetEntryCommandName = std::string("fileset_entry_command"); + std::string filesetEntryCommandTypeId = Type::GenerateAutoTypeId("macho", filesetEntryCommandName); + Ref<Type> filesetEntryCommandType = Type::StructureType(filesetEntryCommandStruct); + DefineType(filesetEntryCommandTypeId, filesetEntryCommandName, filesetEntryCommandType); + + std::vector<SharedCacheCore::MemoryRegion> regionsMappedIntoMemory; + if (auto meta = GetParentView()->GetParentView()->QueryMetadata(SharedCacheCore::SharedCacheMetadataTag)) + { + std::string data = GetParentView()->GetParentView()->GetStringMetadata(SharedCacheCore::SharedCacheMetadataTag); + std::stringstream ss; + ss.str(data); + rapidjson::Document result(rapidjson::kObjectType); + + result.Parse(data.c_str()); + for (auto& imgV : result["regionsMappedIntoMemory"].GetArray()) + { + SharedCacheCore::MemoryRegion region; + region.LoadFromValue(imgV); + regionsMappedIntoMemory.push_back(region); + } + + std::unordered_map<uint64_t, std::string> imageStartToInstallName; + // key "m_imageStarts" + for (auto& imgV : result["m_imageStarts"].GetArray()) + { + std::string name = imgV.GetArray()[0].GetString(); + uint64_t addr = imgV.GetArray()[1].GetUint64(); + imageStartToInstallName[addr] = name; + } + + std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>>> exportInfos; + for (auto& exportInfo : result["exportInfos"].GetArray()) + { + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> exportInfoVec; + for (auto& exportInfoPair : exportInfo.GetArray()) + { + exportInfoVec.push_back({exportInfoPair[0].GetUint64(), { (BNSymbolType)exportInfoPair[1].GetUint(), exportInfoPair[2].GetString()}}); + } + exportInfos.push_back({exportInfo[0].GetUint64(), exportInfoVec}); + } + + std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>>> symbolInfos; + for (auto& symbolInfo : result["symbolInfos"].GetArray()) + { + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> symbolInfoVec; + for (auto& symbolInfoPair : symbolInfo.GetArray()) + { + symbolInfoVec.push_back({symbolInfoPair[0].GetUint64(), { (BNSymbolType)symbolInfoPair[1].GetUint(), symbolInfoPair[2].GetString()}}); + } + symbolInfos.push_back({symbolInfo[0].GetUint64(), symbolInfoVec}); + } + + // We need to re-map data located in the Raw (parent parent) viewtype to the DSCRaw (parent) viewtype. + for (auto region : regionsMappedIntoMemory) + { + GetParentView()->AddUserSegment( + region.rawViewOffsetIfLoaded, region.size, region.rawViewOffsetIfLoaded, region.size, region.flags); + GetParentView()->WriteBuffer( + region.rawViewOffsetIfLoaded, GetParentView()->GetParentView()->ReadBuffer(region.rawViewOffsetIfLoaded, region.size)); + } + + BeginBulkModifySymbols(); + for (const auto & [imageBaseAddr, symbolList] : symbolInfos) + { + std::vector<Ref<Symbol>> symbolsList; + for (const auto & [symAddr, symTypeAndName] : symbolList) + { + symbolsList.push_back(new Symbol(symTypeAndName.first, symTypeAndName.second, symAddr)); + } + + auto typelib = GetTypeLibrary(imageStartToInstallName[imageBaseAddr]); + + for (const auto& symbol : symbolsList) + { + if (typelib) + { + auto type = typelib->GetNamedObject(symbol->GetRawName()); + if (type) + { + DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), symbol, type); + continue; + } + } + DefineAutoSymbol(symbol); + } + } + + for (const auto & [imageBaseAddr, exportList] : exportInfos) + { + std::vector<Ref<Symbol>> symbolsList; + for (const auto & [exportAddr, exportTypeAndName] : exportList) + { + symbolsList.push_back(new Symbol(exportTypeAndName.first, exportTypeAndName.second, exportAddr)); + } + + auto typelib = GetTypeLibrary(imageStartToInstallName[imageBaseAddr]); + + for (const auto& symbol : symbolsList) + { + if (typelib) + { + auto type = typelib->GetNamedObject(symbol->GetRawName()); + if (type) + { + DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), symbol, type); + continue; + } + } + DefineAutoSymbol(symbol); + } + } + EndBulkModifySymbols(); + } + + // uint32_t at 0x10 is offset to mapping table. + // first mapping struct in that table is base of primary + // first uint64_t in that struct is the base address of the primary + // double gpv here because DSCRaw explicitly stops at the start of this mapping table + uint64_t basePointer = 0; + GetParentView()->GetParentView()->Read(&basePointer, 16, 4); + if (basePointer == 0) + { + LogError("Failed to read base pointer"); + return false; + } + uint64_t primaryBase = 0; + GetParentView()->GetParentView()->Read(&primaryBase, basePointer, 8); + if (primaryBase == 0) + { + LogError("Failed to read primary base at 0x%llx", basePointer); + return false; + } + + AddAutoSegment(primaryBase, 0x200, 0, 0x200, SegmentReadable); + DefineType("dyld_cache_header", headerType.name, headerType.type); + DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); + + return true; +} + + +DSCViewType::DSCViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {} + +BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Create(BinaryNinja::BinaryView* data) +{ + return new DSCView(VIEW_NAME, new DSCRawView("DSCRawView", data, false), false); +} + + +Ref<Settings> DSCViewType::GetLoadSettingsForData(BinaryView* data) +{ + Ref<BinaryView> viewRef = Parse(data); + if (!viewRef || !viewRef->Init()) + { + LogError("View type '%s' could not be created", GetName().c_str()); + return nullptr; + } + + Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef); + + // specify default load settings that can be overridden + std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"}; + settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); + + for (const auto& override : overrides) + { + if (settings->Contains(override)) + settings->UpdateProperty(override, "readOnly", false); + } + + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("workflows.functionWorkflow", "core.function.dsc", viewRef); + + settings->RegisterSetting("loader.dsc.processCFStrings", + R"({ + "title" : "Process CFString Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.processObjC", + R"({ + "title" : "Process Objective-C Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements", + R"({ + "title" : "Auto-Load Objective-C Stub Requirements", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData", + R"({ + "title" : "Auto-Load Stub Islands", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.allowLoadingLinkeditSegments", + R"({ + "title" : "Allow Loading __LINKEDIT Segments", + "type" : "boolean", + "default" : false, + "description" : "Allow mapping __LINKEDIT segments. These are large regions of symbol data that are automatically processed by BinaryNinja without the need for mapping. On newer caches, __LINKEDIT for all images may end up merged and be >300MB in size. This will likely cause severe performance degradation with _zero_ benefit." + })"); + + settings->RegisterSetting("loader.dsc.processFunctionStarts", + R"({ + "title" : "Process Mach-O Function Starts Tables", + "type" : "boolean", + "default" : true, + "description" : "Add function starts sourced from the Function Starts tables to the core for analysis." + })"); + + // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O + // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This + // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema + // contains 'loader.macho.universalImageOffset'. + Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName()); + if (loadSettings && !loadSettings->IsEmpty()) + settings->DeserializeSchema(loadSettings->SerializeSchema()); + + return settings; +} + + +BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Parse(BinaryNinja::BinaryView* data) +{ + return new DSCView(VIEW_NAME, new DSCRawView("DSCRawView", data, true), true); +} + +bool DSCViewType::IsTypeValidForData(BinaryNinja::BinaryView* data) +{ + if (!data) + return false; + + DataBuffer sig = data->ReadBuffer(data->GetStart(), 4); + if (sig.GetLength() != 4) + return false; + + const char* magic = (char*)sig.GetData(); + if (strncmp(magic, "dyld", 4) == 0) + return true; + + return false; +} diff --git a/view/sharedcache/core/DSCView.h b/view/sharedcache/core/DSCView.h new file mode 100644 index 00000000..1b891e36 --- /dev/null +++ b/view/sharedcache/core/DSCView.h @@ -0,0 +1,65 @@ +// +// Created by kat on 5/23/23. +// + +#ifndef SHAREDCACHE_DSCVIEW_H +#define SHAREDCACHE_DSCVIEW_H + +#include <binaryninjaapi.h> + +class DSCRawView : public BinaryNinja::BinaryView { + std::string m_filename; +public: + + DSCRawView(const std::string &typeName, BinaryView *data, bool parseOnly = false); + + bool Init() override; +}; + + +class DSCRawViewType : public BinaryNinja::BinaryViewType { + +public: + BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView* data) override; + BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView* data) override; + bool IsTypeValidForData(BinaryNinja::BinaryView *data) override; + + bool IsDeprecated() override { return false; } + + BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView *data) override { return nullptr; } + +public: + DSCRawViewType(); +}; + + +class DSCView : public BinaryNinja::BinaryView { + bool m_parseOnly; +public: + + DSCView(const std::string &typeName, BinaryView *data, bool parseOnly = false); + + ~DSCView() override; + + bool Init() override; +}; + + +class DSCViewType : public BinaryNinja::BinaryViewType { + +public: + DSCViewType(); + + BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView *data) override; + + BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView *data) override; + + bool IsTypeValidForData(BinaryNinja::BinaryView *data) override; + + bool IsDeprecated() override { return false; } + + BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView *data) override; +}; + + +#endif //SHAREDCACHE_DSCVIEW_H diff --git a/view/sharedcache/core/MetadataSerializable.hpp b/view/sharedcache/core/MetadataSerializable.hpp new file mode 100644 index 00000000..c49a9e8c --- /dev/null +++ b/view/sharedcache/core/MetadataSerializable.hpp @@ -0,0 +1,537 @@ +// +// Created by kat on 5/31/23. +// + +/* + * Welcome to, this file. + * + * This is a metadata serialization helper. + * + * Have you ever wished turning a complex datastructure into a Metadata object was as easy in C++ as it is in python? + * Do you like macros and templates? + * + * Great news. + * + * Implement these on your `public MetadataSerializable` subclass: + * ``` + void Store() override { + MSS(m_someVariable); + MSS(m_someOtherVariable); + } + void Load() override { + MSL(m_someVariable); + MSL(m_someOtherVariable); + } + ``` + * Then, you can turn your object into a Metadata object with `AsMetadata()`, and load it back with + `LoadFromMetadata()`. + * + * Serialized fields will be automatically repopulated. + * + * Other ser/deser formats (rapidjson objects, strings) also exist. You can use these to achieve nesting, but probably + avoid that. + * */ + +#include "binaryninjaapi.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/prettywriter.h" + +#ifndef SHAREDCACHE_METADATASERIALIZABLE_HPP +#define SHAREDCACHE_METADATASERIALIZABLE_HPP + +#define MSS(name) store(#name, name) +#define MSS_CAST(name, type) store(#name, (type) name) +#define MSS_SUBCLASS(name) Serialize(#name, name) +#define MSL(name) name = load(#name, name) +#define MSL_CAST(name, storedType, type) name = (type)load(#name, (storedType) name) +#define MSL_SUBCLASS(name) Deserialize(#name, name) + +using namespace BinaryNinja; + +class MetadataSerializable +{ +protected: + struct SerialContext + { + rapidjson::Document doc; + rapidjson::Document::AllocatorType allocator; + }; + struct DeserContext + { + rapidjson::Document doc; + }; + + DeserContext m_activeDeserContext; + SerialContext m_activeContext; + +public: + MetadataSerializable() + { + m_activeContext.doc.SetObject(); + m_activeContext.allocator = m_activeContext.doc.GetAllocator(); + } + + // copy constructor + MetadataSerializable(const MetadataSerializable& other) + { + m_activeContext.doc.CopyFrom(other.m_activeContext.doc, m_activeContext.doc.GetAllocator()); + } + + // copy assignment + MetadataSerializable& operator=(const MetadataSerializable& other) + { + m_activeContext.doc.CopyFrom(other.m_activeContext.doc, m_activeContext.doc.GetAllocator()); + return *this; + } + + virtual ~MetadataSerializable() + { + } + + void SetupSerContext(rapidjson::Document::AllocatorType* alloc = nullptr) + { + m_activeContext.doc.SetObject(); + m_activeContext.allocator = m_activeContext.doc.GetAllocator(); + } + void S() + { + // fixme factor out + } + void Serialize(std::string& name, bool b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, bool& b) { b = m_activeDeserContext.doc[name.c_str()].GetBool(); } + + void Serialize(std::string& name, uint8_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint8_t& b) + { + b = static_cast<uint8_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint16_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint16_t& b) + { + b = static_cast<uint16_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint32_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint32_t& b) + { + b = static_cast<uint32_t>(m_activeDeserContext.doc[name.c_str()].GetUint64()); + } + + void Serialize(std::string& name, uint64_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, uint64_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetUint64(); + } + + void Serialize(std::string& name, int8_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int8_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, int16_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int16_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, int32_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int32_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt(); + } + + void Serialize(std::string& name, int64_t b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, b, m_activeContext.allocator); + } + void Deserialize(std::string& name, int64_t& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetInt64(); + } + + void Serialize(std::string& name, std::string b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value value(b.c_str(), m_activeContext.allocator); + m_activeContext.doc.AddMember(key, value, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::string& b) + { + b = m_activeDeserContext.doc[name.c_str()].GetString(); + } + + void Serialize(std::string& name, std::map<uint64_t, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::map<uint64_t, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::unordered_map<uint64_t, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Serialize(std::string& name, std::unordered_map<std::string, std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + rapidjson::Value _key(i.first.c_str(), m_activeContext.allocator); + rapidjson::Value value(i.second.c_str(), m_activeContext.allocator); + p.PushBack(_key, m_activeContext.allocator); + p.PushBack(value, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<uint64_t, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::unordered_map<uint64_t, uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + p.PushBack(i.first, m_activeContext.allocator); + p.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<uint64_t, uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetUint64(); + } + + // std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>> + void Serialize(std::string& name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value classes(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value classArr(rapidjson::kArrayType); + rapidjson::Value classKey(i.first.c_str(), m_activeContext.allocator); + classArr.PushBack(classKey, m_activeContext.allocator); + rapidjson::Value membersArr(rapidjson::kArrayType); + for (auto& j : i.second) + { + rapidjson::Value member(rapidjson::kArrayType); + member.PushBack(j.first, m_activeContext.allocator); + member.PushBack(j.second, m_activeContext.allocator); + membersArr.PushBack(member, m_activeContext.allocator); + } + classArr.PushBack(membersArr, m_activeContext.allocator); + classes.PushBack(classArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, classes, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::string key = i.GetArray()[0].GetString(); + std::unordered_map<uint64_t, uint64_t> memArray; + for (auto& member : i.GetArray()[1].GetArray()) + { + memArray[member.GetArray()[0].GetUint64()] = member.GetArray()[1].GetUint64(); + } + b[key] = memArray; + } + } + + void Deserialize(std::string& name, std::unordered_map<std::string, std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetString(); + } + + void Serialize(std::string& name, std::vector<std::string> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (const auto& s : b) + { + rapidjson::Value value(s.c_str(), m_activeContext.allocator); + bArr.PushBack(value, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::string>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + b.emplace_back(i.GetString()); + } + + void Serialize(std::string& name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value segV(rapidjson::kArrayType); + segV.PushBack(i.first, m_activeContext.allocator); + segV.PushBack(i.second.first, m_activeContext.allocator); + segV.PushBack(i.second.second, m_activeContext.allocator); + bArr.PushBack(segV, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> j; + j.first = i.GetArray()[0].GetUint64(); + j.second.first = i.GetArray()[1].GetUint64(); + j.second.second = i.GetArray()[2].GetUint64(); + b.push_back(j); + } + } + + void Serialize(std::string& name, std::vector<std::pair<uint64_t, bool>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value segV(rapidjson::kArrayType); + segV.PushBack(i.first, m_activeContext.allocator); + segV.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(segV, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::pair<uint64_t, bool>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::pair<uint64_t, bool> j; + j.first = i.GetArray()[0].GetUint64(); + j.second = i.GetArray()[1].GetBool(); + b.push_back(j); + } + } + + void Serialize(std::string& name, std::vector<uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + bArr.PushBack(i, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + b.push_back(i.GetUint64()); + } + } + + // std::unordered_map<std::string, uint64_t> + void Serialize(std::string& name, std::unordered_map<std::string, uint64_t> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value p(rapidjson::kArrayType); + rapidjson::Value _key(i.first.c_str(), m_activeContext.allocator); + p.PushBack(_key, m_activeContext.allocator); + p.PushBack(i.second, m_activeContext.allocator); + bArr.PushBack(p, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::unordered_map<std::string, uint64_t>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetUint64(); + } + } + // std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>> + void Serialize(std::string& name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& i : b) + { + rapidjson::Value segV(rapidjson::kArrayType); + segV.PushBack(i.first, m_activeContext.allocator); + rapidjson::Value segArr(rapidjson::kArrayType); + for (auto& j : i.second) + { + rapidjson::Value segPair(rapidjson::kArrayType); + segPair.PushBack(j.first, m_activeContext.allocator); + rapidjson::Value segStr(j.second.c_str(), m_activeContext.allocator); + segPair.PushBack(segStr, m_activeContext.allocator); + segArr.PushBack(segPair, m_activeContext.allocator); + } + segV.PushBack(segArr, m_activeContext.allocator); + bArr.PushBack(segV, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + void Deserialize(std::string& name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b) + { + for (auto& i : m_activeDeserContext.doc[name.c_str()].GetArray()) + { + std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>> j; + j.first = i.GetArray()[0].GetUint64(); + for (auto& k : i.GetArray()[1].GetArray()) + { + j.second.push_back({k.GetArray()[0].GetUint64(), k.GetArray()[1].GetString()}); + } + b.push_back(j); + } + } + + template <typename T> + void store(std::string x, T y) + { + Serialize(x, y); + } + + template <typename T> + T load(std::string x, T y) + { + T val; + Deserialize(x, val); + return val; + } + + rapidjson::Document& GetDoc() + { + S(); + Store(); + return m_activeContext.doc; + } + +public: + virtual void Store() = 0; + virtual void Load() = 0; + + std::string AsString() + { + rapidjson::StringBuffer strbuf; + rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(strbuf); + GetDoc().Accept(writer); + + std::string s = strbuf.GetString(); + return s; + } + rapidjson::Document& AsDocument() { return GetDoc(); } + void LoadFromString(const std::string& s) + { + m_activeDeserContext.doc.Parse(s.c_str()); + Load(); + } + void LoadFromValue(rapidjson::Value& s) + { + m_activeDeserContext.doc.CopyFrom(s, m_activeDeserContext.doc.GetAllocator()); + Load(); + } + Ref<Metadata> AsMetadata() { return new Metadata(AsString()); } + bool LoadFromMetadata(const Ref<Metadata>& meta) + { + if (!meta->IsString()) + return false; + LoadFromString(meta->GetString()); + return true; + } +}; + +#endif // SHAREDCACHE_METADATASERIALIZABLE_HPP diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp new file mode 100644 index 00000000..265506c2 --- /dev/null +++ b/view/sharedcache/core/ObjC.cpp @@ -0,0 +1,1524 @@ +#include "ObjC.h" +#include "inttypes.h" +#include "rapidjson/rapidjson.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/prettywriter.h" + +using namespace BinaryNinja; +using namespace DSCObjC; +using namespace SharedCacheCore; + +Ref<Metadata> DSCObjCProcessor::SerializeMethod(uint64_t loc, const Method& method) +{ + std::map<std::string, Ref<Metadata>> methodMeta; + + methodMeta["loc"] = new Metadata(loc); + methodMeta["name"] = new Metadata(method.name); + methodMeta["types"] = new Metadata(method.types); + methodMeta["imp"] = new Metadata(method.imp); + + return new Metadata(methodMeta); +} + + +Ref<Metadata> DSCObjCProcessor::SerializeClass(uint64_t loc, const Class& cls) +{ + std::map<std::string, Ref<Metadata>> clsMeta; + + clsMeta["loc"] = new Metadata(loc); + clsMeta["name"] = new Metadata(cls.name); + clsMeta["typeName"] = new Metadata(cls.associatedName.GetString()); + + std::vector<uint64_t> instanceMethods; + std::vector<uint64_t> classMethods; + instanceMethods.reserve(cls.instanceClass.methodList.size()); + classMethods.reserve(cls.metaClass.methodList.size()); + for (const auto& [location, _] : cls.instanceClass.methodList) + instanceMethods.push_back(location); + + clsMeta["instanceMethods"] = new Metadata(instanceMethods); + clsMeta["classMethods"] = new Metadata(classMethods); + + return new Metadata(clsMeta); +} + +Ref<Metadata> DSCObjCProcessor::SerializeMetadata() +{ + std::map<std::string, Ref<Metadata>> viewMeta; + viewMeta["version"] = new Metadata((uint64_t)1); + + std::vector<Ref<Metadata>> classes; + classes.reserve(m_classes.size()); + std::vector<Ref<Metadata>> categories; + categories.reserve(m_categories.size()); + std::vector<Ref<Metadata>> methods; + methods.reserve(m_localMethods.size()); + + for (const auto& [clsLoc, cls] : m_classes) + classes.push_back(SerializeClass(clsLoc, cls)); + viewMeta["classes"] = new Metadata(classes); + for (const auto& [catLoc, cat] : m_categories) + categories.push_back(SerializeClass(catLoc, cat)); + viewMeta["categories"] = new Metadata(categories); + for (const auto& [methodLoc, method] : m_localMethods) + methods.push_back(SerializeMethod(methodLoc, method)); + viewMeta["methods"] = new Metadata(methods); + + // Required for workflow_objc type guessing, should be removed when that is no longer a thing. + std::vector<Ref<Metadata>> selRefToImps; + selRefToImps.reserve(m_selRefToImplementations.size()); + for (const auto& [selRef, imps] : m_selRefToImplementations) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selRefToImps.push_back(mapObject); + } + viewMeta["selRefImplementations"] = new Metadata(selRefToImps); + + std::vector<Ref<Metadata>> selToImps; + selToImps.reserve(m_selToImplementations.size()); + for (const auto& [selRef, imps] : m_selToImplementations) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selToImps.push_back(mapObject); + } + viewMeta["selImplementations"] = new Metadata(selToImps); + + std::vector<Ref<Metadata>> selRefToName; + selRefToName.reserve(m_selRefToName.size()); + for (const auto& [selRef, name] : m_selRefToName) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(name)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selRefToName.push_back(mapObject); + } + viewMeta["selRefToName"] = new Metadata(selRefToName); + // --- + + + return new Metadata(viewMeta); +} + +std::vector<DSCObjC::QualifiedNameOrType> DSCObjCProcessor::ParseEncodedType(const std::string& encodedType) +{ + std::vector<QualifiedNameOrType> result; + int pointerDepth = 0; + + bool readingNamedType = false; + std::string namedType; + int readingStructDepth = 0; + std::string structType; + char last; + + for (char c : encodedType) + { + if (readingNamedType && c != '"') + { + namedType.push_back(c); + last = c; + continue; + } + else if (readingStructDepth > 0 && c != '{' && c != '}') + { + structType.push_back(c); + last = c; + continue; + } + + if (std::isdigit(c)) + continue; + + QualifiedNameOrType nameOrType; + std::string qualifiedName; + + switch (c) + { + case '^': + pointerDepth++; + last = c; + continue; + + case '"': + if (!readingNamedType) + { + readingNamedType = true; + if (last == '@') + result.pop_back(); // We added an 'id' in the last cycle, remove it + last = c; + continue; + } + else + { + readingNamedType = false; + nameOrType.name = QualifiedName(namedType); + nameOrType.ptrCount = 1; + break; + } + case '{': + readingStructDepth++; + last = c; + continue; + case '}': + readingStructDepth--; + if (readingStructDepth < 0) + return {}; // seriously malformed type. + + if (readingStructDepth == 0) + { + // TODO: Emit real struct types + nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + break; + } + last = c; + continue; + case 'v': + nameOrType.type = Type::VoidType(); + break; + case 'c': + nameOrType.type = Type::IntegerType(1, true); + break; + case 'A': + case 'C': + nameOrType.type = Type::IntegerType(1, false); + break; + case 's': + nameOrType.type = Type::IntegerType(2, true); + break; + case 'S': + nameOrType.type = Type::IntegerType(1, false); + break; + case 'i': + nameOrType.type = Type::IntegerType(4, true); + break; + case 'I': + nameOrType.type = Type::IntegerType(4, false); + break; + case 'l': + nameOrType.type = Type::IntegerType(8, true); + break; + case 'L': + nameOrType.type = Type::IntegerType(8, true); + break; + case 'f': + nameOrType.type = Type::IntegerType(4, true); + break; + case 'b': + case 'B': + nameOrType.type = Type::BoolType(); + break; + case 'q': + qualifiedName = "NSInteger"; + break; + case 'Q': + qualifiedName = "NSUInteger"; + break; + case 'd': + qualifiedName = "CGFloat"; + break; + case '*': + nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::IntegerType(1, true)); + break; + case '@': + qualifiedName = "id"; + // There can be a type after this, like @"NSString", that overrides this + // The handler for " will catch it and drop this "id" entry. + break; + case ':': + qualifiedName = "SEL"; + break; + case '#': + qualifiedName = "objc_class_t"; + break; + case '?': + case 'T': + nameOrType.type = Type::PointerType(8, Type::VoidType()); + break; + default: + // BNLogWarn("Unknown type specifier %c", c); + last = c; + continue; + } + + while (pointerDepth) + { + if (nameOrType.type) + nameOrType.type = Type::PointerType(8, nameOrType.type); + else + nameOrType.ptrCount++; + + pointerDepth--; + } + + if (!qualifiedName.empty()) + nameOrType.name = QualifiedName(qualifiedName); + + if (nameOrType.type == nullptr && nameOrType.name.IsEmpty()) + { + nameOrType.type = Type::VoidType(); + } + + result.push_back(nameOrType); + last = c; + } + + return result; +} + +void DSCObjCProcessor::DefineObjCSymbol( + BNSymbolType type, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred) +{ + DefineObjCSymbol(type, m_data->GetTypeByName(typeName), name, addr, deferred); +} + +void DSCObjCProcessor::DefineObjCSymbol( + BNSymbolType type, Ref<Type> typeRef, const std::string& name, uint64_t addr, bool deferred) +{ + if (name.size() == 0 || addr == 0) + return; + + auto process = [=]() { + NameSpace nameSpace = m_data->GetInternalNameSpace(); + if (type == ExternalSymbol) + { + nameSpace = m_data->GetExternalNameSpace(); + } + + std::string shortName = name; + std::string fullName = name; + + QualifiedName varName; + + return std::pair<Ref<Symbol>, Ref<Type>>( + new Symbol(type, shortName, fullName, name, addr, GlobalBinding, nameSpace), typeRef); + }; + + if (deferred) + { + m_symbolQueue->Append(process, [this, addr = addr](Symbol* symbol, Type* type) { + // Armv7/Thumb: This will rewrite the symbol's address. + // e.g. We pass in 0xc001, it will rewrite it to 0xc000 and create the function w/ the "thumb2" arch. + if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) + m_data->UndefineAutoSymbol(existingSymbol); + auto funcSym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), symbol, type); + if (funcSym->GetType() == FunctionSymbol) + { + uint64_t target = symbol->GetAddress(); + Ref<Platform> targetPlatform = + m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites target. + if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) + { + if (!m_isBackedByDatabase) + targetFunction->SetUserType(type); + } + } + }); + return; + } + + if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) + m_data->UndefineAutoSymbol(existingSymbol); + auto result = process(); + auto sym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), result.first, result.second); + if (sym->GetType() == FunctionSymbol) + { + uint64_t target = result.first->GetAddress(); + Ref<Platform> targetPlatform = m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites + // target. + if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) + { + if (!m_isBackedByDatabase) + targetFunction->SetUserType(result.second); + } + } +} + +void DSCObjCProcessor::LoadClasses(VMReader* reader, Ref<Section> classPtrSection) +{ + if (!classPtrSection) + return; + auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + auto ptrCount = size / ptrSize; + + auto classPtrSectionStart = classPtrSection->GetStart(); + for (size_t i = 0; i < ptrCount; i++) + { + Class cls; + + view_ptr_t classPtr; + class_t clsStruct; + class_ro_t classRO; + + bool hasValidMetaClass = false; + bool hasValidMetaClassRO = false; + class_t metaClsStruct; + class_ro_t metaClassRO; + + view_ptr_t classPointerLocation = classPtrSectionStart + (i * m_data->GetAddressSize()); + reader->Seek(classPointerLocation); + + classPtr = ReadPointerAccountingForRelocations(reader); + reader->Seek(classPtr); + try + { + clsStruct.isa = ReadPointerAccountingForRelocations(reader); + clsStruct.super = reader->ReadPointer(); + clsStruct.cache = reader->ReadPointer(); + clsStruct.vtable = reader->ReadPointer(); + clsStruct.data = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read class data at 0x%llx pointed to by @ 0x%llx", reader->GetOffset(), + classPointerLocation); + continue; + } + if (clsStruct.data & 1) + { + m_logger->LogInfo("Skipping class at 0x%llx as it contains swift types", classPtr); + continue; + } + // unset first two bits + view_ptr_t classROPtr = clsStruct.data & ~3; + reader->Seek(classROPtr); + try + { + classRO.flags = reader->Read32(); + classRO.instanceStart = reader->Read32(); + classRO.instanceSize = reader->Read32(); + if (m_data->GetAddressSize() == 8) + classRO.reserved = reader->Read32(); + classRO.ivarLayout = ReadPointerAccountingForRelocations(reader); + classRO.name = ReadPointerAccountingForRelocations(reader); + classRO.baseMethods = ReadPointerAccountingForRelocations(reader); + classRO.baseProtocols = ReadPointerAccountingForRelocations(reader); + classRO.ivars = ReadPointerAccountingForRelocations(reader); + classRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); + classRO.baseProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read class RO data at 0x%llx. 0x%llx, objc_class_t @ 0x%llx", + reader->GetOffset(), classPointerLocation, classROPtr); + continue; + } + + auto namePtr = classRO.name; + + std::string name; + + reader->Seek(namePtr); + try + { + name = reader->ReadCString(namePtr); + } + catch (...) + { + m_logger->LogWarn( + "Failed to read class name at 0x%llx. Class has been given the placeholder name \"0x%llx\" ", namePtr, + classPtr); + char hexString[9]; + hexString[8] = 0; + snprintf(hexString, sizeof(hexString), "%llx", classPtr); + name = "0x" + std::string(hexString); + } + + cls.name = name; + + DefineObjCSymbol(BNSymbolType::DataSymbol, + Type::PointerType(m_data->GetAddressSize(), m_data->GetTypeByName(m_typeNames.cls)), "clsPtr_" + name, + classPointerLocation, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "cls_" + name, classPtr, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.classRO, "cls_ro_" + name, classROPtr, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::ArrayType(Type::IntegerType(1, true), name.size() + 1), + "clsName_" + name, classRO.name, true); + if (0 && classRO.baseProtocols) + { + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), + "clsProtocols_" + name, classRO.baseProtocols, true); + reader->Seek(classRO.baseProtocols); + uint32_t count = reader->Read64(); + view_ptr_t addr = reader->GetOffset(); + for (uint32_t j = 0; j < count; j++) + { + m_data->DefineDataVariable( + addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); + addr += ptrSize; + } + } + + if (clsStruct.isa) + { + reader->Seek(clsStruct.isa); + try + { + metaClsStruct.isa = ReadPointerAccountingForRelocations(reader); + metaClsStruct.super = reader->ReadPointer(); + metaClsStruct.cache = reader->ReadPointer(); + metaClsStruct.vtable = reader->ReadPointer(); + metaClsStruct.data = ReadPointerAccountingForRelocations(reader) & ~1; + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "metacls_" + name, clsStruct.isa, true); + hasValidMetaClass = true; + } + catch (...) + { + m_logger->LogWarn("Failed to read metaclass data at 0x%llx pointed to by objc_class_t @ 0x%llx", + reader->GetOffset(), classPtr); + } + } + if (hasValidMetaClass && (metaClsStruct.data & 1)) + { + m_logger->LogInfo("Skipping metaclass at 0x%llx as it contains swift types", classPtr); + hasValidMetaClass = false; + } + if (hasValidMetaClass) + { + reader->Seek(metaClsStruct.data); + try + { + metaClassRO.flags = reader->Read32(); + metaClassRO.instanceStart = reader->Read32(); + metaClassRO.instanceSize = reader->Read32(); + if (m_data->GetAddressSize() == 8) + metaClassRO.reserved = reader->Read32(); + metaClassRO.ivarLayout = ReadPointerAccountingForRelocations(reader); + metaClassRO.name = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseMethods = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseProtocols = ReadPointerAccountingForRelocations(reader); + metaClassRO.ivars = ReadPointerAccountingForRelocations(reader); + metaClassRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseProperties = ReadPointerAccountingForRelocations(reader); + DefineObjCSymbol( + BNSymbolType::DataSymbol, m_typeNames.classRO, "metacls_ro_" + name, metaClsStruct.data, true); + hasValidMetaClassRO = true; + } + catch (...) + { + m_logger->LogWarn("Failed to read metaclass RO data at 0x%llx pointed to by meta objc_class_t @ 0x%llx", + reader->GetOffset(), clsStruct.isa); + } + } + + if (classRO.baseMethods) + { + try + { + ReadMethodList(reader, cls.instanceClass, name, classRO.baseMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the method list for class pointed to by 0x%llx", clsStruct.data); + } + } + if (hasValidMetaClassRO && metaClassRO.baseMethods) + { + try + { + ReadMethodList(reader, cls.metaClass, name, metaClassRO.baseMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the method list for metaclass pointed to by 0x%llx", clsStruct.data); + } + } + + if (classRO.ivars) + { + try + { + ReadIvarList(reader, cls.instanceClass, name, classRO.ivars); + } + catch (...) + { + m_logger->LogError("Failed to process ivars for class at 0x%llx", clsStruct.data); + } + } + m_classes[classPtr] = cls; + } +} + +void DSCObjCProcessor::LoadCategories(VMReader* reader, Ref<Section> classPtrSection) +{ + if (!classPtrSection) + return; + auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + auto ptrCount = size / m_data->GetAddressSize(); + + auto classPtrSectionStart = classPtrSection->GetStart(); + auto classPtrSectionEnd = classPtrSection->GetEnd(); + + auto catType = Type::NamedType(m_data, m_typeNames.category); + auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), catType); + for (size_t i = classPtrSectionStart; i < classPtrSectionEnd; i += ptrSize) + { + Class category; + category_t cat; + + reader->Seek(i); + auto catLocation = ReadPointerAccountingForRelocations(reader); + reader->Seek(catLocation); + + try + { + cat.name = ReadPointerAccountingForRelocations(reader); + cat.cls = ReadPointerAccountingForRelocations(reader); + cat.instanceMethods = ReadPointerAccountingForRelocations(reader); + cat.classMethods = ReadPointerAccountingForRelocations(reader); + cat.protocols = ReadPointerAccountingForRelocations(reader); + cat.instanceProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read category pointed to by 0x%llx", i); + continue; + } + + std::string categoryAdditionsName; + std::string categoryBaseClassName; + + if (const auto& it = m_classes.find(cat.cls); it != m_classes.end()) + { + categoryBaseClassName = it->second.name; + category.associatedName = it->second.associatedName; + } + else if (auto symbol = m_data->GetSymbolByAddress(catLocation + m_data->GetAddressSize())) + { + if (symbol->GetType() == ImportedDataSymbol || symbol->GetType() == ImportAddressSymbol) + { + const auto& symbolName = symbol->GetFullName(); + if (symbolName.size() > 14 && symbolName.rfind("_OBJC_CLASS_$_", 0) == 0) + categoryBaseClassName = symbolName.substr(14, symbolName.size() - 14); + } + } + if (categoryBaseClassName.empty()) + { + m_logger->LogError( + "Failed to determine base classname for category at 0x%llx. Using base address as stand-in classname", + catLocation); + categoryBaseClassName = std::to_string(catLocation); + } + try + { + reader->Seek(cat.name); + categoryAdditionsName = reader->ReadCString(cat.name); + } + catch (...) + { + m_logger->LogError( + "Failed to read category name for category at 0x%llx. Using base address as stand-in category name", + catLocation); + categoryAdditionsName = std::to_string(catLocation); + } + category.name = categoryBaseClassName + " (" + categoryAdditionsName + ")"; + DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "categoryPtr_" + category.name, i, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, catType, "category_" + category.name, catLocation, true); + + if (cat.instanceMethods) + { + try + { + ReadMethodList(reader, category.instanceClass, category.name, cat.instanceMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the instance method list for category pointed to by 0x%llx", catLocation); + } + } + if (cat.classMethods) + { + try + { + ReadMethodList(reader, category.metaClass, category.name, cat.classMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the class method list for category pointed to by 0x%llx", catLocation); + } + } + m_categories[catLocation] = category; + } +} + +void DSCObjCProcessor::LoadProtocols(VMReader* reader, Ref<Section> listSection) +{ + if (!listSection) + return; + auto size = listSection->GetEnd() - listSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + + auto listSectionStart = listSection->GetStart(); + auto listSectionEnd = listSection->GetEnd(); + + auto protocolType = Type::NamedType(m_data, m_typeNames.protocol); + auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), protocolType); + for (size_t i = listSectionStart; i < listSectionEnd; i += ptrSize) + { + protocol_t protocol; + reader->Seek(i); + auto protocolLocation = ReadPointerAccountingForRelocations(reader); + reader->Seek(protocolLocation); + + try + { + protocol.isa = ReadPointerAccountingForRelocations(reader); + protocol.mangledName = ReadPointerAccountingForRelocations(reader); + protocol.protocols = ReadPointerAccountingForRelocations(reader); + protocol.instanceMethods = ReadPointerAccountingForRelocations(reader); + protocol.classMethods = ReadPointerAccountingForRelocations(reader); + protocol.optionalInstanceMethods = ReadPointerAccountingForRelocations(reader); + protocol.optionalClassMethods = ReadPointerAccountingForRelocations(reader); + protocol.instanceProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read protocol pointed to by 0x%llx", i); + continue; + } + + std::string protocolName; + try + { + reader->Seek(protocol.mangledName); + protocolName = reader->ReadCString(protocol.mangledName); + DefineObjCSymbol(BNSymbolType::DataSymbol, + Type::ArrayType(Type::IntegerType(1, true), protocolName.size() + 1), "protocolName_" + protocolName, + protocol.mangledName, true); + } + catch (...) + { + m_logger->LogError( + "Failed to read protocol name for protocol at 0x%llx. Using base address as stand-in protocol name", + protocolLocation); + protocolName = std::to_string(protocolLocation); + } + + Protocol protocolClass; + protocolClass.name = protocolName; + DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "protocolPtr_" + protocolName, i, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, protocolType, "protocol_" + protocolName, protocolLocation, true); + if (protocol.protocols) + { + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), + "protoProtocols_" + protocolName, protocol.protocols, true); + reader->Seek(protocol.protocols); + uint32_t count = reader->Read64(); + view_ptr_t addr = reader->GetOffset(); + for (uint32_t j = 0; j < count; j++) + { + m_data->DefineDataVariable( + addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); + addr += ptrSize; + } + } + + if (protocol.instanceMethods) + { + try + { + ReadMethodList(reader, protocolClass.instanceMethods, protocolName, protocol.instanceMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the instance method list for protocol pointed to by 0x%llx", protocolLocation); + } + } + if (protocol.classMethods) + { + try + { + ReadMethodList(reader, protocolClass.classMethods, protocolName, protocol.classMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the class method list for protocol pointed to by 0x%llx", protocolLocation); + } + } + if (protocol.optionalInstanceMethods) + { + try + { + ReadMethodList( + reader, protocolClass.optionalInstanceMethods, protocolName, protocol.optionalInstanceMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the optional instance method list for protocol pointed to by 0x%llx", + protocolLocation); + } + } + if (protocol.optionalClassMethods) + { + try + { + ReadMethodList(reader, protocolClass.optionalClassMethods, protocolName, protocol.optionalClassMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the optional class method list for protocol pointed to by 0x%llx", + protocolLocation); + } + } + m_protocols[protocolLocation] = protocolClass; + } +} + +void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +{ + reader->Seek(start); + method_list_t head; + head.entsizeAndFlags = reader->Read32(); + head.count = reader->Read32(); + if (head.count > 0x1000) + { + m_logger->LogError("Method list at 0x%llx has an invalid count of 0x%x", start, head.count); + return; + } + uint64_t pointerSize = m_data->GetAddressSize(); + bool relativeOffsets = (head.entsizeAndFlags & 0xFFFF0000) & 0x80000000; + bool directSelectors = (head.entsizeAndFlags & 0xFFFF0000) & 0x40000000; + auto methodSize = relativeOffsets ? 12 : pointerSize * 3; + DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + name, start, true); + + for (unsigned i = 0; i < head.count; i++) + { + try + { + Method method; + auto cursor = start + sizeof(method_list_t) + (i * methodSize); + reader->Seek(cursor); + method_t meth; + // workflow_objc support + uint64_t selRefAddr = 0; + uint64_t selAddr = 0; + // -- + if (relativeOffsets) + { + if (m_customRelativeMethodSelectorBase.has_value()) + { + meth.name = m_customRelativeMethodSelectorBase.value() + reader->ReadS32(); + meth.types = reader->GetOffset() + reader->ReadS32(); + meth.imp = reader->GetOffset() + reader->ReadS32(); + } + else + { + meth.name = reader->GetOffset() + reader->ReadS32(); + meth.types = reader->GetOffset() + reader->ReadS32(); + meth.imp = reader->GetOffset() + reader->ReadS32(); + } + } + else + { + meth.name = ReadPointerAccountingForRelocations(reader); + meth.types = ReadPointerAccountingForRelocations(reader); + meth.imp = ReadPointerAccountingForRelocations(reader); + } + if (!relativeOffsets || directSelectors) + { + selAddr = meth.name; + method.name = reader->ReadCString(meth.name); + reader->Seek(meth.types); + method.types = reader->ReadCString(meth.types); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1), + "sel_" + method.name, meth.name, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), + "selTypes_" + method.name, meth.types, true); + } + else + { + std::string sel; + view_ptr_t selRef; + reader->Seek(meth.name); + selRefAddr = meth.name; + selRef = ReadPointerAccountingForRelocations(reader); + reader->Seek(meth.types); + method.types = reader->ReadCString(meth.types); + selAddr = selRef; + if (const auto& it = m_selectorCache.find(selRef); it != m_selectorCache.end()) + method.name = it->second; + else + { + reader->Seek(selRef); + method.name = reader->ReadCString(selRef); + m_selectorCache[selRef] = method.name; + } + auto selType = Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1); + DefineObjCSymbol(DataSymbol, selType, "sel_" + method.name, selRef, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), + "selTypes_" + method.name, meth.types, true); + DefineObjCSymbol(DataSymbol, Type::PointerType(m_data->GetAddressSize(), selType), + "selRef_" + method.name, meth.name, true); + } + // workflow objc support + if (selAddr) + m_selToImplementations[selAddr].push_back(meth.imp); + if (selRefAddr) + m_selRefToImplementations[selRefAddr].push_back(meth.imp); + // -- + + DefineObjCSymbol(DataSymbol, relativeOffsets ? m_typeNames.methodEntry : m_typeNames.method, + "method_" + method.name, cursor, true); + method.imp = meth.imp; + cls.methodList[cursor] = method; + m_localMethods[cursor] = method; + } + catch (...) + { + // m_logger->LogError("Failed to process a method at offset 0x%llx", start + sizeof(method_list_t) + (i * methodSize)); + } + } +} + +void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +{ + reader->Seek(start); + ivar_list_t head; + head.entsizeAndFlags = reader->Read32(); + head.count = reader->Read32(); + auto addressSize = m_data->GetAddressSize(); + DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + name, start, true); + for (unsigned i = 0; i < head.count; i++) + { + try + { + Ivar ivar; + ivar_t ivarStruct; + uint64_t cursor = start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8)); + reader->Seek(cursor); + ivarStruct.offset = ReadPointerAccountingForRelocations(reader); + ivarStruct.name = ReadPointerAccountingForRelocations(reader); + ivarStruct.type = ReadPointerAccountingForRelocations(reader); + ivarStruct.alignmentRaw = reader->Read32(); + ivarStruct.size = reader->Read32(); + + reader->Seek(ivarStruct.offset); + ivar.offset = reader->Read32(); + reader->Seek(ivarStruct.name); + ivar.name = reader->ReadCString(ivarStruct.name); + reader->Seek(ivarStruct.type); + ivar.type = reader->ReadCString(ivarStruct.type); + + DefineObjCSymbol(DataSymbol, m_typeNames.ivar, "ivar_" + ivar.name, cursor, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.name.size() + 1), + "ivarName_" + ivar.name, ivarStruct.name, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.type.size() + 1), + "ivarType_" + ivar.name, ivarStruct.type, true); + + cls.ivarList[cursor] = ivar; + } + catch (...) + { + m_logger->LogError("Failed to process an ivar at offset 0x%llx", + start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8))); + } + } +} + + +std::pair<QualifiedName, Ref<Type>> finalizeStructureBuilder( + Ref<BinaryView> m_data, StructureBuilder sb, std::string name) +{ + auto classTypeStruct = sb.Finalize(); + + QualifiedName classTypeName(name); + auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + auto classType = Type::StructureType(classTypeStruct); + auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + + return {classQualName, classType}; +} + +std::pair<QualifiedName, Ref<Type>> finalizeEnumerationBuilder( + Ref<BinaryView> m_data, EnumerationBuilder eb, uint64_t size, QualifiedName name) +{ + auto enumTypeStruct = eb.Finalize(); + + auto enumTypeId = Type::GenerateAutoTypeId("objc", name); + auto enumType = Type::EnumerationType(enumTypeStruct, size); + auto enumQualName = m_data->DefineType(enumTypeId, name, enumType); + + return {enumQualName, enumType}; +} + +inline QualifiedName defineTypedef(Ref<BinaryView> m_data, const QualifiedName name, Ref<Type> type) +{ + auto typeID = Type::GenerateAutoTypeId("objc", name); + m_data->DefineType(typeID, name, type); + return m_data->GetTypeNameById(typeID); +} + +void DSCObjCProcessor::GenerateClassTypes() +{ + for (auto& [_, cls] : m_classes) + { + QualifiedName typeName; + StructureBuilder classTypeBuilder; + bool failedToDecodeType = false; + for (const auto& [ivarLoc, ivar] : cls.instanceClass.ivarList) + { + auto encodedTypeList = ParseEncodedType(ivar.type); + if (encodedTypeList.empty()) + { + failedToDecodeType = true; + break; + } + auto encodedType = encodedTypeList.at(0); + + Ref<Type> type; + + if (encodedType.type) + type = encodedType.type; + else + { + type = Type::NamedType(encodedType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); + for (size_t i = encodedType.ptrCount; i > 0; i--) + type = Type::PointerType(m_data->GetAddressSize(), type); + } + + if (!type) + type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + + classTypeBuilder.AddMemberAtOffset(type, ivar.name, ivar.offset); + } + if (failedToDecodeType) + continue; + auto classTypeStruct = classTypeBuilder.Finalize(); + QualifiedName classTypeName = cls.name; + std::string classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + Ref<Type> classType = Type::StructureType(classTypeStruct); + QualifiedName classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + cls.associatedName = classTypeName; + } +} + +bool DSCObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod) +{ + std::stringstream r(method.name); + + std::string token; + std::vector<std::string> selectorTokens; + while (std::getline(r, token, ':')) + selectorTokens.push_back(token); + + std::vector<QualifiedNameOrType> typeTokens = ParseEncodedType(method.types); + if (typeTokens.empty()) + return false; + + auto typeForQualifiedNameOrType = [this](QualifiedNameOrType nameOrType) { + Ref<Type> type; + + if (nameOrType.type) + { + type = nameOrType.type; + if (!type) + type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + } + else + { + type = Type::NamedType(nameOrType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); + for (size_t i = nameOrType.ptrCount; i > 0; i--) + type = Type::PointerType(m_data->GetAddressSize(), type); + } + + return type; + }; + + BinaryNinja::QualifiedNameAndType nameAndType; + std::set<BinaryNinja::QualifiedName> typesAllowRedefinition; + + auto retType = typeForQualifiedNameOrType(typeTokens[0]); + + std::vector<BinaryNinja::FunctionParameter> params; + auto cc = m_data->GetDefaultPlatform()->GetDefaultCallingConvention(); + + params.push_back({"self", + cls.associatedName.IsEmpty() ? + Type::NamedType(m_data, {"id"}) : + Type::PointerType(m_data->GetAddressSize(), Type::NamedType(m_data, cls.associatedName)), + true, BinaryNinja::Variable()}); + + params.push_back({"sel", Type::NamedType(m_data, {"SEL"}), true, BinaryNinja::Variable()}); + + for (size_t i = 3; i < typeTokens.size(); i++) + { + std::string suffix; + + params.push_back({selectorTokens.size() > i - 3 ? selectorTokens[i - 3] : "arg", + typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); + } + + auto funcType = BinaryNinja::Type::FunctionType(retType, cc, params); + + // Search for the method's implementation function; apply the type if found. + std::string prefix = isInstanceMethod ? "-" : "+"; + auto name = prefix + "[" + cls.name + " " + method.name + "]"; + + DefineObjCSymbol(FunctionSymbol, funcType, name, method.imp, true); + + return true; +} + +void DSCObjCProcessor::ApplyMethodTypes(Class& cls) +{ + for (auto& [_, method] : cls.instanceClass.methodList) + { + ApplyMethodType(cls, method, true); + } + for (auto& [_, method] : cls.metaClass.methodList) + { + ApplyMethodType(cls, method, false); + } +} + +void DSCObjCProcessor::PostProcessObjCSections(VMReader* reader) +{ + auto ptrSize = m_data->GetAddressSize(); + if (auto imageInfo = m_data->GetSectionByName("__objc_imageinfo")) + { + auto start = imageInfo->GetStart(); + auto type = Type::NamedType(m_data, m_typeNames.imageInfo); + m_data->DefineDataVariable(start, type); + } + if (auto selrefs = m_data->GetSectionByName("__objc_selrefs")) + { + auto start = selrefs->GetStart(); + auto end = selrefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::IntegerType(1, false)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto selLoc = ReadPointerAccountingForRelocations(reader); + std::string sel; + if (const auto& it = m_selectorCache.find(selLoc); it != m_selectorCache.end()) + sel = it->second; + else + { + reader->Seek(selLoc); + sel = reader->ReadCString(selLoc); + m_selectorCache[selLoc] = sel; + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), sel.size() + 1), "sel_" + sel, + selLoc, true); + } + DefineObjCSymbol(DataSymbol, type, "selRef_" + sel, i, true); + } + } + if (auto superRefs = m_data->GetSectionByName("__objc_classrefs")) + { + auto start = superRefs->GetStart(); + auto end = superRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto clsLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) + { + auto& cls = it->second; + std::string name = cls.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "clsRef_" + name, i, true); + } + } + } + if (auto superRefs = m_data->GetSectionByName("__objc_superrefs")) + { + auto start = superRefs->GetStart(); + auto end = superRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto clsLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) + { + auto& cls = it->second; + std::string name = cls.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "superRef_" + name, i, true); + } + } + } + if (auto protoRefs = m_data->GetSectionByName("__objc_protorefs")) + { + auto start = protoRefs->GetStart(); + auto end = protoRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto protoLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_protocols.find(protoLoc); it != m_protocols.end()) + { + auto& proto = it->second; + std::string name = proto.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "protoRef_" + name, i, true); + } + } + } + if (auto ivars = m_data->GetSectionByName("__objc_ivar")) + { + auto start = ivars->GetStart(); + auto end = ivars->GetEnd(); + auto ivarSectionEntryTypeBuilder = new TypeBuilder(Type::IntegerType(8, false)); + ivarSectionEntryTypeBuilder->SetConst(true); + auto type = ivarSectionEntryTypeBuilder->Finalize(); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + m_data->DefineDataVariable(i, type); + } + } +} + +uint64_t DSCObjCProcessor::ReadPointerAccountingForRelocations(VMReader* reader) +{ + if (auto it = m_relocationPointerRewrites.find(reader->GetOffset()); it != m_relocationPointerRewrites.end()) + { + reader->SeekRelative(m_data->GetAddressSize()); + return it->second; + } + // hack + return reader->ReadPointer(); +} + + +DSCObjCProcessor::DSCObjCProcessor(BinaryNinja::BinaryView* data, SharedCache* cache, bool isBackedByDatabase) : + m_isBackedByDatabase(isBackedByDatabase), m_data(data), m_cache(cache) +{ + m_logger = LogRegistry::GetLogger("SharedCache.ObjC", m_data->GetFile()->GetSessionId()); +} + +void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseName) +{ + m_symbolQueue = new SymbolQueue(); + auto addrSize = m_data->GetAddressSize(); + + m_typeNames.relativePtr = defineTypedef(m_data, {"rptr_t"}, Type::IntegerType(4, true)); + auto rptr_t = Type::NamedType(m_data, m_typeNames.relativePtr); + + m_typeNames.id = defineTypedef(m_data, {"id"}, Type::PointerType(addrSize, Type::VoidType())); + m_typeNames.sel = defineTypedef(m_data, {"SEL"}, Type::PointerType(addrSize, Type::IntegerType(1, false))); + + m_typeNames.BOOL = defineTypedef(m_data, {"BOOL"}, Type::IntegerType(1, false)); + m_typeNames.nsInteger = defineTypedef(m_data, {"NSInteger"}, Type::IntegerType(addrSize, true)); + m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false)); + m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize)); + + // https://github.com/apple-oss-distributions/objc4/blob/196363c165b175ed925ef6b9b99f558717923c47/runtime/objc-abi.h + EnumerationBuilder imageInfoFlagBuilder; + imageInfoFlagBuilder.AddMemberWithValue("IsReplacement", 1 << 0); + imageInfoFlagBuilder.AddMemberWithValue("SupportsGC", 1 << 1); + imageInfoFlagBuilder.AddMemberWithValue("RequiresGC", 1 << 2); + imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyld", 1 << 3); + imageInfoFlagBuilder.AddMemberWithValue("CorrectedSynthesize", 1 << 4); + imageInfoFlagBuilder.AddMemberWithValue("IsSimulated", 1 << 5); + imageInfoFlagBuilder.AddMemberWithValue("HasCategoryClassProperties", 1 << 6); + imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyldClosure", 1 << 7); + imageInfoFlagBuilder.AddMemberWithValue("SwiftUnstableVersionMask", 0xff << 8); + imageInfoFlagBuilder.AddMemberWithValue("SwiftStableVersionMask", 0xFFFF << 16); + auto imageInfoFlagType = finalizeEnumerationBuilder(m_data, imageInfoFlagBuilder, 4, {"objc_image_info_flags"}); + m_typeNames.imageInfoFlags = imageInfoFlagType.first; + + EnumerationBuilder swiftVersionBuilder; + swiftVersionBuilder.AddMemberWithValue("SwiftVersion1", 1); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion1_2", 2); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion2", 3); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion3", 4); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4", 5); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_1", 6); // [sic] + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_2", 6); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion5", 7); + auto swiftVersionType = + finalizeEnumerationBuilder(m_data, swiftVersionBuilder, 4, {"objc_image_info_swift_version"}); + m_typeNames.imageInfoSwiftVersion = swiftVersionType.first; + + StructureBuilder imageInfoBuilder; + imageInfoBuilder.AddMember(Type::IntegerType(4, false), "version"); + imageInfoBuilder.AddMember(Type::NamedType(m_data, m_typeNames.imageInfoFlags), "flags"); + auto imageInfoType = finalizeStructureBuilder(m_data, imageInfoBuilder, "objc_image_info_t"); + m_typeNames.imageInfo = imageInfoType.first; + + StructureBuilder methodEntry; + methodEntry.AddMember(rptr_t, "name"); + methodEntry.AddMember(rptr_t, "types"); + methodEntry.AddMember(rptr_t, "imp"); + auto type = finalizeStructureBuilder(m_data, methodEntry, "objc_method_entry_t"); + m_typeNames.methodEntry = type.first; + + StructureBuilder method; + method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "types"); + method.AddMember(Type::PointerType(addrSize, Type::VoidType()), "imp"); + type = finalizeStructureBuilder(m_data, method, "objc_method_t"); + m_typeNames.method = type.first; + + StructureBuilder methList; + methList.AddMember(Type::IntegerType(4, false), "obsolete"); + methList.AddMember(Type::IntegerType(4, false), "count"); + type = finalizeStructureBuilder(m_data, methList, "objc_method_list_t"); + m_typeNames.methodList = type.first; + + StructureBuilder ivarBuilder; + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(4, false)), "offset"); + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "type"); + ivarBuilder.AddMember(Type::IntegerType(4, false), "alignment"); + ivarBuilder.AddMember(Type::IntegerType(4, false), "size"); + type = finalizeStructureBuilder(m_data, ivarBuilder, "objc_ivar_t"); + m_typeNames.ivar = type.first; + + StructureBuilder ivarList; + ivarList.AddMember(Type::IntegerType(4, false), "entsize"); + ivarList.AddMember(Type::IntegerType(4, false), "count"); + type = finalizeStructureBuilder(m_data, ivarList, "objc_ivar_list_t"); + m_typeNames.ivarList = type.first; + + StructureBuilder protocolListBuilder; + protocolListBuilder.AddMember(Type::IntegerType(addrSize, false), "count"); + m_typeNames.protocolList = finalizeStructureBuilder(m_data, protocolListBuilder, "objc_protocol_list_t").first; + + StructureBuilder classROBuilder; + classROBuilder.AddMember(Type::IntegerType(4, false), "flags"); + classROBuilder.AddMember(Type::IntegerType(4, false), "start"); + classROBuilder.AddMember(Type::IntegerType(4, false), "size"); + if (addrSize == 8) + classROBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "ivar_layout"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "methods"); + classROBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.ivarList)), "ivars"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "weak_ivar_layout"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); + type = finalizeStructureBuilder(m_data, classROBuilder, "objc_class_ro_t"); + m_typeNames.classRO = type.first; + + QualifiedName classTypeName("objc_class_t"); + auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + auto isaType = Type::PointerType(m_data->GetDefaultArchitecture(), + TypeBuilder::NamedType( + new NamedTypeReferenceBuilder(StructNamedTypeClass, "", classTypeName), m_data->GetAddressSize(), 4) + .Finalize()); + + StructureBuilder classBuilder; + classBuilder.AddMember(isaType, "isa"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "super"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "cache"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "vtable"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.classRO)), "data"); + + auto classTypeStruct = classBuilder.Finalize(); + auto classType = Type::StructureType(classTypeStruct); + auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + + m_typeNames.cls = classQualName; + + StructureBuilder categoryBuilder; + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "category_name"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.cls)), "class"); + categoryBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "inst_methods"); + categoryBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "class_methods"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "protocols"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); + m_typeNames.category = finalizeStructureBuilder(m_data, categoryBuilder, "objc_category_t").first; + + StructureBuilder protocolBuilder; + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "isa"); + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "mangledName"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "instanceMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "classMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalInstanceMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalClassMethods"); + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "instanceProperties"); + protocolBuilder.AddMember(Type::IntegerType(4, false), "size"); + protocolBuilder.AddMember(Type::IntegerType(4, false), "flags"); + m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first; + + auto reader = VMReader(vm); + + if (auto addr = m_cache->GetImageStart("/usr/lib/libobjc.A.dylib")) + { + auto header = m_cache->HeaderForAddress(addr.value()); + uint64_t scoffs_addr = 0; + size_t scoffs_size = 0; + + for (const auto& section : header->sections) + { + char name[17]; + memcpy(name, section.sectname, 16); + name[16] = 0; + if (std::string(name) == "__objc_scoffs") + { + scoffs_addr = section.addr; + scoffs_size = section.size; + break; + } + } + + if (scoffs_size && scoffs_addr) + { + if (scoffs_size == 0x20) + { + m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr); + } + else + { + m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr + 8); + } + m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", m_customRelativeMethodSelectorBase.value()); + } + } + + + m_data->BeginBulkModifySymbols(); + if (auto classList = m_data->GetSectionByName(baseName + "::__objc_classlist")) + LoadClasses(&reader, classList); + if (auto nonLazyClassList = m_data->GetSectionByName(baseName + "::__objc_nlclslist")) + LoadClasses(&reader, nonLazyClassList); // See: https://stackoverflow.com/a/15318325 + + GenerateClassTypes(); + for (auto& [_, cls] : m_classes) + ApplyMethodTypes(cls); + + if (auto catList = m_data->GetSectionByName(baseName + "::__objc_catlist")) // Do this after loading class type data. + LoadCategories(&reader, catList); + if (auto nonLazyCatList = m_data->GetSectionByName(baseName + "::__objc_nlcatlist")) // Do this after loading class type data. + LoadCategories(&reader, nonLazyCatList); + for (auto& [_, cat] : m_categories) + ApplyMethodTypes(cat); + + if (auto protoList = m_data->GetSectionByName(baseName + "::__objc_protolist")) + LoadProtocols(&reader, protoList); + + PostProcessObjCSections(&reader); + + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + delete m_symbolQueue; + m_data->ForgetUndoActions(id); + + auto meta = SerializeMetadata(); + m_data->StoreMetadata("Objective-C", meta, true); + + m_relocationPointerRewrites.clear(); +} + + +void DSCObjCProcessor::ProcessCFStrings(std::shared_ptr<VM> vm, std::string baseName) +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + // https://github.com/apple/llvm-project/blob/next/clang/lib/CodeGen/CodeGenModule.cpp#L6129 + // See also ASTContext.cpp ctrl+f __NSConstantString_tag + + // The place these flags are used is unclear, along with any clear flag definitions, but they are useful for + // introspection + EnumerationBuilder __cfStringFlagBuilder; + __cfStringFlagBuilder.AddMemberWithValue("SwiftABI", 0b1); + __cfStringFlagBuilder.AddMemberWithValue("Swift4_1", 0b100); + // LLVM also sets 0x7c0 (0b11111000000) on both UTF8 and UTF16 strings however it is unclear what this denotes. + __cfStringFlagBuilder.AddMemberWithValue("UTF8", 0b1000); + __cfStringFlagBuilder.AddMemberWithValue("UTF16", 0b10000); + auto type = finalizeEnumerationBuilder(m_data, __cfStringFlagBuilder, ptrSize, {"CFStringFlag"}); + m_typeNames.cfStringFlag = type.first; + + StructureBuilder __cfStringStructBuilder; + __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + __cfStringStructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); + __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, true)), "data"); + __cfStringStructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); + type = finalizeStructureBuilder(m_data, __cfStringStructBuilder, "__NSConstantString"); + m_typeNames.cfString = type.first; + + StructureBuilder __cfStringUTF16StructBuilder; + __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + __cfStringUTF16StructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); + __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(2, true)), "data"); + __cfStringUTF16StructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); + type = finalizeStructureBuilder(m_data, __cfStringUTF16StructBuilder, "__NSConstantString_UTF16"); + m_typeNames.cfStringUTF16 = type.first; + + auto reader = VMReader(vm); + if (auto cfstrings = m_data->GetSectionByName(baseName + "::__cfstring")) + { + auto start = cfstrings->GetStart(); + auto end = cfstrings->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.cfString)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader.Seek(i + ptrSize); + uint64_t flags = reader.ReadPointer(); + auto strLoc = ReadPointerAccountingForRelocations(&reader); + auto size = reader.ReadPointer(); + std::string str; + if (flags & 0b10000) // UTF16 + { + auto data = m_data->ReadBuffer(strLoc, size * 2); + + str = ""; + for (uint64_t bufferOff = 0; bufferOff < size * 2; bufferOff += 2) + { + uint8_t* rawData = static_cast<uint8_t*>(data.GetData()); + uint8_t* offsetAddress = rawData + bufferOff; + uint16_t c = *reinterpret_cast<uint16_t*>(offsetAddress); + if (c == 0x20) + str.push_back('_'); + else if (c < 0x80) + str.push_back(c); + else + str.push_back('?'); + } + DefineObjCSymbol( + DataSymbol, Type::ArrayType(Type::WideCharType(2), size + 1), "ustr_" + str, strLoc, true); + DefineObjCSymbol( + DataSymbol, Type::NamedType(m_data, m_typeNames.cfStringUTF16), "cfstr_" + str, i, true); + } + else // UTF8 / ASCII + { + reader.Seek(strLoc); + str = reader.ReadCString(strLoc); + for (auto& c : str) + { + if (c == ' ') + c = '_'; + } + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), str.size() + 1), "cstr_" + str, + strLoc, true); + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.cfString), "cfstr_" + str, i, true); + } + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + } + delete m_symbolQueue; +} + +void DSCObjCProcessor::AddRelocatedPointer(uint64_t location, uint64_t rewrite) +{ + m_relocationPointerRewrites[location] = rewrite; +} diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h new file mode 100644 index 00000000..40f2441b --- /dev/null +++ b/view/sharedcache/core/ObjC.h @@ -0,0 +1,233 @@ +// +// Created by kat on 5/23/23. +// + +#ifndef SHAREDCACHE_OBJC_H +#define SHAREDCACHE_OBJC_H + +#include <binaryninjaapi.h> +#include "VM.h" +#include "SharedCache.h" + +using namespace BinaryNinja; + + +namespace DSCObjC { + + // This set of structs is based on the objc4 source, + // however pointers have been replaced with view_ptr_t + + // Used for pointers within BinaryView, primarily to make it far more clear in typedefs + // whether the size of a field can vary between architectures. + // These should _not_ be used in sizeof or direct Read() calls. + typedef uint64_t view_ptr_t; + + typedef struct { + view_ptr_t name; + view_ptr_t types; + view_ptr_t imp; + } method_t; + typedef struct { + uint32_t name; + uint32_t types; + uint32_t imp; + } method_entry_t; + typedef struct { + view_ptr_t offset; + view_ptr_t name; + view_ptr_t type; + uint32_t alignmentRaw; + uint32_t size; + } ivar_t; + typedef struct { + view_ptr_t name; + view_ptr_t attributes; + } property_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } method_list_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } ivar_list_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } property_list_t; + typedef struct { + uint64_t count; + } protocol_list_t; + typedef struct { + view_ptr_t isa; + view_ptr_t mangledName; + view_ptr_t protocols; + view_ptr_t instanceMethods; + view_ptr_t classMethods; + view_ptr_t optionalInstanceMethods; + view_ptr_t optionalClassMethods; + view_ptr_t instanceProperties; + uint32_t size; + uint32_t flags; + } protocol_t; + typedef struct { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; + uint32_t reserved; + view_ptr_t ivarLayout; + view_ptr_t name; + view_ptr_t baseMethods; + view_ptr_t baseProtocols; + view_ptr_t ivars; + view_ptr_t weakIvarLayout; + view_ptr_t baseProperties; + } class_ro_t; + typedef struct { + view_ptr_t isa; + view_ptr_t super; + view_ptr_t cache; + view_ptr_t vtable; + view_ptr_t data; + } class_t; + typedef struct { + view_ptr_t name; + view_ptr_t cls; + view_ptr_t instanceMethods; + view_ptr_t classMethods; + view_ptr_t protocols; + view_ptr_t instanceProperties; + } category_t; + typedef struct { + view_ptr_t receiver; + view_ptr_t current_class; + } objc_super2; + typedef struct { + view_ptr_t imp; + view_ptr_t sel; + } message_ref_t; + + struct Method { + std::string name; + std::string types; + view_ptr_t imp; + }; + + struct Ivar { + uint32_t offset; + std::string name; + std::string type; + uint32_t alignment; + uint32_t size; + }; + + struct Property { + std::string name; + std::string attributes; + }; + + struct ClassBase { + std::map<uint64_t, Method> methodList; + std::map<uint64_t, Ivar> ivarList; + }; + + struct Class { + std::string name; + ClassBase instanceClass; + ClassBase metaClass; + + // Loaded by type processing + QualifiedName associatedName; + }; + + class Protocol { + public: + std::string name; + std::vector<QualifiedName> protocols; + ClassBase instanceMethods; + ClassBase classMethods; + ClassBase optionalInstanceMethods; + ClassBase optionalClassMethods; + }; + + struct QualifiedNameOrType { + BinaryNinja::Ref<BinaryNinja::Type> type = nullptr; + BinaryNinja::QualifiedName name; + size_t ptrCount = 0; + }; + + class DSCObjCProcessor { + struct Types { + QualifiedName relativePtr; + QualifiedName id; + QualifiedName sel; + QualifiedName BOOL; + QualifiedName nsInteger; + QualifiedName nsuInteger; + QualifiedName cgFloat; + QualifiedName cfStringFlag; + QualifiedName cfString; + QualifiedName cfStringUTF16; + QualifiedName imageInfoFlags; + QualifiedName imageInfoSwiftVersion; + QualifiedName imageInfo; + QualifiedName methodEntry; + QualifiedName method; + QualifiedName methodList; + QualifiedName classRO; + QualifiedName cls; + QualifiedName category; + QualifiedName protocol; + QualifiedName protocolList; + QualifiedName ivar; + QualifiedName ivarList; + } m_typeNames; + + bool m_isBackedByDatabase; + + BinaryView* m_data; + SymbolQueue* m_symbolQueue = nullptr; + Ref<Logger> m_logger; + std::map<uint64_t, Class> m_classes; + std::map<uint64_t, Class> m_categories; + std::map<uint64_t, Protocol> m_protocols; + std::unordered_map<uint64_t, std::string> m_selectorCache; + std::unordered_map<uint64_t, Method> m_localMethods; + + // Required for workflow_objc type heuristics, should be removed when that is no longer a thing. + std::map<uint64_t, std::string> m_selRefToName; + std::map<uint64_t, std::vector<uint64_t>> m_selRefToImplementations; + std::map<uint64_t, std::vector<uint64_t>> m_selToImplementations; + // -- + + + std::optional<uint64_t> m_customRelativeMethodSelectorBase = std::nullopt; + SharedCacheCore::SharedCache* m_cache; + + uint64_t ReadPointerAccountingForRelocations(VMReader* reader); + std::unordered_map<uint64_t, uint64_t> m_relocationPointerRewrites; + + static Ref<Metadata> SerializeMethod(uint64_t loc, const Method& method); + static Ref<Metadata> SerializeClass(uint64_t loc, const Class& cls); + + Ref<Metadata> SerializeMetadata(); + std::vector<QualifiedNameOrType> ParseEncodedType(const std::string& type); + void DefineObjCSymbol(BNSymbolType symbolType, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred); + void DefineObjCSymbol(BNSymbolType symbolType, Ref<Type> type, const std::string& name, uint64_t addr, bool deferred); + void ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start); + void ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start); + void LoadClasses(VMReader* reader, Ref<Section> listSection); + void LoadCategories(VMReader* reader, Ref<Section> listSection); + void LoadProtocols(VMReader* reader, Ref<Section> listSection); + void GenerateClassTypes(); + bool ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod); + void ApplyMethodTypes(Class& cls); + void PostProcessObjCSections(VMReader* reader); + public: + DSCObjCProcessor(BinaryView* data, SharedCacheCore::SharedCache* cache, bool isBackedByDatabase); + void ProcessObjCData(std::shared_ptr<VM> vm, std::string baseName); + void ProcessCFStrings(std::shared_ptr<VM> vm, std::string baseName); + void AddRelocatedPointer(uint64_t location, uint64_t rewrite); + }; +} +#endif //SHAREDCACHE_OBJC_H diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp new file mode 100644 index 00000000..d468ff9a --- /dev/null +++ b/view/sharedcache/core/SharedCache.cpp @@ -0,0 +1,3286 @@ +// +// Created by kat on 5/19/23. +// + +#include "binaryninjaapi.h" + +/* --- + * This is the primary image loader logic for Shared Caches + * + * It is standalone code that operates on a DSCView. + * + * This has to recreate _all_ of the Mach-O View logic, but slightly differently, as everything is spicy and weird and + * different enough that it's not worth trying to make a shared base class. + * + * Essentially: + * 1. Make an API call through this class' FFI wrapper like SharedCache::GetAvailableImages(BinaryView* view) + * 2. That hops into core and makes a `new SharedCache(view)` core object (every time, yes) that holds a bv member + * variable + * 3. Deserializes information from that BinaryView. + * 4. Does this api call require reading the original cache? Then map VM pages + * 5. Do your stuff, save any changes to the BinaryView's metadata + * 6. Unmap VM pages (freeing file pointers, making sure this SharedCache object doesnt leak, etc) + * 7. Return + * + * Since we do everything properly (mmaped files, etc) this is not actually that slow. It can handle being done for + * several context menu items without visual lag. + * + * + * Strategy notes: + * + * While it is probably faster to map a file and read out metadata direct from disk, + * this results in an extreme amount of duplicate code. + * + * So, we try to pre-load a ton of metadata out of the cache and store it on the view, for developer sanity reasons. + * + * For performance reasons related to serdes speeds we cache this view metadata deserialized, per application lifetime. + * We could probably clear this when the file is closed? + * + * */ + +#include "SharedCache.h" +#include "ObjC.h" +#include <filesystem> +#include <utility> +#include <fcntl.h> +#include <memory> +#include <chrono> +#include <thread> + + +using namespace BinaryNinja; +using namespace SharedCacheCore; + +#ifdef _MSC_VER + +int count_trailing_zeros(uint64_t value) { + unsigned long index; // 32-bit long on Windows + if (_BitScanForward64(&index, value)) { + return index; + } else { + return 64; // If the value is 0, return 64. + } +} +#else +int count_trailing_zeros(uint64_t value) { + return value == 0 ? 64 : __builtin_ctzll(value); +} +#endif + +struct ViewStateCacheStore { + SharedCache::SharedCacheFormat m_cacheFormat; + + DSCViewState m_viewState; + + std::unordered_map<std::string, uint64_t> m_imageStarts; + std::unordered_map<uint64_t, SharedCacheMachOHeader> m_headers; + + std::vector<CacheImage> m_images; + std::vector<MemoryRegion> m_regionsMappedIntoMemory; + + std::vector<BackingCache> m_backingCaches; + std::vector<MemoryRegion> m_stubIslandRegions; // TODO honestly both of these should be refactored into nonImageRegions. :p + std::vector<MemoryRegion> m_dyldDataRegions; + std::vector<MemoryRegion> m_nonImageRegions; + + std::string m_baseFilePath; + + std::unordered_map<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>> m_exportInfos; + std::unordered_map<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>> m_symbolInfos; +}; + +static std::recursive_mutex viewStateMutex; +static std::unordered_map<uint64_t, ViewStateCacheStore> viewStateCache; + +std::mutex progressMutex; +std::unordered_map<uint64_t, BNDSCViewLoadProgress> progressMap; + +struct ViewSpecificMutexes { + std::mutex viewOperationsThatInfluenceMetadataMutex; + std::mutex typeLibraryLookupAndApplicationMutex; +}; + +static std::unordered_map<uint64_t, ViewSpecificMutexes> viewSpecificMutexes; + + +std::string base_name(std::string const& path) +{ + return path.substr(path.find_last_of("/\\") + 1); +} + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t& offset) +{ + uint8_t cur; + int64_t value = 0; + size_t shift = 0; + while (offset < length) + { + cur = buffer[offset++]; + value |= (cur & 0x7f) << shift; + shift += 7; + if ((cur & 0x80) == 0) + break; + } + value = (value << (64 - shift)) >> (64 - shift); + return value; +} +#pragma clang diagnostic pop + + +static uint64_t readLEB128(DataBuffer& p, size_t end, size_t& offset) +{ + uint64_t result = 0; + int bit = 0; + do + { + if (offset >= end) + return -1; + + uint64_t slice = p[offset] & 0x7f; + + if (bit > 63) + return -1; + else + { + result |= (slice << bit); + bit += 7; + } + } while (p[offset++] & 0x80); + return result; +} + + +uint64_t readValidULEB128(DataBuffer& buffer, size_t& cursor) +{ + uint64_t value = readLEB128(buffer, buffer.GetLength(), cursor); + if ((int64_t)value == -1) + throw ReadException(); + return value; +} + + +uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) +{ + auto baseFile = MMappedFileAccessor::Open(dscView->GetFile()->GetSessionId(), dscView->GetFile()->GetOriginalFilename())->lock(); + + dyld_cache_header header {}; + size_t header_size = baseFile->ReadUInt32(16); + baseFile->Read(&header, 0, std::min(header_size, sizeof(dyld_cache_header))); + + SharedCacheFormat cacheFormat; + + if (header.imagesCountOld != 0) + cacheFormat = RegularCacheFormat; + + size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); + size_t headerEnd = header.mappingOffset; + if (headerEnd > subCacheOff) + { + if (header.cacheType != 2) + { + if (std::filesystem::exists(baseFile->Path() + ".01")) + cacheFormat = LargeCacheFormat; + else + cacheFormat = SplitCacheFormat; + } + else + cacheFormat = iOS16CacheFormat; + } + + switch (cacheFormat) + { + case RegularCacheFormat: + { + return 1; + } + case LargeCacheFormat: + { + auto mainFileName = baseFile->Path(); + auto subCacheCount = header.subCacheArrayCount; + return subCacheCount + 1; + } + case SplitCacheFormat: + { + auto mainFileName = baseFile->Path(); + auto subCacheCount = header.subCacheArrayCount; + return subCacheCount + 2; + } + case iOS16CacheFormat: + { + auto mainFileName = baseFile->Path(); + auto subCacheCount = header.subCacheArrayCount; + return subCacheCount + 2; + } + } +} + + +void SharedCache::PerformInitialLoad() +{ + m_logger->LogInfo("Performing initial load of Shared Cache"); + auto path = m_dscView->GetFile()->GetOriginalFilename(); + auto baseFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), path)->lock(); + + progressMutex.lock(); + progressMap[m_dscView->GetFile()->GetSessionId()] = LoadProgressLoadingCaches; + progressMutex.unlock(); + + m_baseFilePath = path; + + DataBuffer sig = *baseFile->ReadBuffer(0, 4); + if (sig.GetLength() != 4) + abort(); + const char* magic = (char*)sig.GetData(); + if (strncmp(magic, "dyld", 4) != 0) + abort(); + + m_cacheFormat = RegularCacheFormat; + + dyld_cache_header primaryCacheHeader {}; + size_t header_size = baseFile->ReadUInt32(16); + baseFile->Read(&primaryCacheHeader, 0, std::min(header_size, sizeof(dyld_cache_header))); + + if (primaryCacheHeader.imagesCountOld != 0) + m_cacheFormat = RegularCacheFormat; + + size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); + size_t headerEnd = primaryCacheHeader.mappingOffset; + if (headerEnd > subCacheOff) + { + if (primaryCacheHeader.cacheType != 2) + { + if (std::filesystem::exists(baseFile->Path() + ".01")) + m_cacheFormat = LargeCacheFormat; + else + m_cacheFormat = SplitCacheFormat; + } + else + m_cacheFormat = iOS16CacheFormat; + } + + switch (m_cacheFormat) + { + case RegularCacheFormat: + { + dyld_cache_mapping_info mapping {}; + BackingCache cache; + cache.isPrimary = true; + cache.path = baseFile->Path(); + + for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) + { + baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = mapping.fileOffset; + mapRawToAddrAndSize.second.first = mapping.address; + mapRawToAddrAndSize.second.second = mapping.size; + cache.mappings.push_back(mapRawToAddrAndSize); + } + m_backingCaches.push_back(cache); + + dyld_cache_image_info img {}; + + for (size_t i = 0; i < primaryCacheHeader.imagesCountOld; i++) + { + baseFile->Read(&img, primaryCacheHeader.imagesOffsetOld + (i * sizeof(img)), sizeof(img)); + auto iname = baseFile->ReadNullTermString(img.pathFileOffset); + m_imageStarts[iname] = img.address; + } + + m_logger->LogInfo("Found %d images in the shared cache", primaryCacheHeader.imagesCountOld); + + if (primaryCacheHeader.branchPoolsCount) + { + std::vector<uint64_t> addresses; + for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) + { + addresses.push_back(baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()))); + } + baseFile.reset(); // No longer needed, we're about to remap this file into VM space so we can load these. + uint64_t i = 0; + for (auto address : addresses) + { + i++; + auto vm = GetVMMap(true); + auto machoHeader = SharedCache::LoadHeaderForAddress(vm, address, "dyld_shared_cache_branch_islands_" + std::to_string(i)); + if (machoHeader) + { + for (const auto& segment : machoHeader->segments) + { + MemoryRegion stubIslandRegion; + stubIslandRegion.start = segment.vmaddr; + stubIslandRegion.size = segment.filesize; + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + std::string segNameStr = std::string(segName); + stubIslandRegion.prettyName = "dyld_shared_cache_branch_islands_" + std::to_string(i) + "::" + segNameStr; + stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); + m_stubIslandRegions.push_back(stubIslandRegion); + } + } + } + } + + m_logger->LogInfo("Found %d branch pools in the shared cache", primaryCacheHeader.branchPoolsCount); + + break; + } + case LargeCacheFormat: + { + dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it + // briefly. + + BackingCache cache; + cache.isPrimary = true; + cache.path = baseFile->Path(); + + for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) + { + baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = mapping.fileOffset; + mapRawToAddrAndSize.second.first = mapping.address; + mapRawToAddrAndSize.second.second = mapping.size; + cache.mappings.push_back(mapRawToAddrAndSize); + } + m_backingCaches.push_back(cache); + + dyld_cache_image_info img {}; + + for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) + { + baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); + auto iname = baseFile->ReadNullTermString(img.pathFileOffset); + m_imageStarts[iname] = img.address; + } + + if (primaryCacheHeader.branchPoolsCount) + { + std::vector<uint64_t> pool {}; + for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) + { + m_imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); + } + } + + auto mainFileName = baseFile->Path(); + auto subCacheCount = primaryCacheHeader.subCacheArrayCount; + + dyld_subcache_entry2 _entry {}; + std::vector<dyld_subcache_entry2> subCacheEntries; + for (size_t i = 0; i < subCacheCount; i++) + { + baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), + sizeof(dyld_subcache_entry2)); + subCacheEntries.push_back(_entry); + } + + baseFile.reset(); + for (const auto& entry : subCacheEntries) + { + std::string subCachePath; + if (std::string(entry.fileExtension).find('.') != std::string::npos) + subCachePath = mainFileName + entry.fileExtension; + else + subCachePath = mainFileName + "." + entry.fileExtension; + auto subCacheFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), subCachePath)->lock(); + + dyld_cache_header subCacheHeader {}; + uint64_t headerSize = subCacheFile->ReadUInt32(16); + if (headerSize > sizeof(dyld_cache_header)) + { + m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, + sizeof(dyld_cache_header)); + headerSize = sizeof(dyld_cache_header); + } + subCacheFile->Read(&subCacheHeader, 0, headerSize); + + dyld_cache_mapping_info subCacheMapping {}; + BackingCache subCache; + subCache.isPrimary = false; + subCache.path = subCachePath; + + for (size_t j = 0; j < subCacheHeader.mappingCount; j++) + { + subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), + sizeof(subCacheMapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = subCacheMapping.fileOffset; + mapRawToAddrAndSize.second.first = subCacheMapping.address; + mapRawToAddrAndSize.second.second = subCacheMapping.size; + subCache.mappings.push_back(mapRawToAddrAndSize); + } + + if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 + && subCacheHeader.imagesTextOffset == 0) + { + auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); + uint64_t address = subCacheMapping.address; + uint64_t size = subCacheMapping.size; + MemoryRegion stubIslandRegion; + stubIslandRegion.start = address; + stubIslandRegion.size = size; + stubIslandRegion.prettyName = pathBasename + "::_stubs"; + stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); + m_stubIslandRegions.push_back(stubIslandRegion); + } + + m_backingCaches.push_back(subCache); + } + break; + } + case SplitCacheFormat: + { + dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it + // briefly. + BackingCache cache; + cache.isPrimary = true; + cache.path = baseFile->Path(); + + for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) + { + baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = mapping.fileOffset; + mapRawToAddrAndSize.second.first = mapping.address; + mapRawToAddrAndSize.second.second = mapping.size; + cache.mappings.push_back(mapRawToAddrAndSize); + } + m_backingCaches.push_back(cache); + + dyld_cache_image_info img {}; + + for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) + { + baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); + auto iname = baseFile->ReadNullTermString(img.pathFileOffset); + m_imageStarts[iname] = img.address; + } + + if (primaryCacheHeader.branchPoolsCount) + { + std::vector<uint64_t> pool {}; + for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) + { + m_imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); + } + } + + auto mainFileName = baseFile->Path(); + auto subCacheCount = primaryCacheHeader.subCacheArrayCount; + + baseFile.reset(); + + for (size_t i = 1; i <= subCacheCount; i++) + { + auto subCachePath = mainFileName + "." + std::to_string(i); + auto subCacheFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), subCachePath)->lock(); + + dyld_cache_header subCacheHeader {}; + uint64_t headerSize = subCacheFile->ReadUInt32(16); + if (headerSize > sizeof(dyld_cache_header)) + { + m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, + sizeof(dyld_cache_header)); + headerSize = sizeof(dyld_cache_header); + } + subCacheFile->Read(&subCacheHeader, 0, headerSize); + + BackingCache subCache; + subCache.isPrimary = false; + subCache.path = subCachePath; + + dyld_cache_mapping_info subCacheMapping {}; + + for (size_t j = 0; j < subCacheHeader.mappingCount; j++) + { + subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), + sizeof(subCacheMapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = subCacheMapping.fileOffset; + mapRawToAddrAndSize.second.first = subCacheMapping.address; + mapRawToAddrAndSize.second.second = subCacheMapping.size; + subCache.mappings.push_back(mapRawToAddrAndSize); + } + + m_backingCaches.push_back(subCache); + + if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 + && subCacheHeader.imagesTextOffset == 0) + { + auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); + uint64_t address = subCacheMapping.address; + uint64_t size = subCacheMapping.size; + MemoryRegion stubIslandRegion; + stubIslandRegion.start = address; + stubIslandRegion.size = size; + stubIslandRegion.prettyName = pathBasename + "::_stubs"; + stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); + m_stubIslandRegions.push_back(stubIslandRegion); + } + } + + // Load .symbols subcache + + auto subCachePath = mainFileName + ".symbols"; + auto subCacheFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), subCachePath)->lock(); + + dyld_cache_header subCacheHeader {}; + uint64_t headerSize = subCacheFile->ReadUInt32(16); + if (headerSize > sizeof(dyld_cache_header)) + { + m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, + sizeof(dyld_cache_header)); + headerSize = sizeof(dyld_cache_header); + } + subCacheFile->Read(&subCacheHeader, 0, headerSize); + + dyld_cache_mapping_info subCacheMapping {}; + BackingCache subCache; + + for (size_t j = 0; j < subCacheHeader.mappingCount; j++) + { + subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), + sizeof(subCacheMapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = subCacheMapping.fileOffset; + mapRawToAddrAndSize.second.first = subCacheMapping.address; + mapRawToAddrAndSize.second.second = subCacheMapping.size; + subCache.mappings.push_back(mapRawToAddrAndSize); + } + + m_backingCaches.push_back(subCache); + break; + } + case iOS16CacheFormat: + { + dyld_cache_mapping_info mapping {}; + + BackingCache cache; + cache.isPrimary = true; + cache.path = baseFile->Path(); + + for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) + { + baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = mapping.fileOffset; + mapRawToAddrAndSize.second.first = mapping.address; + mapRawToAddrAndSize.second.second = mapping.size; + cache.mappings.push_back(mapRawToAddrAndSize); + } + + m_backingCaches.push_back(cache); + + dyld_cache_image_info img {}; + + for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) + { + baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); + auto iname = baseFile->ReadNullTermString(img.pathFileOffset); + m_imageStarts[iname] = img.address; + } + + if (primaryCacheHeader.branchPoolsCount) + { + std::vector<uint64_t> pool {}; + for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) + { + m_imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); + } + } + + auto mainFileName = baseFile->Path(); + auto subCacheCount = primaryCacheHeader.subCacheArrayCount; + + dyld_subcache_entry2 _entry {}; + + std::vector<dyld_subcache_entry2> subCacheEntries; + for (size_t i = 0; i < subCacheCount; i++) + { + baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), + sizeof(dyld_subcache_entry2)); + subCacheEntries.push_back(_entry); + } + + baseFile.reset(); + + for (const auto& entry : subCacheEntries) + { + std::string subCachePath; + if (std::string(entry.fileExtension).find('.') != std::string::npos) + subCachePath = mainFileName + entry.fileExtension; + else + subCachePath = mainFileName + "." + entry.fileExtension; + + auto subCacheFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), subCachePath)->lock(); + + dyld_cache_header subCacheHeader {}; + uint64_t headerSize = subCacheFile->ReadUInt32(16); + if (headerSize > sizeof(dyld_cache_header)) + { + m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, + sizeof(dyld_cache_header)); + headerSize = sizeof(dyld_cache_header); + } + subCacheFile->Read(&subCacheHeader, 0, headerSize); + + dyld_cache_mapping_info subCacheMapping {}; + + BackingCache subCache; + subCache.isPrimary = false; + subCache.path = subCachePath; + + for (size_t j = 0; j < subCacheHeader.mappingCount; j++) + { + subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), + sizeof(subCacheMapping)); + + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = subCacheMapping.fileOffset; + mapRawToAddrAndSize.second.first = subCacheMapping.address; + mapRawToAddrAndSize.second.second = subCacheMapping.size; + subCache.mappings.push_back(mapRawToAddrAndSize); + + if (subCachePath.find(".dylddata") != std::string::npos) + { + auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); + uint64_t address = subCacheMapping.address; + uint64_t size = subCacheMapping.size; + MemoryRegion dyldDataRegion; + dyldDataRegion.start = address; + dyldDataRegion.size = size; + dyldDataRegion.prettyName = pathBasename + "::_data" + std::to_string(j); + dyldDataRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable); + m_dyldDataRegions.push_back(dyldDataRegion); + } + } + + m_backingCaches.push_back(subCache); + + if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 + && subCacheHeader.imagesTextOffset == 0) + { + auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); + uint64_t address = subCacheMapping.address; + uint64_t size = subCacheMapping.size; + MemoryRegion stubIslandRegion; + stubIslandRegion.start = address; + stubIslandRegion.size = size; + stubIslandRegion.prettyName = pathBasename + "::_stubs"; + stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); + m_stubIslandRegions.push_back(stubIslandRegion); + } + } + + // Load .symbols subcache + try + { + auto subCachePath = mainFileName + ".symbols"; + auto subCacheFile = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), subCachePath)->lock(); + dyld_cache_header subCacheHeader {}; + uint64_t headerSize = subCacheFile->ReadUInt32(16); + if (subCacheFile->ReadUInt32(16) > sizeof(dyld_cache_header)) + { + m_logger->LogDebug("Header size is larger than expected, using default size"); + headerSize = sizeof(dyld_cache_header); + } + subCacheFile->Read(&subCacheHeader, 0, headerSize); + + BackingCache subCache; + subCache.isPrimary = false; + subCache.path = subCachePath; + + dyld_cache_mapping_info subCacheMapping {}; + + for (size_t j = 0; j < subCacheHeader.mappingCount; j++) + { + subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), + sizeof(subCacheMapping)); + std::pair<uint64_t, std::pair<uint64_t, uint64_t>> mapRawToAddrAndSize; + mapRawToAddrAndSize.first = subCacheMapping.fileOffset; + mapRawToAddrAndSize.second.first = subCacheMapping.address; + mapRawToAddrAndSize.second.second = subCacheMapping.size; + subCache.mappings.push_back(mapRawToAddrAndSize); + } + + m_backingCaches.push_back(subCache); + } + catch (...) + {} + break; + } + } + baseFile.reset(); + progressMutex.lock(); + progressMap[m_dscView->GetFile()->GetSessionId()] = LoadProgressLoadingImages; + progressMutex.unlock(); + + // We have set up enough metadata to map VM now. + + auto vm = GetVMMap(true); + if (!vm) + { + m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal."); + return; + } + for (const auto &start : m_imageStarts) + { + try { + auto imageHeader = SharedCache::LoadHeaderForAddress(vm, start.second, start.first); + if (imageHeader) + { + if (imageHeader->linkeditPresent && vm->AddressIsMapped(imageHeader->linkeditSegment.vmaddr)) + { + auto mapping = vm->MappingAtAddress(imageHeader->linkeditSegment.vmaddr); + imageHeader->exportTriePath = mapping.first.filePath; + } + m_headers[start.second] = imageHeader.value(); + CacheImage image; + image.installName = start.first; + image.headerLocation = start.second; + for (const auto& segment : imageHeader->segments) + { + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + MemoryRegion sectionRegion; + sectionRegion.prettyName = std::string(segName); + sectionRegion.start = segment.vmaddr; + sectionRegion.size = segment.vmsize; + uint32_t flags = 0; + if (segment.initprot & MACHO_VM_PROT_READ) + flags |= SegmentReadable; + if (segment.initprot & MACHO_VM_PROT_WRITE) + flags |= SegmentWritable; + if (segment.initprot & MACHO_VM_PROT_EXECUTE) + flags |= SegmentExecutable; + if (((segment.initprot & MACHO_VM_PROT_WRITE) == 0) && + ((segment.maxprot & MACHO_VM_PROT_WRITE) == 0)) + flags |= SegmentDenyWrite; + if (((segment.initprot & MACHO_VM_PROT_EXECUTE) == 0) && + ((segment.maxprot & MACHO_VM_PROT_EXECUTE) == 0)) + flags |= SegmentDenyExecute; + + // if we're positive we have an entry point for some reason, force the segment + // executable. this helps with kernel images. + for (auto &entryPoint: imageHeader->m_entryPoints) + if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) + flags |= SegmentExecutable; + + sectionRegion.flags = (BNSegmentFlag)flags; + image.regions.push_back(sectionRegion); + } + m_images.push_back(image); + } + else + { + m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str()); + } + } + catch (std::exception& ex) + { + m_logger->LogError("Failed to load Mach-O header for %s: %s", start.first.c_str(), ex.what()); + } + } + + m_logger->LogInfo("Loaded %d Mach-O headers", m_headers.size()); + + for (const auto& cache : m_backingCaches) + { + size_t i = 0; + for (const auto& mapping : cache.mappings) + { + MemoryRegion region; + region.start = mapping.second.first; + region.size = mapping.second.second; + region.prettyName = base_name(cache.path) + "::" + std::to_string(i); + // FIXME flags!!! BackingCache.mapping needs refactored to store this information! + region.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); + m_nonImageRegions.push_back(region); + i++; + } + } + + // Iterate through each Mach-O header + if (!m_dyldDataRegions.empty()) + { + for (const auto& [headerKey, header] : m_headers) + { + // Iterate through each segment of the header + for (const auto& segment : header.segments) + { + uint64_t segmentStart = segment.vmaddr; + uint64_t segmentEnd = segmentStart + segment.vmsize; + + // Iterate through each region in m_dyldDataRegions + for (auto it = m_dyldDataRegions.begin(); it != m_dyldDataRegions.end();) + { + uint64_t regionStart = it->start; + uint64_t regionSize = it->size; + uint64_t regionEnd = regionStart + regionSize; + + // Check if the region overlaps with the segment + if (segmentStart < regionEnd && segmentEnd > regionStart) + { + // Split the region into two, removing the overlapped portion + std::vector<MemoryRegion> newRegions; + + // Part before the overlap + if (regionStart < segmentStart) + { + MemoryRegion newRegion; + newRegion.start = regionStart; + newRegion.size = segmentStart - regionStart; + newRegion.prettyName = it->prettyName; + newRegions.push_back(newRegion); + } + + // Part after the overlap + if (regionEnd > segmentEnd) + { + MemoryRegion newRegion; + newRegion.start = segmentEnd; + newRegion.size = regionEnd - segmentEnd; + newRegion.prettyName = it->prettyName; + newRegions.push_back(newRegion); + } + + // Erase the original region + it = m_dyldDataRegions.erase(it); + + // Insert the new regions (if any) + for (const auto& newRegion : newRegions) + { + it = m_dyldDataRegions.insert(it, newRegion); + ++it; // Move iterator to the next position + } + } + else + { + ++it; // No overlap, move to the next region + } + } + } + } + } + + // Iterate through each Mach-O header + if (!m_nonImageRegions.empty()) + { + for (const auto& [headerKey, header] : m_headers) + { + // Iterate through each segment of the header + for (const auto& segment : header.segments) + { + uint64_t segmentStart = segment.vmaddr; + uint64_t segmentEnd = segmentStart + segment.vmsize; + + // Iterate through each region in m_dyldDataRegions + for (auto it = m_nonImageRegions.begin(); it != m_nonImageRegions.end();) + { + uint64_t regionStart = it->start; + uint64_t regionSize = it->size; + uint64_t regionEnd = regionStart + regionSize; + + // Check if the region overlaps with the segment + if (segmentStart < regionEnd && segmentEnd > regionStart) + { + // Split the region into two, removing the overlapped portion + std::vector<MemoryRegion> newRegions; + + // Part before the overlap + if (regionStart < segmentStart) + { + MemoryRegion newRegion; + newRegion.start = regionStart; + newRegion.size = segmentStart - regionStart; + newRegion.prettyName = it->prettyName; + newRegions.push_back(newRegion); + } + + // Part after the overlap + if (regionEnd > segmentEnd) + { + MemoryRegion newRegion; + newRegion.start = segmentEnd; + newRegion.size = regionEnd - segmentEnd; + newRegion.prettyName = it->prettyName; + newRegions.push_back(newRegion); + } + + // Erase the original region + it = m_nonImageRegions.erase(it); + + // Insert the new regions (if any) + for (const auto& newRegion : newRegions) + { + it = m_nonImageRegions.insert(it, newRegion); + ++it; // Move iterator to the next position + } + } + else + { + ++it; // No overlap, move to the next region + } + } + } + } + } + SaveToDSCView(); + + m_logger->LogDebug("Finished initial load of Shared Cache"); + + progressMutex.lock(); + progressMap[m_dscView->GetFile()->GetSessionId()] = LoadProgressFinished; + progressMutex.unlock(); +} + +std::shared_ptr<VM> SharedCache::GetVMMap(bool mapPages) +{ + std::shared_ptr<VM> vm = std::make_shared<VM>(0x1000); + + if (mapPages) + { + for (const auto& cache : m_backingCaches) + { + for (const auto& mapping : cache.mappings) + { + vm->MapPages(m_dscView->GetFile()->GetSessionId(), mapping.second.first, mapping.first, mapping.second.second, cache.path, + [this, vm=vm](std::shared_ptr<MMappedFileAccessor> mmap){ + ParseAndApplySlideInfoForFile(mmap); + }); + } + } + } + + return vm; +} + + +void SharedCache::DeserializeFromRawView() +{ + if (m_dscView->QueryMetadata(SharedCacheMetadataTag)) + { + std::unique_lock<std::recursive_mutex> viewStateCacheLock(viewStateMutex); + if (viewStateCache.find(m_dscView->GetFile()->GetSessionId()) != viewStateCache.end()) + { + auto c = viewStateCache[m_dscView->GetFile()->GetSessionId()]; + m_imageStarts = c.m_imageStarts; + m_cacheFormat = c.m_cacheFormat; + m_backingCaches = c.m_backingCaches; + m_viewState = c.m_viewState; + m_headers = c.m_headers; + m_images = c.m_images; + m_regionsMappedIntoMemory = c.m_regionsMappedIntoMemory; + m_stubIslandRegions = c.m_stubIslandRegions; + m_dyldDataRegions = c.m_dyldDataRegions; + m_nonImageRegions = c.m_nonImageRegions; + m_baseFilePath = c.m_baseFilePath; + m_exportInfos = c.m_exportInfos; + m_symbolInfos = c.m_symbolInfos; + } + else + { + LoadFromString(m_dscView->GetStringMetadata(SharedCacheMetadataTag)); + } + } + else + { + m_viewState = DSCViewStateUnloaded; + m_images.clear(); // fixme ?? + } +} + + +std::string to_hex_string(uint64_t value) +{ + std::stringstream ss; + ss << std::hex << value; + return ss.str(); +} + + +void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file) +{ + if (file->SlideInfoWasApplied()) + return; + std::vector<std::pair<uint64_t, uint64_t>> rewrites; + + dyld_cache_header baseHeader; + file->Read(&baseHeader, 0, sizeof(dyld_cache_header)); + uint64_t base = UINT64_MAX; + for (const auto& backingCache : m_backingCaches) + { + for (const auto& mapping : backingCache.mappings) + { + if (mapping.second.first < base) + { + base = mapping.second.first; + break; + } + } + } + + std::vector<std::pair<uint64_t, MappingInfo>> mappings; + + if (baseHeader.slideInfoOffsetUnused) + { + // Legacy + + auto slideInfoOff = baseHeader.slideInfoOffsetUnused; + auto slideInfoVersion = file->ReadUInt32(slideInfoOff); + if (slideInfoVersion != 2 && slideInfoVersion != 3) + { + abort(); + } + + MappingInfo map; + + file->Read(&map.mappingInfo, baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info), sizeof(dyld_cache_mapping_info)); + map.file = file; + map.slideInfoVersion = slideInfoVersion; + if (map.slideInfoVersion == 2) + file->Read(&map.slideInfoV2, slideInfoOff, sizeof(dyld_cache_slide_info_v2)); + else if (map.slideInfoVersion == 3) + file->Read(&map.slideInfoV3, slideInfoOff, sizeof(dyld_cache_slide_info_v3)); + + mappings.emplace_back(slideInfoOff, map); + } + else + { + dyld_cache_header targetHeader; + file->Read(&targetHeader, 0, sizeof(dyld_cache_header)); + + if (targetHeader.mappingWithSlideCount == 0) + { + m_logger->LogDebug("No mappings with slide info found"); + } + + for (auto i = 0; i < targetHeader.mappingWithSlideCount; i++) + { + dyld_cache_mapping_and_slide_info mappingAndSlideInfo; + file->Read(&mappingAndSlideInfo, targetHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info)), sizeof(dyld_cache_mapping_and_slide_info)); + if (mappingAndSlideInfo.slideInfoFileOffset) + { + MappingInfo map; + map.file = file; + if (mappingAndSlideInfo.size == 0) + continue; + map.slideInfoVersion = file->ReadUInt32(mappingAndSlideInfo.slideInfoFileOffset); + m_logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); + map.mappingInfo.address = mappingAndSlideInfo.address; + map.mappingInfo.size = mappingAndSlideInfo.size; + map.mappingInfo.fileOffset = mappingAndSlideInfo.fileOffset; + if (map.slideInfoVersion == 2) + { + file->Read( + &map.slideInfoV2, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v2)); + } + else if (map.slideInfoVersion == 3) + { + file->Read( + &map.slideInfoV3, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v3)); + map.slideInfoV3.auth_value_add = base; + } + else if (map.slideInfoVersion == 5) + { + file->Read( + &map.slideInfoV5, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info5)); + map.slideInfoV5.value_add = base; + } + else + { + m_logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); + continue; + } + + uint64_t slideInfoOffset = mappingAndSlideInfo.slideInfoFileOffset; + mappings.emplace_back(slideInfoOffset, map); + m_logger->LogDebug("Filename: %s", file->Path().c_str()); + m_logger->LogDebug("Slide Info Offset: 0x%llx", slideInfoOffset); + m_logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); + m_logger->LogDebug("Slide Info v", map.slideInfoVersion); + } + } + } + + if (mappings.empty()) + { + m_logger->LogDebug("No slide info found"); + file->SetSlideInfoWasApplied(true); + return; + } + + for (const auto& [off, mapping] : mappings) + { + m_logger->LogDebug("Slide Info Version: %d", mapping.slideInfoVersion); + uint64_t pageStartsOffset = off; + uint64_t pageStartCount; + uint64_t pageSize; + std::vector<uint64_t> pageStartFileOffsets; + + if (mapping.slideInfoVersion == 2) + { + pageStartsOffset += mapping.slideInfoV2.page_starts_offset; + pageStartCount = mapping.slideInfoV2.page_starts_count; + pageSize = mapping.slideInfoV2.page_size; + + pageStartFileOffsets.reserve(pageStartCount); + size_t cursor = pageStartsOffset; + + for (size_t i = 0; i < pageStartCount; i++) + { + try + { + uint64_t pageStart = mapping.file->ReadUShort(cursor) * 4; + cursor += 2; + if (pageStart & 0x4000) + continue; + pageStartFileOffsets.push_back(mapping.mappingInfo.fileOffset + (pageSize * i) + pageStart); + } + catch (MappingReadException& ex) + { + + } + } + } + else if (mapping.slideInfoVersion == 3) { + // Slide Info Version 3 Logic + pageStartsOffset += sizeof(dyld_cache_slide_info_v3); + pageStartCount = mapping.slideInfoV3.page_starts_count; + pageSize = mapping.slideInfoV3.page_size; + + pageStartFileOffsets.reserve(pageStartCount); + size_t cursor = pageStartsOffset; + + for (size_t i = 0; i < pageStartCount; i++) + { + try + { + uint64_t pageStart = mapping.file->ReadUShort(cursor); + cursor += 2; + if (pageStart & 0x4000) + continue; + pageStartFileOffsets.push_back(mapping.mappingInfo.fileOffset + (pageSize * i) + pageStart); + } + catch (MappingReadException& ex) + { + m_logger->LogError("Failed to read slide info at 0x%llx\n", cursor); + } + } + } + else if (mapping.slideInfoVersion == 5) + { + pageStartsOffset += sizeof(dyld_cache_slide_info5); + pageStartCount = mapping.slideInfoV5.page_starts_count; + m_logger->LogDebug("Page Start Count: %d", pageStartCount); + pageSize = mapping.slideInfoV5.page_size; + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + try + { + uint64_t pageStart = mapping.file->ReadUShort(cursor); + cursor += 2; + if (pageStart == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) + continue; + pageStartFileOffsets.push_back(mapping.mappingInfo.fileOffset + (pageSize * i) + pageStart); + } + catch (MappingReadException& ex) + { + m_logger->LogError("Failed to read slide info at 0x%llx\n", cursor); + } + } + } + if (pageStartFileOffsets.empty()) + { + m_logger->LogDebug("No page start file offsets found"); + } + for (auto pageStart : pageStartFileOffsets) + { + if (mapping.slideInfoVersion == 2) + { + auto deltaMask = mapping.slideInfoV2.delta_mask; + auto valueMask = ~deltaMask; + auto valueAdd = mapping.slideInfoV2.value_add; + + auto deltaShift = count_trailing_zeros(deltaMask) - 2; + + uint64_t delta = 1; + uint64_t loc = pageStart; + while (delta != 0) + { + try { + uint64_t rawValue = file->ReadULong(loc); + delta = (rawValue & deltaMask) >> deltaShift; + uint64_t value = (rawValue & valueMask); + if (valueMask != 0) + { + value += valueAdd; + } + rewrites.emplace_back(loc, value); + } + catch (MappingReadException& ex) + { + m_logger->LogError("Failed to read slide info at 0x%llx\n", loc); + delta = 0; + } + loc += delta; + } + } + else if (mapping.slideInfoVersion == 3) + { + uint64_t loc = pageStart; + uint64_t delta = 1; + while (delta != 0) + { + dyld_cache_slide_pointer3 slideInfo; + try + { + file->Read(&slideInfo, loc, 8); + delta = slideInfo.plain.offsetToNextPointer * 8; + + if (slideInfo.auth.authenticated) + { + uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; + value += mapping.slideInfoV3.auth_value_add; + rewrites.emplace_back(loc, value); + } + else + { + uint64_t value51 = slideInfo.plain.pointerValue; + uint64_t top8Bits = value51 & 0x0007F80000000000; + uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; + uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; + rewrites.emplace_back(loc, value); + } + loc += delta; + } + catch (MappingReadException& ex) + { + m_logger->LogError("Failed to read slide info at 0x%llx\n", loc); + delta = 0; + } + } + } + else if (mapping.slideInfoVersion == 5) + { + uint64_t loc = pageStart; + uint64_t delta = 1; + while (delta != 0) + { + dyld_cache_slide_pointer5 slideInfo; + try + { + file->Read(&slideInfo, loc, 8); + delta = slideInfo.regular.next * 8; + if (slideInfo.auth.auth) + { + uint64_t value = slideInfo.auth.runtimeOffset + mapping.slideInfoV5.value_add; + rewrites.emplace_back(loc, value); + } + else + { + uint64_t value = base + slideInfo.regular.runtimeOffset; + rewrites.emplace_back(loc, value); + } + loc += delta; + } + catch (MappingReadException& ex) + { + m_logger->LogError("Failed to read slide info at 0x%llx\n", loc); + delta = 0; + } + } + } + } + } + for (const auto& [loc, value] : rewrites) + { + file->WritePointer(loc, value); +#ifdef SLIDEINFO_DEBUG_TAGS + uint64_t vmAddr = 0; + { + for (uint64_t off = baseHeader.mappingOffset; off < baseHeader.mappingOffset + baseHeader.mappingCount * sizeof(dyld_cache_mapping_info); off += sizeof(dyld_cache_mapping_info)) + { + dyld_cache_mapping_info mapping; + file->Read(&mapping, off, sizeof(dyld_cache_mapping_info)); + if (mapping.fileOffset <= loc && loc < mapping.fileOffset + mapping.size) + { + vmAddr = mapping.address + (loc - mapping.fileOffset); + break; + } + } + } + Ref<TagType> type = m_dscView->GetTagType("slideinfo"); + if (!type) + { + m_dscView->AddTagType(new TagType(m_dscView, "slideinfo", "\xF0\x9F\x9A\x9E")); + type = m_dscView->GetTagType("slideinfo"); + } + m_dscView->AddAutoDataTag(vmAddr, new Tag(type, "0x" + to_hex_string(file->ReadULong(loc)) + " => 0x" + to_hex_string(value))); +#endif + } + m_logger->LogDebug("Applied slide info for %s (0x%llx rewrites)", file->Path().c_str(), rewrites.size()); + file->SetSlideInfoWasApplied(true); +} + + +SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : m_dscView(dscView) +{ + sharedCacheReferences++; + INIT_SHAREDCACHE_API_OBJECT() + m_logger = LogRegistry::GetLogger("SharedCache", dscView->GetFile()->GetSessionId()); + DeserializeFromRawView(); + if (m_viewState == DSCViewStateUnloaded) + { + if (m_viewState == DSCViewStateUnloaded) + { + // maybe we are getting called from UI thread. probably. do this on a separate thread just in case. + // TODO `wait` argument + WorkerEnqueue([this]() { + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + try { + PerformInitialLoad(); + } + catch (...) + { + m_logger->LogError("Failed to perform initial load of Shared Cache"); + } + + for (const auto& [_, header] : m_headers) + { + if (header.installName.find("libsystem_c.dylib") != std::string::npos) + { + lock.unlock(); + m_logger->LogInfo("Loading core libsystem_c.dylib library"); + LoadImageWithInstallName(header.installName); + lock.lock(); + break ; + } + } + + m_viewState = DSCViewStateLoaded; + SaveToDSCView(); + }); + } + } + else + { + progressMutex.lock(); + progressMap[m_dscView->GetFile()->GetSessionId()] = LoadProgressFinished; + progressMutex.unlock(); + } +} + +SharedCache::~SharedCache() { + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + sharedCacheReferences--; +} + +SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) +{ + try { + return new SharedCache(dscView); + } + catch (...) + { + return nullptr; + } +} + +std::optional<uint64_t> SharedCache::GetImageStart(std::string installName) +{ + for (const auto& [name, start] : m_imageStarts) + { + if (name == installName) + { + return start; + } + } + return {}; +} + +std::optional<SharedCacheMachOHeader> SharedCache::HeaderForAddress(uint64_t address) +{ + // We _could_ mark each page with the image start? :grimacing emoji: + // But that'd require mapping pages :grimacing emoji: :grimacing emoji: + // There's not really any other hacks that could make this faster, that I can think of... + for (const auto& [start, header] : m_headers) + { + for (const auto& segment : header.segments) + { + if (segment.vmaddr <= address && segment.vmaddr + segment.vmsize > address) + { + return header; + } + } + } + return {}; +} + +std::string SharedCache::NameForAddress(uint64_t address) +{ + for (const auto& stubIsland : m_stubIslandRegions) + { + if (stubIsland.start <= address && stubIsland.start + stubIsland.size > address) + { + return stubIsland.prettyName; + } + } + for (const auto& dyldData : m_dyldDataRegions) + { + if (dyldData.start <= address && dyldData.start + dyldData.size > address) + { + return dyldData.prettyName; + } + } + for (const auto& nonImageRegion : m_nonImageRegions) + { + if (nonImageRegion.start <= address && nonImageRegion.start + nonImageRegion.size > address) + { + return nonImageRegion.prettyName; + } + } + if (auto header = HeaderForAddress(address)) + { + for (const auto& section : header->sections) + { + if (section.addr <= address && section.addr + section.size > address) + { + char sectionName[17]; + strncpy(sectionName, section.sectname, 16); + sectionName[16] = '\0'; + return header->identifierPrefix + "::" + sectionName; + } + } + } + return ""; +} + +std::string SharedCache::ImageNameForAddress(uint64_t address) +{ + if (auto header = HeaderForAddress(address)) + { + return header->identifierPrefix; + } + return ""; +} + +bool SharedCache::LoadImageContainingAddress(uint64_t address) +{ + for (const auto& [start, header] : m_headers) + { + for (const auto& segment : header.segments) + { + if (segment.vmaddr <= address && segment.vmaddr + segment.vmsize > address) + { + return LoadImageWithInstallName(header.installName); + } + } + } + + return false; +} + +bool SharedCache::LoadSectionAtAddress(uint64_t address) +{ + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + DeserializeFromRawView(); + auto vm = GetVMMap(); + if (!vm) + { + m_logger->LogError("Failed to map VM pages for Shared Cache."); + return false; + } + + SharedCacheMachOHeader targetHeader; + CacheImage* targetImage = nullptr; + MemoryRegion* targetSegment = nullptr; + + for (auto& image : m_images) + { + for (auto& region : image.regions) + { + if (region.start <= address && region.start + region.size > address) + { + targetHeader = m_headers[image.headerLocation]; + targetImage = ℑ + targetSegment = ®ion; + break; + } + } + if (targetSegment) + break; + } + if (!targetSegment) + { + for (auto& stubIsland : m_stubIslandRegions) + { + if (stubIsland.start <= address && stubIsland.start + stubIsland.size > address) + { + if (stubIsland.loaded) + { + return true; + } + m_logger->LogInfo("Loading stub island %s @ 0x%llx", stubIsland.prettyName.c_str(), stubIsland.start); + auto targetFile = vm->MappingAtAddress(stubIsland.start).first.fileAccessor->lock(); + ParseAndApplySlideInfoForFile(targetFile); + auto reader = VMReader(vm); + auto buff = reader.ReadBuffer(stubIsland.start, stubIsland.size); + auto rawViewEnd = m_dscView->GetParentView()->GetEnd(); + + auto name = stubIsland.prettyName; + m_dscView->GetParentView()->GetParentView()->WriteBuffer( + m_dscView->GetParentView()->GetParentView()->GetEnd(), *buff); + m_dscView->GetParentView()->AddAutoSegment(rawViewEnd, stubIsland.size, rawViewEnd, stubIsland.size, + SegmentReadable | SegmentExecutable); + m_dscView->AddUserSegment(stubIsland.start, stubIsland.size, rawViewEnd, stubIsland.size, + SegmentReadable | SegmentExecutable); + m_dscView->AddUserSection(name, stubIsland.start, stubIsland.size, ReadOnlyCodeSectionSemantics); + m_dscView->WriteBuffer(stubIsland.start, *buff); + + stubIsland.loaded = true; + + stubIsland.rawViewOffsetIfLoaded = rawViewEnd; + + m_regionsMappedIntoMemory.push_back(stubIsland); + + SaveToDSCView(); + + m_dscView->AddAnalysisOption("linearsweep"); + m_dscView->UpdateAnalysis(); + + return true; + } + } + + for (auto& dyldData : m_dyldDataRegions) + { + if (dyldData.start <= address && dyldData.start + dyldData.size > address) + { + if (dyldData.loaded) + { + return true; + } + m_logger->LogInfo("Loading dyld data %s", dyldData.prettyName.c_str()); + auto targetFile = vm->MappingAtAddress(dyldData.start).first.fileAccessor->lock(); + ParseAndApplySlideInfoForFile(targetFile); + auto reader = VMReader(vm); + auto buff = reader.ReadBuffer(dyldData.start, dyldData.size); + auto rawViewEnd = m_dscView->GetParentView()->GetEnd(); + + auto name = dyldData.prettyName; + m_dscView->GetParentView()->GetParentView()->WriteBuffer( + m_dscView->GetParentView()->GetParentView()->GetEnd(), *buff); + m_dscView->GetParentView()->WriteBuffer(rawViewEnd, *buff); + m_dscView->GetParentView()->AddAutoSegment(rawViewEnd, dyldData.size, rawViewEnd, dyldData.size, + SegmentReadable); + m_dscView->AddUserSegment(dyldData.start, dyldData.size, rawViewEnd, dyldData.size, SegmentReadable); + m_dscView->AddUserSection(name, dyldData.start, dyldData.size, ReadOnlyCodeSectionSemantics); + m_dscView->WriteBuffer(dyldData.start, *buff); + + dyldData.loaded = true; + dyldData.rawViewOffsetIfLoaded = rawViewEnd; + + m_regionsMappedIntoMemory.push_back(dyldData); + + SaveToDSCView(); + + m_dscView->AddAnalysisOption("linearsweep"); + m_dscView->UpdateAnalysis(); + + return true; + } + } + + for (auto& region : m_nonImageRegions) + { + if (region.start <= address && region.start + region.size > address) + { + if (region.loaded) + { + return true; + } + m_logger->LogInfo("Loading non-image region %s", region.prettyName.c_str()); + auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); + ParseAndApplySlideInfoForFile(targetFile); + auto reader = VMReader(vm); + auto buff = reader.ReadBuffer(region.start, region.size); + auto rawViewEnd = m_dscView->GetParentView()->GetEnd(); + + auto name = region.prettyName; + m_dscView->GetParentView()->GetParentView()->WriteBuffer( + m_dscView->GetParentView()->GetParentView()->GetEnd(), *buff); + m_dscView->GetParentView()->WriteBuffer(rawViewEnd, *buff); + m_dscView->GetParentView()->AddAutoSegment(rawViewEnd, region.size, rawViewEnd, region.size, region.flags); + m_dscView->AddUserSegment(region.start, region.size, rawViewEnd, region.size, region.flags); + m_dscView->AddUserSection(name, region.start, region.size, ReadOnlyCodeSectionSemantics); + m_dscView->WriteBuffer(region.start, *buff); + + region.loaded = true; + region.rawViewOffsetIfLoaded = rawViewEnd; + + m_regionsMappedIntoMemory.push_back(region); + + SaveToDSCView(); + + m_dscView->AddAnalysisOption("linearsweep"); + m_dscView->UpdateAnalysis(); + + return true; + } + } + + m_logger->LogError("Failed to find a segment containing address 0x%llx", address); + return false; + } + + auto id = m_dscView->BeginUndoActions(); + auto rawViewEnd = m_dscView->GetParentView()->GetEnd(); + auto reader = VMReader(vm); + + m_logger->LogDebug("Partial loading image %s", targetHeader.installName.c_str()); + + auto targetFile = vm->MappingAtAddress(targetSegment->start).first.fileAccessor->lock(); + ParseAndApplySlideInfoForFile(targetFile); + auto buff = reader.ReadBuffer(targetSegment->start, targetSegment->size); + m_dscView->GetParentView()->GetParentView()->WriteBuffer( + m_dscView->GetParentView()->GetParentView()->GetEnd(), *buff); + m_dscView->GetParentView()->WriteBuffer(rawViewEnd, *buff); + m_dscView->GetParentView()->AddAutoSegment( + rawViewEnd, targetSegment->size, rawViewEnd, targetSegment->size, SegmentReadable); + m_dscView->AddUserSegment( + targetSegment->start, targetSegment->size, rawViewEnd, targetSegment->size, targetSegment->flags); + m_dscView->WriteBuffer(targetSegment->start, *buff); + + targetSegment->loaded = true; + targetSegment->rawViewOffsetIfLoaded = rawViewEnd; + + m_regionsMappedIntoMemory.push_back(*targetSegment); + + SaveToDSCView(); + + if (!targetSegment->headerInitialized) + { + SharedCache::InitializeHeader(m_dscView, vm.get(), targetHeader, {targetSegment}); + } + + m_dscView->AddAnalysisOption("linearsweep"); + m_dscView->UpdateAnalysis(); + + m_dscView->CommitUndoActions(id); + + return true; +} + +bool SharedCache::LoadImageWithInstallName(std::string installName) +{ + auto settings = m_dscView->GetLoadSettings(VIEW_NAME); + + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + + DeserializeFromRawView(); + m_logger->LogInfo("Loading image %s", installName.c_str()); + + auto vm = GetVMMap(); + CacheImage* targetImage = nullptr; + + for (auto& cacheImage : m_images) + { + if (cacheImage.installName == installName) + { + targetImage = &cacheImage; + break; + } + } + + auto header = m_headers[targetImage->headerLocation]; + + auto id = m_dscView->BeginUndoActions(); + m_viewState = DSCViewStateLoadedWithImages; + + auto reader = VMReader(vm); + reader.Seek(targetImage->headerLocation); + + std::vector<MemoryRegion*> regionsToLoad; + + for (auto& region : targetImage->regions) + { + bool allowLoadingLinkedit = false; + if (settings && settings->Contains("loader.dsc.allowLoadingLinkeditSegments")) + allowLoadingLinkedit = settings->Get<bool>("loader.dsc.allowLoadingLinkeditSegments", m_dscView); + if ((region.prettyName.find("__LINKEDIT") != std::string::npos) && !allowLoadingLinkedit) + continue; + + if (region.loaded) + { + m_logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str()); + continue; + } + + auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); + ParseAndApplySlideInfoForFile(targetFile); + + auto rawViewEnd = m_dscView->GetParentView()->GetEnd(); + + auto buff = reader.ReadBuffer(region.start, region.size); + m_dscView->GetParentView()->GetParentView()->WriteBuffer(rawViewEnd, *buff); + m_dscView->GetParentView()->WriteBuffer(rawViewEnd, *buff); + + region.loaded = true; + region.rawViewOffsetIfLoaded = rawViewEnd; + + m_regionsMappedIntoMemory.push_back(region); + + m_dscView->GetParentView()->AddAutoSegment(rawViewEnd, region.size, rawViewEnd, region.size, region.flags); + m_dscView->AddUserSegment(region.start, region.size, rawViewEnd, region.size, region.flags); + m_dscView->WriteBuffer(region.start, *buff); + + regionsToLoad.push_back(®ion); + } + + if (regionsToLoad.empty()) + { + m_logger->LogWarn("No regions to load for image %s", installName.c_str()); + return false; + } + + std::unique_lock<std::mutex> typelibLock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].typeLibraryLookupAndApplicationMutex); + auto typeLib = m_dscView->GetTypeLibrary(header.installName); + + if (!typeLib) + { + auto typeLibs = m_dscView->GetDefaultPlatform()->GetTypeLibrariesByName(header.installName); + if (!typeLibs.empty()) + { + typeLib = typeLibs[0]; + m_dscView->AddTypeLibrary(typeLib); + m_logger->LogInfo("shared-cache: adding type library for '%s': %s (%s)", + targetImage->installName.c_str(), typeLib->GetName().c_str(), typeLib->GetGuid().c_str()); + } + } + typelibLock.unlock(); + + SaveToDSCView(); + + auto h = SharedCache::LoadHeaderForAddress(vm, targetImage->headerLocation, installName); + if (!h.has_value()) + { + return false; + } + + std::vector<MemoryRegion*> regions; + for (auto& region : regionsToLoad) + { + regions.push_back(region); + } + + SharedCache::InitializeHeader(m_dscView, vm.get(), *h, regions); + + try + { + auto objc = std::make_unique<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); + + bool processCFStrings = true; + bool processObjCMetadata = true; + if (settings && settings->Contains("loader.dsc.processCFStrings")) + processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", m_dscView); + if (settings && settings->Contains("loader.dsc.processObjC")) + processObjCMetadata = settings->Get<bool>("loader.dsc.processObjC", m_dscView); + if (processObjCMetadata) + objc->ProcessObjCData(vm, h->identifierPrefix); + if (processCFStrings) + objc->ProcessCFStrings(vm, h->identifierPrefix); + } + catch (const std::exception& ex) + { + m_logger->LogWarn("Error processing ObjC data: %s", ex.what()); + } + catch (...) + { + m_logger->LogWarn("Error processing ObjC data"); + } + + m_dscView->AddAnalysisOption("linearsweep"); + m_dscView->UpdateAnalysis(); + + m_dscView->CommitUndoActions(id); + + return true; +} + +std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::shared_ptr<VM> vm, uint64_t address, std::string installName) +{ + SharedCacheMachOHeader header; + + header.textBase = address; + header.installName = installName; + header.identifierPrefix = base_name(installName); + + std::string errorMsg; + // address is a Raw file offset + VMReader reader(vm); + reader.Seek(address); + + header.ident.magic = reader.Read32(); + + BNEndianness endianness; + if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) + endianness = LittleEndian; + else if (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) + endianness = BigEndian; + else + { + return {}; + } + + reader.SetEndianness(endianness); + header.ident.cputype = reader.Read32(); + header.ident.cpusubtype = reader.Read32(); + header.ident.filetype = reader.Read32(); + header.ident.ncmds = reader.Read32(); + header.ident.sizeofcmds = reader.Read32(); + header.ident.flags = reader.Read32(); + if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 + { + header.ident.reserved = reader.Read32(); + } + header.loadCommandOffset = reader.GetOffset(); + + bool first = true; + // Parse segment commands + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + // BNLogInfo("of 0x%llx", reader.GetOffset()); + load_command load; + segment_command_64 segment64; + section_64 sect; + memset(§, 0, sizeof(sect)); + size_t curOffset = reader.GetOffset(); + load.cmd = reader.Read32(); + load.cmdsize = reader.Read32(); + size_t nextOffset = curOffset + load.cmdsize; + if (load.cmdsize < sizeof(load_command)) + return {}; + + switch (load.cmd) + { + case LC_MAIN: + { + uint64_t entryPoint = reader.Read64(); + header.entryPoints.push_back({entryPoint, true}); + (void)reader.Read64(); // Stack start + break; + } + case LC_SEGMENT: // map the 32bit version to 64 bits + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.Read32(); + segment64.vmsize = reader.Read32(); + segment64.fileoff = reader.Read32(); + segment64.filesize = reader.Read32(); + segment64.maxprot = reader.Read32(); + segment64.initprot = reader.Read32(); + segment64.nsects = reader.Read32(); + segment64.flags = reader.Read32(); + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.Read32(); + sect.size = reader.Read32(); + sect.offset = reader.Read32(); + sect.align = reader.Read32(); + sect.reloff = reader.Read32(); + sect.nreloc = reader.Read32(); + sect.flags = reader.Read32(); + sect.reserved1 = reader.Read32(); + sect.reserved2 = reader.Read32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_SEGMENT_64: + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.Read64(); + segment64.vmsize = reader.Read64(); + segment64.fileoff = reader.Read64(); + segment64.filesize = reader.Read64(); + segment64.maxprot = reader.Read32(); + segment64.initprot = reader.Read32(); + segment64.nsects = reader.Read32(); + segment64.flags = reader.Read32(); + if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) + { + header.linkeditSegment = segment64; + header.linkeditPresent = true; + } + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.Read64(); + sect.size = reader.Read64(); + sect.offset = reader.Read32(); + sect.align = reader.Read32(); + sect.reloff = reader.Read32(); + sect.nreloc = reader.Read32(); + sect.flags = reader.Read32(); + sect.reserved1 = reader.Read32(); + sect.reserved2 = reader.Read32(); + sect.reserved3 = reader.Read32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_ROUTINES: // map the 32bit version to 64bits + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.Read32(); + header.routines64.init_module = reader.Read32(); + header.routines64.reserved1 = reader.Read32(); + header.routines64.reserved2 = reader.Read32(); + header.routines64.reserved3 = reader.Read32(); + header.routines64.reserved4 = reader.Read32(); + header.routines64.reserved5 = reader.Read32(); + header.routines64.reserved6 = reader.Read32(); + header.routinesPresent = true; + break; + case LC_ROUTINES_64: + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.Read64(); + header.routines64.init_module = reader.Read64(); + header.routines64.reserved1 = reader.Read64(); + header.routines64.reserved2 = reader.Read64(); + header.routines64.reserved3 = reader.Read64(); + header.routines64.reserved4 = reader.Read64(); + header.routines64.reserved5 = reader.Read64(); + header.routines64.reserved6 = reader.Read64(); + header.routinesPresent = true; + break; + case LC_FUNCTION_STARTS: + header.functionStarts.funcoff = reader.Read32(); + header.functionStarts.funcsize = reader.Read32(); + header.functionStartsPresent = true; + break; + case LC_SYMTAB: + header.symtab.symoff = reader.Read32(); + header.symtab.nsyms = reader.Read32(); + header.symtab.stroff = reader.Read32(); + header.symtab.strsize = reader.Read32(); + break; + case LC_DYSYMTAB: + header.dysymtab.ilocalsym = reader.Read32(); + header.dysymtab.nlocalsym = reader.Read32(); + header.dysymtab.iextdefsym = reader.Read32(); + header.dysymtab.nextdefsym = reader.Read32(); + header.dysymtab.iundefsym = reader.Read32(); + header.dysymtab.nundefsym = reader.Read32(); + header.dysymtab.tocoff = reader.Read32(); + header.dysymtab.ntoc = reader.Read32(); + header.dysymtab.modtaboff = reader.Read32(); + header.dysymtab.nmodtab = reader.Read32(); + header.dysymtab.extrefsymoff = reader.Read32(); + header.dysymtab.nextrefsyms = reader.Read32(); + header.dysymtab.indirectsymoff = reader.Read32(); + header.dysymtab.nindirectsyms = reader.Read32(); + header.dysymtab.extreloff = reader.Read32(); + header.dysymtab.nextrel = reader.Read32(); + header.dysymtab.locreloff = reader.Read32(); + header.dysymtab.nlocrel = reader.Read32(); + header.dysymPresent = true; + break; + case LC_DYLD_CHAINED_FIXUPS: + header.chainedFixups.dataoff = reader.Read32(); + header.chainedFixups.datasize = reader.Read32(); + header.chainedFixupsPresent = true; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + header.dyldInfo.rebase_off = reader.Read32(); + header.dyldInfo.rebase_size = reader.Read32(); + header.dyldInfo.bind_off = reader.Read32(); + header.dyldInfo.bind_size = reader.Read32(); + header.dyldInfo.weak_bind_off = reader.Read32(); + header.dyldInfo.weak_bind_size = reader.Read32(); + header.dyldInfo.lazy_bind_off = reader.Read32(); + header.dyldInfo.lazy_bind_size = reader.Read32(); + header.dyldInfo.export_off = reader.Read32(); + header.dyldInfo.export_size = reader.Read32(); + header.exportTrie.dataoff = header.dyldInfo.export_off; + header.exportTrie.datasize = header.dyldInfo.export_size; + header.exportTriePresent = true; + header.dyldInfoPresent = true; + break; + case LC_DYLD_EXPORTS_TRIE: + header.exportTrie.dataoff = reader.Read32(); + header.exportTrie.datasize = reader.Read32(); + header.exportTriePresent = true; + break; + case LC_THREAD: + case LC_UNIXTHREAD: + /*while (reader.GetOffset() < nextOffset) + { + + thread_command thread; + thread.flavor = reader.Read32(); + thread.count = reader.Read32(); + switch (m_archId) + { + case MachOx64: + m_logger->LogDebug("x86_64 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex64, sizeof(thread.statex64)); + header.entryPoints.push_back({thread.statex64.rip, false}); + break; + case MachOx86: + m_logger->LogDebug("x86 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE32) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex86, sizeof(thread.statex86)); + header.entryPoints.push_back({thread.statex86.eip, false}); + break; + case MachOArm: + m_logger->LogDebug("Arm Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); + header.entryPoints.push_back({thread.statearmv7.r15, false}); + break; + case MachOAarch64: + case MachOAarch6432: + m_logger->LogDebug("Aarch64 Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); + header.entryPoints.push_back({thread.stateaarch64.pc, false}); + break; + case MachOPPC: + m_logger->LogDebug("PPC Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //Read individual entries for endian reasons + header.entryPoints.push_back({reader.Read32(), false}); + (void)reader.Read32(); + (void)reader.Read32(); + //Read the rest of the structure + (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); + break; + case MachOPPC64: + m_logger->LogDebug("PPC64 Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + header.entryPoints.push_back({reader.Read64(), false}); + (void)reader.Read64(); + (void)reader.Read64(); // Stack start + (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); + break; + default: + m_logger->LogError("Unknown archid: %x", m_archId); + } + + }*/ + break; + case LC_LOAD_DYLIB: + { + uint32_t offset = reader.Read32(); + if (offset < nextOffset) + { + reader.Seek(curOffset + offset); + std::string libname = reader.ReadCString(reader.GetOffset()); + header.dylibs.push_back(libname); + } + } + break; + case LC_BUILD_VERSION: + { + // m_logger->LogDebug("LC_BUILD_VERSION:"); + header.buildVersion.platform = reader.Read32(); + header.buildVersion.minos = reader.Read32(); + header.buildVersion.sdk = reader.Read32(); + header.buildVersion.ntools = reader.Read32(); + // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); + // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); + // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); + for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) + { + uint32_t tool = reader.Read32(); + uint32_t version = reader.Read32(); + header.buildToolVersions.push_back({tool, version}); + // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), + // BuildToolVersionToString(version).c_str()); + } + break; + } + case LC_FILESET_ENTRY: + { + throw ReadException(); + } + default: + // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), + // load.cmdsize); + break; + } + if (reader.GetOffset() != nextOffset) + { + // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, + // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); + } + reader.Seek(nextOffset); + } + + for (auto& section : header.sections) + { + char sectionName[17]; + memcpy(sectionName, section.sectname, sizeof(section.sectname)); + sectionName[16] = 0; + if (header.identifierPrefix.empty()) + header.sectionNames.push_back(sectionName); + else + header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); + } + } + catch (ReadException&) + { + return {}; + } + + return header; +} + +void SharedCache::InitializeHeader( + Ref<BinaryView> view, VM* vm, SharedCacheMachOHeader header, std::vector<MemoryRegion*> regionsToLoad) +{ + + Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME); + bool applyFunctionStarts = true; + if (settings && settings->Contains("loader.dsc.processFunctionStarts")) + applyFunctionStarts = settings->Get<bool>("loader.dsc.processFunctionStarts", view); + + for (size_t i = 0; i < header.sections.size(); i++) + { + bool skip = false; + for (const auto& region : regionsToLoad) + { + if (header.sections[i].addr >= region->start && header.sections[i].addr < region->start + region->size) + { + if (region->headerInitialized) + { + skip = true; + } + break; + } + } + if (!header.sections[i].size || skip) + continue; + + std::string type; + BNSectionSemantics semantics = DefaultSectionSemantics; + switch (header.sections[i].flags & 0xff) + { + case S_REGULAR: + if (header.sections[i].flags & S_ATTR_PURE_INSTRUCTIONS) + { + type = "PURE_CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else if (header.sections[i].flags & S_ATTR_SOME_INSTRUCTIONS) + { + type = "CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else + { + type = "REGULAR"; + } + break; + case S_ZEROFILL: + type = "ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_CSTRING_LITERALS: + type = "CSTRING_LITERALS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_4BYTE_LITERALS: + type = "4BYTE_LITERALS"; + break; + case S_8BYTE_LITERALS: + type = "8BYTE_LITERALS"; + break; + case S_LITERAL_POINTERS: + type = "LITERAL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_NON_LAZY_SYMBOL_POINTERS: + type = "NON_LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_LAZY_SYMBOL_POINTERS: + type = "LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_SYMBOL_STUBS: + type = "SYMBOL_STUBS"; + semantics = ReadOnlyCodeSectionSemantics; + break; + case S_MOD_INIT_FUNC_POINTERS: + type = "MOD_INIT_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_MOD_TERM_FUNC_POINTERS: + type = "MOD_TERM_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_COALESCED: + type = "COALESCED"; + break; + case S_GB_ZEROFILL: + type = "GB_ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_INTERPOSING: + type = "INTERPOSING"; + break; + case S_16BYTE_LITERALS: + type = "16BYTE_LITERALS"; + break; + case S_DTRACE_DOF: + type = "DTRACE_DOF"; + break; + case S_LAZY_DYLIB_SYMBOL_POINTERS: + type = "LAZY_DYLIB_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_THREAD_LOCAL_REGULAR: + type = "THREAD_LOCAL_REGULAR"; + break; + case S_THREAD_LOCAL_ZEROFILL: + type = "THREAD_LOCAL_ZEROFILL"; + break; + case S_THREAD_LOCAL_VARIABLES: + type = "THREAD_LOCAL_VARIABLES"; + break; + case S_THREAD_LOCAL_VARIABLE_POINTERS: + type = "THREAD_LOCAL_VARIABLE_POINTERS"; + break; + case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: + type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; + break; + default: + type = "UNKNOWN"; + break; + } + if (i >= header.sectionNames.size()) + break; + if (strncmp(header.sections[i].sectname, "__text", sizeof(header.sections[i].sectname)) == 0) + semantics = ReadOnlyCodeSectionSemantics; + if (strncmp(header.sections[i].sectname, "__const", sizeof(header.sections[i].sectname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + if (strncmp(header.sections[i].sectname, "__data", sizeof(header.sections[i].sectname)) == 0) + semantics = ReadWriteDataSectionSemantics; + if (strncmp(header.sections[i].segname, "__DATA_CONST", sizeof(header.sections[i].segname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + + view->AddUserSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, + type, header.sections[i].align); + } + + auto typeLib = view->GetTypeLibrary(header.installName); + + BinaryReader virtualReader(view); + + bool applyHeaderTypes = false; + for (const auto& region : regionsToLoad) + { + if (header.textBase >= region->start && header.textBase < region->start + region->size) + { + if (!region->headerInitialized) + applyHeaderTypes = true; + break; + } + } + if (applyHeaderTypes) + { + view->DefineDataVariable(header.textBase, Type::NamedType(view, QualifiedName("mach_header_64"))); + view->DefineAutoSymbol( + new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); + + try + { + virtualReader.Seek(header.textBase + sizeof(mach_header_64)); + size_t sectionNum = 0; + for (size_t i = 0; i < header.ident.ncmds; i++) + { + load_command load; + uint64_t curOffset = virtualReader.GetOffset(); + load.cmd = virtualReader.Read32(); + load.cmdsize = virtualReader.Read32(); + uint64_t nextOffset = curOffset + load.cmdsize; + switch (load.cmd) + { + case LC_SEGMENT: + { + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command"))); + virtualReader.SeekRelative(5 * 8); + size_t numSections = virtualReader.Read32(); + virtualReader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + view->DefineDataVariable( + virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section"))); + view->DefineUserSymbol(new Symbol(DataSymbol, + "__macho_section::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", + virtualReader.GetOffset(), LocalBinding)); + virtualReader.SeekRelative((8 * 8) + 4); + } + break; + } + case LC_SEGMENT_64: + { + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command_64"))); + virtualReader.SeekRelative(7 * 8); + size_t numSections = virtualReader.Read32(); + virtualReader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + view->DefineDataVariable( + virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section_64"))); + view->DefineUserSymbol(new Symbol(DataSymbol, + "__macho_section_64::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", + virtualReader.GetOffset(), LocalBinding)); + virtualReader.SeekRelative(10 * 8); + } + break; + } + case LC_SYMTAB: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("symtab"))); + break; + case LC_DYSYMTAB: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dysymtab"))); + break; + case LC_UUID: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("uuid"))); + break; + case LC_ID_DYLIB: + case LC_LOAD_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dylib_command"))); + if (load.cmdsize - 24 <= 150) + view->DefineDataVariable( + curOffset + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); + break; + case LC_CODE_SIGNATURE: + case LC_SEGMENT_SPLIT_INFO: + case LC_FUNCTION_STARTS: + case LC_DATA_IN_CODE: + case LC_DYLIB_CODE_SIGN_DRS: + case LC_DYLD_EXPORTS_TRIE: + case LC_DYLD_CHAINED_FIXUPS: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("linkedit_data"))); + break; + case LC_ENCRYPTION_INFO: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("encryption_info"))); + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("version_min"))); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dyld_info"))); + break; + default: + view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("load_command"))); + break; + } + + view->DefineAutoSymbol(new Symbol(DataSymbol, + "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, + LocalBinding)); + virtualReader.Seek(nextOffset); + } + } + catch (ReadException&) + { + LogError("Error when applying Mach-O header types at %" PRIx64, header.textBase); + } + } + + if (applyFunctionStarts && header.functionStartsPresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + { + auto funcStarts = + vm->MappingAtAddress(header.linkeditSegment.vmaddr) + .first.fileAccessor->lock() + ->ReadBuffer(header.functionStarts.funcoff, header.functionStarts.funcsize); + size_t i = 0; + uint64_t curfunc = header.textBase; + uint64_t curOffset; + + while (i < header.functionStarts.funcsize) + { + curOffset = readLEB128(*funcStarts, header.functionStarts.funcsize, i); + bool addFunction = false; + for (const auto& region : regionsToLoad) + { + if (curfunc >= region->start && curfunc < region->start + region->size) + { + if (!region->headerInitialized) + addFunction = true; + } + } + // LogError("0x%llx, 0x%llx", header.textBase, curOffset); + if (curOffset == 0 || !addFunction) + continue; + curfunc += curOffset; + uint64_t target = curfunc; + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + view->AddFunctionForAnalysis(targetPlatform, target); + } + } + + view->BeginBulkModifySymbols(); + if (header.symtab.symoff != 0 && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + { + // Mach-O View symtab processing with + // a ton of stuff cut out so it can work + + auto reader = vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); + // auto symtab = reader->ReadBuffer(header.symtab.symoff, header.symtab.nsyms * sizeof(nlist_64)); + auto strtab = reader->ReadBuffer(header.symtab.stroff, header.symtab.strsize); + nlist_64 sym; + memset(&sym, 0, sizeof(sym)); + auto N_TYPE = 0xE; // idk + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> symbolInfos; + for (size_t i = 0; i < header.symtab.nsyms; i++) + { + reader->Read(&sym, header.symtab.symoff + i * sizeof(nlist_64), sizeof(nlist_64)); + if (sym.n_strx >= header.symtab.strsize || ((sym.n_type & N_TYPE) == N_INDR)) + continue; + + std::string symbol((char*)strtab->GetDataAt(sym.n_strx)); + // BNLogError("%s: 0x%llx", symbol.c_str(), sym.n_value); + if (symbol == "<redacted>") + continue; + + BNSymbolType type = DataSymbol; + uint32_t flags; + if ((sym.n_type & N_TYPE) == N_SECT && sym.n_sect > 0 && (size_t)(sym.n_sect - 1) < header.sections.size()) + {} + else if ((sym.n_type & N_TYPE) == N_ABS) + {} + else if ((sym.n_type & 0x1)) + { + type = ExternalSymbol; + } + else + continue; + + for (auto s : header.sections) + { + if (s.addr < sym.n_value) + { + if (s.addr + s.size > sym.n_value) + { + flags = s.flags; + } + } + } + + if (type != ExternalSymbol) + { + if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + type = FunctionSymbol; + else + type = DataSymbol; + } + if ((sym.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) + sym.n_value++; + + auto symbolObj = new Symbol(type, symbol, sym.n_value, GlobalBinding); + if (type == FunctionSymbol) + { + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + view->AddFunctionForAnalysis(targetPlatform, sym.n_value); + } + if (typeLib) + { + auto _type = m_dscView->ImportTypeLibraryObject(typeLib, {symbolObj->GetFullName()}); + if (_type) + { + view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbolObj, _type); + } + else + view->DefineAutoSymbol(symbolObj); + } + else + view->DefineAutoSymbol(symbolObj); + symbolInfos.push_back({sym.n_value, {type, symbol}}); + } + m_symbolInfos[header.textBase] = symbolInfos; + } + + if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + { + auto symbols = SharedCache::ParseExportTrie(vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(), header); + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> exportMapping; + for (const auto& symbol : symbols) + { + exportMapping.push_back({symbol->GetAddress(), {symbol->GetType(), symbol->GetRawName()}}); + if (typeLib) + { + auto type = m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}); + + if (type) + { + view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); + } + else + view->DefineAutoSymbol(symbol); + + if (view->GetAnalysisFunction(view->GetDefaultPlatform(), symbol->GetAddress())) + { + auto func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbol->GetAddress()); + if (symbol->GetFullName() == "_objc_msgSend") + { + func->SetHasVariableArguments(false); + } + else if (symbol->GetFullName().find("_objc_retain_x") != std::string::npos || symbol->GetFullName().find("_objc_release_x") != std::string::npos) + { + auto x = symbol->GetFullName().rfind("x"); + auto num = symbol->GetFullName().substr(x + 1); + + std::vector<BinaryNinja::FunctionParameter> callTypeParams; + auto cc = m_dscView->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + + callTypeParams.push_back({"obj", m_dscView->GetTypeByName({ "id" }), true, BinaryNinja::Variable()}); + + auto funcType = BinaryNinja::Type::FunctionType(m_dscView->GetTypeByName({ "id" }), cc, callTypeParams); + func->SetUserType(funcType); + } + } + } + else + view->DefineAutoSymbol(symbol); + } + m_exportInfos[header.textBase] = exportMapping; + } + view->EndBulkModifySymbols(); + + for (auto region : regionsToLoad) + { + region->headerInitialized = true; + } +} + +struct ExportNode +{ + std::string text; + uint64_t offset; + uint64_t flags; +}; + + +void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, SharedCacheMachOHeader& header, DataBuffer& buffer, uint64_t textBase, + const std::string& currentText, size_t cursor, uint32_t endGuard) +{ + + if (cursor > endGuard) + throw ReadException(); + + uint64_t terminalSize = readValidULEB128(buffer, cursor); + uint64_t childOffset = cursor + terminalSize; + if (terminalSize != 0) { + uint64_t imageOffset = 0; + uint64_t flags = readValidULEB128(buffer, cursor); + if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) + { + imageOffset = readValidULEB128(buffer, cursor); + auto symbolType = m_dscView->GetAnalysisFunctionsForAddress(textBase + imageOffset).size() ? FunctionSymbol : DataSymbol; + { + if (!currentText.empty() && textBase + imageOffset) + { + uint32_t flags; + BNSymbolType type; + for (auto s : header.sections) + { + if (s.addr < textBase + imageOffset) + { + if (s.addr + s.size > textBase + imageOffset) + { + flags = s.flags; + } + } + } + if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + type = FunctionSymbol; + else + type = DataSymbol; + +#if EXPORT_TRIE_DEBUG + // BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset); +#endif + auto sym = new Symbol(type, currentText, textBase + imageOffset); + symbolList.push_back(sym); + } + } + } + } + cursor = childOffset; + uint8_t childCount = buffer[cursor]; + cursor++; + if (cursor > endGuard) + throw ReadException(); + for (uint8_t i = 0; i < childCount; ++i) + { + std::string childText; + while (buffer[cursor] != 0 & cursor <= endGuard) + childText.push_back(buffer[cursor++]); + cursor++; + if (cursor > endGuard) + throw ReadException(); + auto next = readValidULEB128(buffer, cursor); + if (next == 0) + throw ReadException(); + ReadExportNode(symbolList, header, buffer, textBase, currentText + childText, next, endGuard); + } +} + + +std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, SharedCacheMachOHeader header) +{ + std::vector<Ref<Symbol>> symbols; + try + { + auto reader = linkeditFile; + + std::vector<ExportNode> nodes; + + DataBuffer* buffer = reader->ReadBuffer(header.exportTrie.dataoff, header.exportTrie.datasize); + ReadExportNode(symbols, header, *buffer, header.textBase, "", 0, header.exportTrie.datasize); + } + catch (std::exception& e) + { + BNLogError("Failed to load Export Trie"); + } + return symbols; +} + +std::vector<std::string> SharedCache::GetAvailableImages() +{ + std::vector<std::string> installNames; + for (const auto& header : m_headers) + { + installNames.push_back(header.second.installName); + } + return installNames; +} + + +std::vector<std::pair<std::string, Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait() +{ + std::unique_lock<std::mutex> initialLoadBlock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + + std::vector<std::pair<std::string, Ref<Symbol>>> symbols; + for (const auto& img : m_images) + { + auto header = HeaderForAddress(img.headerLocation); + std::shared_ptr<MMappedFileAccessor> mapping; + try { + mapping = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), header->exportTriePath)->lock(); + } + catch (...) + { + m_logger->LogWarn("Serious Error: Failed to open export trie for %s", header->installName.c_str()); + continue; + } + auto exportList = SharedCache::ParseExportTrie(mapping, *header); + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> exportMapping; + for (const auto& sym : exportList) + { + exportMapping.push_back({sym->GetAddress(), {sym->GetType(), sym->GetRawName()}}); + symbols.push_back({img.installName, sym}); + } + m_exportInfos[header->textBase] = exportMapping; + } + + SaveToDSCView(); + + return symbols; +} + + +std::string SharedCache::SerializedImageHeaderForAddress(uint64_t address) +{ + auto header = HeaderForAddress(address); + if (header) + { + return header->AsString(); + } + return ""; +} + + +std::string SharedCache::SerializedImageHeaderForName(std::string name) +{ + auto header = HeaderForAddress(m_imageStarts[name]); + if (header) + { + return header->AsString(); + } + return ""; +} + + +void SharedCache::FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) +{ + std::string prefix = ""; + if (symbolLocation != targetLocation) + prefix = "j_"; + if (auto preexistingSymbol = m_dscView->GetSymbolByAddress(targetLocation)) + { + if (preexistingSymbol->GetFullName().find("j_") != std::string::npos) + return; + } + auto id = m_dscView->BeginUndoActions(); + if (auto loadedSymbol = m_dscView->GetSymbolByAddress(symbolLocation)) + { + if (m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) + m_dscView->DefineUserSymbol(new Symbol(FunctionSymbol, prefix + loadedSymbol->GetFullName(), targetLocation)); + else + m_dscView->DefineUserSymbol(new Symbol(loadedSymbol->GetType(), prefix + loadedSymbol->GetFullName(), targetLocation)); + } + else if (auto sym = m_dscView->GetSymbolByAddress(symbolLocation)) + { + if (m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) + m_dscView->DefineUserSymbol(new Symbol(FunctionSymbol, prefix + sym->GetFullName(), targetLocation)); + else + m_dscView->DefineUserSymbol(new Symbol(sym->GetType(), prefix + sym->GetFullName(), targetLocation)); + } + m_dscView->ForgetUndoActions(id); + auto header = HeaderForAddress(symbolLocation); + if (header) + { + std::shared_ptr<MMappedFileAccessor> mapping; + try { + mapping = MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), header->exportTriePath)->lock(); + } + catch (...) + { + m_logger->LogWarn("Serious Error: Failed to open export trie for %s", header->installName.c_str()); + return; + } + auto exportList = SharedCache::ParseExportTrie(mapping, *header); + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> exportMapping; + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].typeLibraryLookupAndApplicationMutex); + auto typeLib = m_dscView->GetTypeLibrary(header->installName); + if (!typeLib) + { + auto typeLibs = m_dscView->GetDefaultPlatform()->GetTypeLibrariesByName(header->installName); + if (!typeLibs.empty()) + { + typeLib = typeLibs[0]; + m_dscView->AddTypeLibrary(typeLib); + } + } + lock.unlock(); + id = m_dscView->BeginUndoActions(); + m_dscView->BeginBulkModifySymbols(); + for (const auto& sym : exportList) + { + exportMapping.push_back({sym->GetAddress(), {sym->GetType(), sym->GetRawName()}}); + if (sym->GetAddress() == symbolLocation) + { + if (auto func = m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) + { + m_dscView->DefineUserSymbol( + new Symbol(FunctionSymbol, prefix + sym->GetFullName(), targetLocation)); + + if (typeLib) + if (auto type = m_dscView->ImportTypeLibraryObject(typeLib, {sym->GetFullName()})) + func->SetUserType(type); + } + else + { + m_dscView->DefineUserSymbol( + new Symbol(sym->GetType(), prefix + sym->GetFullName(), targetLocation)); + + if (typeLib) + if (auto type = m_dscView->ImportTypeLibraryObject(typeLib, {sym->GetFullName()})) + m_dscView->DefineUserDataVariable(targetLocation, type); + } + if (triggerReanalysis) + { + auto func = m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation); + if (func) + func->Reanalyze(); + } + break; + } + } + { + std::unique_lock<std::mutex> _lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + m_exportInfos[header->textBase] = exportMapping; + } + m_dscView->EndBulkModifySymbols(); + m_dscView->ForgetUndoActions(id); + } +} + + +bool SharedCache::SaveToDSCView() +{ + if (m_dscView) + { + auto data = AsMetadata(); + m_dscView->StoreMetadata(SharedCacheMetadataTag, data); + m_dscView->GetParentView()->GetParentView()->StoreMetadata(SharedCacheMetadataTag, data); + std::unique_lock<std::recursive_mutex> viewStateCacheLock(viewStateMutex); + ViewStateCacheStore c; + c.m_imageStarts = m_imageStarts; + c.m_cacheFormat = m_cacheFormat; + c.m_backingCaches = m_backingCaches; + c.m_viewState = m_viewState; + c.m_headers = m_headers; + c.m_images = m_images; + c.m_regionsMappedIntoMemory = m_regionsMappedIntoMemory; + c.m_stubIslandRegions = m_stubIslandRegions; + c.m_dyldDataRegions = m_dyldDataRegions; + c.m_nonImageRegions = m_nonImageRegions; + c.m_baseFilePath = m_baseFilePath; + c.m_exportInfos = m_exportInfos; + c.m_symbolInfos = m_symbolInfos; + viewStateCache[m_dscView->GetFile()->GetSessionId()] = c; + + return true; + } + return false; +} +std::vector<MemoryRegion> SharedCache::GetMappedRegions() const +{ + std::unique_lock<std::mutex> lock(viewSpecificMutexes[m_dscView->GetFile()->GetSessionId()].viewOperationsThatInfluenceMetadataMutex); + return m_regionsMappedIntoMemory; +} + +extern "C" +{ + BNSharedCache* BNGetSharedCache(BNBinaryView* data) + { + if (!data) + return nullptr; + + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + if (auto cache = SharedCache::GetFromDSCView(view)) + { + cache->AddAPIRef(); + return cache->GetAPIObject(); + } + + return nullptr; + } + + BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache) + { + if (!cache->object) + return nullptr; + + cache->object->AddAPIRef(); + return cache; + } + + void BNFreeSharedCacheReference(BNSharedCache* cache) + { + if (!cache->object) + return; + + cache->object->ReleaseAPIRef(); + } + + bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name) + { + std::string imageName = std::string(name); + BNFreeString(name); + + if (cache->object) + return cache->object->LoadImageWithInstallName(imageName); + + return false; + } + + bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t addr) + { + if (cache->object) + { + return cache->object->LoadSectionAtAddress(addr); + } + + return false; + } + + bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address) + { + if (cache->object) + { + return cache->object->LoadImageContainingAddress(address); + } + + return false; + } + + char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count) + { + if (cache->object) + { + auto value = cache->object->GetAvailableImages(); + *count = value.size(); + + std::vector<const char*> cstrings; + for (size_t i = 0; i < value.size(); i++) + { + cstrings.push_back(value[i].c_str()); + } + return BNAllocStringList(cstrings.data(), cstrings.size()); + } + *count = 0; + return nullptr; + } + + BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count) + { + if (cache->object) + { + auto value = cache->object->LoadAllSymbolsAndWait(); + *count = value.size(); + + BNDSCSymbolRep* symbols = (BNDSCSymbolRep*)malloc(sizeof(BNDSCSymbolRep) * value.size()); + for (size_t i = 0; i < value.size(); i++) + { + symbols[i].address = value[i].second->GetAddress(); + symbols[i].name = BNAllocString(value[i].second->GetRawName().c_str()); + symbols[i].image = BNAllocString(value[i].first.c_str()); + } + return symbols; + } + *count = 0; + return nullptr; + } + + void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count) + { + for (size_t i = 0; i < count; i++) + { + BNFreeString(symbols[i].name); + BNFreeString(symbols[i].image); + } + delete symbols; + } + + char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address) + { + if (cache->object) + { + return BNAllocString(cache->object->NameForAddress(address).c_str()); + } + + return nullptr; + } + + char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address) + { + if (cache->object) + { + return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); + } + + return nullptr; + } + + uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache) + { + // FIXME? + return 0; + } + + BNDSCViewState BNDSCViewGetState(BNSharedCache* cache) + { + if (cache->object) + { + return (BNDSCViewState)cache->object->State(); + } + + return BNDSCViewState::Unloaded; + } + + + BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count) + { + if (cache->object) + { + auto regions = cache->object->GetMappedRegions(); + *count = regions.size(); + BNDSCMappedMemoryRegion* mappedRegions = (BNDSCMappedMemoryRegion*)malloc(sizeof(BNDSCMappedMemoryRegion) * regions.size()); + for (size_t i = 0; i < regions.size(); i++) + { + mappedRegions[i].vmAddress = regions[i].start; + mappedRegions[i].size = regions[i].size; + mappedRegions[i].name = BNAllocString(regions[i].prettyName.c_str()); + } + return mappedRegions; + } + *count = 0; + return nullptr; + } + + void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count) + { + for (size_t i = 0; i < count; i++) + { + BNFreeString(images[i].name); + } + delete images; + } + + + BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count) + { + BNDSCBackingCache* caches = nullptr; + + if (cache->object) + { + auto viewCaches = cache->object->BackingCaches(); + *count = viewCaches.size(); + caches = (BNDSCBackingCache*)malloc(sizeof(BNDSCBackingCache) * viewCaches.size()); + for (size_t i = 0; i < viewCaches.size(); i++) + { + caches[i].path = BNAllocString(viewCaches[i].path.c_str()); + caches[i].isPrimary = viewCaches[i].isPrimary; + + BNDSCBackingCacheMapping* mappings; + mappings = (BNDSCBackingCacheMapping*)malloc(sizeof(BNDSCBackingCacheMapping) * viewCaches[i].mappings.size()); + + size_t j = 0; + for (const auto& [fileOffset, mapping] : viewCaches[i].mappings) + { + mappings[j].vmAddress = mapping.first; + mappings[j].size = mapping.second; + mappings[j].fileOffset = fileOffset; + j++; + } + caches[i].mappings = mappings; + caches[i].mappingCount = viewCaches[i].mappings.size(); + } + } + + return caches; + } + + void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count) + { + for (size_t i = 0; i < count; i++) + { + delete[] caches[i].mappings; + BNFreeString(caches[i].path); + } + delete[] caches; + } + + void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) + { + if (cache->object) + { + cache->object->FindSymbolAtAddrAndApplyToAddr(symbolLocation, targetLocation, triggerReanalysis); + } + } + + BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count) + { + if (cache->object) + { + auto vm = cache->object->GetVMMap(true); + auto viewImageHeaders = cache->object->AllImageHeaders(); + *count = viewImageHeaders.size(); + BNDSCImage* images = (BNDSCImage*)malloc(sizeof(BNDSCImage) * viewImageHeaders.size()); + size_t i = 0; + for (const auto& [baseAddress, header] : viewImageHeaders) + { + images[i].name = BNAllocString(header.installName.c_str()); + images[i].headerAddress = baseAddress; + images[i].mappingCount = header.sections.size(); + images[i].mappings = (BNDSCImageMemoryMapping*)malloc(sizeof(BNDSCImageMemoryMapping) * header.sections.size()); + for (size_t j = 0; j < header.sections.size(); j++) + { + images[i].mappings[j].rawViewOffset = header.sections[j].offset; + images[i].mappings[j].vmAddress = header.sections[j].addr; + images[i].mappings[j].size = header.sections[j].size; + images[i].mappings[j].name = BNAllocString(header.sectionNames[j].c_str()); + images[i].mappings[j].filePath = BNAllocString(vm->MappingAtAddress(header.sections[j].addr).first.filePath.c_str()); + } + i++; + } + return images; + } + *count = 0; + return nullptr; + } + + void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count) + { + for (size_t i = 0; i < count; i++) + { + for (size_t j = 0; j < images[i].mappingCount; j++) + { + BNFreeString(images[i].mappings[j].name); + BNFreeString(images[i].mappings[j].filePath); + } + delete[] images[i].mappings; + BNFreeString(images[i].name); + } + delete[] images; + } + + char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address) + { + if (cache->object) + { + auto header = cache->object->SerializedImageHeaderForAddress(address); + return BNAllocString(header.c_str()); + } + + return nullptr; + } + + char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name) + { + std::string imageName = std::string(name); + BNFreeString(name); + if (cache->object) + { + auto header = cache->object->SerializedImageHeaderForName(imageName); + return BNAllocString(header.c_str()); + } + + return nullptr; + } + + BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo() + { + BNDSCMemoryUsageInfo info; + info.mmapRefs = mmapCount.load(); + info.sharedCacheRefs = sharedCacheReferences.load(); + return info; + } + + BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID) + { + progressMutex.lock(); + if (progressMap.find(sessionID) == progressMap.end()) + { + progressMutex.unlock(); + return LoadProgressNotStarted; + } + auto progress = progressMap[sessionID]; + progressMutex.unlock(); + return progress; + } + + uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* data) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + return SharedCache::FastGetBackingCacheCount(view); + } +} + +[[maybe_unused]] DSCViewType* g_dscViewType; +[[maybe_unused]] DSCRawViewType* g_dscRawViewType; + +void InitDSCViewType() +{ + MMappedFileAccessor::InitialVMSetup(); + std::atexit(VMShutdown); + + static DSCRawViewType rawType; + BinaryViewType::Register(&rawType); + static DSCViewType type; + BinaryViewType::Register(&type); + g_dscViewType = &type; + g_dscRawViewType = &rawType; +} diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h new file mode 100644 index 00000000..2f099773 --- /dev/null +++ b/view/sharedcache/core/SharedCache.h @@ -0,0 +1,1197 @@ +// +// Created by kat on 5/19/23. +// + +#include <binaryninjaapi.h> +#include "DSCView.h" +#include "VM.h" +#include "view/macho/machoview.h" +#include "MetadataSerializable.hpp" +#include "../api/sharedcachecore.h" + +#ifndef SHAREDCACHE_SHAREDCACHE_H +#define SHAREDCACHE_SHAREDCACHE_H + +DECLARE_SHAREDCACHE_API_OBJECT(BNSharedCache, SharedCache); + +namespace SharedCacheCore { + + enum DSCViewState + { + DSCViewStateUnloaded, + DSCViewStateLoaded, + DSCViewStateLoadedWithImages, + }; + + + const std::string SharedCacheMetadataTag = "SHAREDCACHE-SharedCacheData"; + + struct MemoryRegion : public MetadataSerializable + { + std::string prettyName; + uint64_t start; + uint64_t size; + bool loaded = false; + uint64_t rawViewOffsetIfLoaded = 0; + bool headerInitialized = false; + BNSegmentFlag flags; + + void Store() override + { + MSS(prettyName); + MSS(start); + MSS(size); + MSS(loaded); + MSS(rawViewOffsetIfLoaded); + MSS_CAST(flags, uint64_t); + } + + void Load() override + { + MSL(prettyName); + MSL(start); + MSL(size); + MSL(loaded); + MSL(rawViewOffsetIfLoaded); + MSL_CAST(flags, uint64_t, BNSegmentFlag); + } + }; + + struct CacheImage : public MetadataSerializable + { + std::string installName; + uint64_t headerLocation; + std::vector<MemoryRegion> regions; + + void Store() override + { + MSS(installName); + MSS(headerLocation); + rapidjson::Value key("regions", m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& region : regions) + { + bArr.PushBack(rapidjson::Value(region.AsString().c_str(), m_activeContext.allocator), m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Load() override + { + MSL(installName); + MSL(headerLocation); + auto bArr = m_activeDeserContext.doc["regions"].GetArray(); + regions.clear(); + for (auto& region : bArr) + { + MemoryRegion r; + r.LoadFromString(region.GetString()); + regions.push_back(r); + } + } + }; + + struct BackingCache : public MetadataSerializable + { + std::string path; + bool isPrimary = false; + std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> mappings; + + void Store() override + { + MSS(path); + MSS(isPrimary); + MSS(mappings); + } + void Load() override + { + MSL(path); + MSL(isPrimary); + MSL(mappings); + } + }; + + #if defined(__GNUC__) || defined(__clang__) + #define PACKED_STRUCT __attribute__((packed)) + #else + #define PACKED_STRUCT + #endif + + #if defined(_MSC_VER) + #pragma pack(push, 1) + #else + + #endif + + struct PACKED_STRUCT dyld_cache_mapping_info + { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t maxProt; + uint32_t initProt; + }; + + struct LoadedMapping + { + std::shared_ptr<MMappedFileAccessor> backingFile; + dyld_cache_mapping_info mappingInfo; + }; + + struct PACKED_STRUCT dyld_cache_mapping_and_slide_info + { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint64_t slideInfoFileOffset; + uint64_t slideInfoFileSize; + uint64_t flags; + uint32_t maxProt; + uint32_t initProt; + }; + + struct PACKED_STRUCT dyld_cache_slide_info_v2 + { + uint32_t version; + uint32_t page_size; + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; + uint64_t value_add; + }; + + struct PACKED_STRUCT dyld_cache_slide_info_v3 + { + uint32_t version; + uint32_t page_size; + uint32_t page_starts_count; + uint32_t pad_i_guess; + uint64_t auth_value_add; + }; + + + // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE + struct dyld_chained_ptr_arm64e_shared_cache_rebase + { + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + high8 : 8, + unused : 10, + next : 11, // 8-byte stide + auth : 1; // == 0 + }; + + // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE + struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase + { + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + diversity : 16, + addrDiv : 1, + keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA + next : 11, // 8-byte stide + auth : 1; // == 1 + }; + + // dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + + #define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + + struct PACKED_STRUCT dyld_cache_slide_info5 + { + uint32_t version; // currently 5 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint64_t value_add; + // uint16_t page_starts[/* page_starts_count */]; + }; + + + struct PACKED_STRUCT dyld_cache_image_info + { + uint64_t address; + uint64_t modTime; + uint64_t inode; + uint32_t pathFileOffset; + uint32_t pad; + }; + + union dyld_cache_slide_pointer5 + { + uint64_t raw; + struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; + struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; + }; + + + struct PACKED_STRUCT dyld_cache_local_symbols_info + { + uint32_t nlistOffset; // offset into this chunk of nlist entries + uint32_t nlistCount; // count of nlist entries + uint32_t stringsOffset; // offset into this chunk of string pool + uint32_t stringsSize; // byte count of string pool + uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry + uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array + }; + + struct PACKED_STRUCT dyld_cache_local_symbols_entry + { + uint32_t dylibOffset; // offset in cache file of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib + }; + + struct PACKED_STRUCT dyld_cache_local_symbols_entry_64 + { + uint64_t dylibOffset; // offset in cache buffer of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib + }; + + union dyld_cache_slide_pointer3 + { + uint64_t raw; + struct + { + uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2; + } plain; + + struct + { + uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2, + offsetToNextPointer : 11, unused : 1, + authenticated : 1; // = 1; + } auth; + }; + + + struct PACKED_STRUCT dyld_cache_header + { + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info + uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info + uint64_t localSymbolsOffset; // file offset of where local symbols are stored + uint64_t localSymbolsSize; // size of local symbols information + uint8_t uuid[16]; // unique value for each shared cache file + uint64_t cacheType; // 0 for development, 1 for production // Kat: , 2 for iOS 16? + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t accelerateInfoAddr; // (unslid) address of optimization info + uint64_t accelerateInfoSize; // size of optimization info + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries + uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info + uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info + uint64_t otherImageGroupAddrUnused; // unused + uint64_t otherImageGroupSizeUnused; // unused + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid + simulator : 1, // for simulator of specified platform + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides + padding : 20; // TBD + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any + uint64_t maxSlide; // runtime slide of cache can be between zero and this value + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures + uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info + uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries + uint64_t dylibsPBLStateArrayAddrUnused; // unused + uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs + uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program + uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program + uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet + uint32_t programTrieSize; + uint32_t osVersion; // OS Version of dylibs in this cache for the main platform + uint32_t altPlatform; // e.g. iOSMac on macOS + uint32_t altOsVersion; // e.g. 14.0 for iOSMac + uint64_t swiftOptsOffset; // file offset to Swift optimizations header + uint64_t swiftOptsSize; // size of Swift optimizations header + uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry + uint32_t subCacheArrayCount; // number of subCache entries + uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols + uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data + uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region + uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data + uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + }; + + struct PACKED_STRUCT dyld_subcache_entry + { + char uuid[16]; + uint64_t address; + }; + + struct PACKED_STRUCT dyld_subcache_entry2 + { + char uuid[16]; + uint64_t address; + char fileExtension[32]; + }; + + #if defined(_MSC_VER) + #pragma pack(pop) + #else + + #endif + + using namespace BinaryNinja; + struct SharedCacheMachOHeader : public MetadataSerializable + { + uint64_t textBase = 0; + uint64_t loadCommandOffset = 0; + mach_header_64 ident; + std::string identifierPrefix; + std::string installName; + + std::vector<std::pair<uint64_t, bool>> entryPoints; + std::vector<uint64_t> m_entryPoints; // list of entrypoints + + symtab_command symtab; + dysymtab_command dysymtab; + dyld_info_command dyldInfo; + routines_command_64 routines64; + function_starts_command functionStarts; + std::vector<section_64> moduleInitSections; + linkedit_data_command exportTrie; + linkedit_data_command chainedFixups {}; + + uint64_t relocationBase; + // Section and program headers, internally use 64-bit form as it is a superset of 32-bit + std::vector<segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT + segment_command_64 linkeditSegment; + std::vector<section_64> sections; + std::vector<std::string> sectionNames; + + std::vector<section_64> symbolStubSections; + std::vector<section_64> symbolPointerSections; + + std::vector<std::string> dylibs; + + build_version_command buildVersion; + std::vector<build_tool_version> buildToolVersions; + + std::string exportTriePath; + + bool linkeditPresent = false; + bool dysymPresent = false; + bool dyldInfoPresent = false; + bool exportTriePresent = false; + bool chainedFixupsPresent = false; + bool routinesPresent = false; + bool functionStartsPresent = false; + bool relocatable = false; + void Serialize(const std::string& name, mach_header_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.magic, m_activeContext.allocator); + bArr.PushBack(b.cputype, m_activeContext.allocator); + bArr.PushBack(b.cpusubtype, m_activeContext.allocator); + bArr.PushBack(b.filetype, m_activeContext.allocator); + bArr.PushBack(b.ncmds, m_activeContext.allocator); + bArr.PushBack(b.sizeofcmds, m_activeContext.allocator); + bArr.PushBack(b.flags, m_activeContext.allocator); + bArr.PushBack(b.reserved, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, mach_header_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.magic = bArr[0].GetUint(); + b.cputype = bArr[1].GetUint(); + b.cpusubtype = bArr[2].GetUint(); + b.filetype = bArr[3].GetUint(); + b.ncmds = bArr[4].GetUint(); + b.sizeofcmds = bArr[5].GetUint(); + b.flags = bArr[6].GetUint(); + b.reserved = bArr[7].GetUint(); + } + + void Serialize(const std::string& name, symtab_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.symoff, m_activeContext.allocator); + bArr.PushBack(b.nsyms, m_activeContext.allocator); + bArr.PushBack(b.stroff, m_activeContext.allocator); + bArr.PushBack(b.strsize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, symtab_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.symoff = bArr[2].GetUint(); + b.nsyms = bArr[3].GetUint(); + b.stroff = bArr[4].GetUint(); + b.strsize = bArr[5].GetUint(); + } + + void Serialize(const std::string& name, dysymtab_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.ilocalsym, m_activeContext.allocator); + bArr.PushBack(b.nlocalsym, m_activeContext.allocator); + bArr.PushBack(b.iextdefsym, m_activeContext.allocator); + bArr.PushBack(b.nextdefsym, m_activeContext.allocator); + bArr.PushBack(b.iundefsym, m_activeContext.allocator); + bArr.PushBack(b.nundefsym, m_activeContext.allocator); + bArr.PushBack(b.tocoff, m_activeContext.allocator); + bArr.PushBack(b.ntoc, m_activeContext.allocator); + bArr.PushBack(b.modtaboff, m_activeContext.allocator); + bArr.PushBack(b.nmodtab, m_activeContext.allocator); + bArr.PushBack(b.extrefsymoff, m_activeContext.allocator); + bArr.PushBack(b.nextrefsyms, m_activeContext.allocator); + bArr.PushBack(b.indirectsymoff, m_activeContext.allocator); + bArr.PushBack(b.nindirectsyms, m_activeContext.allocator); + bArr.PushBack(b.extreloff, m_activeContext.allocator); + bArr.PushBack(b.nextrel, m_activeContext.allocator); + bArr.PushBack(b.locreloff, m_activeContext.allocator); + bArr.PushBack(b.nlocrel, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, dysymtab_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.ilocalsym = bArr[2].GetUint(); + b.nlocalsym = bArr[3].GetUint(); + b.iextdefsym = bArr[4].GetUint(); + b.nextdefsym = bArr[5].GetUint(); + b.iundefsym = bArr[6].GetUint(); + b.nundefsym = bArr[7].GetUint(); + b.tocoff = bArr[8].GetUint(); + b.ntoc = bArr[9].GetUint(); + b.modtaboff = bArr[10].GetUint(); + b.nmodtab = bArr[11].GetUint(); + b.extrefsymoff = bArr[12].GetUint(); + b.nextrefsyms = bArr[13].GetUint(); + b.indirectsymoff = bArr[14].GetUint(); + b.nindirectsyms = bArr[15].GetUint(); + b.extreloff = bArr[16].GetUint(); + b.nextrel = bArr[17].GetUint(); + b.locreloff = bArr[18].GetUint(); + b.nlocrel = bArr[19].GetUint(); + } + + void Serialize(const std::string& name, dyld_info_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.rebase_off, m_activeContext.allocator); + bArr.PushBack(b.rebase_size, m_activeContext.allocator); + bArr.PushBack(b.bind_off, m_activeContext.allocator); + bArr.PushBack(b.bind_size, m_activeContext.allocator); + bArr.PushBack(b.weak_bind_off, m_activeContext.allocator); + bArr.PushBack(b.weak_bind_size, m_activeContext.allocator); + bArr.PushBack(b.lazy_bind_off, m_activeContext.allocator); + bArr.PushBack(b.lazy_bind_size, m_activeContext.allocator); + bArr.PushBack(b.export_off, m_activeContext.allocator); + bArr.PushBack(b.export_size, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, dyld_info_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.rebase_off = bArr[2].GetUint(); + b.rebase_size = bArr[3].GetUint(); + b.bind_off = bArr[4].GetUint(); + b.bind_size = bArr[5].GetUint(); + b.weak_bind_off = bArr[6].GetUint(); + b.weak_bind_size = bArr[7].GetUint(); + b.lazy_bind_off = bArr[8].GetUint(); + b.lazy_bind_size = bArr[9].GetUint(); + b.export_off = bArr[10].GetUint(); + b.export_size = bArr[11].GetUint(); + } + + void Serialize(const std::string& name, routines_command_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.init_address, m_activeContext.allocator); + bArr.PushBack(b.init_module, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, routines_command_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.init_address = bArr[2].GetUint(); + b.init_module = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, function_starts_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.funcoff, m_activeContext.allocator); + bArr.PushBack(b.funcsize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, function_starts_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.funcoff = bArr[2].GetUint(); + b.funcsize = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, std::vector<section_64> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + std::string sectNameStr; + char sectName[16]; + memcpy(sectName, s.sectname, 16); + sectName[15] = 0; + sectNameStr = std::string(sectName); + sArr.PushBack( + rapidjson::Value(sectNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + std::string segNameStr; + char segName[16]; + memcpy(segName, s.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + sArr.PushBack( + rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + sArr.PushBack(s.addr, m_activeContext.allocator); + sArr.PushBack(s.size, m_activeContext.allocator); + sArr.PushBack(s.offset, m_activeContext.allocator); + sArr.PushBack(s.align, m_activeContext.allocator); + sArr.PushBack(s.reloff, m_activeContext.allocator); + sArr.PushBack(s.nreloc, m_activeContext.allocator); + sArr.PushBack(s.flags, m_activeContext.allocator); + sArr.PushBack(s.reserved1, m_activeContext.allocator); + sArr.PushBack(s.reserved2, m_activeContext.allocator); + sArr.PushBack(s.reserved3, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<section_64>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + section_64 sec; + auto s2 = s.GetArray(); + std::string sectNameStr = s2[0].GetString(); + memset(sec.sectname, 0, 16); + memcpy(sec.sectname, sectNameStr.c_str(), sectNameStr.size()); + std::string segNameStr = s2[1].GetString(); + memset(sec.segname, 0, 16); + memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); + sec.addr = s2[2].GetUint64(); + sec.size = s2[3].GetUint64(); + sec.offset = s2[4].GetUint(); + sec.align = s2[5].GetUint(); + sec.reloff = s2[6].GetUint(); + sec.nreloc = s2[7].GetUint(); + sec.flags = s2[8].GetUint(); + sec.reserved1 = s2[9].GetUint(); + sec.reserved2 = s2[10].GetUint(); + sec.reserved3 = s2[11].GetUint(); + b.push_back(sec); + } + } + + void Serialize(const std::string& name, linkedit_data_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.dataoff, m_activeContext.allocator); + bArr.PushBack(b.datasize, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, linkedit_data_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.dataoff = bArr[2].GetUint(); + b.datasize = bArr[3].GetUint(); + } + + void Serialize(const std::string& name, segment_command_64 b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + std::string segNameStr; + char segName[16]; + memcpy(segName, b.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + bArr.PushBack(rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + bArr.PushBack(b.vmaddr, m_activeContext.allocator); + bArr.PushBack(b.vmsize, m_activeContext.allocator); + bArr.PushBack(b.fileoff, m_activeContext.allocator); + bArr.PushBack(b.filesize, m_activeContext.allocator); + bArr.PushBack(b.maxprot, m_activeContext.allocator); + bArr.PushBack(b.initprot, m_activeContext.allocator); + bArr.PushBack(b.nsects, m_activeContext.allocator); + bArr.PushBack(b.flags, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, segment_command_64& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + std::string segNameStr = bArr[0].GetString(); + memset(b.segname, 0, 16); + memcpy(b.segname, segNameStr.c_str(), segNameStr.size()); + b.vmaddr = bArr[1].GetUint64(); + b.vmsize = bArr[2].GetUint64(); + b.fileoff = bArr[3].GetUint64(); + b.filesize = bArr[4].GetUint64(); + b.maxprot = bArr[5].GetUint(); + b.initprot = bArr[6].GetUint(); + b.nsects = bArr[7].GetUint(); + b.flags = bArr[8].GetUint(); + } + + void Serialize(const std::string& name, std::vector<segment_command_64> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + std::string segNameStr; + char segName[16]; + memcpy(segName, s.segname, 16); + segName[15] = 0; + segNameStr = std::string(segName); + sArr.PushBack( + rapidjson::Value(segNameStr.c_str(), m_activeContext.allocator), m_activeContext.allocator); + sArr.PushBack(s.vmaddr, m_activeContext.allocator); + sArr.PushBack(s.vmsize, m_activeContext.allocator); + sArr.PushBack(s.fileoff, m_activeContext.allocator); + sArr.PushBack(s.filesize, m_activeContext.allocator); + sArr.PushBack(s.maxprot, m_activeContext.allocator); + sArr.PushBack(s.initprot, m_activeContext.allocator); + sArr.PushBack(s.nsects, m_activeContext.allocator); + sArr.PushBack(s.flags, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<segment_command_64>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + segment_command_64 sec; + auto s2 = s.GetArray(); + std::string segNameStr = s2[0].GetString(); + memset(sec.segname, 0, 16); + memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); + sec.vmaddr = s2[1].GetUint64(); + sec.vmsize = s2[2].GetUint64(); + sec.fileoff = s2[3].GetUint64(); + sec.filesize = s2[4].GetUint64(); + sec.maxprot = s2[5].GetUint(); + sec.initprot = s2[6].GetUint(); + sec.nsects = s2[7].GetUint(); + sec.flags = s2[8].GetUint(); + b.push_back(sec); + } + } + + void Serialize(const std::string& name, build_version_command b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + bArr.PushBack(b.cmd, m_activeContext.allocator); + bArr.PushBack(b.cmdsize, m_activeContext.allocator); + bArr.PushBack(b.platform, m_activeContext.allocator); + bArr.PushBack(b.minos, m_activeContext.allocator); + bArr.PushBack(b.sdk, m_activeContext.allocator); + bArr.PushBack(b.ntools, m_activeContext.allocator); + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, build_version_command& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + b.cmd = bArr[0].GetUint(); + b.cmdsize = bArr[1].GetUint(); + b.platform = bArr[2].GetUint(); + b.minos = bArr[3].GetUint(); + b.sdk = bArr[4].GetUint(); + b.ntools = bArr[5].GetUint(); + } + + void Serialize(const std::string& name, std::vector<build_tool_version> b) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value bArr(rapidjson::kArrayType); + for (auto& s : b) + { + rapidjson::Value sArr(rapidjson::kArrayType); + sArr.PushBack(s.tool, m_activeContext.allocator); + sArr.PushBack(s.version, m_activeContext.allocator); + bArr.PushBack(sArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, bArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector<build_tool_version>& b) + { + auto bArr = m_activeDeserContext.doc[name.c_str()].GetArray(); + for (auto& s : bArr) + { + build_tool_version sec; + auto s2 = s.GetArray(); + sec.tool = s2[0].GetUint(); + sec.version = s2[1].GetUint(); + b.push_back(sec); + } + } + + void Store() override + { + MSS(textBase); + MSS(loadCommandOffset); + MSS_SUBCLASS(ident); + MSS(identifierPrefix); + MSS(installName); + MSS(entryPoints); + MSS(m_entryPoints); + MSS_SUBCLASS(symtab); + MSS_SUBCLASS(dysymtab); + MSS_SUBCLASS(dyldInfo); + // MSS_SUBCLASS(routines64); + MSS_SUBCLASS(functionStarts); + MSS_SUBCLASS(moduleInitSections); + MSS_SUBCLASS(exportTrie); + MSS_SUBCLASS(chainedFixups); + MSS(relocationBase); + MSS_SUBCLASS(segments); + MSS_SUBCLASS(linkeditSegment); + MSS_SUBCLASS(sections); + MSS(sectionNames); + MSS_SUBCLASS(symbolStubSections); + MSS_SUBCLASS(symbolPointerSections); + MSS(dylibs); + MSS_SUBCLASS(buildVersion); + MSS_SUBCLASS(buildToolVersions); + MSS(linkeditPresent); + MSS(exportTriePath); + MSS(dysymPresent); + MSS(dyldInfoPresent); + MSS(exportTriePresent); + MSS(chainedFixupsPresent); + MSS(routinesPresent); + MSS(functionStartsPresent); + MSS(relocatable); + } + void Load() override + { + MSL(textBase); + MSL(loadCommandOffset); + MSL_SUBCLASS(ident); + MSL(identifierPrefix); + MSL(installName); + MSL(entryPoints); + MSL(m_entryPoints); + MSL_SUBCLASS(symtab); + MSL_SUBCLASS(dysymtab); + MSL_SUBCLASS(dyldInfo); + // MSL_SUBCLASS(routines64); // FIXME CRASH but also do we even use this? + MSL_SUBCLASS(functionStarts); + MSL_SUBCLASS(moduleInitSections); + MSL_SUBCLASS(exportTrie); + MSL_SUBCLASS(chainedFixups); + MSL(relocationBase); + MSL_SUBCLASS(segments); + MSL_SUBCLASS(linkeditSegment); + MSL_SUBCLASS(sections); + MSL(sectionNames); + MSL_SUBCLASS(symbolStubSections); + MSL_SUBCLASS(symbolPointerSections); + MSL(dylibs); + MSL_SUBCLASS(buildVersion); + MSL_SUBCLASS(buildToolVersions); + MSL(linkeditPresent); + MSL(exportTriePath); + MSL(dysymPresent); + MSL(dyldInfoPresent); + MSL(exportTriePresent); + MSL(chainedFixupsPresent); + // MSL(routinesPresent); + MSL(functionStartsPresent); + MSL(relocatable); + } + }; + + + struct MappingInfo + { + std::shared_ptr<MMappedFileAccessor> file; + dyld_cache_mapping_info mappingInfo; + uint32_t slideInfoVersion; + dyld_cache_slide_info_v2 slideInfoV2; + dyld_cache_slide_info_v3 slideInfoV3; + dyld_cache_slide_info5 slideInfoV5; + }; + + + class ScopedVMMapSession; + + static std::atomic<uint64_t> sharedCacheReferences = 0; + + class SharedCache : public MetadataSerializable + { + IMPLEMENT_SHAREDCACHE_API_OBJECT(BNSharedCache); + + std::atomic<int> m_refs = 0; + + public: + virtual void AddRef() { m_refs.fetch_add(1); } + + virtual void Release() + { + // undo actions will lock a file lock we hold and then wait for main thread + // so we need to release the ref later. + WorkerPriorityEnqueue([this]() { + if (m_refs.fetch_sub(1) == 1) + delete this; + }); + } + + virtual void AddAPIRef() { AddRef(); } + + virtual void ReleaseAPIRef() { Release(); } + + public: + enum SharedCacheFormat + { + RegularCacheFormat, + SplitCacheFormat, + LargeCacheFormat, + iOS16CacheFormat, + }; + + void Store() override + { + MSS(m_viewState); + MSS_CAST(m_cacheFormat, uint8_t); + MSS(m_imageStarts); + MSS(m_baseFilePath); + rapidjson::Value headers(rapidjson::kArrayType); + for (auto [k, v] : m_headers) + { + headers.PushBack(v.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("headers", headers, m_activeContext.allocator); + // std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>>> m_exportInfos + rapidjson::Value exportInfos(rapidjson::kArrayType); + for (auto& exportInfo : m_exportInfos) + { + rapidjson::Value exportInfoArr(rapidjson::kArrayType); + for (auto& exportInfoPair : exportInfo.second) + { + rapidjson::Value exportInfoPairArr(rapidjson::kArrayType); + exportInfoPairArr.PushBack(exportInfoPair.first, m_activeContext.allocator); + exportInfoPairArr.PushBack(exportInfoPair.second.first, m_activeContext.allocator); + exportInfoPairArr.PushBack( + rapidjson::Value(exportInfoPair.second.second.c_str(), m_activeContext.allocator), + m_activeContext.allocator); + exportInfoArr.PushBack(exportInfoPairArr, m_activeContext.allocator); + } + exportInfoArr.PushBack(exportInfoArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember("exportInfos", exportInfos, m_activeContext.allocator); + // std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>>> m_symbolInfos + rapidjson::Value symbolInfos(rapidjson::kArrayType); + for (auto& symbolInfo : m_symbolInfos) + { + rapidjson::Value symbolInfoArr(rapidjson::kArrayType); + for (auto& symbolInfoPair : symbolInfo.second) + { + rapidjson::Value symbolInfoPairArr(rapidjson::kArrayType); + symbolInfoPairArr.PushBack(symbolInfoPair.first, m_activeContext.allocator); + symbolInfoPairArr.PushBack(symbolInfoPair.second.first, m_activeContext.allocator); + symbolInfoPairArr.PushBack( + rapidjson::Value(symbolInfoPair.second.second.c_str(), m_activeContext.allocator), + m_activeContext.allocator); + symbolInfoArr.PushBack(symbolInfoPairArr, m_activeContext.allocator); + } + symbolInfoArr.PushBack(symbolInfoArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember("symbolInfos", symbolInfos, m_activeContext.allocator); + + rapidjson::Value backingCaches(rapidjson::kArrayType); + for (auto bc : m_backingCaches) + { + backingCaches.PushBack(bc.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("backingCaches", backingCaches, m_activeContext.allocator); + rapidjson::Value stubIslands(rapidjson::kArrayType); + for (auto si : m_stubIslandRegions) + { + stubIslands.PushBack(si.AsDocument(), m_activeContext.allocator); + } + rapidjson::Value images(rapidjson::kArrayType); + for (auto img : m_images) + { + images.PushBack(img.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("images", images, m_activeContext.allocator); + rapidjson::Value regionsMappedIntoMemory(rapidjson::kArrayType); + for (auto r : m_regionsMappedIntoMemory) + { + regionsMappedIntoMemory.PushBack(r.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("regionsMappedIntoMemory", regionsMappedIntoMemory, m_activeContext.allocator); + m_activeContext.doc.AddMember("stubIslands", stubIslands, m_activeContext.allocator); + rapidjson::Value dyldDataSections(rapidjson::kArrayType); + for (auto si : m_dyldDataRegions) + { + dyldDataSections.PushBack(si.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("dyldDataSections", dyldDataSections, m_activeContext.allocator); + rapidjson::Value nonImageRegions(rapidjson::kArrayType); + for (auto si : m_nonImageRegions) + { + nonImageRegions.PushBack(si.AsDocument(), m_activeContext.allocator); + } + m_activeContext.doc.AddMember("nonImageRegions", nonImageRegions, m_activeContext.allocator); + } + void Load() override + { + m_viewState = MSL_CAST(m_viewState, uint8_t, DSCViewState); + m_cacheFormat = MSL_CAST(m_cacheFormat, uint8_t, SharedCacheFormat); + m_headers.clear(); + for (auto& startAndHeader : m_activeDeserContext.doc["headers"].GetArray()) + { + SharedCacheMachOHeader header; + header.LoadFromValue(startAndHeader); + m_headers[header.textBase] = header; + } + MSL(m_imageStarts); + MSL(m_baseFilePath); + m_exportInfos.clear(); + for (auto& exportInfo : m_activeDeserContext.doc["exportInfos"].GetArray()) + { + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> exportInfoVec; + for (auto& exportInfoPair : exportInfo.GetArray()) + { + exportInfoVec.push_back({exportInfoPair[0].GetUint64(), + {(BNSymbolType)exportInfoPair[1].GetUint(), exportInfoPair[2].GetString()}}); + } + m_exportInfos[exportInfo[0].GetUint64()] = exportInfoVec; + } + m_symbolInfos.clear(); + for (auto& symbolInfo : m_activeDeserContext.doc["symbolInfos"].GetArray()) + { + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> symbolInfoVec; + for (auto& symbolInfoPair : symbolInfo.GetArray()) + { + symbolInfoVec.push_back({symbolInfoPair[0].GetUint64(), + {(BNSymbolType)symbolInfoPair[1].GetUint(), symbolInfoPair[2].GetString()}}); + } + m_symbolInfos[symbolInfo[0].GetUint64()] = symbolInfoVec; + } + m_backingCaches.clear(); + for (auto& bcV : m_activeDeserContext.doc["backingCaches"].GetArray()) + { + BackingCache bc; + bc.LoadFromValue(bcV); + m_backingCaches.push_back(bc); + } + m_images.clear(); + for (auto& imgV : m_activeDeserContext.doc["images"].GetArray()) + { + CacheImage img; + img.LoadFromValue(imgV); + m_images.push_back(img); + } + m_regionsMappedIntoMemory.clear(); + for (auto& rV : m_activeDeserContext.doc["regionsMappedIntoMemory"].GetArray()) + { + MemoryRegion r; + r.LoadFromValue(rV); + m_regionsMappedIntoMemory.push_back(r); + } + m_stubIslandRegions.clear(); + for (auto& siV : m_activeDeserContext.doc["stubIslands"].GetArray()) + { + MemoryRegion si; + si.LoadFromValue(siV); + m_stubIslandRegions.push_back(si); + } + m_dyldDataRegions.clear(); + for (auto& siV : m_activeDeserContext.doc["dyldDataSections"].GetArray()) + { + MemoryRegion si; + si.LoadFromValue(siV); + m_dyldDataRegions.push_back(si); + } + m_nonImageRegions.clear(); + for (auto& siV : m_activeDeserContext.doc["nonImageRegions"].GetArray()) + { + MemoryRegion si; + si.LoadFromValue(siV); + m_nonImageRegions.push_back(si); + } + } + + private: + Ref<Logger> m_logger; + /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */ + + // Updated as the view is loaded further, more images are added, etc + DSCViewState m_viewState; + std::unordered_map<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>> + m_exportInfos; + std::unordered_map<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>> + m_symbolInfos; + // --- + + // Serialized once by PerformInitialLoad and available after m_viewState == Loaded + std::string m_baseFilePath; + SharedCacheFormat m_cacheFormat; + + std::unordered_map<std::string, uint64_t> m_imageStarts; + std::unordered_map<uint64_t, SharedCacheMachOHeader> m_headers; + + std::vector<CacheImage> m_images; + + std::vector<MemoryRegion> m_regionsMappedIntoMemory; + + std::vector<BackingCache> m_backingCaches; + + std::vector<MemoryRegion> m_stubIslandRegions; + std::vector<MemoryRegion> m_dyldDataRegions; + std::vector<MemoryRegion> m_nonImageRegions; + + /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */ + + /* API VIEW START */ + BinaryNinja::Ref<BinaryNinja::BinaryView> m_dscView; + /* API VIEW END */ + + private: + void PerformInitialLoad(); + void DeserializeFromRawView(); + + public: + std::shared_ptr<VM> GetVMMap(bool mapPages = true); + + static SharedCache* GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); + static uint64_t FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); + bool SaveToDSCView(); + + void ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file); + std::optional<uint64_t> GetImageStart(std::string installName); + std::optional<SharedCacheMachOHeader> HeaderForAddress(uint64_t); + bool LoadImageWithInstallName(std::string installName); + bool LoadSectionAtAddress(uint64_t address); + bool LoadImageContainingAddress(uint64_t address); + std::string NameForAddress(uint64_t address); + std::string ImageNameForAddress(uint64_t address); + std::vector<std::string> GetAvailableImages(); + + std::vector<MemoryRegion> GetMappedRegions() const; + + std::vector<std::pair<std::string, Ref<Symbol>>> LoadAllSymbolsAndWait(); + + std::unordered_map<std::string, uint64_t> AllImageStarts() const { return m_imageStarts; } + std::unordered_map<uint64_t, SharedCacheMachOHeader> AllImageHeaders() const { return m_headers; } + + std::string SerializedImageHeaderForAddress(uint64_t address); + std::string SerializedImageHeaderForName(std::string name); + + void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); + + std::vector<BackingCache> BackingCaches() const { return m_backingCaches; } + + DSCViewState State() const { return m_viewState; } + + explicit SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> rawView); + ~SharedCache() override; + + std::optional<SharedCacheMachOHeader> LoadHeaderForAddress( + std::shared_ptr<VM> vm, uint64_t address, std::string installName); + void InitializeHeader( + Ref<BinaryView> view, VM* vm, SharedCacheMachOHeader header, std::vector<MemoryRegion*> regionsToLoad); + void ReadExportNode(std::vector<Ref<Symbol>>& symbolList, SharedCacheMachOHeader& header, DataBuffer& buffer, uint64_t textBase, + const std::string& currentText, size_t cursor, uint32_t endGuard); + std::vector<Ref<Symbol>> ParseExportTrie( + std::shared_ptr<MMappedFileAccessor> linkeditFile, SharedCacheMachOHeader header); + }; + + +} + +void InitDSCViewType(); + +#endif //SHAREDCACHE_SHAREDCACHE_H + diff --git a/view/sharedcache/core/VM.cpp b/view/sharedcache/core/VM.cpp new file mode 100644 index 00000000..7fe01471 --- /dev/null +++ b/view/sharedcache/core/VM.cpp @@ -0,0 +1,793 @@ +// +// Created by kat on 5/23/23. +// + +/* + This is the cross-plat file buffering logic used for SharedCache processing. + This is used for reading large amounts of large files in a performant manner. + + Here be no dragons, but this code is very complex, beware. + + We in _all_ cases memory map the files, as we hardly ever need more than a few pages per file for most intensive operations. + + Memory Map Implementation: + Of interest is that on several platforms we have to account for very low file pointer limits, and when mapping + 40+ files, these are trivially reachable. + + We handle this with a "SelfAllocatingWeakPtr": + - Calling .lock() ALWAYS delivers a shared_ptr guaranteed to stay valid. This may block waiting for a free pointer + - As soon as that lock is released, that file pointer MAY be freed if another thread wants to open a new one, and we are at our limit. + - Calling .lock() again on this same theoretical object will then wait for another file pointer to be freeable. + + VM Implementation: + + + Since the caches we're operating on are by nature page aligned, we are able to use nice optimizations under the hood to translate + "VM Addresses" to their actual in-memory counterparts. + + We do this with a page table, which is a map of page -> file offset. + + We also implement a "VMReader" here, which is a drop-in replacement for BinaryReader that operates on the VM. + see "ObjC.cpp" for where this is used. + +*/ + + +#include "VM.h" +#include <utility> +#include <memory> +#include <cstring> +#include <stdio.h> +#include <binaryninjaapi.h> + +#ifdef _MSC_VER + #include <windows.h> +#else + #include <sys/mman.h> + #include <fcntl.h> + #include <stdlib.h> + #include <sys/resource.h> +#endif + +void VMShutdown() +{ + std::unique_lock<std::mutex> lock2(fileAccessorsMutex); + std::unique_lock<std::mutex> lock(fileAccessorDequeMutex); + + // This will trigger the deallocation logic for these. + // It is background threaded to avoid a deadlock on exit. + fileAccessorReferenceHolder.clear(); + fileAccessors.clear(); +} + +void MMAP::Map() +{ + if (mapped) + return; +#ifdef _MSC_VER + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile, &fileSize)) + { + // Handle error + CloseHandle(hFile); + return; + } + len = static_cast<size_t>(fileSize.QuadPart); + + HANDLE hMapping = CreateFileMapping( + hFile, // file handle + NULL, // security attributes + PAGE_WRITECOPY, // protection + 0, // maximum size (high-order DWORD) + 0, // maximum size (low-order DWORD) + NULL); // name of the mapping object + + if (hMapping == NULL) + { + // Handle error + CloseHandle(hFile); + return; + } + + _mmap = MapViewOfFile( + hMapping, // handle to the file mapping object + FILE_MAP_COPY, // desired access + 0, // file offset (high-order DWORD) + 0, // file offset (low-order DWORD) + 0); // number of bytes to map (0 = entire file) + + if (_mmap == nullptr) + { + // Handle error + CloseHandle(hMapping); + CloseHandle(hFile); + return; + } + + mapped = true; + + CloseHandle(hMapping); + CloseHandle(hFile); + +#else + fseek(fd, 0L, SEEK_END); + len = ftell(fd); + fseek(fd, 0L, SEEK_SET); + + _mmap = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u); + if (_mmap == MAP_FAILED) + { + // Handle error + return; + } + + mapped = true; +#endif +} + +void MMAP::Unmap() +{ +#ifdef _MSC_VER + if (_mmap) + { + UnmapViewOfFile(_mmap); + mapped = false; + } +#else + if (mapped) + { + munmap(_mmap, len); + mapped = false; + } +#endif +} + + +std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>> MMappedFileAccessor::Open(const uint64_t sessionID, const std::string &path, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) +{ + std::scoped_lock<std::mutex> lock(fileAccessorsMutex); + if (fileAccessors.count(path) == 0) + { + auto fileAcccessor = std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>>(new SelfAllocatingWeakPtr<MMappedFileAccessor>( + // Allocator logic for the SelfAllocatingWeakPtr + [path=path, sessionID=sessionID](){ + std::unique_lock<std::mutex> _lock(fileAccessorDequeMutex); + + // Iterate through held references and start removing them until we can get a file pointer + // FIXME: This could clear all currently used file pointers and still not get one. FIX! + // We should probably use a condition variable here to wait for a file pointer to be released!!! + for (auto& [_, fileAccessorDeque] : fileAccessorReferenceHolder) + { + if (fileAccessorSemaphore.try_acquire()) + break; + fileAccessorDeque.pop_front(); + } + + mmapCount++; + _lock.unlock(); + auto accessor = std::shared_ptr<MMappedFileAccessor>(new MMappedFileAccessor(path), [](MMappedFileAccessor* accessor){ + // worker thread or we can deadlock on exit here. + BinaryNinja::WorkerEnqueue([accessor](){ + fileAccessorSemaphore.release(); + mmapCount--; + if (fileAccessors.count(accessor->m_path)) + { + std::scoped_lock<std::mutex> lock(fileAccessorsMutex); + fileAccessors.erase(accessor->m_path); + } + delete accessor; + }, "MMappedFileAccessor Destructor"); + }); + _lock.lock(); + // If some background thread has managed to try and open a file when the BV was already closed, + // we can still give them the file they want so they dont crash, but as soon as they let go it's gone. + if (!blockedSessionIDs.count(sessionID)) + fileAccessorReferenceHolder[sessionID].push_back(accessor); + return accessor; + }, + [postAllocationRoutine=postAllocationRoutine](std::shared_ptr<MMappedFileAccessor> accessor){ + if (postAllocationRoutine) + postAllocationRoutine(accessor); + })); + fileAccessors.insert_or_assign(path, fileAcccessor); + } + return fileAccessors.at(path); +} + + +void MMappedFileAccessor::CloseAll(const uint64_t sessionID) +{ + blockedSessionIDs.insert(sessionID); + if (fileAccessorReferenceHolder.count(sessionID) == 0) + return; + fileAccessorReferenceHolder.erase(sessionID); +} + + +void MMappedFileAccessor::InitialVMSetup() +{ + // check for BN_SHAREDCACHE_FP_MAX + // if it exists, set maxFPLimit to that value + maxFPLimit = 0; + if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env) + { + // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh. + maxFPLimit = strtoull(env, nullptr, 0); + if (maxFPLimit < 10) + { + BinaryNinja::LogWarn("BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis on SharedCache Binaries."); + } + if (maxFPLimit == 0) + { + BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1"); + maxFPLimit = 1; + } + } + else + { + if (maxFPLimit < 10) { +#ifdef _MSC_VER + // It is not _super_ clear what the max file pointer limit is on windows, + // but to my understanding, we are using the windows API to map files, + // so we should have at least 2^24; + // kind of funny to me that windows would be the most effecient OS to + // parallelize sharedcache processing on in terms of FP usage concerns + maxFPLimit = 0x1000000; +#else + // unix in comparison will likely have a very small limit, especially mac, necessitating all of this consideration + struct rlimit rlim; + getrlimit(RLIMIT_NOFILE, &rlim); + maxFPLimit = rlim.rlim_cur / 2; +#endif + } + } + BinaryNinja::LogInfo("SharedCache processing initialized with a max file pointer limit of 0x%llx", maxFPLimit); + fileAccessorSemaphore.set_count(maxFPLimit); +} + + +MMappedFileAccessor::MMappedFileAccessor(const std::string& path) : m_path(path) +{ +#ifdef _MSC_VER + m_mmap.hFile = CreateFile( + path.c_str(), // file name + GENERIC_READ, // desired access (read-only) + FILE_SHARE_READ, // share mode + NULL, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flags and attributes + NULL); // template file + + if (m_mmap.hFile == INVALID_HANDLE_VALUE) + { + // BNLogInfo("Couldn't read file at %s", path.c_str()); + throw MissingFileException(); + } + +#else +#ifdef ABORT_FAILURES + if (path.empty()) + { + cerr << "Path is empty." << endl; + abort(); + } +#endif + m_mmap.fd = fopen(path.c_str(), "r"); + if (m_mmap.fd == nullptr) + { + BNLogError("Serious VM Error: Couldn't read file at %s", path.c_str()); + +#ifndef _MSC_VER + try { + throw BinaryNinja::ExceptionWithStackTrace("Unable to Read file"); + } + catch (ExceptionWithStackTrace &ex) + { + BNLogError("%s", ex.m_stackTrace.c_str()); + BNLogError("Error: %d (%s)", errno, strerror(errno)); + } +#endif + throw MissingFileException(); + } +#endif + + m_mmap.Map(); +} + +MMappedFileAccessor::~MMappedFileAccessor() +{ + // BNLogInfo("Unmapping %s", m_path.c_str()); + m_mmap.Unmap(); + +#ifdef _MSC_VER + if (m_mmap.hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_mmap.hFile); + } +#else + if (m_mmap.fd != nullptr) + { + fclose(m_mmap.fd); + } +#endif +} + +void MMappedFileAccessor::WritePointer(size_t address, size_t pointer) +{ + ((size_t*)(&((uint8_t*)m_mmap._mmap)[address]))[0] = pointer; +} + +std::string MMappedFileAccessor::ReadNullTermString(size_t address) +{ + if (address > m_mmap.len) + return ""; + size_t max = m_mmap.len; + size_t i = address; + std::string str; + str.reserve(140); + while (i < max) + { + char c = ((char*)(&((uint8_t*)m_mmap._mmap)[i]))[0]; + if (c == 0) + break; + str += c; + i++; + } + str.shrink_to_fit(); + return str; +} + +uint8_t MMappedFileAccessor::ReadUChar(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((uint8_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +int8_t MMappedFileAccessor::ReadChar(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((int8_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +uint16_t MMappedFileAccessor::ReadUShort(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((uint16_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +int16_t MMappedFileAccessor::ReadShort(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((int16_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +uint32_t MMappedFileAccessor::ReadUInt32(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((uint32_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +int32_t MMappedFileAccessor::ReadInt32(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((int32_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +uint64_t MMappedFileAccessor::ReadULong(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((uint64_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +int64_t MMappedFileAccessor::ReadLong(size_t address) +{ + if (address > m_mmap.len) + throw MappingReadException(); + return ((int64_t*)(&(((uint8_t*)m_mmap._mmap)[address])))[0]; +} + +BinaryNinja::DataBuffer* MMappedFileAccessor::ReadBuffer(size_t address, size_t length) +{ + if (address > m_mmap.len) + throw MappingReadException(); + if (address + length > m_mmap.len) + throw MappingReadException(); + void* data = (void*)(&(((uint8_t*)m_mmap._mmap)[address])); + void* dataCopy = malloc(length); + memcpy(dataCopy, data, length); + return new BinaryNinja::DataBuffer(dataCopy, length); +} + +void MMappedFileAccessor::Read(void* dest, size_t address, size_t length) +{ + if (address > m_mmap.len) + throw MappingReadException(); + if (address + length > m_mmap.len) + throw MappingReadException(); + memcpy(dest, (void*)&(((uint8_t*)m_mmap._mmap)[address]), length); +} + + +VM::VM(size_t pageSize, bool safe) : m_pageSize(pageSize), m_safe(safe) +{ + unsigned bits, var = (m_pageSize - 1 < 0) ? -(m_pageSize - 1) : m_pageSize - 1; + for (bits = 0; var != 0; ++bits) + var >>= 1; + m_pageSizeBits = bits; +} + +VM::~VM() +{ +} + + +void VM::MapPages(uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, std::string filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) +{ + // The mappings provided for shared caches will always be page aligned. + // We can use this to our advantage and gain considerable performance via page tables. + // This could probably be sped up if c++ were avoided? + // We want to create a map of page -> file offset + + if (vm_address % m_pageSize != 0 || size % m_pageSize != 0) + { + throw MappingPageAlignmentException(); + } + + size_t pagesRemainingCount = size / m_pageSize; + for (size_t i = 0; i < size; i += m_pageSize) + { + // Our pages will be delimited by shifting off the page size + // So, 0x12345000 will become 0x12345 (assuming m_pageSize is 0x1000) + auto page = (vm_address + (i)) >> m_pageSizeBits; + if (m_map.count(page) != 0) + { + if (m_safe) + { + BNLogWarn("Remapping page 0x%lx (i == 0x%lx) (a: 0x%zx, f: 0x%zx)", page, i, vm_address, fileoff); + throw MappingCollisionException(); + } + } + m_map.insert_or_assign(page, PageMapping(filePath, MMappedFileAccessor::Open(sessionID, filePath, postAllocationRoutine), i + fileoff)); + } +} + +std::pair<PageMapping, size_t> VM::MappingAtAddress(size_t address) +{ + // Get the page (e.g. 0x12345678 will become 0x12345 on 0x1000 aligned caches) + auto page = address >> m_pageSizeBits; + if (auto f = m_map.find(page); f != m_map.end()) + { + // The PageMapping object returned contains the page, and more importantly, the file pointer (there can be + // multiple in newer caches) This is relevant for reading out the data in the rest of this file. The second item + // in this pair is created by taking the fileOffset (which will be a page but with the trailing bits (e.g. + // 0x12345000) + // and will add the "extra" bits lopped off when determining the page. (e.g. 0x12345678 -> 0x678) + return {f->second, f->second.fileOffset + (address & (m_pageSize - 1))}; + } + + throw MappingReadException(); +} + + +bool VM::AddressIsMapped(uint64_t address) +{ + try + { + MappingAtAddress(address); + return true; + } + catch (...) + {} + return false; +} + + +uint64_t VMReader::ReadULEB128(size_t limit) +{ + uint64_t result = 0; + int bit = 0; + auto mapping = m_vm->MappingAtAddress(m_cursor); + auto fileCursor = mapping.second; + auto fileLimit = fileCursor + (limit - m_cursor); + auto fa = mapping.first.fileAccessor->lock(); + auto* fileBuff = (uint8_t*)fa->Data(); + do + { + if (fileCursor >= fileLimit) + return -1; + uint64_t slice = ((uint64_t*)&((fileBuff)[fileCursor]))[0] & 0x7f; + if (bit > 63) + return -1; + else + { + result |= (slice << bit); + bit += 7; + } + } while (((uint64_t*)&(fileBuff[fileCursor++]))[0] & 0x80); + fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer + return result; +} + + +int64_t VMReader::ReadSLEB128(size_t limit) +{ + uint8_t cur; + int64_t value = 0; + size_t shift = 0; + + auto mapping = m_vm->MappingAtAddress(m_cursor); + auto fileCursor = mapping.second; + auto fileLimit = fileCursor + (limit - m_cursor); + auto fa = mapping.first.fileAccessor->lock(); + auto* fileBuff = (uint8_t*)fa->Data(); + + while (fileCursor < fileLimit) + { + cur = ((uint64_t*)&((fileBuff)[fileCursor]))[0]; + fileCursor++; + value |= (cur & 0x7f) << shift; + shift += 7; + if ((cur & 0x80) == 0) + break; + } + value = (value << (64 - shift)) >> (64 - shift); + fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer + return value; +} + +std::string VM::ReadNullTermString(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); +} + +uint8_t VM::ReadUChar(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); +} + +int8_t VM::ReadChar(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); +} + +uint16_t VM::ReadUShort(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); +} + +int16_t VM::ReadShort(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); +} + +uint32_t VM::ReadUInt32(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); +} + +int32_t VM::ReadInt32(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); +} + +uint64_t VM::ReadULong(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); +} + +int64_t VM::ReadLong(size_t address) +{ + auto mapping = MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); +} + +BinaryNinja::DataBuffer* VM::ReadBuffer(size_t addr, size_t length) +{ + auto mapping = MappingAtAddress(addr); + return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); +} + + +void VM::Read(void* dest, size_t addr, size_t length) +{ + auto mapping = MappingAtAddress(addr); + mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); +} + +VMReader::VMReader(std::shared_ptr<VM> vm, size_t addressSize) : m_vm(vm), m_cursor(0), m_addressSize(addressSize) {} + + +void VMReader::Seek(size_t address) +{ + m_cursor = address; +} + +void VMReader::SeekRelative(size_t offset) +{ + m_cursor += offset; +} + +std::string VMReader::ReadCString(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); +} + +uint8_t VMReader::ReadUChar(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 1; + return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); +} + +int8_t VMReader::ReadChar(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 1; + return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); +} + +uint16_t VMReader::ReadUShort(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 2; + return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); +} + +int16_t VMReader::ReadShort(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 2; + return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); +} + +uint32_t VMReader::ReadUInt32(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 4; + return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); +} + +int32_t VMReader::ReadInt32(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 4; + return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); +} + +uint64_t VMReader::ReadULong(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 8; + return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); +} + +int64_t VMReader::ReadLong(size_t address) +{ + auto mapping = m_vm->MappingAtAddress(address); + m_cursor = address + 8; + return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); +} + + +size_t VMReader::ReadPointer(size_t address) +{ + if (m_addressSize == 8) + return ReadULong(address); + else if (m_addressSize == 4) + return ReadUInt32(address); + + // no idea what horrible arch we have, should probably die here. + return 0; +} + + +size_t VMReader::ReadPointer() +{ + if (m_addressSize == 8) + return Read64(); + else if (m_addressSize == 4) + return Read32(); + + return 0; +} + +BinaryNinja::DataBuffer* VMReader::ReadBuffer(size_t length) +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += length; + return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); +} + +BinaryNinja::DataBuffer* VMReader::ReadBuffer(size_t addr, size_t length) +{ + auto mapping = m_vm->MappingAtAddress(addr); + m_cursor = addr + length; + return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); +} + +void VMReader::Read(void* dest, size_t length) +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += length; + mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); +} + +void VMReader::Read(void* dest, size_t addr, size_t length) +{ + auto mapping = m_vm->MappingAtAddress(addr); + m_cursor = addr + length; + mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); +} + + +uint8_t VMReader::Read8() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 1; + return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); +} + +int8_t VMReader::ReadS8() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 1; + return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); +} + +uint16_t VMReader::Read16() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 2; + return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); +} + +int16_t VMReader::ReadS16() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 2; + return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); +} + +uint32_t VMReader::Read32() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 4; + return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); +} + +int32_t VMReader::ReadS32() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 4; + return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); +} + +uint64_t VMReader::Read64() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 8; + return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); +} + +int64_t VMReader::ReadS64() +{ + auto mapping = m_vm->MappingAtAddress(m_cursor); + m_cursor += 8; + return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); +} diff --git a/view/sharedcache/core/VM.h b/view/sharedcache/core/VM.h new file mode 100644 index 00000000..d85a766d --- /dev/null +++ b/view/sharedcache/core/VM.h @@ -0,0 +1,330 @@ +// +// Created by kat on 5/23/23. +// + +#ifndef SHAREDCACHE_VM_H +#define SHAREDCACHE_VM_H +#include <binaryninjaapi.h> +#include <condition_variable> + +void VMShutdown(); + +class counting_semaphore { +public: + explicit counting_semaphore(int count = 0) : count_(count) {} + + void release(int update = 1) { + std::unique_lock<std::mutex> lock(mutex_); + count_ += update; + cv_.notify_all(); + } + + void acquire() { + std::unique_lock<std::mutex> lock(mutex_); + cv_.wait(lock, [this]() { return count_ > 0; }); + --count_; + } + + bool try_acquire() { + std::unique_lock<std::mutex> lock(mutex_); + if (count_ > 0) { + --count_; + return true; + } + return false; + } + + void set_count(int new_count) { + std::unique_lock<std::mutex> lock(mutex_); + count_ = new_count; + cv_.notify_all(); + } + +private: + std::mutex mutex_; + std::condition_variable cv_; + int count_; +}; + + +template <typename T> +class SelfAllocatingWeakPtr { +public: + SelfAllocatingWeakPtr(std::function<std::shared_ptr<T>()> allocator, std::function<void(std::shared_ptr<T>)> postAlloc) + : allocator(allocator), postAlloc(postAlloc) {} + + std::shared_ptr<T> lock() { + std::shared_ptr<T> sharedPtr = weakPtr.lock(); + if (!sharedPtr) { + sharedPtr = allocator(); + postAlloc(sharedPtr); + weakPtr = sharedPtr; + } + return sharedPtr; + } + + std::shared_ptr<T> lock_no_allocate() { + return weakPtr.lock(); + } + +private: + std::weak_ptr<T> weakPtr; // Weak reference to the object + std::function<std::shared_ptr<T>()> allocator; // Function to recreate the object + std::function<void(std::shared_ptr<T>)> postAlloc; // Function to call after the object is allocated +}; + + +class MissingFileException : public std::exception +{ + virtual const char* what() const throw() + { + return "Missing File."; + } +}; + +class MMappedFileAccessor; + +class MMAP { + friend MMappedFileAccessor; + + void *_mmap; + FILE *fd; + size_t len; + +#ifdef _MSC_VER + HANDLE hFile = INVALID_HANDLE_VALUE; // For Windows +#endif + + bool mapped = false; + + void Map(); + + void Unmap(); +}; + +static uint64_t maxFPLimit; +static std::mutex fileAccessorDequeMutex; +static std::unordered_map<uint64_t, std::deque<std::shared_ptr<MMappedFileAccessor>>> fileAccessorReferenceHolder; +static std::set<uint64_t> blockedSessionIDs; +static std::mutex fileAccessorsMutex; +static std::unordered_map<std::string, std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>>> fileAccessors; +static counting_semaphore fileAccessorSemaphore(0); + +static std::atomic<uint64_t> mmapCount = 0; + +class MMappedFileAccessor { + std::string m_path; + MMAP m_mmap; + bool m_slideInfoWasApplied = false; + +public: + MMappedFileAccessor(const std::string &path); + ~MMappedFileAccessor(); + + static std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>> Open(const uint64_t sessionID, const std::string &path, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine = nullptr); + + static void CloseAll(const uint64_t sessionID); + + static void InitialVMSetup(); + + std::string Path() const { return m_path; }; + + size_t Length() const { return m_mmap.len; }; + + void *Data() const { return m_mmap._mmap; }; + + bool SlideInfoWasApplied() const { return m_slideInfoWasApplied; } + + void SetSlideInfoWasApplied(bool slideInfoWasApplied) { m_slideInfoWasApplied = slideInfoWasApplied; } + + /** + * Writes to files are implemented for performance reasons and should be treated with utmost care + * + * They _MAY_ disappear as _soon_ as you release the lock on the this file. + * They may also NOT disappear for the lifetime of the application. + * + * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical scenario. + * + * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be used for anything else. + * + * \param address + * \param pointer + */ + void WritePointer(size_t address, size_t pointer); + + std::string ReadNullTermString(size_t address); + + uint8_t ReadUChar(size_t address); + + int8_t ReadChar(size_t address); + + uint16_t ReadUShort(size_t address); + + int16_t ReadShort(size_t address); + + uint32_t ReadUInt32(size_t address); + + int32_t ReadInt32(size_t address); + + uint64_t ReadULong(size_t address); + + int64_t ReadLong(size_t address); + + BinaryNinja::DataBuffer *ReadBuffer(size_t addr, size_t length); + + void Read(void *dest, size_t addr, size_t length); +}; + + +struct PageMapping { + std::string filePath; + std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>> fileAccessor; + size_t fileOffset; + PageMapping(std::string filePath, std::shared_ptr<SelfAllocatingWeakPtr<MMappedFileAccessor>> fileAccessor, size_t fileOffset) + : filePath(filePath), fileAccessor(fileAccessor), fileOffset(fileOffset) {} +}; + + +class VMException : public std::exception { + virtual const char *what() const throw() { + return "Generic VM Exception"; + } +}; + +class MappingPageAlignmentException : public VMException { + virtual const char *what() const throw() { + return "Tried to create a mapping not aligned to given page size"; + } +}; + +class MappingReadException : VMException { + virtual const char *what() const throw() { + return "Tried to access unmapped page"; + } +}; + +class MappingCollisionException : VMException { + virtual const char *what() const throw() { + return "Tried to remap a page"; + } +}; + +class VMReader; + + +class VM { + std::map<size_t, PageMapping> m_map; + size_t m_pageSize; + size_t m_pageSizeBits; + bool m_safe; + + friend VMReader; + +public: + + VM(size_t pageSize, bool safe = true); + + ~VM(); + + void MapPages(uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, std::string filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine); + + bool AddressIsMapped(uint64_t address); + + std::pair<PageMapping, size_t> MappingAtAddress(size_t address); + + std::string ReadNullTermString(size_t address); + + uint8_t ReadUChar(size_t address); + + int8_t ReadChar(size_t address); + + uint16_t ReadUShort(size_t address); + + int16_t ReadShort(size_t address); + + uint32_t ReadUInt32(size_t address); + + int32_t ReadInt32(size_t address); + + uint64_t ReadULong(size_t address); + + int64_t ReadLong(size_t address); + + BinaryNinja::DataBuffer *ReadBuffer(size_t addr, size_t length); + + void Read(void *dest, size_t addr, size_t length); +}; + + +class VMReader { + std::shared_ptr<VM> m_vm; + size_t m_cursor; + size_t m_addressSize; + + BNEndianness m_endianness = LittleEndian; + +public: + VMReader(std::shared_ptr<VM> vm, size_t addressSize = 8); + + void SetEndianness(BNEndianness endianness) { m_endianness = endianness; } + + BNEndianness GetEndianness() const { return m_endianness; } + + void Seek(size_t address); + + void SeekRelative(size_t offset); + + [[nodiscard]] size_t GetOffset() const { return m_cursor; } + + std::string ReadCString(size_t address); + + uint64_t ReadULEB128(size_t cursorLimit); + + int64_t ReadSLEB128(size_t cursorLimit); + + uint8_t Read8(); + + int8_t ReadS8(); + + uint16_t Read16(); + + int16_t ReadS16(); + + uint32_t Read32(); + + int32_t ReadS32(); + + uint64_t Read64(); + + int64_t ReadS64(); + + size_t ReadPointer(); + + uint8_t ReadUChar(size_t address); + + int8_t ReadChar(size_t address); + + uint16_t ReadUShort(size_t address); + + int16_t ReadShort(size_t address); + + uint32_t ReadUInt32(size_t address); + + int32_t ReadInt32(size_t address); + + uint64_t ReadULong(size_t address); + + int64_t ReadLong(size_t address); + + size_t ReadPointer(size_t address); + + BinaryNinja::DataBuffer *ReadBuffer(size_t length); + + BinaryNinja::DataBuffer *ReadBuffer(size_t addr, size_t length); + + void Read(void *dest, size_t length); + + void Read(void *dest, size_t addr, size_t length); +}; + +#endif //SHAREDCACHE_VM_H diff --git a/view/sharedcache/ui/CMakeLists.txt b/view/sharedcache/ui/CMakeLists.txt new file mode 100644 index 00000000..04e2f3cb --- /dev/null +++ b/view/sharedcache/ui/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(sharedcacheui) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) + +file(GLOB SOURCES *.cpp *.h) +list(FILTER SOURCES EXCLUDE REGEX moc_.*) +list(FILTER SOURCES EXCLUDE REGEX qrc_.*) + +add_library(sharedcacheui SHARED ${SOURCES}) + +if (VIEW_NAME) + target_compile_definitions(sharedcacheui PRIVATE VIEW_NAME="${VIEW_NAME}") +else() + error("VIEW_NAME must be defined") +endif() + +if(BN_INTERNAL_BUILD) + set_target_properties(sharedcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else() + set_target_properties(sharedcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + ) +endif() + +set_target_properties(sharedcacheui PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ) + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(sharedcacheapi INCLUDES) + +target_include_directories(sharedcacheui PRIVATE ${INCLUDES}) + +target_link_libraries(sharedcacheui sharedcacheapi sharedcache binaryninjaui Qt6::Core Qt6::Gui Qt6::Widgets) + diff --git a/view/sharedcache/ui/Plugin.cpp b/view/sharedcache/ui/Plugin.cpp new file mode 100644 index 00000000..1d52fd6a --- /dev/null +++ b/view/sharedcache/ui/Plugin.cpp @@ -0,0 +1,25 @@ +// +// Created by kat on 8/6/24. +// +#include <binaryninjaapi.h> +#include "SharedCacheUINotifications.h" +#include "dsctriage.h" + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + BN_DECLARE_UI_ABI_VERSION + + BINARYNINJAPLUGIN bool UIPluginInit() + { + UINotifications::init(); + UIAction::registerAction("Load Image by Name"); + UIAction::registerAction("Load Section by Address"); + UIAction::registerAction("Load ADDRHERE"); + UIAction::registerAction("Load IMGHERE"); + + DSCTriageViewType::Register(); + + return true; + } +}
\ No newline at end of file diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.cpp b/view/sharedcache/ui/SharedCacheBDNotifications.cpp new file mode 100644 index 00000000..f59f313c --- /dev/null +++ b/view/sharedcache/ui/SharedCacheBDNotifications.cpp @@ -0,0 +1,69 @@ +// +// Created by kat on 8/22/24. +// + +#include "SharedCacheBDNotifications.h" + + +SharedCacheBDNotifications::SharedCacheBDNotifications(Ref<BinaryView> view) + : BinaryDataNotification(FunctionUpdates | DataVariableUpdates) +{ +} + +void SharedCacheBDNotifications::OnAnalysisFunctionAdded(BinaryView* view, Function* func) +{ + // + // We just cannot do this until one of: + // "Component::AddAutoFunction" + // BinaryView::BeginIgnoredUndoActions + // some similar fix + + /* + if (view->GetTypeName() == VIEW_NAME) + { + auto sections = view->GetSectionsAt(func->GetStart()); + if (sections.size() > 0) + { + auto section = sections[0]; + auto imageName = section->GetName().substr(0, section->GetName().find("::")); + auto id = view->BeginUndoActions(); + auto comp = view->GetComponentByPath(imageName); + if (!comp) + { + comp = view->CreateComponentWithName(imageName); + } + comp.value()->AddFunction(func); + view->ForgetUndoActions(id); + } + } + */ +} + + +void SharedCacheBDNotifications::OnSectionAdded(BinaryView* data, Section* section) +{ + +} + + +void SharedCacheBDNotifications::OnDataVariableAdded(BinaryView* view, const DataVariable& var) +{ + /* + if (view->GetTypeName() == VIEW_NAME) + { + auto sections = view->GetSectionsAt(var.address); + if (sections.size() > 0) + { + auto section = sections[0]; + auto imageName = section->GetName().substr(0, section->GetName().find("::")); + auto comp = view->GetComponentByPath(imageName); + auto id = view->BeginUndoActions(); + if (!comp) + { + comp = view->CreateComponentWithName(imageName); + } + comp.value()->AddDataVariable(var); + view->ForgetUndoActions(id); + } + }*/ +} diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.h b/view/sharedcache/ui/SharedCacheBDNotifications.h new file mode 100644 index 00000000..632fca28 --- /dev/null +++ b/view/sharedcache/ui/SharedCacheBDNotifications.h @@ -0,0 +1,20 @@ +// +// Created by kat on 8/22/24. +// + +#pragma once + +#include <binaryninjaapi.h> +#include "ui/uicontext.h" +#include "SharedCacheUINotifications.h" + +using namespace BinaryNinja; + +class SharedCacheBDNotifications : public BinaryDataNotification +{ +public: + SharedCacheBDNotifications(Ref<BinaryView> view); + void OnAnalysisFunctionAdded(BinaryView* view, Function* func) override; + void OnDataVariableAdded(BinaryView* view, const DataVariable& var) override; + void OnSectionAdded(BinaryView* data, Section* section) override; +}; diff --git a/view/sharedcache/ui/SharedCacheUINotifications.cpp b/view/sharedcache/ui/SharedCacheUINotifications.cpp new file mode 100644 index 00000000..6c64c9ef --- /dev/null +++ b/view/sharedcache/ui/SharedCacheUINotifications.cpp @@ -0,0 +1,143 @@ +// +// Created by kat on 5/8/23. +// + +#include "SharedCacheUINotifications.h" +#include <QLayout> +#include <sharedcacheapi.h> +#include "ui/sidebar.h" +#include "ui/linearview.h" +#include "ui/viewframe.h" +#include "dscpicker.h" +#include "progresstask.h" +#include "SharedCacheBDNotifications.h" + +UINotifications* UINotifications::m_instance = nullptr; + +void UINotifications::init() +{ + m_instance = new UINotifications; + UIContext::registerNotification(m_instance); +} + + +void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type) +{ + if (!frame) + return; + + // FIXME there is a bv func for this + static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) { + if (view && view->GetTypeName() == VIEW_NAME) + { + for (const auto& seg : view->GetSegments()) + { + if (seg->GetStart() <= addr && seg->GetEnd() > addr) + return true; + } + } + return false; + }; + + auto view = frame->getCurrentBinaryView(); + if (view && view->GetTypeName() == VIEW_NAME) + { + if (auto viewInt = frame->getCurrentViewInterface()) + { + auto ah = viewInt->actionHandler(); + if (!ah->isBoundAction("Load Image by Name")) + { + ah->bindAction("Load Image by Name", UIAction([view = view](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(view); + DisplayDSCPicker(ctx.context, view); + })); + ah->bindAction("Load Section by Address", UIAction([view = view](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = 0; + bool gotAddr = GetAddressInput(addr, "Address", "Address"); + if (gotAddr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadSectionAtAddress(addr); + })->start(); + } + })); + ah->bindAction("Load ADDRHERE", + UIAction( + [](const UIActionContext& ctx) { + Ref<BinaryView> view = ctx.binaryView; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadSectionAtAddress(addr); + })->start(); + } + }, + [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (isAddrMapped(ctx.binaryView, addr)) + return false; + return addr && cache->GetNameForAddress(addr) != ""; // bool + })); + ah->bindAction("Load IMGHERE", + UIAction( + [](const UIActionContext& ctx) { + Ref<BinaryView> view = ctx.binaryView; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(view); + uint64_t addr = ctx.token.token.value; + if (addr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadImageContainingAddress(addr); + })->start(); + } + }, + [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (isAddrMapped(ctx.binaryView, addr)) + return false; + return addr && cache->GetImageNameForAddress(addr) != ""; // bool + })); + ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + return QString("Load ") + cache->GetNameForAddress(addr).c_str(); + return QString("Error"); + }); + ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + return QString("Load ") + cache->GetImageNameForAddress(addr).c_str(); + return QString("Error"); + }); + if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) + { + linearView->contextMenu().addAction("Load ADDRHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); + linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); + linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); + linearView->contextMenu().setGroupOrdering("DSCView2", 1); + } + } + } + } +} +void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) +{ + if (frame->getCurrentBinaryView()) + { + auto listener = new SharedCacheBDNotifications(frame->getCurrentBinaryView()); + frame->getCurrentBinaryView()->RegisterNotification(listener); + } + UIContextNotification::OnAfterOpenFile(context, file, frame); +} diff --git a/view/sharedcache/ui/SharedCacheUINotifications.h b/view/sharedcache/ui/SharedCacheUINotifications.h new file mode 100644 index 00000000..9ea4b50d --- /dev/null +++ b/view/sharedcache/ui/SharedCacheUINotifications.h @@ -0,0 +1,23 @@ +// +// Created by kat on 5/8/23. +// +#include "ui/uicontext.h" + +#ifndef SHAREDCACHE_NOTIFICATIONS_H +#define SHAREDCACHE_NOTIFICATIONS_H + +class UINotifications : public UIContextNotification { + static UINotifications* m_instance; + + std::vector<size_t> m_sessionsAlreadyDisplayedPickerFor; + +public: + virtual void OnViewChange(UIContext *context, ViewFrame *frame, const QString &type) override; + // bool OnAfterOpenDatabase(UIContext* context, FileMetadataRef metadata, BinaryViewRef data) override; + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + + static void init(); +}; + + +#endif //SHAREDCACHE_NOTIFICATIONS_H diff --git a/view/sharedcache/ui/dscpicker.cpp b/view/sharedcache/ui/dscpicker.cpp new file mode 100644 index 00000000..33877675 --- /dev/null +++ b/view/sharedcache/ui/dscpicker.cpp @@ -0,0 +1,42 @@ +// +// Created by kat on 5/22/23. +// + +#include "dscpicker.h" +#include <sharedcacheapi.h> +#include "progresstask.h" + +#include <utility> + +using namespace BinaryNinja; + +void DisplayDSCPicker(UIContext* ctx, Ref<BinaryView> dscView) +{ + BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr)->thenBackground( + [dscView=dscView](QVariant var) { + QStringList entries; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + + for (const auto& img : cache->GetAvailableImages()) + entries.push_back(QString::fromStdString(img)); + + return entries; + })->thenMainThread([ctx](QVariant var){ + QStringList entries = var.toStringList(); + + auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); + choiceDialog->AddWidthRequiredByItem(ctx, 300); + choiceDialog->AddHeightRequiredByItem(ctx, 150); + choiceDialog->exec(); + + if (choiceDialog->GetChosenEntry().has_value()) + return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); + else + return QVariant(""); + })->thenBackground([dscView=dscView](QVariant var){ + if (var.toString().isEmpty()) + return; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + cache->LoadImageWithInstallName(var.toString().toStdString()); + })->start(); +} diff --git a/view/sharedcache/ui/dscpicker.h b/view/sharedcache/ui/dscpicker.h new file mode 100644 index 00000000..6c4b15b5 --- /dev/null +++ b/view/sharedcache/ui/dscpicker.h @@ -0,0 +1,12 @@ +// +// Created by kat on 5/22/23. +// + +#ifndef SHAREDCACHE_DSCPICKER_H +#define SHAREDCACHE_DSCPICKER_H + +#include <binaryninjaapi.h> +#include <ui/metadatachoicedialog.h> +void DisplayDSCPicker(UIContext* ctx = nullptr, BinaryNinja::Ref<BinaryNinja::BinaryView> dscView = nullptr); + +#endif //SHAREDCACHE_DSCPICKER_H diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp new file mode 100644 index 00000000..0dca96bf --- /dev/null +++ b/view/sharedcache/ui/dsctriage.cpp @@ -0,0 +1,1012 @@ +// +// Created by kat on 8/15/24. +// + +#include "dsctriage.h" +#include "ui/fontsettings.h" +#include <QPainter> +#include <QTextBrowser> +#include "tabwidget.h" +#include "globalarea.h" +#include "progresstask.h" + +#include <cmath> +#include <QMessageBox> + + +#define QSETTINGS_KEY_SELECTED_TAB "DSCTriage-SelectedTab" +#define QSETTINGS_KEY_TAB_LAYOUT "DSCTriage-TabLayout" +#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "DSCTriage-ImageLoadTabLayout" +#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "DSCTriage-AlphaPopupSeen" + + +DSCCacheBlocksView::DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache) + : QWidget(parent), m_data(data), m_cache(cache) +{ + setMouseTracking(true); + m_backingCacheCount = SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data); + m_blockLuminance.resize(m_backingCacheCount, 128); + m_blockSizeRatios.resize(m_backingCacheCount, 1); + m_currentProgress = m_cache->GetLoadProgress(data); + m_targetBlockSizeForAnimation.resize(m_backingCacheCount, 0); + + m_blockWaveAnimation = Animation::create(this) + ->withDuration(1200) + ->withEasingCurve(QEasingCurve::Linear) + ->thenOnValueChanged([this](double v) + { + for (size_t i = 0; i < m_backingCacheCount; i++) + { + // Create a wave effect. + // We use sine to create the initial wave effect, and then cube it to make it more pronounced. + m_blockLuminance[i] = 128 + 95 * (pow((sin(v * 2 * M_PI + i * M_PI / m_backingCacheCount) + 1) / 2, 3)); + } + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + m_currentProgress = m_cache->GetLoadProgress(m_data); + if (m_currentProgress == BNDSCViewLoadProgress::LoadProgressFinished) + { + m_backingCaches = m_cache->GetBackingCaches(); + m_blockExpandAnimation->start(); + } + else + { + m_blockWaveAnimation->start(); + } + }); + m_blockExpandAnimation = Animation::create(this) + ->withDuration(600) + ->withEasingCurve(QEasingCurve::InOutCirc) + ->thenOnStart([this](QAbstractAnimation::Direction) + { + uint64_t totalSize = 0; + uint64_t sumCountForAvg = 0; + for (size_t i = 0; i < m_backingCacheCount; i++) + { + const auto& backingCache = m_backingCaches[i]; + double sizeSum = 0.0; + + for (const auto& mapping : backingCache.mappings) + { + sizeSum += mapping.size; + } + m_targetBlockSizeForAnimation[i] = sizeSum; + totalSize += sizeSum; + sumCountForAvg++; + } + + uint64_t avgSize = totalSize / sumCountForAvg; + + for (size_t i = 0; i < m_backingCacheCount; i++) + { + m_blockSizeRatios[i] = avgSize; + } + + m_averageBlockSizeForAnimationInterp = avgSize; + }) + ->thenOnValueChanged([this](double v) + { + for (size_t i = 0; i < m_backingCacheCount; i++) + { + m_blockSizeRatios[i] = m_averageBlockSizeForAnimationInterp + (v/2) * (m_targetBlockSizeForAnimation[i] - ((1.0 - (v/2)) * m_averageBlockSizeForAnimationInterp)); + + // Adjust luminance based on animation progress + m_blockLuminance[i] = 128 + (63 * v); + } + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + update(); + // wait 300, somehow + emit loadDone(); + m_selectedBlock = 0; + m_blockAutoselectAnimation->start(); + }); + + m_blockAutoselectAnimation = Animation::create(this) + ->withDuration(100) + ->withEasingCurve(QEasingCurve::InOutCirc) + ->thenOnValueChanged([this](double v){ + m_blockLuminance[0] = 191 + (64 * v); + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + emit selectionChanged(m_backingCaches[0], true); + }); + + m_blockWaveAnimation->setDirection(QAbstractAnimation::Backward); + m_blockWaveAnimation->start(); + +} + +DSCCacheBlocksView::~DSCCacheBlocksView() +{ + +} + +void DSCCacheBlocksView::mousePressEvent(QMouseEvent* event) +{ + if (m_currentProgress != BNDSCViewLoadProgress::LoadProgressFinished + || m_selectedBlock == -1) + { + return; + } + int blockIndex = getBlockIndexAtPosition(event->pos()); + blockSelected(blockIndex); + QWidget::mousePressEvent(event); +} + + +void DSCCacheBlocksView::mouseReleaseEvent(QMouseEvent* event) +{ + QWidget::mouseReleaseEvent(event); +} + + +void DSCCacheBlocksView::mouseDoubleClickEvent(QMouseEvent* event) +{ + QWidget::mouseDoubleClickEvent(event); +} + + +void DSCCacheBlocksView::mouseMoveEvent(QMouseEvent* event) +{ + if (m_selectedBlock == -1) + { + return; + } + uint64_t hoveredIndex = getBlockIndexAtPosition(event->pos()); + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + if (hoveredIndex != -1) + { + m_blockLuminance[hoveredIndex] = 255 - 32; + } + m_blockLuminance[m_selectedBlock] = 255; + update(); +} + + +void DSCCacheBlocksView::keyPressEvent(QKeyEvent* event) +{ + QWidget::keyPressEvent(event); +} + + +void DSCCacheBlocksView::keyReleaseEvent(QKeyEvent* event) +{ + QWidget::keyReleaseEvent(event); + if (m_selectedBlock == -1) + { + return; + } + + // left/right arrows, inc/dec m_selectedBlock + if (event->key() == Qt::Key_Left) + { + if (m_selectedBlock > 0) + { + blockSelected(m_selectedBlock - 1); + } + } + else if (event->key() == Qt::Key_Right) + { + if (m_selectedBlock < m_backingCacheCount - 1) + { + blockSelected(m_selectedBlock + 1); + } + } +} + + +void DSCCacheBlocksView::focusInEvent(QFocusEvent* event) +{ + QWidget::focusInEvent(event); +} + + +void DSCCacheBlocksView::focusOutEvent(QFocusEvent* event) +{ + QWidget::focusOutEvent(event); +} + + +void DSCCacheBlocksView::enterEvent(QEnterEvent* event) +{ + QWidget::enterEvent(event); +} + + +void DSCCacheBlocksView::leaveEvent(QEvent* event) +{ + QWidget::leaveEvent(event); +} + +void DSCCacheBlocksView::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Initial X position and total width of the widget + int totalWidth = this->width(); + int totalHeight = 30; // Height of the rectangles + int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; + int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding + + // Calculate the total ratio of block sizes + uint64_t totalRatio = 0; + for (const auto& ratio : m_blockSizeRatios) { + totalRatio += ratio; + } + + std::vector<int> originalWidths; + originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); + + + // Calculate center points for each block + std::vector<int> centers; + centers.reserve(m_blockSizeRatios.size()); + int currentX = 50; + for (size_t i = 0; i < originalWidths.size(); ++i) { + centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point + currentX += originalWidths[i] + 5; // Update currentX for the next block + } + + // Now draw the blocks, adjusting the position to keep the center point constant + currentX = 50; + uint64_t lastBlockEnd = currentX - 5; + for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) { + // Recalculate the width during animation + uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; + int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); + + // Calculate the new X position to maintain the center + int newX = centers[i] - (blockWidth / 2); + if (newX > lastBlockEnd + 5) + { + int diff = newX - (lastBlockEnd + 5); + newX -= diff; + blockWidth += diff; + } + if (newX < lastBlockEnd + 5) + { + int diff = (lastBlockEnd + 5) - newX; + newX += diff; + blockWidth -= diff; + } + lastBlockEnd = newX + blockWidth; + + QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); + QColor blockColor(m_blockLuminance[i], m_blockLuminance[i], m_blockLuminance[i]); + painter.setBrush(blockColor); + painter.setPen(blockColor); + painter.drawRect(blockRect); + + currentX += blockWidth + 5; // Move to the next block's position + } +} + + +int DSCCacheBlocksView::getBlockIndexAtPosition(const QPoint& clickPosition) +{ + // Initial X position and total width of the widget + int totalWidth = this->width(); + int totalHeight = 50; // Height of the rectangles + int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; + int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding + + // Calculate the total ratio of block sizes + uint64_t totalRatio = 0; + for (const auto& ratio : m_blockSizeRatios) + { + totalRatio += ratio; + } + + // Calculate center points for each block + std::vector<int> originalWidths; + originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); + + std::vector<int> centers; + centers.reserve(m_blockSizeRatios.size()); + int currentX = 50; + for (size_t i = 0; i < originalWidths.size(); ++i) + { + centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point + currentX += originalWidths[i] + 5; // Update currentX for the next block + } + + // Now find the block that contains the click + currentX = 50; + uint64_t lastBlockEnd = currentX - 5; + for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) + { + // Recalculate the width during animation + uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; + int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); + + // Calculate the new X position to maintain the center + int newX = centers[i] - (blockWidth / 2); + if (newX > lastBlockEnd + 5) + { + int diff = newX - (lastBlockEnd + 5); + newX -= diff; + blockWidth += diff; + } + if (newX < lastBlockEnd + 5) + { + int diff = (lastBlockEnd + 5) - newX; + newX += diff; + blockWidth -= diff; + } + lastBlockEnd = newX + blockWidth; + + // Check if the clickPosition is inside the current block's rectangle + QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); + if (blockRect.contains(clickPosition)) + { + return static_cast<int>(i); // Return the index of the clicked block + } + + currentX += blockWidth + 5; // Move to the next block's position + } + + return -1; // Return -1 if no block was clicked +} + + +void DSCCacheBlocksView::blockSelected(int index) +{ + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + m_selectedBlock = index; + if (index != -1) + m_blockLuminance[index] = 255; + update(); + if (index != -1) + emit selectionChanged(m_backingCaches[index], false); +} + + +void DSCCacheBlocksView::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); +} + + +QSize DSCCacheBlocksView::sizeHint() const +{ + return QWidget::sizeHint(); +} + + +QSize DSCCacheBlocksView::minimumSizeHint() const +{ + return QWidget::minimumSizeHint(); +} + + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(parent) { +} + +int SymbolTableModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + return static_cast<int>(m_symbols.size()); +} + +int SymbolTableModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + // We have 3 columns: Address, Name, and Image + return 3; +} + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const SharedCacheAPI::DSCSymbol& symbol = m_symbols.at(index.row()); + + switch (index.column()) { + case 0: // Address column + return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal + case 1: // Name column + return QString::fromStdString(symbol.name); + case 2: // Image column + return QString::fromStdString(symbol.image); + default: + return QVariant(); + } +} + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Name"); + case 2: + return QString("Image"); + default: + return QVariant(); + } +} + +void SymbolTableModel::updateSymbols() { + m_symbols = m_parent->m_symbols; + setFilter(m_filter); +} + +const SharedCacheAPI::DSCSymbol& SymbolTableModel::symbolAt(int row) const { + return m_symbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_symbols.clear(); + + if (m_filter.empty()) + { + m_symbols = m_parent->m_symbols; + } + else + { + m_symbols.reserve(m_parent->m_symbols.size()); + for (const auto& symbol : m_parent->m_symbols) + { + if (symbol.name.find(m_filter) != std::string::npos) + { + m_symbols.push_back(symbol); + } + } + m_symbols.shrink_to_fit(); + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache) + : m_model(new SymbolTableModel(this)){ + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + BackgroundThread::create(this)->thenBackground([this, cache=cache](){ + // LogInfo("Symbol Search: Loading symbols..."); + m_symbols = cache->LoadAllSymbolsAndWait(); + // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); + })->thenMainThread([this](){ + m_model->updateSymbols(); + })->start(); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} + + +DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new SharedCacheAPI::SharedCache(data)) +{ + setBinaryDataNavigable(false); + setupView(this); + + m_triageCollection = new DockableTabCollection(); + m_triageTabs = new SplitTabWidget(m_triageCollection); + + auto triageTabStyle = new GlobalAreaTabStyle(); + m_triageTabs->setTabStyle(triageTabStyle); + + auto cacheInfoWidget = new QWidget; + auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); + + QSplitter* containerWidget = new QSplitter; + containerWidget->setOrientation(Qt::Vertical); + + DSCCacheBlocksView* cacheBlocksView = new DSCCacheBlocksView(containerWidget, data, m_cache); + cacheBlocksView->setMinimumHeight(60); + + auto cacheInfo = new CollapsibleSection(this); + cacheInfo->setTitle(QString::fromStdString(data->GetFile()->GetOriginalFilename().substr(data->GetFile()->GetOriginalFilename().find_last_of('/') + 1))); + + auto cacheInfoSubwidget = new QWidget; + + auto mappingTable = new QTableView(cacheInfoSubwidget); + auto mappingModel = new QStandardItemModel(0, 3, mappingTable); + mappingModel->setHorizontalHeaderLabels({"VM Address", "File Address", "Size"}); + + mappingTable->setModel(mappingModel); + + mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + + auto sectionTable = new QTableView(cacheInfoSubwidget); + auto sectionModel = new QStandardItemModel(0, 3, sectionTable); + sectionModel->setHorizontalHeaderLabels({"Name", "VM Address", "Size"}); + + sectionTable->setModel(sectionModel); + + sectionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + sectionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + sectionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + + auto mappingLabel = new QLabel("Mappings"); + auto sectionLabel = new QLabel("Sections"); + + auto mappingLayout = new QVBoxLayout; + mappingLayout->addWidget(mappingLabel); + mappingLayout->addWidget(mappingTable); + + auto sectionLayout = new QVBoxLayout; + sectionLayout->addWidget(sectionLabel); + sectionLayout->addWidget(sectionTable); + + cacheInfoLayout->addLayout(mappingLayout); + cacheInfoLayout->addLayout(sectionLayout); + + cacheInfo->setContentWidget(cacheInfoSubwidget); + + cacheInfo->setMinimumHeight(170); + + connect(cacheBlocksView, &DSCCacheBlocksView::selectionChanged, [this, sectionModel, cacheInfo, cacheInfoWidget, mappingModel](const SharedCacheAPI::BackingCache& index, bool _auto) + { + if (!_auto) + m_triageTabs->selectWidget(cacheInfoWidget); + mappingModel->removeRows(0, mappingModel->rowCount()); + sectionModel->removeRows(0, sectionModel->rowCount()); + auto basename = index.path.substr(index.path.find_last_of('/') + 1); + cacheInfo->setTitle(QString::fromStdString(basename)); + size_t sizeInBits = 0; + for (const auto& mapping : index.mappings) + { + sizeInBits += mapping.size; + mappingModel->appendRow({ + new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16))}); + } + + for (const auto& header : m_headers) + { + uint64_t i = 0; + for (const auto& section : header.sections) + { + for (const auto& mapping : index.mappings) + { + if (section.addr >= mapping.vmAddress && section.addr < mapping.vmAddress + mapping.size) + { + sectionModel->appendRow({ + new QStandardItem(QString::fromStdString(header.sectionNames[i])), + new QStandardItem(QString("0x%1").arg(section.addr, 0, 16)), + new QStandardItem(QString("0x%1").arg(section.size, 0, 16))}); + break; + } + } + i++; + } + continue; + } + + std::string sizeStr; + if (sizeInBits < 1024) + { + sizeStr = std::to_string(sizeInBits) + " B"; + } + else if (sizeInBits < 1024 * 1024) + { + sizeStr = std::to_string(sizeInBits / 1024) + " KB"; + } + else if (sizeInBits < 1024 * 1024 * 1024) + { + sizeStr = std::to_string(sizeInBits / (1024 * 1024)) + " MB"; + } + else + { + sizeStr = std::to_string(sizeInBits / (1024 * 1024 * 1024)) + " GB"; + } + + cacheInfo->setSubtitleRight(QString::fromStdString(sizeStr)); + }); + + containerWidget->addWidget(cacheInfo); + + QWidget* defaultWidget; + + // check for alpha popup qsetting + QSettings settings; + if (!(settings.contains(QSETTINGS_KEY_ALPHA_POPUP_SEEN) && settings.value(QSETTINGS_KEY_ALPHA_POPUP_SEEN).toBool())) + { + + QTextBrowser *tb = new QTextBrowser(this); + { + tb->setOpenExternalLinks(true); + auto alphaHtml = + R"( +<br> +<h1>Shared Cache Alpha</h1> + +<p> This is the alpha release of the sharedcache viewer! We are hard at work improving this and adding features, but we wanted +to make it available for users to play with as soon as possible. </p> + +<h2> Supported Platforms </h2> +<ul> + <li> iOS 11-17 (full) </li> + <li> iOS 18 (partial, Objective-C optimization parsing is not implemented yet.) </li> + <li> macOS x86/arm64e (partial) </li> +</ul> + +<p> iOS parsing should work well for now. macOS parsing should be usable, but is still a work in progress. </p> + +<h2> Getting the latest version of the plugin </h2> + +<p> We frequently release "dev" builds which will contain the latest version of the SharedCache plugin (and many other things). + +You can find instructions on how to install these builds <a href="https://docs.binary.ninja/guide/index.html#development-branch">here</a>. </p> + +<h3> Reading / building the source </h3> +<p>You can read the source and find instructions for building it <a href="https://github.com/Vector35/binaryninja-api/tree/dev/view/sharedcache">here</a>. + +Contributions are always welcome! </p> +)"; + tb->setHtml(alphaHtml); + + m_triageTabs->addTab(tb, "Shared Cache Alpha"); + + } + settings.setValue(QSETTINGS_KEY_ALPHA_POPUP_SEEN, true); + defaultWidget = tb; + } + + m_bottomRegionCollection = new DockableTabCollection(); + m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); + m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); + + auto loadImageTable = new FilterableTableView; + { + auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); + { + connect( + cacheBlocksView, &DSCCacheBlocksView::loadDone, [this, loadImageModel, cacheInfo]() + { + for (const auto& img : m_cache->GetImages()) + { + if (auto header = m_cache->GetMachOHeaderForAddress(img.headerAddress); header) + { + m_headers.push_back(*header); + } + loadImageModel->appendRow({ + new QStandardItem(QString::fromStdString(img.name)), + new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16))}); + } + }); + loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); + } // loadImageModel + + auto loadImageButton = new CustomStyleFlatPushButton(); + { + connect(loadImageButton, &QPushButton::clicked, + [this, loadImageTable, cacheInfo, mappingModel, sectionModel](bool) { + auto selected = loadImageTable->selectionModel()->selectedRows(); + if (selected.size() == 0) + { + return; + } + + auto name = selected[0].data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + }); + loadImageButton->setText("Load"); + + loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + loadImageButton->setMinimumWidth(100); + loadImageButton->setMinimumHeight(30); + + } // loadImageButton + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + auto loadImageFilterEdit = new FilterEdit(loadImageTable); + { + connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { + loadImageTable->setFilter(filter.toStdString()); + }); + } // loadImageFilterEdit + + connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) + { + auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); + WorkerPriorityEnqueue([this, name]() + { + m_cache->LoadImageWithInstallName(name); + }); + }); + connect(loadImageTable, &FilterableTableView::doubleClicked, this, [=](const QModelIndex& index) + { + auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); + WorkerPriorityEnqueue([this, name]() + { + m_cache->LoadImageWithInstallName(name); + }); + }); + + auto loadImageLayout = new QVBoxLayout; + loadImageLayout->addWidget(loadImageFilterEdit); + loadImageLayout->addWidget(loadImageTable); + loadImageLayout->addWidget(loadImageButton); + + auto loadImageWidget = new QWidget; + loadImageWidget->setLayout(loadImageLayout); + + m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + + loadImageTable->setModel(loadImageModel); + + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + loadImageTable->setSelectionMode(QAbstractItemView::SingleSelection); + + m_triageTabs->addTab(loadImageWidget, "Images"); + if (!defaultWidget) + defaultWidget = loadImageWidget; + m_triageTabs->setCanCloseTab(loadImageWidget, false); + } // loadImageTable + + auto symbolSearch = new SymbolTableView(this, m_cache); + { + auto symbolFilterEdit = new FilterEdit(symbolSearch); + { + connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { + symbolSearch->setFilter(filter.toStdString()); + }); + } + + auto symbolLayout = new QVBoxLayout; + symbolLayout->addWidget(symbolFilterEdit); + symbolLayout->addWidget(symbolSearch); + + auto symbolWidget = new QWidget; + symbolWidget->setLayout(symbolLayout); + + symbolSearch->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address + symbolSearch->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name + symbolSearch->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image + + symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); + symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) + { + auto symbol = symbolSearch->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); + dialog->setText("Load " + QString::fromStdString(symbol.image) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) + { + if (button == dialog->button(QMessageBox::Yes)) + { + WorkerPriorityEnqueue([this, symbol]() + { + m_cache->LoadImageWithInstallName(symbol.image); + }); + } + }); + dialog->exec(); + }); + + m_triageTabs->addTab(symbolWidget, "Symbol Search"); + m_triageTabs->setCanCloseTab(symbolWidget, false); + } // symbolSearch + + auto loadedRegions = new QTreeView; + { + auto loadedRegionsModel = new QStandardItemModel(0, 3, loadedRegions); + loadedRegionsModel->setHorizontalHeaderLabels({"VM Address", "Size", "Pretty Name"}); + + auto loadedRegionsLayout = new QVBoxLayout; + loadedRegionsLayout->addWidget(loadedRegions); + + auto loadedRegionsWidget = new QWidget; + loadedRegionsWidget->setLayout(loadedRegionsLayout); + + loadedRegions->setModel(loadedRegionsModel); + + loadedRegions->header()->setSectionResizeMode(QHeaderView::Stretch); + + loadedRegions->setSelectionBehavior(QAbstractItemView::SelectRows); + loadedRegions->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(loadedRegions, &QTreeView::doubleClicked, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + connect(loadedRegions, &QTreeView::activated, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + // m_triageTabs->addTab(loadedRegionsWidget, "Loaded Regions"); + } // loadedRegions + + containerWidget->addWidget(m_bottomRegionTabs); + + m_triageTabs->addTab(cacheInfoWidget, "Cache Info"); + m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + + m_layout = new QVBoxLayout(this); + m_layout->addWidget(cacheBlocksView); + m_layout->addWidget(m_triageTabs); + setLayout(m_layout); + + m_triageTabs->selectWidget(defaultWidget); +} + + +DSCTriageView::~DSCTriageView() {} + + +QFont DSCTriageView::getFont() +{ + return getMonospaceFont(this); +} + + +BinaryViewRef DSCTriageView::getData() +{ + return m_data; +} + + +bool DSCTriageView::navigate(uint64_t offset) +{ + return true; +} + + +uint64_t DSCTriageView::getCurrentOffset() +{ + return 0; +} + + +CollapsibleSection::CollapsibleSection(QWidget* parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + { + layout->setContentsMargins(0, 0, 0, 0); + + auto hLayout = new QHBoxLayout; + { + hLayout->setContentsMargins(0, 0, 0, 0); + + m_titleLabel = new QLabel; + m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); + hLayout->addWidget(m_titleLabel, 1); + + m_subtitleRightLabel = new QLabel; + m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); + hLayout->addWidget(m_subtitleRightLabel); + + m_collapseButton = new CustomStyleFlatPushButton; + m_collapseButton->setFlat(true); + m_collapseButton->setCheckable(true); + } + + layout->addLayout(hLayout); + } + + m_contentWidgetContainer = new QWidget; + { + layout->addWidget(m_contentWidgetContainer); + new QVBoxLayout(m_contentWidgetContainer); + } + +} + + +void CollapsibleSection::setTitle(const QString& title) +{ + m_titleLabel->setText(title); +} + + +void CollapsibleSection::setSubtitleRight(const QString& subtitle) +{ + m_subtitleRightLabel->setVisible(subtitle != ""); + m_subtitleRightLabel->setText(subtitle); +} + + +void CollapsibleSection::setContentWidget(QWidget* contentWidget) +{ + m_contentWidget = contentWidget; + m_contentWidgetContainer->layout()->addWidget(contentWidget); +} + + +QSize CollapsibleSection::sizeHint() const +{ + return QWidget::sizeHint(); +} + + +void CollapsibleSection::setCollapsed(bool collapsed, bool animated) +{ + if (collapsed == m_collapsed) + { + return; + } + + m_collapsed = collapsed; + + if (m_collapsed) + { + m_contentWidget->hide(); + } + else + { + m_contentWidget->show(); + } + + if (animated) + { + m_onContentAddedAnimation->start(); + } +} + + +DSCTriageViewType::DSCTriageViewType() + : ViewType("DSCTriage", "Shared Cache Triage") +{ + +} + + +int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +{ + if (data->GetTypeName() == VIEW_NAME) + { + return 100; + } + return 1; +} + + +QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) +{ + if (data->GetTypeName() != VIEW_NAME) + { + return nullptr; + } + return new DSCTriageView(viewFrame, data); +} + + +void DSCTriageViewType::Register() +{ + ViewType::registerViewType(new DSCTriageViewType()); +} diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h new file mode 100644 index 00000000..96099a53 --- /dev/null +++ b/view/sharedcache/ui/dsctriage.h @@ -0,0 +1,297 @@ +// +// Created by kat on 8/15/24. +// + +#include <sharedcacheapi.h> +#include <binaryninjaapi.h> +#include "uitypes.h" +#include "viewframe.h" +#include "animation.h" +#include "uicontext.h" + +#include <QTableView> +#include <QStandardItemModel> +#include <QSortFilterProxyModel> +#include <QHeaderView> +#include "filter.h" + +#ifndef BINARYNINJA_DSCTRIAGE_H +#define BINARYNINJA_DSCTRIAGE_H + + +class DSCCacheBlocksView : public QWidget +{ + Q_OBJECT + + BinaryViewRef m_data; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + + uint64_t m_backingCacheCount = 0; + std::vector<SharedCacheAPI::BackingCache> m_backingCaches; + + std::atomic<BNDSCViewLoadProgress> m_currentProgress; + std::vector<uint64_t> m_blockSizeRatios; + std::vector<uint64_t> m_targetBlockSizeForAnimation; + uint64_t m_averageBlockSizeForAnimationInterp = 0; + std::vector<uint64_t> m_blockLuminance; + Animation* m_blockWaveAnimation; + Animation* m_blockExpandAnimation; + Animation* m_blockAutoselectAnimation; + + int m_selectedBlock = -1; + + int getBlockIndexAtPosition(const QPoint& clickPosition); + + void blockSelected(int index); + +public: + DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache); + virtual ~DSCCacheBlocksView() override; + +protected: + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + void enterEvent(QEnterEvent* event) override; + void leaveEvent(QEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +public: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +signals: + void loadDone(); + void selectionChanged(const SharedCacheAPI::BackingCache& index, bool automatic); +}; + + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + + QLabel* m_titleLabel; + QLabel* m_subtitleRightLabel; + QPushButton* m_collapseButton; + + bool m_collapsed = true; + + Animation* m_onContentAddedAnimation; + + QWidget* m_contentWidgetContainer; + QWidget* m_contentWidget; + +protected: + QSize sizeHint() const override; + +public: + CollapsibleSection(QWidget* parent); + void setTitle(const QString& title); + void setSubtitleRight(const QString& subtitle); + + void setContentWidget(QWidget* contentWidget); + + void setCollapsed(bool collapsed, bool animated = true); + bool isCollapsed() const { return m_collapsed; } +}; + + +class FilterableTableView : public QTableView, public FilterTarget { + Q_OBJECT + + bool m_filterByHiding; + +public: + FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) + : QTableView(parent), m_filterByHiding(filterByHiding) { + viewport()->installEventFilter(this); + } + + ~FilterableTableView() override {} + + void setFilter(const std::string& filter) override { + if (!m_filterByHiding) + { + emit filterTextChanged(QString::fromStdString(filter)); + return; + } + QString qFilter = QString::fromStdString(filter); + for (int row = 0; row < model()->rowCount(); ++row) { + bool match = false; + for (int col = 0; col < model()->columnCount(); ++col) { + QModelIndex index = model()->index(row, col); + QString data = model()->data(index).toString(); + if (data.contains(qFilter, Qt::CaseInsensitive)) { + match = true; + break; + } + } + setRowHidden(row, !match); + } + } + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + bool eventFilter(QObject* obj, QEvent* event) override { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Escape) { + clearSelection(); + return true; + } + if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { + emit activated(currentIndex()); + return true; + } + } + return QTableView::eventFilter(obj, event); + } + +signals: + void filterTextChanged(const QString& text); +}; + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector<SharedCacheAPI::DSCSymbol> m_symbols; + +public: + explicit SymbolTableModel(SymbolTableView* parent); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void updateSymbols(); + + void setFilter(std::string text); + + const SharedCacheAPI::DSCSymbol& symbolAt(int row) const; +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ + Q_OBJECT + friend class SymbolTableModel; + + std::vector<SharedCacheAPI::DSCSymbol> m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache); + virtual ~SymbolTableView() override; + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + SharedCacheAPI::DSCSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +}; + + +class DSCTriageView : public QWidget, public View +{ + BinaryViewRef m_data; + QVBoxLayout* m_layout; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + + SplitTabWidget* m_triageTabs; + DockableTabCollection* m_triageCollection; + + SplitTabWidget* m_bottomRegionTabs; + QTimer* m_tabLayoutTimer; + DockableTabCollection* m_bottomRegionCollection; + + std::vector<SharedCacheAPI::SharedCacheMachOHeader> m_headers; + +public: + DSCTriageView(QWidget* parent, BinaryViewRef data); + virtual ~DSCTriageView() override; + BinaryViewRef getData() override; + void setSelectionOffsets(BNAddressRange range) override {}; + QFont getFont() override; + bool navigate(uint64_t offset) override; + uint64_t getCurrentOffset() override; +}; + + +class DSCTriageViewType : public ViewType +{ +public: + DSCTriageViewType(); + int getPriority(BinaryViewRef data, const QString& filename) override; + QWidget* create(BinaryViewRef data, ViewFrame* viewFrame) override; + static void Register(); +}; + + +#endif // BINARYNINJA_DSCTRIAGE_H diff --git a/view/sharedcache/ui/dscwidget.cpp b/view/sharedcache/ui/dscwidget.cpp new file mode 100644 index 00000000..2483a26e --- /dev/null +++ b/view/sharedcache/ui/dscwidget.cpp @@ -0,0 +1,437 @@ +// +// by kat // 9/15/22. +// + +// CURRENTLY UNUSED CODE + +#include "dscwidget.h" + +#include "ui/viewframe.h" +#include "ui/progresstask.h" + +#include <QtCore/QMimeData> +#include <QtWidgets/QHeaderView> +#include <QtWidgets/QVBoxLayout> +#include <filesystem> +#include <QtWidgets> + +namespace fs = std::filesystem; + + +/// Format an address as hexadecimal. Does not include leading '0x' prefix. +QString formatAddress(uint64_t address) +{ + return QString::number(address, 16).rightJustified(8, '0'); +}; + +//===-- DSCContentsModelItem ------------------------------------------------===// + +DSCContentsModelItem::DSCContentsModelItem(DSCContentsModelItem* parent) : DSCContentsModelItem(nullptr, {}, {}, parent) +{} + +DSCContentsModelItem::DSCContentsModelItem( + BinaryViewRef view, std::string name, std::string installName, DSCContentsModelItem* parent) : + m_bv(view), + m_name(name), m_installName(installName), m_parent(parent) +{ + if (!installName.empty()) + m_type = ImageModelItem; + else + m_type = FolderModelItem; +} + +QString DSCContentsModelItem::displayName() const +{ + return QString::fromStdString(m_name); +} + +size_t DSCContentsModelItem::childCount() const +{ + return m_children.size(); +} + +DSCContentsModelItem* DSCContentsModelItem::child(size_t index) +{ + if (index < 0 || index >= m_children.size()) + return nullptr; + + return m_children[index]; +} + +void DSCContentsModelItem::addChild(DSCContentsModelItem* item) +{ + item->m_parent = this; + m_children.push_back(item); +} + +DSCContentsModelItem* DSCContentsModelItem::parent() const +{ + return m_parent; +} + +size_t DSCContentsModelItem::row() const +{ + if (!m_parent) + return 0; + auto it = std::find(m_parent->m_children.begin(), m_parent->m_children.end(), this); + return it - m_parent->m_children.begin(); +} + +QVariant DSCContentsModelItem::data(int column) const +{ + switch (column) + { + case DSCContentsModel::NameColumn: + return displayName(); + + default: + return QVariant(); + } +} + +QImage DSCContentsModelItem::icon() const +{ + auto kind = data(DSCContentsModel::KindColumn).toString(); + auto icon = QImage(":/icons/images/ComponentTree_" + kind + ".png"); + + return icon.scaled(16, 16, Qt::KeepAspectRatio); +} + +//===-- DSCContentsModel ----------------------------------------------------===// + +DSCContentsModel::DSCContentsModel(BinaryViewRef bv, QObject* parent) : QAbstractItemModel(parent), m_bv(bv) +{ + m_cache = new SharedCacheAPI::SharedCache(bv); + refresh(); +} + +struct ItemNode +{ + ItemNode* parent = nullptr; + std::string fullPath; + std::string path; + DSCContentsModelItem* assignedModelItem = nullptr; + std::unordered_map<std::string, ItemNode*> edges {}; +}; + +std::vector<std::string> split(std::string str, std::string token) +{ + std::vector<std::string> result; + while (str.size()) + { + int index = str.find(token); + if (index != std::string::npos) + { + result.push_back(str.substr(0, index)); + str = str.substr(index + token.size()); + if (str.size() == 0) + result.push_back(str); + } + else + { + result.push_back(str); + str = ""; + } + } + return result; +} + +void DSCContentsModel::refresh() +{ + std::scoped_lock<std::mutex> lock(m_updateMutex); + + // Using `{begin,end}ResetModel` here is not ideal and is a temporary + // hack at best. Actual model indices should be updated. That requires + // more work and will be implemented after more important things have + // been taken care of. + beginResetModel(); + + auto inames = m_cache->GetAvailableImages(); + + m_root = new DSCContentsModelItem(); + + std::unordered_map<std::string, DSCContentsModelItem*> folders {}; + folders["/"] = m_root; + for (const auto& iname : inames) + { + auto pathItems = split(iname, "/"); + pathItems.pop_back(); // skip filenames + std::string fullPath = "/"; + + for (const auto& item : pathItems) + { + if (item.empty()) + continue; + auto parentPath = fullPath; + fullPath += item + "/"; + if (folders.count(fullPath) == 0) + { + auto pnode = folders.at(parentPath); + auto* nnode = new DSCContentsModelItem(m_bv, item, "", pnode); + pnode->addChild(nnode); + folders[fullPath] = nnode; + } + } + } + + // Ok, all our folders are in place. Put files in them. + + for (const auto& iname : inames) + { + auto file = fs::path(iname).filename().string(); + auto folderName = fs::path(iname).parent_path().string() + "/"; + if (auto folder = folders.find(folderName); folder != folders.end()) + { + auto* nnode = new DSCContentsModelItem(m_bv, file, iname, folder->second); + folder->second->addChild(nnode); + } + else + BNLogError("DSCView Sidebar Logic Error: Couldn't find folder for %s %s %s", iname.c_str(), file.c_str(), + folderName.c_str()); + } + + endResetModel(); +} + +QModelIndex DSCContentsModel::index(int row, int column, const QModelIndex& parentIndex) const +{ + if (!hasIndex(row, column, parentIndex)) + return QModelIndex(); + + // Use the parent index's item if it is valid, otherwise use the root. + DSCContentsModelItem* parent = nullptr; + if (parentIndex.isValid()) + parent = static_cast<DSCContentsModelItem*>(parentIndex.internalPointer()); + else + parent = m_root; + + // If the child is found, create an index for it; use an invalid index otherwise. + auto item = parent->child(row); + if (item) + return createIndex(row, column, item); + + return QModelIndex(); +} + +QModelIndex DSCContentsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + auto child = static_cast<DSCContentsModelItem*>(index.internalPointer()); + auto parent = child->parent(); + if (parent == m_root || parent == nullptr) + return QModelIndex(); + + return createIndex(parent->row(), 0, parent); +} + +QVariant DSCContentsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case DSCContentsModel::NameColumn: + return "Name"; + default: + return ""; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); +} + +constexpr int ComponentGuidDataRole = 64; + +QVariant DSCContentsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + auto item = static_cast<DSCContentsModelItem*>(index.internalPointer()); + if (!item) + return {}; + + switch (role) + { + case Qt::DisplayRole: + return item->data(index.column()); + default: + return {}; + } +} + +bool DSCContentsModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + return false; +} + +Qt::ItemFlags DSCContentsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; // Root node + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + return flags; +} + + +int DSCContentsModel::rowCount(const QModelIndex& parent) const +{ + DSCContentsModelItem* item; + if (!parent.isValid()) + item = m_root; + else + item = static_cast<DSCContentsModelItem*>(parent.internalPointer()); + + return item->childCount(); +} + +int DSCContentsModel::columnCount(const QModelIndex& parent) const +{ + return 1; +} + +Qt::DropActions DSCContentsModel::supportedDropActions() const +{ + return Qt::IgnoreAction; +} + + +//===-- ComponentFilterModel ----------------------------------------------===// + +DSCFilterModel::DSCFilterModel(BinaryViewRef data, QObject* parent) : + QSortFilterProxyModel(parent), m_model(new DSCContentsModel(data)) +{ + setSourceModel(m_model); +} + +bool DSCFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + if (!index.isValid()) + return false; + + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} + +DSCSidebarView::DSCSidebarView(ViewFrame* frame, BinaryViewRef data, QWidget* parent) : + QTreeView(parent), m_data(data), m_frame(frame), m_parent(parent) +{ + connect(this, &DSCSidebarView::doubleClicked, this, &DSCSidebarView::navigateToIndex); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &DSCSidebarView::customContextMenuRequested, [this](const QPoint& p) { + auto menu = createContextMenu(); + menu->popup(viewport()->mapToGlobal(p)); + }); +} + + +void DSCSidebarView::navigateToIndex(const QModelIndex& index) +{ + auto filterParent = static_cast<DSCSidebarWidget*>(m_parent); + if (!filterParent) + return; + auto modelItem = static_cast<DSCContentsModelItem*>(filterParent->m_model->mapToSource(index).internalPointer()); + + if (modelItem->m_installName.empty()) + return; + + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, "Load Image", "Load " + QString::fromStdString(modelItem->m_name) + "?", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + SharedCacheAPI::SharedCache* cache = new SharedCacheAPI::SharedCache(m_data); + cache->LoadImageWithInstallName(modelItem->m_installName); + m_data->UpdateAnalysis(); + } +} + +QMenu* DSCSidebarView::createContextMenu() +{ + auto menu = new QMenu(); + + return menu; +} + +//===-- ComponentTree -----------------------------------------------------===// + +DSCSidebarWidget::DSCSidebarWidget(ViewFrame* frame, BinaryViewRef data) : + SidebarWidget("dyld_shared_cache"), m_data(data), m_frame(frame), m_header(new QWidget) +{ + auto view = data; + m_tree = new DSCSidebarView(frame, view, this); + m_model = new DSCFilterModel(view); + m_tree->setDragDropMode(QAbstractItemView::DragDrop); + m_tree->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_tree->setDragEnabled(true); + m_tree->setAcceptDrops(true); + m_tree->setDropIndicatorShown(true); + m_tree->header()->setSectionsMovable(false); + + m_tree->setModel(m_model); + m_model->setRecursiveFilteringEnabled(true); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_tree, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search Shared Cache Files"); + + auto headerLayout = new QHBoxLayout(m_header); + headerLayout->setContentsMargins(0, 0, 0, 0); + headerLayout->addWidget(m_filterEdit); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_filterView); +} + +//===-- ComponentTree - FilterTarget --------------------------------------===// + +void DSCSidebarWidget::setFilter(const std::string& filter) +{ + m_model->setFilterFixedString(QString::fromStdString(filter)); +} + +void DSCSidebarWidget::scrollToFirstItem() {} + +void DSCSidebarWidget::scrollToCurrentItem() {} + +void DSCSidebarWidget::selectFirstItem() {} + +void DSCSidebarWidget::activateFirstItem() {} + +//===-- DSCSidebarWidget - SidebarWidget -------------------------------------===// + +QWidget* DSCSidebarWidget::headerWidget() +{ + return m_header; +} + +void DSCSidebarWidget::focus() {} + +QImage temporaryIcon() +{ + QImage icon(56, 56, QImage::Format_RGB32); + icon.fill(0); + + QPainter p; + p.begin(&icon); + p.setFont({"Inter", 16}); + p.setPen({255, 255, 255, 255}); + p.drawText(QRectF {0, 0, 56, 56}, Qt::AlignCenter, "DSC"); + p.end(); + + return icon; +} + +DSCSidebarWidgetType::DSCSidebarWidgetType() : SidebarWidgetType(temporaryIcon(), "Shared Cache") {} + +SidebarWidget* DSCSidebarWidgetType::createWidget(ViewFrame* frame, BinaryViewRef data) +{ + return new DSCSidebarWidget(frame, data); +} diff --git a/view/sharedcache/ui/dscwidget.h b/view/sharedcache/ui/dscwidget.h new file mode 100644 index 00000000..0d5b9e0b --- /dev/null +++ b/view/sharedcache/ui/dscwidget.h @@ -0,0 +1,190 @@ +// +// by kat // 9/15/22. +// + +#ifndef SHAREDCACHE_DSCSIDEBARWIDGET_H +#define SHAREDCACHE_DSCSIDEBARWIDGET_H + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QSortFilterProxyModel> +#include <QtWidgets/QTreeView> + +#include "ui/filter.h" +#include "ui/sidebar.h" +#include "ui/uitypes.h" +#include <binaryninjaapi.h> +#include <sharedcacheapi.h> + +#include <mutex> + + +class DSCContentsModel; + +class DSCFilterModel; + +class DSCSidebarView; + +enum ModelItemType { + FolderModelItem, + ImageModelItem +}; + +class DSCContentsModelItem { + friend class ComponentModel; + + friend class ComponentFilterModel; + + friend class DSCSidebarView; + + ModelItemType m_type; + + DSCContentsModelItem *m_parent; + std::vector<DSCContentsModelItem *> m_children; + + BinaryViewRef m_bv; + + std::string m_name; + std::string m_installName; // only set on images, not dirs + + bool m_hasDataVar = false; + BinaryNinja::DataVariable m_dataVar; + +public: + explicit DSCContentsModelItem(DSCContentsModelItem *parent = nullptr); + + explicit DSCContentsModelItem(BinaryViewRef, std::string, std::string, DSCContentsModelItem *parent = nullptr); + + /// Get the "name" that should be displayed for an item. + QString displayName() const; + + size_t childCount() const; + + DSCContentsModelItem *child(size_t); + + void addChild(DSCContentsModelItem *); + + DSCContentsModelItem *parent() const; + + size_t row() const; + + QVariant data(int column) const; + + QImage icon() const; +}; + +class DSCContentsModel : public QAbstractItemModel { +Q_OBJECT + + BinaryViewRef m_bv; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + DSCContentsModelItem *m_root; + + std::unordered_map<std::string, DSCContentsModelItem *> m_dscItems; + + std::mutex m_updateMutex; + + void refresh(); + +public: + enum Column : int { + NameColumn = 0, + AddressColumn, + KindColumn, + }; + + DSCContentsModel(BinaryViewRef, QObject *parent = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &) const override; + + QVariant headerData(int, Qt::Orientation, int role = Qt::DisplayRole) const override; + + QVariant data(const QModelIndex &, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex &) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + Qt::DropActions supportedDropActions() const override; + +}; + +/// Filtering model to wrap a `ComponentModel`. +class DSCFilterModel : public QSortFilterProxyModel { +Q_OBJECT + + DSCContentsModel *m_model; + +public: + DSCFilterModel(BinaryViewRef, QObject *parent = nullptr); + + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; +}; + +class DSCSidebarView : public QTreeView { + BinaryViewRef m_data; + ViewFrame *m_frame; + QWidget *m_parent; + + void navigateToIndex(const QModelIndex &); + + QMenu *createContextMenu(); + +public: + DSCSidebarView(ViewFrame *, BinaryViewRef, QWidget *parent = nullptr); +}; + +class DSCSidebarWidget : public SidebarWidget, public FilterTarget { +Q_OBJECT + + friend DSCSidebarView; + + BinaryViewRef m_data; + ViewFrame *m_frame; + QWidget *m_header; + + DSCSidebarView *m_tree; + + QSortFilterProxyModel *m_model; + + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + +public: + DSCSidebarWidget(ViewFrame *, BinaryViewRef); + + QWidget *headerWidget() override; + + void focus() override; + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override; + + void scrollToCurrentItem() override; + + void selectFirstItem() override; + + void activateFirstItem() override; +}; + +class DSCSidebarWidgetType : public SidebarWidgetType { +public: + DSCSidebarWidgetType(); + + bool ValidForView(BinaryNinja::BinaryView* view) + { + if (!view) + return false; + return (view->GetTypeName() == VIEW_NAME); + } + + SidebarWidget *createWidget(ViewFrame *, BinaryViewRef) override; +}; + +#endif //SHAREDCACHE_DSCSIDEBARWIDGET_H diff --git a/view/sharedcache/workflow/CMakeLists.txt b/view/sharedcache/workflow/CMakeLists.txt new file mode 100644 index 00000000..81e4498c --- /dev/null +++ b/view/sharedcache/workflow/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(sharedcacheworkflow) +file(GLOB SOURCES *.cpp *.h) +add_library(sharedcacheworkflow OBJECT ${SOURCES}) + +if (VIEW_NAME) + target_compile_definitions(sharedcacheworkflow PRIVATE VIEW_NAME="${VIEW_NAME}") +else() + error("VIEW_NAME must be defined") +endif() + + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(binaryninjaapi INCLUDES) + +target_include_directories(sharedcacheworkflow + PUBLIC ${PROJECT_SOURCE_DIR} ${INCLUDES}) + +set_target_properties(sharedcacheworkflow PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/out) diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.cpp b/view/sharedcache/workflow/SharedCacheWorkflow.cpp new file mode 100644 index 00000000..4b6afe97 --- /dev/null +++ b/view/sharedcache/workflow/SharedCacheWorkflow.cpp @@ -0,0 +1,496 @@ +// +// Created by kat on 8/6/24. +// + +// TODO We could use an LLIL/MLIL workflow to rewrite off-image value-loads +// (i.e. MLIL_VAR_LOAD.MLIL_DEREF.MLIL_CONST_PTR) to just read the value out of the cache and replace the load +// in stub regions. +// +// This is a pretty rough workflow and has huge room for improvements all around. + +#include "SharedCacheWorkflow.h" +#include "lowlevelilinstruction.h" +#include "mediumlevelilinstruction.h" +#include "../api/sharedcacheapi.h" +#include "thread" + + +std::unordered_map<uint64_t, std::mutex> imageLoadMutex; + + +std::vector<std::string> splitSelector(const std::string& selector) { + std::vector<std::string> components; + std::istringstream stream(selector); + std::string component; + + while (std::getline(stream, component, ':')) { + if (!component.empty()) { + components.push_back(component); + } + } + + return components; +} + +std::vector<std::string> generateArgumentNames(const std::vector<std::string>& components) { + std::vector<std::string> argumentNames; + + for (const std::string& component : components) { + size_t startPos = component.find_last_of(" "); + std::string argumentName = (startPos == std::string::npos) ? component : component.substr(startPos + 1); + argumentNames.push_back(argumentName); + } + + return argumentNames; +} + + +void SharedCacheWorkflow::ProcessOffImageCall(Ref<AnalysisContext> ctx, Ref<Function> func, Ref<MediumLevelILFunction> mssa, const MediumLevelILInstruction dest, ExprId exprIndex, bool applySymbolIfFoundToCurrentFunction) +{ + auto bv = func->GetView(); + WorkerPriorityEnqueue([bv=bv, dest=dest, func=func, applySymbolIfFoundToCurrentFunction]() + { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(bv); + Ref<Settings> settings = bv->GetLoadSettings(VIEW_NAME); + bool autoLoadStubsAndDyldData = true; + if (settings && settings->Contains("loader.dsc.autoLoadStubsAndDyldData")) + { + autoLoadStubsAndDyldData = settings->Get<bool>("loader.dsc.autoLoadStubsAndDyldData", bv); + } + if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST) + return; + if (autoLoadStubsAndDyldData && + (cache->GetNameForAddress(dest.GetConstant()).find("dyld_shared_cache_branch_islands") != std::string::npos + || cache->GetNameForAddress(dest.GetConstant()).find("::_stubs") != std::string::npos + ) + ) + + { + if (cache->LoadSectionAtAddress(dest.GetConstant())) + { + func->Reanalyze(); + } + } + else + { + if (applySymbolIfFoundToCurrentFunction) + cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), func->GetStart(), false); + else + cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), dest.GetConstant(), false); + } + }); +} + + +void SharedCacheWorkflow::FixupStubs(Ref<AnalysisContext> ctx) +{ + try + { + const auto func = ctx->GetFunction(); + const auto arch = func->GetArchitecture(); + + const auto bv = func->GetView(); + + auto funcStart = func->GetStart(); + auto sectionExists = !bv->GetSectionsAt(funcStart).empty(); + if (!sectionExists) + return; + auto section = bv->GetSectionsAt(funcStart)[0]; + + auto imageName = section->GetName(); + // remove everything after :: + auto pos = imageName.find("::"); + if (pos != std::string::npos) + imageName = imageName.substr(0, pos); + + const auto llil = ctx->GetLowLevelILFunction(); + if (!llil) { + return; + } + const auto ssa = llil->GetSSAForm(); + if (!ssa) { + return; + } + + const auto mlil = ctx->GetMediumLevelILFunction(); + if (!mlil) { + return; + } + const auto mssa = mlil->GetSSAForm(); + if (!mssa) { + return; + } + + // FIXME optimize + Ref<Settings> settings = bv->GetLoadSettings(VIEW_NAME); + bool autoLoadObjC = true; + if (settings && settings->Contains("loader.dsc.autoLoadObjCStubRequirements")) + { + autoLoadObjC = settings->Get<bool>("loader.dsc.autoLoadObjCStubRequirements", bv); + } + + // Processor that automatically loads the libObjC image when it encounters a stub (so we can do inlining). + if (autoLoadObjC && section->GetName().find("__objc_stubs") != std::string::npos) + { + auto firstInstruction = mlil->GetInstruction(0); + if (firstInstruction.operation == MLIL_TAILCALL) + { + auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>(); + if (dest.operation == MLIL_CONST_PTR) + { + // We're ready, everything is here + func->SetAutoInlinedDuringAnalysis(true); + return; + } + } + for (const auto& block : mssa->GetBasicBlocks()) + { + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + { + auto instr = mssa->GetInstruction(i); + // current_il_function.ssa_form.get_ssa_var_value(current_il_instruction.dest.var) + if (instr.operation == MLIL_JUMP) + { + if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) + { + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + // RegisterValue value = mssa->GetSSAVarValue(instr.GetDestExpr().GetSourceSSAVariable()) + // ExprId def = mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable()); + // MLILInstruction defInstr = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())); + // targetOffset = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())).GetSourceExpr().GetSourceExpr().GetConstant(); + auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); + if (value.state == UndeterminedValue) + { + bool otherFunctionAlreadyRunning; + { + otherFunctionAlreadyRunning = !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock(); + if (!otherFunctionAlreadyRunning) + imageLoadMutex[bv->GetFile()->GetSessionId()].unlock(); + } + if (otherFunctionAlreadyRunning) + { + return; + } + + std::unique_lock<std::mutex> lock(imageLoadMutex[bv->GetFile()->GetSessionId()]); + auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); + auto defInstr = mssa->GetInstruction(def); + auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); + auto sharedCache = SharedCacheAPI::SharedCache(bv); + if (!sharedCache.GetImageNameForAddress(targetOffset).empty()) + { + sharedCache.LoadImageContainingAddress(targetOffset); + } + else + { + sharedCache.LoadSectionAtAddress(targetOffset); + } + for (const auto §Func : bv->GetAnalysisFunctionList()) + { + if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) + { + func->Reanalyze(); + } + } + } + } + + else if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_CONST_PTR) + { + bool otherFunctionAlreadyRunning; + { + otherFunctionAlreadyRunning = !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock(); + if (!otherFunctionAlreadyRunning) + imageLoadMutex[bv->GetFile()->GetSessionId()].unlock(); + } + if (otherFunctionAlreadyRunning) + { + return; + } + + std::unique_lock<std::mutex> lock(imageLoadMutex[bv->GetFile()->GetSessionId()]); + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + auto targetOffset = dest.GetConstant(); + auto sharedCache = SharedCacheAPI::SharedCache(bv); + if (!sharedCache.GetImageNameForAddress(targetOffset).empty()) + { + sharedCache.LoadImageContainingAddress(targetOffset); + } + else + { + sharedCache.LoadSectionAtAddress(targetOffset); + } + for (const auto §Func : bv->GetAnalysisFunctionList()) + { + if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) + { + func->Reanalyze(); + } + } + } + } + } + } + + return; + } + + if (section->GetName().find("::_stubs") != std::string::npos // Branch Islands (iOS 16) + || section->GetName().find("dyld_shared_cache_branch_islands") != std::string::npos // Branch Islands (iOS 11-?) + || section->GetName().find("::__stubs") != std::string::npos // Stubs (non arm64e) + || section->GetName().find("::__auth_stubs") != std::string::npos // Stubs (arm64e) + ) + { + auto firstInstruction = mlil->GetInstruction(0); + if (firstInstruction.operation == MLIL_TAILCALL) + { + auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>(); + if (dest.operation == MLIL_CONST_PTR) + { + if (auto symbol = bv->GetSymbolByAddress(dest.GetConstant())) + { + auto newSymbol = new Symbol(FunctionSymbol, "j_" + symbol->GetRawName(), func->GetStart()); + bv->DefineUserSymbol(newSymbol); + } + } + } + else if (firstInstruction.operation == MLIL_JUMP) + { + auto dest = firstInstruction.GetDestExpr<MLIL_JUMP>(); + if (dest.operation == MLIL_CONST_PTR) + { + if (!bv->IsValidOffset(dest.GetConstant())) + { + ProcessOffImageCall(ctx, func, mssa, dest, firstInstruction.GetSSAExprIndex(), true); + } + } + + else if (dest.operation == MLIL_LOAD) + { + if (dest.GetSourceExpr().operation == MLIL_CONST_PTR) + { + dest = dest.GetSourceExpr(); + if (!bv->IsValidOffset(dest.GetConstant())) + { + ProcessOffImageCall(ctx, func, mssa, dest, firstInstruction.GetSSAExprIndex()); + } + } + } + } + + else + { + for (const auto& block : mssa->GetBasicBlocks()) + { + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + { + auto instr = mssa->GetInstruction(i); + // current_il_function.ssa_form.get_ssa_var_value(current_il_instruction.dest.var) + if (instr.operation == MLIL_JUMP) + { + if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) + { + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + // RegisterValue value = mssa->GetSSAVarValue(instr.GetDestExpr().GetSourceSSAVariable()) ExprId def = mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable()); + // MLILInstruction defInstr = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())); + // targetOffset = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())).GetSourceExpr().GetSourceExpr().GetConstant(); + auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); + if (value.state == UndeterminedValue) + { + bool otherFunctionAlreadyRunning; + { + otherFunctionAlreadyRunning = + !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock(); + if (!otherFunctionAlreadyRunning) + imageLoadMutex[bv->GetFile()->GetSessionId()].unlock(); + } + if (otherFunctionAlreadyRunning) + { + return; + } + + std::unique_lock<std::mutex> lock( + imageLoadMutex[bv->GetFile()->GetSessionId()]); + auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); + auto defInstr = mssa->GetInstruction(def); + auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); + auto sharedCache = SharedCacheAPI::SharedCache(bv); + if (!bv->IsValidOffset(targetOffset)) + { + if (!sharedCache.GetImageNameForAddress(targetOffset).empty()) + { + sharedCache.LoadImageContainingAddress(targetOffset); + } + else + { + sharedCache.LoadSectionAtAddress(targetOffset); + } + for (const auto& sectFunc : bv->GetAnalysisFunctionList()) + { + if (section->GetStart() <= sectFunc->GetStart() + && sectFunc->GetStart() < section->GetEnd()) + { + func->Reanalyze(); + } + } + } + } + } + + } + } + } + } + + return; + } + + for (const auto& block : mssa->GetBasicBlocks()) + { + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + { + auto instr = mssa->GetInstruction(i); + if (instr.operation == MLIL_CALL_SSA) + { + if (instr.GetDestExpr<MLIL_CALL_SSA>().operation == MLIL_CONST_PTR) + { + auto dest = instr.GetDestExpr<MLIL_CALL_SSA>(); + if (!bv->IsValidOffset(dest.GetConstant())) + { + ProcessOffImageCall(ctx, func, mssa, dest, instr.GetSSAExprIndex()); + } + } + } + } + } + } + catch (...) + {} +} + + +static constexpr auto workflowInfo = R"({ + "title": "Shared Cache Workflow", + "description": "Shared Cache Workflow", + "capabilities": [] +})"; + + +void fixObjCCallTypes(Ref<AnalysisContext> ctx) +{ + const auto func = ctx->GetFunction(); + const auto arch = func->GetArchitecture(); + const auto bv = func->GetView(); + + const auto llil = ctx->GetLowLevelILFunction(); + if (!llil) { + return; + } + const auto ssa = llil->GetSSAForm(); + if (!ssa) { + return; + } + + const auto rewriteIfEligible = [bv, ssa](size_t insnIndex) { + auto insn = ssa->GetInstruction(insnIndex); + + if (insn.operation == LLIL_CALL_SSA) + { + // Filter out calls that aren't to `objc_msgSend`. + auto callExpr = insn.GetDestExpr<LLIL_CALL_SSA>(); + bool isMessageSend = false; + if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value)) + isMessageSend = symbol->GetRawName() == "_objc_msgSend"; + if (!isMessageSend) + return; + + const auto llil = ssa->GetNonSSAForm(); + const auto insn = ssa->GetInstruction(insnIndex); + const auto params = insn.GetParameterExprs<LLIL_CALL_SSA>(); + + // The second parameter passed to the objc_msgSend call is the address of + // either the selector reference or the method's name, which in both cases + // is dereferenced to retrieve a selector. + if (params.size() < 2) + return; + uint64_t rawSelector = 0; + if (params[1].operation == LLIL_REG_SSA) + { + const auto selectorRegister = params[1].GetSourceSSARegister<LLIL_REG_SSA>(); + rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; + } + else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA) + { + if (params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>().size() == 0) + { + return; + } + const auto selectorRegister = params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>()[1].GetSourceSSARegister<LLIL_REG_SSA>(); + rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; + } + if (rawSelector == 0) + return; + + // -- Do callsite override + auto reader = BinaryNinja::BinaryReader(bv); + reader.Seek(rawSelector); + auto selector = reader.ReadCString(500); + auto additionalArgumentCount = std::count(selector.begin(), selector.end(), ':'); + + auto retType = bv->GetTypeByName({ "id" }); + if (!retType) + retType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::VoidType()); + + std::vector<BinaryNinja::FunctionParameter> callTypeParams; + auto cc = bv->GetDefaultPlatform()->GetDefaultCallingConvention(); + + callTypeParams.push_back({"self", retType, true, BinaryNinja::Variable()}); + + auto selType = bv->GetTypeByName({ "SEL" }); + if (!selType) + selType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::IntegerType(1, true)); + callTypeParams.push_back({"sel", selType, true, BinaryNinja::Variable()}); + + std::vector<std::string> selectorComponents = splitSelector(selector); + std::vector<std::string> argumentNames = generateArgumentNames(selectorComponents); + + for (size_t i = 0; i < additionalArgumentCount; i++) + { + auto argType = BinaryNinja::Type::IntegerType(bv->GetAddressSize(), true); + if (argumentNames.size() > i && !argumentNames[i].empty()) + callTypeParams.push_back({argumentNames[i], argType, true, BinaryNinja::Variable()}); + else + callTypeParams.push_back({"arg" + std::to_string(i), argType, true, BinaryNinja::Variable()}); + } + + auto funcType = BinaryNinja::Type::FunctionType(retType, cc, callTypeParams); + ssa->GetFunction()->SetAutoCallTypeAdjustment(ssa->GetFunction()->GetArchitecture(), insn.address, {funcType, BN_DEFAULT_CONFIDENCE}); + // -- + } + }; + + for (const auto& block : ssa->GetBasicBlocks()) + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + rewriteIfEligible(i); +} + + + +void SharedCacheWorkflow::Register() +{ + const auto wf = BinaryNinja::Workflow::Instance()->Clone("core.function.dsc"); + wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.dscstubs", &SharedCacheWorkflow::FixupStubs)); + wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.fixObjCCallTypes", &fixObjCCallTypes)); + wf->Insert("core.function.analyzeTailCalls", "core.analysis.fixObjCCallTypes"); + wf->Insert("core.function.analyzeTailCalls", "core.analysis.dscstubs"); + + BinaryNinja::Workflow::RegisterWorkflow(wf, workflowInfo); +} + +extern "C" +{ + void RegisterSharedCacheWorkflow() + { + SharedCacheWorkflow::Register(); + } +} diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.h b/view/sharedcache/workflow/SharedCacheWorkflow.h new file mode 100644 index 00000000..67ccb8fe --- /dev/null +++ b/view/sharedcache/workflow/SharedCacheWorkflow.h @@ -0,0 +1,27 @@ + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; + + +#ifndef SHAREDCACHE_SHAREDCACHEWORKFLOW_H +#define SHAREDCACHE_SHAREDCACHEWORKFLOW_H + + +class SharedCacheWorkflow +{ +public: + static void ProcessOffImageCall(Ref<AnalysisContext> ctx, Ref<Function> func, Ref<MediumLevelILFunction> il, const MediumLevelILInstruction instr, ExprId exprIndex, bool applySymbolIfFoundToCurrentFunction = false); + static void FixupStubs(Ref<AnalysisContext> ctx); + static void Register(); +}; + +#ifdef __cplusplus +extern "C" { +#endif + void RegisterSharedCacheWorkflow(); +#ifdef __cplusplus +} +#endif + +#endif //SHAREDCACHE_SHAREDCACHEWORKFLOW_H |
