From 25cc02431b61097b2adfc2fbc493b648b0300c3b Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Mon, 10 Mar 2025 11:05:40 -0400 Subject: [SharedCache] Refactor Shared Cache In absence of a better name, this commit refactors the shared cache code. --- view/sharedcache/core/SharedCacheController.cpp | 261 ++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 view/sharedcache/core/SharedCacheController.cpp (limited to 'view/sharedcache/core/SharedCacheController.cpp') diff --git a/view/sharedcache/core/SharedCacheController.cpp b/view/sharedcache/core/SharedCacheController.cpp new file mode 100644 index 00000000..29c1192a --- /dev/null +++ b/view/sharedcache/core/SharedCacheController.cpp @@ -0,0 +1,261 @@ +#include "SharedCacheController.h" + +#include + +#include "MachOProcessor.h" +#include "ObjC.h" +#include "SlideInfo.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +// Unique ID for a given Binary View. +typedef uint64_t ViewId; + +std::shared_mutex GlobalControllersMutex; + +std::map>& GlobalControllers() +{ + // To make initialization order consistent we place the static in a function. + static std::map> g_dscViews = {}; + return g_dscViews; +} + +ViewId GetViewIdFromView(BinaryView& view) +{ + // Currently the view id is just the views session id. + // NOTE: If we want more than one shared cache controller per view we would need to make this more unique. + return view.GetFile()->GetSessionId(); +} + +void DeleteController(BinaryView& view) +{ + const auto id = GetViewIdFromView(view); + std::unique_lock lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + if (auto it = dscViews.find(id); it != dscViews.end()) + { + // Someone is still holding the controller, lets warn about this. + if (it->second->m_refs > 1) + LogWarn("Deleting SharedCacheController for view %llx, but there are still %d references", id, + it->second->m_refs.load()); + dscViews.erase(it); + LogDebug("Deleted SharedCacheController for view %s", view.GetFile()->GetFilename().c_str()); + } +} + +void RegisterSharedCacheControllerDestructor() +{ + BNObjectDestructionCallbacks callbacks = {}; + callbacks.destructBinaryView = [](void* ctx, BNBinaryView* obj) -> void { + auto view = BinaryView(obj); + DeleteController(view); + }; + BNRegisterObjectDestructionCallbacks(&callbacks); +} + +SharedCacheController::SharedCacheController(SharedCache cache, Ref logger) : m_cache(std::move(cache)) +{ + INIT_DSC_API_OBJECT(); + m_logger = std::move(logger); + m_loadedRegions = {}; + m_loadedImages = {}; +} + +DSCRef SharedCacheController::Initialize(BinaryView& view, SharedCache cache) +{ + auto id = GetViewIdFromView(view); + std::unique_lock lock(GlobalControllersMutex); + auto logger = new Logger("SharedCacheController", view.GetFile()->GetSessionId()); + DSCRef dscView = new SharedCacheController(std::move(cache), logger); + + // Pull the settings from the view. + if (Ref settings = view.GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processObjC")) + dscView->m_processObjC = settings->Get("loader.dsc.processObjC", &view); + if (settings->Contains("loader.dsc.processCFStrings")) + dscView->m_processCFStrings = settings->Get("loader.dsc.processCFStrings", &view); + if (settings->Contains("loader.dsc.regionFilter")) + dscView->m_regionFilter = std::regex(settings->Get("loader.dsc.regionFilter", &view)); + } + + // Check the view auto metadata for shared cache information. + // This effectively restores the state of the opened database to when it was last saved. + auto metadata = view.GetAutoMetadata()->GetKeyValueStore(); + if (metadata.find(METADATA_KEY) != metadata.end()) + dscView->LoadMetadata(*metadata[METADATA_KEY]); + + GlobalControllers().insert({id, dscView}); + return dscView; +} + +DSCRef SharedCacheController::FromView(BinaryView& view) +{ + auto id = GetViewIdFromView(view); + std::shared_lock lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + auto dscView = dscViews.find(id); + if (dscView == dscViews.end()) + return nullptr; + return dscView->second; +} + +bool SharedCacheController::ApplyRegionAtAddress(BinaryView& view, const uint64_t address) +{ + auto region = m_cache.GetRegionAt(address); + if (!region) + return false; + return ApplyRegion(view, *region); +} + +bool SharedCacheController::ApplyRegion(BinaryView& view, const CacheRegion& region) +{ + // TODO: Check m_loadedRegions? This seems redundant... + // Loads the given region into the BinaryView and marks it as loaded. + // First check to make sure there isn't already a segment at the address in the view. + if (view.GetSegmentAt(region.start)) + return false; + + // Skip filtered regions, this defaults to just LINKEDIT regions. + if (std::regex_match(region.name, m_regionFilter)) + { + LogDebug("Skipping filtered region at %llx", region.start); + return false; + } + + auto vm = m_cache.GetVirtualMemory(); + auto reader = VirtualMemoryReader(vm); + DataBuffer buffer = {}; + try + { + buffer = reader.ReadBuffer(region.start, region.size); + } + catch (std::exception& e) + { + // This happens if we have not mapped in all the relevant entries. + m_logger->LogError("Failed to read region: %s", e.what()); + return false; + } + + // Unique memory region name so that we don't cause collisions. + // TODO: Better name? I dont really think so... + const auto memoryRegionName = fmt::format("{}_0x{:x}", region.name, region.start); + + // NOTE: Adding a data memory region will store the entire contents of the region in the BNDB. + // TODO: We can use the AddRemoteMemoryRegion if we want to reload on view init. + // TODO: ^ The above is only useful if we assume that all files will be available across database loads. + // TODO: we might allow a user to select non-persisted memory regions as an option. + view.GetMemoryMap()->AddDataMemoryRegion(memoryRegionName, region.start, buffer, region.flags); + // TODO: We might want to make this auto if we decide to "reload" all loaded region in view init. + // If we are not associated with an image we can create a section here to set the semantics. + // This is important for stub regions, as they will deref non image data that we want to retrieve the value of. + if (region.type != CacheRegionType::Image) + view.AddUserSection(memoryRegionName, region.start, region.size, region.SectionSemanticsForRegion()); + + m_loadedRegions.insert(region.start); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + return true; +} + +bool SharedCacheController::IsRegionLoaded(const CacheRegion& region) const +{ + return std::any_of(m_loadedRegions.begin(), m_loadedRegions.end(), [&](const auto& loadedRegion) { + return loadedRegion == region.start; + }); +} + +bool SharedCacheController::ApplyImage(BinaryView& view, const CacheImage& image) +{ + // TODO: Check m_loadedImages? This seems redundant... + // Load all regions of an image and mark the image as loaded. + bool loadedRegion = false; + for (const auto& regionStart : image.regionStarts) + if (ApplyRegionAtAddress(view, regionStart)) + loadedRegion = true; + + // If there was no loaded regions than we just want to forgo loading the image. + if (!loadedRegion) + return false; + + if (image.header) + { + // Header information is applied to the view here, such as sections. + auto machoProcessor = SharedCacheMachOProcessor(&view, m_cache.GetVirtualMemory()); + machoProcessor.ApplyHeader(*image.header); + + // TODO: Make this optional. + // Load objective-c information. + auto objcProcessor = DSCObjC::SharedCacheObjCProcessor(&view, false); + // TODO: Passing in an image name here is weird considering this is shared with the MACHO view. + // TODO: We should abstract out the "image" into an objc image type that represents what is required, which ig is the name? + try + { + if (m_processObjC) + objcProcessor.ProcessObjCData(image.GetName()); + if (m_processObjC) + objcProcessor.ProcessCFStrings(image.GetName()); + } + catch (std::exception& e) + { + // Let the user know there was an error in processing the objc stuff but let the image load + // regardless, as its non-critical. + m_logger->LogError("Failed to process ObjC information: %s", e.what()); + } + } + + m_loadedImages.insert(image.headerAddress); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + // TODO: Partial failure state (i.e. 2 regions loaded, one failed) + return true; +} + +bool SharedCacheController::IsImageLoaded(const CacheImage& image) const +{ + return std::any_of(m_loadedImages.begin(), m_loadedImages.end(), [&](const auto& loadedImage) { + return loadedImage == image.headerAddress; + }); +} + +Ref SharedCacheController::GetMetadata() const +{ + std::map> controllerMeta; + + std::vector loadedImages; + std::vector loadedRegions; + loadedImages.reserve(m_loadedImages.size()); + loadedRegions.reserve(m_loadedRegions.size()); + for (const auto& loadedImage : m_loadedImages) + loadedImages.push_back(loadedImage); + for (const auto& loadedRegion : m_loadedRegions) + loadedRegions.push_back(loadedRegion); + + controllerMeta["loadedImages"] = new Metadata(loadedImages); + controllerMeta["loadedRegions"] = new Metadata(loadedRegions); + + return new Metadata(controllerMeta); +} + +void SharedCacheController::LoadMetadata(const Metadata& metadata) +{ + auto controllerMeta = metadata.GetKeyValueStore(); + if (controllerMeta.find("loadedImages") != controllerMeta.end()) + { + const auto loadedImages = controllerMeta["loadedImages"]->GetUnsignedIntegerList(); + for (const auto& image : loadedImages) + m_loadedImages.insert(image); + } + + if (controllerMeta.find("loadedRegions") != controllerMeta.end()) + { + const auto loadedRegions = controllerMeta["loadedRegions"]->GetUnsignedIntegerList(); + for (const auto& region : loadedRegions) + m_loadedImages.insert(region); + } +} -- cgit v1.3.1