Skip to content

Commit 158df93

Browse files
committed
[MachO] Set segment flags for kernel images based on how XNU initially maps them
This was done for kernel cache in #7519, and is now being extended to Mach-O images that appear to be XNU kernels (that is, they contain `__KLD` segments). This improves the experience when opening kernels from the macOS Kernel Debug Kit.
1 parent e5805fe commit 158df93

File tree

1 file changed

+164
-28
lines changed

1 file changed

+164
-28
lines changed

view/macho/machoview.cpp

Lines changed: 164 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ using namespace std;
2323

2424
static MachoViewType* g_machoViewType = nullptr;
2525

26-
static string CommandToString(uint32_t lcCommand)
26+
namespace {
27+
28+
string CommandToString(uint32_t lcCommand)
2729
{
2830
switch(lcCommand)
2931
{
@@ -91,7 +93,7 @@ static string CommandToString(uint32_t lcCommand)
9193
}
9294

9395

94-
static string BuildPlatformToString(uint32_t platform)
96+
string BuildPlatformToString(uint32_t platform)
9597
{
9698
switch (platform)
9799
{
@@ -110,7 +112,7 @@ static string BuildPlatformToString(uint32_t platform)
110112
}
111113

112114

113-
static string BuildToolToString(uint32_t tool)
115+
string BuildToolToString(uint32_t tool)
114116
{
115117
switch (tool)
116118
{
@@ -127,7 +129,7 @@ static string BuildToolToString(uint32_t tool)
127129
}
128130

129131

130-
static string BuildToolVersionToString(uint32_t version)
132+
string BuildToolVersionToString(uint32_t version)
131133
{
132134
uint32_t major = (version >> 16) & 0xffff;
133135
uint32_t minor = (version >> 8) & 0xff;
@@ -139,15 +141,7 @@ static string BuildToolVersionToString(uint32_t version)
139141
}
140142

141143

142-
void BinaryNinja::InitMachoViewType()
143-
{
144-
static MachoViewType type;
145-
BinaryViewType::Register(&type);
146-
g_machoViewType = &type;
147-
}
148-
149-
150-
static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t &offset)
144+
int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t &offset)
151145
{
152146
uint8_t cur;
153147
int64_t value = 0;
@@ -165,7 +159,7 @@ static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t &offset)
165159
}
166160

167161

168-
static uint64_t readLEB128(DataBuffer& p, size_t end, size_t &offset)
162+
uint64_t readLEB128(DataBuffer& p, size_t end, size_t &offset)
169163
{
170164
uint64_t result = 0;
171165
int bit = 0;
@@ -194,7 +188,7 @@ uint64_t readValidULEB128(DataBuffer& buffer, size_t& cursor)
194188
return value;
195189
}
196190

197-
static void CollectSectionByType(MachOHeader& header, section_64& sect)
191+
void CollectSectionByType(MachOHeader& header, section_64& sect)
198192
{
199193
uint32_t sectionType = sect.flags & SECTION_TYPE;
200194
switch (sectionType)
@@ -219,6 +213,150 @@ static void CollectSectionByType(MachOHeader& header, section_64& sect)
219213
}
220214
}
221215

