summaryrefslogtreecommitdiff
path: root/view/kernelcache
diff options
context:
space:
mode:
Diffstat (limited to 'view/kernelcache')
-rw-r--r--view/kernelcache/core/KernelCache.cpp2
-rw-r--r--view/kernelcache/core/KernelCacheController.cpp7
-rw-r--r--view/kernelcache/core/MachOProcessor.cpp5
-rw-r--r--view/kernelcache/core/Utility.cpp146
-rw-r--r--view/kernelcache/core/Utility.h8
5 files changed, 146 insertions, 22 deletions
diff --git a/view/kernelcache/core/KernelCache.cpp b/view/kernelcache/core/KernelCache.cpp
index 3750e795..ef5fd8a5 100644
--- a/view/kernelcache/core/KernelCache.cpp
+++ b/view/kernelcache/core/KernelCache.cpp
@@ -77,7 +77,7 @@ bool KernelCache::ProcessEntryImage(Ref<BinaryView> bv, const std::string& path,
// Associate this region with this image, this makes it easier to identify what image owns this region.
sectionRegion.imageStart = image.headerFileAddress;
- uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot);
+ uint32_t flags = SegmentFlagsForSegment(segment);
// if we're positive we have an entry point for some reason, force the segment
// executable. this helps with kernel images.
for (const auto& entryPoint : imageHeader->m_entryPoints)
diff --git a/view/kernelcache/core/KernelCacheController.cpp b/view/kernelcache/core/KernelCacheController.cpp
index 40d4a1df..753c28ad 100644
--- a/view/kernelcache/core/KernelCacheController.cpp
+++ b/view/kernelcache/core/KernelCacheController.cpp
@@ -124,10 +124,13 @@ bool KernelCacheController::ApplyImage(BinaryView& view, const CacheImage& image
loadedRegion = true;
for (const auto& segment : image.header->segments)
{
- auto flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot);
+ if (segment.vmsize == 0)
+ continue;
+
+ auto flags = SegmentFlagsForSegment(segment);
view.AddAutoSegment(segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize, flags);
- auto relocations = m_cache.GetRelocations();
+ const auto& relocations = m_cache.GetRelocations();
auto begin = std::lower_bound(relocations.begin(), relocations.end(), segment.vmaddr,
[](const std::pair<uint64_t, uint64_t>& reloc, uint64_t addr) {
diff --git a/view/kernelcache/core/MachOProcessor.cpp b/view/kernelcache/core/MachOProcessor.cpp
index 42faba24..fb3d2b27 100644
--- a/view/kernelcache/core/MachOProcessor.cpp
+++ b/view/kernelcache/core/MachOProcessor.cpp
@@ -193,8 +193,9 @@ uint64_t KernelCacheMachOProcessor::ApplyHeaderSections(KernelCacheMachOHeader&
semantics = ReadWriteDataSectionSemantics;
if (strncmp(section.sectname, "__auth_got", sizeof(section.sectname)) == 0)
semantics = ReadOnlyDataSectionSemantics;
- if (strncmp(section.segname, "__DATA_CONST", sizeof(section.segname)) == 0)
- semantics = ReadOnlyDataSectionSemantics;
+
+ if (auto overriddenSemantics = SectionSemanticsForSection(section))
+ semantics = static_cast<BNSectionSemantics>(overriddenSemantics);
// Typically a view would add auto sections but those won't persist when loading the BNDB.
// if we want to use an auto section here we would need to allow the core to apply auto sections from the database.
diff --git a/view/kernelcache/core/Utility.cpp b/view/kernelcache/core/Utility.cpp
index 515d62c7..d2788faf 100644
--- a/view/kernelcache/core/Utility.cpp
+++ b/view/kernelcache/core/Utility.cpp
@@ -4,22 +4,6 @@
using namespace BinaryNinja;
-BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt)
-{
- uint32_t flags = 0;
- if (initProt & MACHO_VM_PROT_READ)
- flags |= SegmentReadable;
- if (initProt & MACHO_VM_PROT_WRITE)
- flags |= SegmentWritable;
- if (initProt & MACHO_VM_PROT_EXECUTE)
- flags |= SegmentExecutable;
- if (((initProt & MACHO_VM_PROT_WRITE) == 0) && ((maxProt & MACHO_VM_PROT_WRITE) == 0))
- flags |= SegmentDenyWrite;
- if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && ((maxProt & MACHO_VM_PROT_EXECUTE) == 0))
- flags |= SegmentDenyExecute;
- return static_cast<BNSegmentFlag>(flags);
-}
-
int64_t readSLEB128(const uint8_t*& current, const uint8_t* end)
{
uint8_t cur;
@@ -157,3 +141,133 @@ bool IsSameFolder(Ref<ProjectFolder> a, Ref<ProjectFolder> b)
return a->GetId() == b->GetId();
return false;
}
+
+namespace {
+
+// Protection combinations used in XNU. Named to match the conventions in arm_vm_init.c
+constexpr uint32_t PROT_RNX = SegmentReadable | SegmentContainsData | SegmentDenyWrite | SegmentDenyExecute;
+constexpr uint32_t PROT_ROX = SegmentReadable | SegmentExecutable | SegmentContainsCode | SegmentDenyWrite;
+constexpr uint32_t PROT_RWNX = SegmentReadable | SegmentWritable | SegmentContainsData | SegmentDenyExecute;
+
+struct XNUSegmentProtection {
+ std::string_view name;
+ uint32_t protection;
+};
+
+// Protections taken from arm_vm_prot_init at
+// https://github.com/apple-oss-distributions/xnu/blob/xnu-12377.1.9/osfmk/arm64/arm_vm_init.c
+constexpr std::array<XNUSegmentProtection, 22> s_initialSegmentProtections = {{
+ // Core XNU Kernel Segments
+ {"__TEXT", PROT_RNX},
+ {"__TEXT_EXEC", PROT_ROX},
+ {"__DATA_CONST", PROT_RWNX},
+ {"__DATA", PROT_RWNX},
+ {"__HIB", PROT_RWNX},
+ {"__BOOTDATA", PROT_RWNX},
+ {"__KLD", PROT_ROX},
+ {"__KLDDATA", PROT_RNX},
+ {"__LINKEDIT", PROT_RWNX},
+ {"__LAST", PROT_ROX},
+ {"__LASTDATA_CONST", PROT_RWNX},
+
+ // Prelinked Kext Segments
+ {"__PRELINK_TEXT", PROT_RWNX},
+ {"__PLK_DATA_CONST", PROT_RWNX},
+ {"__PLK_TEXT_EXEC", PROT_ROX},
+ {"__PRELINK_DATA", PROT_RWNX},
+ {"__PLK_LINKEDIT", PROT_RWNX},
+ {"__PRELINK_INFO", PROT_RWNX},
+ {"__PLK_LLVM_COV", PROT_RWNX},
+
+ // PPL (Page Protection Layer) Segments
+ {"__PPLTEXT", PROT_ROX},
+ {"__PPLTRAMP", PROT_ROX},
+ {"__PPLDATA_CONST", PROT_RNX},
+ {"__PPLDATA", PROT_RWNX},
+}};
+
+std::string FormatSegmentFlags(uint32_t flags)
+{
+ std::string perms;
+ perms += (flags & SegmentReadable) ? 'R' : '-';
+ perms += (flags & SegmentWritable) ? 'W' : '-';
+ perms += (flags & SegmentExecutable) ? 'X' : '-';
+
+ std::string type;
+ if (flags & SegmentContainsCode)
+ type = " [CODE]";
+ else if (flags & SegmentContainsData)
+ type = " [DATA]";
+
+ std::string denies;
+ if (flags & SegmentDenyWrite)
+ denies += 'W';
+ if (flags & SegmentDenyExecute)
+ denies += 'X';
+ if (!denies.empty())
+ denies = fmt::format(" (deny:{})", denies);
+
+ return fmt::format("{}{}{}", perms, type, denies);
+}
+
+// XNU maps certain segments with specific protections regardless of what is in the load command.
+uint32_t SegmentFlagsForKnownXNUSegment(std::string_view segmentName)
+{
+ for (const auto& entry : s_initialSegmentProtections)
+ {
+ if (segmentName == entry.name)
+ return entry.protection;
+ }
+ return 0;
+}
+
+uint32_t SegmentFlagsFromMachOProtections(int initProt, int maxProt)
+{
+ uint32_t flags = 0;
+ if (initProt & MACHO_VM_PROT_READ)
+ flags |= SegmentReadable;
+ if (initProt & MACHO_VM_PROT_WRITE)
+ flags |= SegmentWritable;
+ if (initProt & MACHO_VM_PROT_EXECUTE)
+ flags |= SegmentExecutable;
+ if ((initProt & MACHO_VM_PROT_WRITE) == 0 && (maxProt & MACHO_VM_PROT_WRITE) == 0)
+ flags |= SegmentDenyWrite;
+ if ((initProt & MACHO_VM_PROT_EXECUTE) == 0 && (maxProt & MACHO_VM_PROT_EXECUTE) == 0)
+ flags |= SegmentDenyExecute;
+ return static_cast<BNSegmentFlag>(flags);
+}
+
+} // unnamed namespace
+
+uint32_t SegmentFlagsForSegment(const segment_command_64& segment)
+{
+ std::string_view segmentName(segment.segname, std::find(segment.segname, std::end(segment.segname), '\0'));
+ uint32_t flagsFromLoadCommand = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot);
+ if (uint32_t flagsFromKnownXNUSegment = SegmentFlagsForKnownXNUSegment(segmentName))
+ {
+ constexpr int MASK = ~(SegmentContainsData | SegmentContainsCode);
+ if ((flagsFromKnownXNUSegment & MASK) != (flagsFromLoadCommand & MASK))
+ LogDebugF("Overriding segment protections from load command ({}) with known segment protections {} for segment {} ({:#x} - {:#x})",
+ FormatSegmentFlags(flagsFromLoadCommand), FormatSegmentFlags(flagsFromKnownXNUSegment), segmentName,
+ segment.vmaddr, segment.vmaddr + segment.vmsize);
+ return flagsFromKnownXNUSegment;
+ }
+
+ return flagsFromLoadCommand;
+}
+
+uint32_t SectionSemanticsForSection(const section_64& section)
+{
+ std::string_view segmentName(section.segname, std::find(section.segname, std::end(section.segname), '\0'));
+ int flags = SegmentFlagsForKnownXNUSegment(segmentName);
+ if (!flags)
+ return 0;
+
+ if (flags & SegmentExecutable)
+ return ReadOnlyCodeSectionSemantics;
+
+ if (flags & SegmentWritable)
+ return ReadWriteDataSectionSemantics;
+
+ return ReadOnlyDataSectionSemantics;
+}
diff --git a/view/kernelcache/core/Utility.h b/view/kernelcache/core/Utility.h
index 5664fd9b..6d0bf0a0 100644
--- a/view/kernelcache/core/Utility.h
+++ b/view/kernelcache/core/Utility.h
@@ -25,7 +25,13 @@ inline int CountTrailingZeros(uint64_t value)
}
#endif
-BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt);
+namespace BinaryNinja {
+ struct segment_command_64;
+ struct section_64;
+}
+
+uint32_t SegmentFlagsForSegment(const BinaryNinja::segment_command_64& segment);
+uint32_t SectionSemanticsForSection(const BinaryNinja::section_64& section);
int64_t readSLEB128(const uint8_t*& current, const uint8_t* end);