@@ -23,7 +23,9 @@ using namespace std;
2323
2424static 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
223361MachoView::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