216+
// TODO: This logic for determining semantics for XNU segments is duplicated in kernelcache.
217+
// Protection combinations used in XNU. Named to match the conventions in arm_vm_init.c
218+
constexpr uint32_t PROT_RNX = SegmentReadable | SegmentContainsData | SegmentDenyWrite | SegmentDenyExecute;
219+
constexpr uint32_t PROT_ROX = SegmentReadable | SegmentExecutable | SegmentContainsCode | SegmentDenyWrite;
220+
constexpr uint32_t PROT_RWNX = SegmentReadable | SegmentWritable | SegmentContainsData | SegmentDenyExecute;
221+
222+
struct XNUSegmentProtection {
223+
std::string_view name;
224+
uint32_t protection;
225+
};
226+
227+
// Protections taken from arm_vm_prot_init at
228+
// https://github.com/apple-oss-distributions/xnu/blob/xnu-12377.1.9/osfmk/arm64/arm_vm_init.c
229+
constexpr std::array<XNUSegmentProtection, 22> s_initialSegmentProtections = {{
230+
// Core XNU Kernel Segments
231+
{"__TEXT", PROT_RNX},
232+
{"__TEXT_EXEC", PROT_ROX},
233+
{"__DATA_CONST", PROT_RWNX},
234+
{"__DATA", PROT_RWNX},
235+
{"__HIB", PROT_RWNX},
236+
{"__BOOTDATA", PROT_RWNX},
237+
{"__KLD", PROT_ROX},
238+
{"__KLDDATA", PROT_RNX},
239+
{"__LINKEDIT", PROT_RWNX},
240+
{"__LAST", PROT_ROX},
241+
{"__LASTDATA_CONST", PROT_RWNX},
242+
243+
// Prelinked Kext Segments
244+
{"__PRELINK_TEXT", PROT_RWNX},
245+
{"__PLK_DATA_CONST", PROT_RWNX},
246+
{"__PLK_TEXT_EXEC", PROT_ROX},
247+
{"__PRELINK_DATA", PROT_RWNX},
248+
{"__PLK_LINKEDIT", PROT_RWNX},
249+
{"__PRELINK_INFO", PROT_RWNX},
250+
{"__PLK_LLVM_COV", PROT_RWNX},
251+
252+
// PPL (Page Protection Layer) Segments
253+
{"__PPLTEXT", PROT_ROX},
254+
{"__PPLTRAMP", PROT_ROX},
255+
{"__PPLDATA_CONST", PROT_RNX},
256+
{"__PPLDATA", PROT_RWNX},
257+
}};
258+
259+
std::string FormatSegmentFlags(uint32_t flags)
260+
{
261+
std::string perms;
262+
perms += (flags & SegmentReadable) ? 'R' : '-';
263+
perms += (flags & SegmentWritable) ? 'W' : '-';
264+
perms += (flags & SegmentExecutable) ? 'X' : '-';
265+
266+
std::string type;
267+
if (flags & SegmentContainsCode)
268+
type = " [CODE]";
269+
else if (flags & SegmentContainsData)
270+
type = " [DATA]";
271+
272+
std::string denies;
273+
if (flags & SegmentDenyWrite)
274+
denies += 'W';
275+
if (flags & SegmentDenyExecute)
276+
denies += 'X';
277+
if (!denies.empty())
278+
denies = fmt::format(" (deny:{})", denies);
279+
280+
return fmt::format("{}{}{}", perms, type, denies);
281+
}
282+
283+
// XNU maps certain segments with specific protections regardless of what is in the load command.
284+
uint32_t SegmentFlagsForKnownXNUSegment(std::string_view segmentName)
285+
{
286+
for (const auto& entry : s_initialSegmentProtections)
287+
{
288+
if (segmentName == entry.name)
289+
return entry.protection;
290+
}
291+
return 0;
292+
}
293+
294+
uint32_t SegmentFlagsFromMachOProtections(int initProt, int maxProt)
295+
{
296+
uint32_t flags = 0;
297+
if (initProt & MACHO_VM_PROT_READ)
298+
flags |= SegmentReadable;
299+
if (initProt & MACHO_VM_PROT_WRITE)
300+
flags |= SegmentWritable;
301+
if (initProt & MACHO_VM_PROT_EXECUTE)
302+
flags |= SegmentExecutable;
303+
if ((initProt & MACHO_VM_PROT_WRITE) == 0 && (maxProt & MACHO_VM_PROT_WRITE) == 0)
304+
flags |= SegmentDenyWrite;
305+
if ((initProt & MACHO_VM_PROT_EXECUTE) == 0 && (maxProt & MACHO_VM_PROT_EXECUTE) == 0)
306+
flags |= SegmentDenyExecute;
307+
return static_cast<BNSegmentFlag>(flags);
308+
}
309+
310+
// Determine segment flags for Mach-O segments, applying XNU overrides if necessary.
311+
uint32_t SegmentFlagsForSegment(bool isXNU, const segment_command_64& segment)
312+
{
313+
std::string_view segmentName(segment.segname, std::find(segment.segname, std::end(segment.segname), '\0'));
314+
uint32_t flagsFromLoadCommand = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot);
315+
if (!isXNU)
316+
return flagsFromLoadCommand;
317+
318+
if (uint32_t flagsFromKnownXNUSegment = SegmentFlagsForKnownXNUSegment(segmentName))
319+
{
320+
constexpr int MASK = ~(SegmentContainsData | SegmentContainsCode);
321+
if ((flagsFromKnownXNUSegment & MASK) != (flagsFromLoadCommand & MASK))
322+
LogDebugF("Overriding segment protections from load command ({}) with known segment protections {} for segment {} ({:#x} - {:#x})",
323+
FormatSegmentFlags(flagsFromLoadCommand), FormatSegmentFlags(flagsFromKnownXNUSegment), segmentName,
324+
segment.vmaddr, segment.vmaddr + segment.vmsize);
325+
return flagsFromKnownXNUSegment;
326+
}
327+
328+
return flagsFromLoadCommand;
329+
}
330+
331+
// Determine overridden section semantics for XNU mapped segments.
332+
// Returns 0 if no overrides are necessary (not XNU or no overrides for the segment).
333+
uint32_t OverriddenSectionSemanticsForSection(bool isXNU, const section_64& section)
334+
{
335+
if (!isXNU)
336+
return 0;
337+
338+
std::string_view segmentName(section.segname, std::find(section.segname, std::end(section.segname), '\0'));
339+
int flags = SegmentFlagsForKnownXNUSegment(segmentName);
340+
if (!flags)
341+
return 0;
342+
343+
if (flags & SegmentExecutable)
344+
return ReadOnlyCodeSectionSemantics;
345+
346+
if (flags & SegmentWritable)
347+
return ReadWriteDataSectionSemantics;
348+
349+
return ReadOnlyDataSectionSemantics;
350+
}
351+
352+
} // unnamed namespace
353+
354+
void BinaryNinja::InitMachoViewType()
355+
{
356+
static MachoViewType type;
357+
BinaryViewType::Register(&type);
358+
g_machoViewType = &type;
359+
}
222360

