Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion view/kernelcache/core/KernelCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions view/kernelcache/core/KernelCacheController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions view/kernelcache/core/MachOProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
146 changes: 130 additions & 16 deletions view/kernelcache/core/Utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
8 changes: 7 additions & 1 deletion view/kernelcache/core/Utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading