diff options
Diffstat (limited to 'view/kernelcache/core/Utility.cpp')
| -rw-r--r-- | view/kernelcache/core/Utility.cpp | 146 |
1 files changed, 130 insertions, 16 deletions
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; +} |
