diff options
| author | Mark Rowe <mark@vector35.com> | 2025-10-22 19:52:19 -0700 |
|---|---|---|
| committer | Mark Rowe <mark@vector35.com> | 2025-10-23 13:33:08 -0700 |
| commit | 9a8faad50f56d748fcd1498c170797c67bb53217 (patch) | |
| tree | 2b39b444d1c51c680a89d86de40af85b5f913592 /view/kernelcache/core/Utility.cpp | |
| parent | b57e208e1aa7f261cb1d0980cada3af1c05fe6d1 (diff) | |
[KernelCache] Set segment permissions based on how XNU initially maps them
XNU maps kernel cache segments in with different permissions than the
load commands indicate. For instance, `__DATA_CONST` is initially mapped
as read-write before later being re-mapped as read-only. Treating it as
read-only results in analysis falsely assuming that global variables
cannot change.
To work around this we maintain a mapping from segment name to initial
permissions (i.e., most lax permissions) and favor them over permissions
derived from the segment load command. Section semantics are also
derived from the segment's permissions when the segment is present in
the mapping.
The mapping is based on the initial permissions established by
`arm_vm_prot_init` within the XNU source.
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; +} |
