mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-03-30 14:00:16 +02:00
Add IDA Pro MIG Subsystem Scanner for identifying and labeling MIG subsystems in Mach binaries
This commit is contained in:
189
X. NU/custom/mach_ipc/ida_mig_subsystem_scanner.py
Normal file
189
X. NU/custom/mach_ipc/ida_mig_subsystem_scanner.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
IDA Pro MIG Subsystem Scanner
|
||||
Automatically identifies and labels Mach Interface Generator (MIG) subsystems
|
||||
in XNU kernelcache, kext binaries and other Mach-based binaries.
|
||||
|
||||
Author: Karol Mazurek @karmaz95
|
||||
Based on: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
|
||||
Usage: Run from IDA Pro's script console or via File > Script file
|
||||
"""
|
||||
|
||||
import idc
|
||||
import idautils
|
||||
import idaapi
|
||||
import ida_bytes
|
||||
import ida_name
|
||||
import ida_segment
|
||||
import ida_funcs
|
||||
|
||||
|
||||
class MIGSubsystemScanner:
|
||||
"""Scanner for MIG subsystem structures in Mach kernel binaries."""
|
||||
|
||||
# Target segments where MIG subsystems are typically stored
|
||||
TARGET_SEGMENTS = [
|
||||
"__DATA:__const",
|
||||
"__CONST:__constdata",
|
||||
"__DATA_CONST:__const",
|
||||
"__const"
|
||||
]
|
||||
|
||||
# MIG subsystem structure offsets (arm64)
|
||||
OFFSET_START = 0x08 # subsystem start ID (u32)
|
||||
OFFSET_END = 0x0C # subsystem end ID (u32)
|
||||
OFFSET_RESERVED = 0x18 # reserved field (u64)
|
||||
OFFSET_ROUTINES = 0x20 # routine array start (u64)
|
||||
|
||||
# MIG routine descriptor size
|
||||
ROUTINE_SIZE = 0x28 # 40 bytes per routine
|
||||
ROUTINE_STUB_OFFSET = 0x08 # stub_routine pointer offset
|
||||
|
||||
def __init__(self):
|
||||
self.found_count = 0
|
||||
self.total_messages = 0
|
||||
|
||||
def is_valid_subsystem(self, addr):
|
||||
"""
|
||||
Validate potential MIG subsystem structure using heuristics.
|
||||
|
||||
Args:
|
||||
addr: Address to check
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid, start_id, end_id) or (False, 0, 0)
|
||||
"""
|
||||
# Check reserved field must be 0
|
||||
reserved = ida_bytes.get_qword(addr + self.OFFSET_RESERVED)
|
||||
if reserved != 0:
|
||||
return (False, 0, 0)
|
||||
|
||||
# Check first routine impl must be 0 (first entry is always NULL)
|
||||
routine0_impl = ida_bytes.get_qword(addr + self.OFFSET_ROUTINES)
|
||||
if routine0_impl != 0:
|
||||
return (False, 0, 0)
|
||||
|
||||
# Read subsystem ID range
|
||||
start_id = ida_bytes.get_dword(addr + self.OFFSET_START)
|
||||
end_id = ida_bytes.get_dword(addr + self.OFFSET_END)
|
||||
|
||||
# Validate ID range
|
||||
num_msgs = end_id - start_id
|
||||
if start_id == 0 or num_msgs <= 0 or num_msgs >= 1024:
|
||||
return (False, 0, 0)
|
||||
|
||||
return (True, start_id, end_id)
|
||||
|
||||
def label_subsystem(self, addr, start_id, end_id):
|
||||
"""
|
||||
Label MIG subsystem structure and its routine handlers.
|
||||
|
||||
Args:
|
||||
addr: Address of subsystem structure
|
||||
start_id: Starting message ID
|
||||
end_id: Ending message ID
|
||||
"""
|
||||
num_msgs = end_id - start_id
|
||||
subsys_name = f"MIG_subsystem_{start_id}"
|
||||
|
||||
print(f"[+] Found {subsys_name} at {hex(addr)}")
|
||||
print(f" Message range: {start_id} - {end_id} ({num_msgs} handlers)")
|
||||
|
||||
# Label the subsystem structure
|
||||
ida_name.set_name(addr, subsys_name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK)
|
||||
idc.set_cmt(addr, f"MIG Subsystem {start_id}-{end_id}", 0)
|
||||
|
||||
# Process each routine in the subsystem
|
||||
array_base = addr + self.OFFSET_ROUTINES
|
||||
labeled_count = 0
|
||||
|
||||
for i in range(num_msgs):
|
||||
routine_addr = array_base + (i * self.ROUTINE_SIZE)
|
||||
stub_ptr_addr = routine_addr + self.ROUTINE_STUB_OFFSET
|
||||
stub_func_ea = ida_bytes.get_qword(stub_ptr_addr)
|
||||
msg_id = start_id + i
|
||||
|
||||
# Skip NULL entries
|
||||
if stub_func_ea == 0 or stub_func_ea == 0xFFFFFFFFFFFFFFFF:
|
||||
continue
|
||||
|
||||
# Verify pointer points to valid code segment
|
||||
if not ida_segment.getseg(stub_func_ea):
|
||||
continue
|
||||
|
||||
# Create descriptive name for message handler
|
||||
handler_name = f"MIG_msg_{msg_id}_handler"
|
||||
|
||||
# Label the handler function
|
||||
if ida_name.set_name(stub_func_ea, handler_name, ida_name.SN_NOWARN):
|
||||
labeled_count += 1
|
||||
|
||||
# Make sure it's treated as a function
|
||||
if not ida_funcs.get_func(stub_func_ea):
|
||||
ida_funcs.add_func(stub_func_ea)
|
||||
|
||||
# Add comment at pointer location
|
||||
idc.set_cmt(stub_ptr_addr, f"Handler for MIG message {msg_id}", 0)
|
||||
|
||||
print(f" Labeled {labeled_count}/{num_msgs} handlers")
|
||||
self.total_messages += labeled_count
|
||||
self.found_count += 1
|
||||
|
||||
def scan_segment(self, seg_ea):
|
||||
"""
|
||||
Scan a single segment for MIG subsystems.
|
||||
|
||||
Args:
|
||||
seg_ea: Segment address
|
||||
"""
|
||||
seg_name = idc.get_segm_name(seg_ea)
|
||||
start = idc.get_segm_start(seg_ea)
|
||||
end = idc.get_segm_end(seg_ea)
|
||||
|
||||
# Leave safety buffer for structure reads
|
||||
scan_end = end - self.ROUTINE_SIZE
|
||||
|
||||
print(f"\n[*] Scanning {seg_name} ({hex(start)} - {hex(end)})")
|
||||
|
||||
# Scan with 8-byte alignment (pointer size on arm64)
|
||||
for addr in range(start, scan_end, 8):
|
||||
is_valid, start_id, end_id = self.is_valid_subsystem(addr)
|
||||
|
||||
if is_valid:
|
||||
self.label_subsystem(addr, start_id, end_id)
|
||||
|
||||
def scan(self):
|
||||
"""Main scanning routine - iterate all relevant segments."""
|
||||
print("=" * 70)
|
||||
print("MIG Subsystem Scanner for IDA Pro")
|
||||
print("=" * 70)
|
||||
|
||||
# Find and scan target segments
|
||||
scanned_segments = 0
|
||||
for seg_ea in idautils.Segments():
|
||||
seg_name = idc.get_segm_name(seg_ea)
|
||||
|
||||
if any(target in seg_name for target in self.TARGET_SEGMENTS):
|
||||
self.scan_segment(seg_ea)
|
||||
scanned_segments += 1
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 70)
|
||||
print(f"Scan Complete!")
|
||||
print(f" Segments scanned: {scanned_segments}")
|
||||
print(f" Subsystems found: {self.found_count}")
|
||||
print(f" Message handlers labeled: {self.total_messages}")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for the script."""
|
||||
scanner = MIGSubsystemScanner()
|
||||
scanner.scan()
|
||||
|
||||
# Refresh IDA views to show new names
|
||||
idaapi.refresh_idaview_anyway()
|
||||
print("\n[*] IDA views refreshed. Check Functions window for MIG_msg_* handlers")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user