diff --git a/core/adapters/corelliumadapter.cpp b/core/adapters/corelliumadapter.cpp index 2b3c7b6f..8f30562d 100644 --- a/core/adapters/corelliumadapter.cpp +++ b/core/adapters/corelliumadapter.cpp @@ -669,6 +669,14 @@ std::vector CorelliumAdapter::GetModuleList() } +std::vector CorelliumAdapter::GetMemoryRegions() +{ + // Corellium adapter currently doesn't implement memory region enumeration + // TODO: Implement this by querying the Corellium-specific memory layout APIs + return {}; +} + + std::string CorelliumAdapter::GetTargetArchitecture() { return m_remoteArch; diff --git a/core/adapters/corelliumadapter.h b/core/adapters/corelliumadapter.h index 805b8783..0a8b2c5e 100644 --- a/core/adapters/corelliumadapter.h +++ b/core/adapters/corelliumadapter.h @@ -106,6 +106,8 @@ namespace BinaryNinjaDebugger std::string GetRemoteFile(const std::string& path); std::vector GetModuleList() override; + std::vector GetMemoryRegions() override; + std::string GetTargetArchitecture() override; DebugStopReason StopReason() override; diff --git a/core/adapters/dbgengadapter.cpp b/core/adapters/dbgengadapter.cpp index d6919beb..3b85436d 100644 --- a/core/adapters/dbgengadapter.cpp +++ b/core/adapters/dbgengadapter.cpp @@ -1207,6 +1207,70 @@ std::vector DbgEngAdapter::GetModuleList() return modules; } + +std::vector DbgEngAdapter::GetMemoryRegions() +{ + std::vector regions; + + if (!this->m_debugDataSpaces) + return regions; + + // Start from address 0 and enumerate all virtual memory regions + ULONG64 address = 0; + MEMORY_BASIC_INFORMATION64 mbi; + + while (true) + { + HRESULT hr = this->m_debugDataSpaces->QueryVirtual(address, &mbi); + if (hr != S_OK) + break; + + // Only include committed memory regions + if (mbi.State == MEM_COMMIT) + { + uint32_t permissions = 0; + if (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) + permissions |= DebugMemoryRegion::PermRead; + if (mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) + permissions |= DebugMemoryRegion::PermWrite; + if (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) + permissions |= DebugMemoryRegion::PermExecute; + + std::string name; + switch (mbi.Type) + { + case MEM_IMAGE: + name = "[image]"; + break; + case MEM_MAPPED: + name = "[mapped]"; + break; + case MEM_PRIVATE: + name = "[private]"; + break; + default: + name = "[unknown]"; + break; + } + + regions.emplace_back( + mbi.BaseAddress, + mbi.BaseAddress + mbi.RegionSize, + permissions, + name, + "" + ); + } + + // Move to next region + address = mbi.BaseAddress + mbi.RegionSize; + if (address == 0) // Overflow check + break; + } + + return regions; +} + bool DbgEngAdapter::BreakInto() { if (ExecStatus() == DEBUG_STATUS_BREAK || ExecStatus() == DEBUG_STATUS_NO_DEBUGGEE) diff --git a/core/adapters/dbgengadapter.h b/core/adapters/dbgengadapter.h index c802095c..f5cefeed 100644 --- a/core/adapters/dbgengadapter.h +++ b/core/adapters/dbgengadapter.h @@ -212,6 +212,8 @@ namespace BinaryNinjaDebugger { // bool WriteMemory(std::uintptr_t address, const void* out, std::size_t size) override; std::vector GetModuleList() override; + std::vector GetMemoryRegions() override; + std::string GetTargetArchitecture() override; DebugStopReason StopReason() override; diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index 02ba52e0..36001f45 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -790,6 +790,69 @@ std::vector GdbAdapter::GetModuleList() } +std::vector GdbAdapter::GetMemoryRegions() +{ + if (m_isTargetRunning) + return {}; + + const auto path = "/proc/" + std::to_string(this->m_lastActiveThreadId) + "/maps"; + std::string data = GetRemoteFile(path); + if (data.empty()) + return {}; + + std::vector regions; + + for (const std::string& line: RspConnector::Split(data, "\n")) + { + std::string_view v = line; + v.remove_prefix(std::min(v.find_first_not_of(" "), v.size())); + auto trimPosition = v.find_last_not_of(" "); + if (trimPosition != v.npos) + v.remove_suffix(v.size() - trimPosition - 1); + + // regex_match() requires the first argument to be const + const std::string trimmedLine = std::string(v); + + std::smatch match; + // Pattern: start-end permissions offset dev inode pathname + // Example: 00400000-00401000 r-xp 00000000 08:01 1234567 /bin/ls + const std::regex region_regex("^([0-9a-f]+)-([0-9a-f]+) ([rwxp-]{4}) [0-9a-f]+ [0-9a-f]+:[0-9a-f]+ [0-9]+(?: (.*))?$"); + bool found = std::regex_match(trimmedLine, match, region_regex); + if (found && match.size() >= 4) + { + std::string startString = match[1].str(); + uint64_t start = std::strtoull(startString.c_str(), nullptr, 16); + std::string endString = match[2].str(); + uint64_t end = std::strtoull(endString.c_str(), nullptr, 16); + std::string perms = match[3].str(); + std::string name = (match.size() > 4) ? match[4].str() : ""; + + // Parse permissions + uint32_t permissions = 0; + if (perms[0] == 'r') permissions |= DebugMemoryRegion::PermRead; + if (perms[1] == 'w') permissions |= DebugMemoryRegion::PermWrite; + if (perms[2] == 'x') permissions |= DebugMemoryRegion::PermExecute; + + // Determine region type/name if empty + if (name.empty()) + { + if (permissions & DebugMemoryRegion::PermExecute) + name = "[executable]"; + else if (permissions & DebugMemoryRegion::PermWrite) + name = "[heap/stack]"; + else + name = "[anonymous]"; + } + + DebugMemoryRegion region(start, end, permissions, name, name); + regions.push_back(region); + } + } + + return regions; +} + + std::string GdbAdapter::GetTargetArchitecture() { return m_remoteArch; diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index ebcd2e0f..1bbd4252 100644 --- a/core/adapters/gdbadapter.h +++ b/core/adapters/gdbadapter.h @@ -124,6 +124,8 @@ namespace BinaryNinjaDebugger std::string GetRemoteFile(const std::string& path); std::vector GetModuleList() override; + std::vector GetMemoryRegions() override; + std::string GetTargetArchitecture() override; DebugStopReason StopReason() override; diff --git a/core/adapters/lldbadapter.cpp b/core/adapters/lldbadapter.cpp index d03d8af9..8ee022b8 100644 --- a/core/adapters/lldbadapter.cpp +++ b/core/adapters/lldbadapter.cpp @@ -1214,6 +1214,66 @@ std::vector LldbAdapter::GetModuleList() } +std::vector LldbAdapter::GetMemoryRegions() +{ + std::vector regions; + + if (!m_process.IsValid() || m_process.GetState() != lldb::eStateStopped) + return regions; + + // LLDB doesn't have a direct API to enumerate all memory regions + // We'll use the memory region info API to build a list + // by probing the address space in chunks + + lldb::addr_t address = 0; + + while (address != LLDB_INVALID_ADDRESS) + { + lldb::SBMemoryRegionInfo regionInfo; + lldb::SBError error = m_process.GetMemoryRegionInfo(address, regionInfo); + + if (error.Fail()) + break; + + if (regionInfo.IsMapped()) + { + uint32_t permissions = 0; + if (regionInfo.IsReadable()) permissions |= DebugMemoryRegion::PermRead; + if (regionInfo.IsWritable()) permissions |= DebugMemoryRegion::PermWrite; + if (regionInfo.IsExecutable()) permissions |= DebugMemoryRegion::PermExecute; + + std::string name = regionInfo.GetName() ? regionInfo.GetName() : ""; + if (name.empty()) + { + if (permissions & DebugMemoryRegion::PermExecute) + name = "[executable]"; + else if (permissions & DebugMemoryRegion::PermWrite) + name = "[heap/stack]"; + else + name = "[anonymous]"; + } + + DebugMemoryRegion region( + regionInfo.GetRegionBase(), + regionInfo.GetRegionEnd(), + permissions, + name, + name + ); + regions.push_back(region); + } + + // Move to the next region + lldb::addr_t nextAddress = regionInfo.GetRegionEnd(); + if (nextAddress <= address) + break; // Prevent infinite loop + address = nextAddress; + } + + return regions; +} + + std::string LldbAdapter::GetTargetArchitecture() { SBPlatform platform = m_target.GetPlatform(); diff --git a/core/adapters/lldbadapter.h b/core/adapters/lldbadapter.h index e1f42984..0ebca48b 100644 --- a/core/adapters/lldbadapter.h +++ b/core/adapters/lldbadapter.h @@ -106,6 +106,8 @@ namespace BinaryNinjaDebugger { std::vector GetModuleList() override; + std::vector GetMemoryRegions() override; + std::string GetTargetArchitecture() override; DebugStopReason StopReason() override; diff --git a/core/debugadapter.h b/core/debugadapter.h index c6e3d5bc..c21dc50a 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -189,6 +189,32 @@ namespace BinaryNinjaDebugger { {} }; + struct DebugMemoryRegion + { + std::uintptr_t m_start = 0; + std::uintptr_t m_end = 0; + uint32_t m_permissions = 0; // flags for read/write/execute permissions + std::string m_name; + std::string m_module; // associated module/file if applicable + + // Permission flags + static constexpr uint32_t PermRead = 1; + static constexpr uint32_t PermWrite = 2; + static constexpr uint32_t PermExecute = 4; + + DebugMemoryRegion() = default; + DebugMemoryRegion(std::uintptr_t start, std::uintptr_t end, uint32_t permissions, + const std::string& name = "", const std::string& module = "") : + m_start(start), m_end(end), m_permissions(permissions), m_name(name), m_module(module) + {} + + std::size_t GetSize() const { return m_end > m_start ? m_end - m_start : 0; } + bool IsReadable() const { return (m_permissions & PermRead) != 0; } + bool IsWritable() const { return (m_permissions & PermWrite) != 0; } + bool IsExecutable() const { return (m_permissions & PermExecute) != 0; } + bool Contains(std::uintptr_t address) const { return address >= m_start && address < m_end; } + }; + class DebuggerController; class DebugAdapter { @@ -276,6 +302,8 @@ namespace BinaryNinjaDebugger { virtual std::vector GetModuleList() = 0; + virtual std::vector GetMemoryRegions() { return {}; } + virtual std::string GetTargetArchitecture() = 0; virtual DebugStopReason StopReason() = 0; diff --git a/core/debuggerstate.cpp b/core/debuggerstate.cpp index 0de03083..67d57cbd 100644 --- a/core/debuggerstate.cpp +++ b/core/debuggerstate.cpp @@ -512,6 +512,85 @@ std::vector DebuggerModules::GetAllModules() } +DebuggerMemoryRegions::DebuggerMemoryRegions(DebuggerState* state) : m_state(state) +{ + MarkDirty(); +} + + +void DebuggerMemoryRegions::MarkDirty() +{ + std::unique_lock lock(m_regionsMutex); + m_dirty = true; + m_regions.clear(); +} + + +void DebuggerMemoryRegions::Update() +{ + DebugAdapter* adapter = m_state->GetAdapter(); + if (!adapter) + return; + + if (!m_state->IsConnected()) + return; + + std::unique_lock lock(m_regionsMutex); + m_regions = adapter->GetMemoryRegions(); + m_dirty = false; +} + + +std::vector DebuggerMemoryRegions::GetAllRegions() +{ + if (m_dirty) + Update(); + + std::unique_lock lock(m_regionsMutex); + return m_regions; +} + + +DebugMemoryRegion DebuggerMemoryRegions::GetRegionForAddress(uint64_t address) +{ + auto regions = GetAllRegions(); + for (const auto& region : regions) + { + if (region.Contains(address)) + return region; + } + return DebugMemoryRegion{}; // Return empty region if not found +} + + +bool DebuggerMemoryRegions::IsAddressValid(uint64_t address) +{ + auto region = GetRegionForAddress(address); + return region.GetSize() > 0; // Non-empty region means address is valid +} + + +bool DebuggerMemoryRegions::IsAddressReadable(uint64_t address) +{ + auto region = GetRegionForAddress(address); + return region.IsReadable(); +} + + +bool DebuggerMemoryRegions::IsAddressWritable(uint64_t address) +{ + auto region = GetRegionForAddress(address); + return region.IsWritable(); +} + + +bool DebuggerMemoryRegions::IsAddressExecutable(uint64_t address) +{ + auto region = GetRegionForAddress(address); + return region.IsExecutable(); +} + + DebuggerBreakpoints::DebuggerBreakpoints(DebuggerState* state, std::vector initial) : m_state(state), m_breakpoints(std::move(initial)) {} @@ -777,6 +856,18 @@ DataBuffer DebuggerMemory::ReadBlock(uint64_t block) if (!m_state->IsConnected()) return {}; + // Check if the block address is in a valid memory region + auto regions = m_state->GetMemoryRegions()->GetAllRegions(); + if (!regions.empty()) // Only check regions if they are available + { + if (!m_state->GetMemoryRegions()->IsAddressReadable(block)) + { + // Address is not in a readable memory region, return empty buffer + m_valueCache[block] = {{}, FailedToReadStatus, NoSource}; + return {}; + } + } + auto iter = m_valueCache.find(block); if (iter != m_valueCache.end()) { @@ -905,6 +996,7 @@ DebuggerState::DebuggerState(BinaryViewRef data, DebuggerController* controller) m_adapter = nullptr; m_modules = new DebuggerModules(this); + m_memoryRegions = new DebuggerMemoryRegions(this); m_registers = new DebuggerRegisters(this); m_threads = new DebuggerThreads(this); m_breakpoints = new DebuggerBreakpoints(this); @@ -921,6 +1013,7 @@ DebuggerState::~DebuggerState() { delete m_adapter; delete m_modules; + delete m_memoryRegions; delete m_registers; delete m_threads; delete m_breakpoints; @@ -1013,6 +1106,7 @@ void DebuggerState::MarkDirty() m_registers->MarkDirty(); m_threads->MarkDirty(); m_modules->MarkDirty(); + m_memoryRegions->MarkDirty(); m_memory->MarkDirty(); } @@ -1033,6 +1127,9 @@ void DebuggerState::UpdateCaches() if (m_modules->IsDirty()) m_modules->Update(); + + if (m_memoryRegions->IsDirty()) + m_memoryRegions->Update(); } diff --git a/core/debuggerstate.h b/core/debuggerstate.h index 82719cd1..d0f4cfc1 100644 --- a/core/debuggerstate.h +++ b/core/debuggerstate.h @@ -77,6 +77,29 @@ namespace BinaryNinjaDebugger { }; + class DebuggerMemoryRegions + { + private: + DebuggerState* m_state; + std::vector m_regions; + bool m_dirty; + std::recursive_mutex m_regionsMutex; + + public: + DebuggerMemoryRegions(DebuggerState* state); + void MarkDirty(); + void Update(); + bool IsDirty() const { return m_dirty; } + + std::vector GetAllRegions(); + DebugMemoryRegion GetRegionForAddress(uint64_t address); + bool IsAddressValid(uint64_t address); + bool IsAddressReadable(uint64_t address); + bool IsAddressWritable(uint64_t address); + bool IsAddressExecutable(uint64_t address); + }; + + class DebuggerBreakpoints { private: @@ -181,6 +204,7 @@ namespace BinaryNinjaDebugger { DebugAdapter* m_adapter; DebuggerModules* m_modules; + DebuggerMemoryRegions* m_memoryRegions; DebuggerRegisters* m_registers; DebuggerThreads* m_threads; DebuggerBreakpoints* m_breakpoints; @@ -203,6 +227,7 @@ namespace BinaryNinjaDebugger { DebuggerController* GetController() const { return m_controller; } DebuggerModules* GetModules() const { return m_modules; } + DebuggerMemoryRegions* GetMemoryRegions() const { return m_memoryRegions; } DebuggerBreakpoints* GetBreakpoints() const { return m_breakpoints; } DebuggerRegisters* GetRegisters() const { return m_registers; } DebuggerThreads* GetThreads() const { return m_threads; }