223361
MachoView::MachoView(const string& typeName, BinaryView* data, bool parseOnly): BinaryView(typeName, data->GetFile(), data),
224362
m_universalImageOffset(0), m_parseOnly(parseOnly)
@@ -1612,26 +1750,21 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_
16121750
}
16131751
}
16141752

1753+
// If the binary contains a __KLD segment, segment flags will be based on XNU's known
1754+
// initial segment permissions rather than the flags stored in the Mach-O headers.
1755+
bool isXNU = std::any_of(header.segments.begin(), header.segments.end(),
1756+
[](const segment_command_64& seg) {
1757+
return strncmp(seg.segname, "__KLD", 16) == 0;
1758+
});
1759+
16151760
if (!(m_header.ident.filetype == MH_FILESET && isMainHeader)) \
16161761
{
16171762
BeginBulkAddSegments();
16181763
for (auto &segment: header.segments) {
16191764
if ((segment.initprot == MACHO_VM_PROT_NONE) || (!segment.vmsize))
16201765
continue;
16211766

1622-
uint32_t flags = 0;
1623-
if (segment.initprot & MACHO_VM_PROT_READ)
1624-
flags |= SegmentReadable;
1625-
if (segment.initprot & MACHO_VM_PROT_WRITE)
1626-
flags |= SegmentWritable;
1627-
if (segment.initprot & MACHO_VM_PROT_EXECUTE)
1628-
flags |= SegmentExecutable;
1629-
if (((segment.initprot & MACHO_VM_PROT_WRITE) == 0) &&
1630-
((segment.maxprot & MACHO_VM_PROT_WRITE) == 0))
1631-
flags |= SegmentDenyWrite;
1632-
if (((segment.initprot & MACHO_VM_PROT_EXECUTE) == 0) &&
1633-
((segment.maxprot & MACHO_VM_PROT_EXECUTE) == 0))
1634-
flags |= SegmentDenyExecute;
1767+
uint32_t flags = SegmentFlagsForSegment(isXNU, segment);
16351768

16361769
// if we're positive we have an entry point for some reason, force the segment
16371770
// executable. this helps with kernel images.
@@ -1804,6 +1937,9 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_
18041937
header.chainStarts = header.sections[i];
18051938
}
18061939

1940+
if (uint32_t overriddenSemantics = OverriddenSectionSemanticsForSection(isXNU, header.sections[i]))
1941+
semantics = static_cast<BNSectionSemantics>(overriddenSemantics);
1942+
18071943
AddAutoSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, type, header.sections[i].align);
18081944
}
18091945
if (isMainHeader)

0 commit comments

Comments
 (0)