diff options
Diffstat (limited to 'view/kernelcache')
| -rw-r--r-- | view/kernelcache/core/KernelCache.cpp | 2 | ||||
| -rw-r--r-- | view/kernelcache/core/KernelCacheController.cpp | 7 | ||||
| -rw-r--r-- | view/kernelcache/core/MachOProcessor.cpp | 5 | ||||
| -rw-r--r-- | view/kernelcache/core/Utility.cpp | 146 | ||||
| -rw-r--r-- | view/kernelcache/core/Utility.h | 8 |
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); |
