593 lines
27 KiB
Python
593 lines
27 KiB
Python
|
#!/usr/bin/env python3
|
||
|
#
|
||
|
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
|
||
|
#
|
||
|
|
||
|
import os, plistlib, struct
|
||
|
|
||
|
from .loader import QlLoader
|
||
|
|
||
|
from qiling.exception import *
|
||
|
from qiling.const import *
|
||
|
|
||
|
from .macho_parser.parser import *
|
||
|
from .macho_parser.const import *
|
||
|
from .macho_parser.utils import *
|
||
|
from qiling.os.macos.kernel_api.hook import *
|
||
|
from qiling.os.memory import QlMemoryHeap
|
||
|
|
||
|
from qiling.os.macos.const import *
|
||
|
from qiling.os.macos.task import MachoTask
|
||
|
from qiling.os.macos.kernel_func import FileSystem, map_commpage
|
||
|
from qiling.os.macos.mach_port import MachPort, MachPortManager
|
||
|
from qiling.os.macos.subsystems import MachHostServer, MachTaskServer
|
||
|
from qiling.os.macos.utils import env_dict_to_array, page_align_end
|
||
|
from qiling.os.macos.thread import QlMachoThreadManagement, QlMachoThread
|
||
|
|
||
|
|
||
|
# commpage is a shared mem space which is in a static address
|
||
|
def load_commpage(ql):
|
||
|
if ql.arch.type == QL_ARCH.X8664:
|
||
|
COMM_PAGE_START_ADDRESS = X8664_COMM_PAGE_START_ADDRESS
|
||
|
else:
|
||
|
COMM_PAGE_START_ADDRESS = ARM64_COMM_PAGE_START_ADDRESS
|
||
|
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_SIGNATURE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_CPU_CAPABILITIES64, b'\x00\x00\x00\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_UNUSED, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_VERSION, b'\x0d')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_CPU_CAPABILITIES, b'\x00\x00\x00\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NCPUS, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_UNUSED0, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_CACHE_LINESIZE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_SCHED_GEN, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_MEMORY_PRESSURE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_SPIN_COUNT, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_ACTIVE_CPUS, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_PHYSICAL_CPUS, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_LOGICAL_CPUS, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_UNUSED1, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_MEMORY_SIZE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_CPUFAMILY, b'\xec\x5e\x3b\x57')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_KDEBUG_ENABLE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_ATM_DIAGNOSTIC_CONFIG, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_UNUSED2, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_TIME_DATA_START, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NT_TSC_BASE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NT_SCALE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NT_SHIFT, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NT_NS_BASE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_NT_GENERATION, b'\x01') # someflag seem important
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_GTOD_GENERATION, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_GTOD_NS_BASE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_GTOD_SEC_BASE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_APPROX_TIME, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_APPROX_TIME_SUPPORTED, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_CONT_TIMEBASE, b'\x00')
|
||
|
ql.mem.write(COMM_PAGE_START_ADDRESS + COMM_PAGE_BOOTTIME_USEC, b'\x00')
|
||
|
|
||
|
|
||
|
class QlLoaderMACHO(QlLoader):
|
||
|
# macho x8664 loader
|
||
|
def __init__(self, ql, dyld_path=None):
|
||
|
super(QlLoaderMACHO, self).__init__(ql)
|
||
|
self.dyld_path = dyld_path
|
||
|
self.ql = ql
|
||
|
|
||
|
#FIXME: Demigod needs a better way to handle kext file
|
||
|
if os.path.isdir(self.ql.argv[0]):
|
||
|
basename = os.path.basename(self.ql.argv[0])
|
||
|
self.kext_name = os.path.splitext(basename)[0]
|
||
|
filename = self.ql.argv
|
||
|
self.ql._argv = [self.ql.argv[0] + "/Contents/MacOS/" + self.kext_name]
|
||
|
self.plist = plistlib.load(open(filename[0] + "/Contents/Info.plist", "rb"))
|
||
|
if "IOKitPersonalities" in self.plist:
|
||
|
self.IOKit = True
|
||
|
else:
|
||
|
self.IOKit = False
|
||
|
else:
|
||
|
self.kext_name = None
|
||
|
|
||
|
def run(self):
|
||
|
self.profile = self.ql.profile
|
||
|
stack_address = int(self.profile.get("OS64", "stack_address"), 16)
|
||
|
stack_size = int(self.profile.get("OS64", "stack_size"), 16)
|
||
|
vmmap_trap_address = int(self.profile.get("OS64", "vmmap_trap_address"), 16)
|
||
|
self.heap_address = int(self.profile.get("OS64", "heap_address"), 16)
|
||
|
self.heap_size = int(self.profile.get("OS64", "heap_size"), 16)
|
||
|
self.stack_address = stack_address
|
||
|
self.stack_size = stack_size
|
||
|
|
||
|
if self.ql.code:
|
||
|
self.ql.mem.map(self.ql.os.entry_point, self.ql.os.code_ram_size, info="[shellcode_stack]")
|
||
|
self.ql.os.entry_point = (self.ql.os.entry_point + 0x200000 - 0x1000)
|
||
|
|
||
|
self.ql.mem.write(self.entry_point, self.ql.code)
|
||
|
|
||
|
self.ql.arch.regs.arch_sp = self.ql.os.entry_point
|
||
|
return
|
||
|
|
||
|
self.ql.os.macho_task = MachoTask()
|
||
|
self.ql.os.macho_fs = FileSystem(self.ql)
|
||
|
self.ql.os.macho_mach_port = MachPort(2187)
|
||
|
self.ql.os.macho_port_manager = MachPortManager(self.ql, self.ql.os.macho_mach_port)
|
||
|
self.ql.os.macho_host_server = MachHostServer(self.ql)
|
||
|
self.ql.os.macho_task_server = MachTaskServer(self.ql)
|
||
|
|
||
|
self.envs = env_dict_to_array(self.env)
|
||
|
self.apples = self.ql.os.path.transform_to_relative_path(self.ql.path)
|
||
|
self.ql.os.heap = QlMemoryHeap(self.ql, self.heap_address, self.heap_address + self.heap_size)
|
||
|
|
||
|
# FIXME: Not working due to overlarge mapping, need to fix it
|
||
|
# vm_shared_region_enter(self.ql)
|
||
|
|
||
|
map_commpage(self.ql)
|
||
|
|
||
|
self.ql.os.thread_management = QlMachoThreadManagement(self.ql)
|
||
|
self.ql.os.macho_thread = QlMachoThread(self.ql)
|
||
|
self.ql.os.thread_management.cur_thread = self.ql.os.macho_thread
|
||
|
self.ql.os.macho_vmmap_end = vmmap_trap_address
|
||
|
self.stack_sp = stack_address + stack_size
|
||
|
self.macho_file = MachoParser(self.ql, self.ql.path)
|
||
|
self.is_driver = (self.macho_file.header.file_type == 0xb)
|
||
|
self.loading_file = self.macho_file
|
||
|
self.slide = int(self.profile.get("LOADER", "slide"), 16)
|
||
|
self.dyld_slide = int(self.profile.get("LOADER", "dyld_slide"), 16)
|
||
|
self.string_align = 8
|
||
|
self.ptr_align = 8
|
||
|
self.binary_entry = 0x0
|
||
|
self.proc_entry = 0x0
|
||
|
self.argvs = [self.ql.path]
|
||
|
self.argc = 1
|
||
|
self.using_dyld = False
|
||
|
self.vm_end_addr = 0x0
|
||
|
self.ql.mem.map(self.stack_address, self.stack_size, info="[stack]")
|
||
|
if self.is_driver:
|
||
|
self.loadDriver(self.stack_address)
|
||
|
self.ql.hook_code(hook_kernel_api)
|
||
|
else:
|
||
|
self.loadMacho()
|
||
|
self.stack_address = (int(self.stack_sp))
|
||
|
self.ql.arch.regs.arch_sp = self.stack_address # self.stack_sp
|
||
|
self.init_sp = self.ql.arch.regs.arch_sp
|
||
|
self.ql.os.macho_task.min_offset = page_align_end(self.vm_end_addr, PAGE_SIZE)
|
||
|
|
||
|
def loadDriver(self, stack_addr, loadbase = -1, argv = [], env = {}):
|
||
|
self.import_symbols = {}
|
||
|
PAGE_SIZE = 0x1000
|
||
|
if loadbase < 0:
|
||
|
loadbase = 0xffffff7000000000
|
||
|
self.slide = loadbase
|
||
|
self.load_address = loadbase
|
||
|
cmds = self.macho_file.commands
|
||
|
for cmd in cmds:
|
||
|
if cmd.cmd_id == LC_SEGMENT_64:
|
||
|
self.loadSegment64(cmd, False)
|
||
|
|
||
|
self.kext_size = self.vm_end_addr - loadbase
|
||
|
|
||
|
kernel_path = os.path.join(self.ql.rootfs, "System/Library/Kernels/kernel.development")
|
||
|
self.ql.log.info("Parsing kernel:")
|
||
|
self.kernel = MachoParser(self.ql, kernel_path)
|
||
|
|
||
|
# Create memory for external static symbol jmp code
|
||
|
self.static_addr = self.vm_end_addr
|
||
|
self.static_size = PAGE_SIZE
|
||
|
self.ql.mem.map(self.static_addr, self.static_size, info="[STATIC]")
|
||
|
self.vm_end_addr += PAGE_SIZE
|
||
|
self.ql.log.info("Memory for external static symbol is created at 0x%x with size 0x%x" % (self.static_addr, self.static_size))
|
||
|
self.static_symbols = {}
|
||
|
|
||
|
# Load kernel
|
||
|
self.slide = 0
|
||
|
self.loading_file = self.kernel
|
||
|
kern_cmds = self.kernel.commands
|
||
|
self.kernel_base = None
|
||
|
for cmd in kern_cmds:
|
||
|
if cmd.cmd_id == LC_SEGMENT_64:
|
||
|
if self.kernel_base is None:
|
||
|
self.kernel_base = cmd.vm_address
|
||
|
self.loadSegment64(cmd, False)
|
||
|
|
||
|
self.ql.log.info("Kernel loaded at 0x%x" % self.kernel_base)
|
||
|
|
||
|
# Resolve local relocation
|
||
|
for relocation in self.macho_file.dysymbol_table.locreloc:
|
||
|
seg = None
|
||
|
for segment in self.macho_file.segments:
|
||
|
if relocation.symbolnum in segment.sections_index:
|
||
|
seg = segment
|
||
|
break
|
||
|
current_value, = struct.unpack("<Q", self.ql.mem.read(loadbase + relocation.address, 8))
|
||
|
self.ql.log.debug("Patching relocation (0x%x): from 0x%x, update to segment %s at 0x%x" % (loadbase + relocation.address, current_value, seg.name, loadbase + seg.vm_address))
|
||
|
self.ql.mem.write(loadbase + relocation.address, struct.pack("<Q", current_value + loadbase ))
|
||
|
|
||
|
# Resolve dynamic symbols
|
||
|
kernel_local_symbols_index = self.kernel.dysymbol_table.locsymbol_index
|
||
|
kernel_local_symbols_num = self.kernel.dysymbol_table.locsymbol_num
|
||
|
self.kernel_local_symbols_detail = self.kernel.symbol_table.details(kernel_local_symbols_index, kernel_local_symbols_num, self.kernel.string_table)
|
||
|
|
||
|
for key in self.kernel_local_symbols_detail:
|
||
|
value = self.kernel_local_symbols_detail[key]
|
||
|
self.import_symbols[value["n_value"]] = key.decode('ascii')
|
||
|
|
||
|
kernel_extrn_symbols_index = self.kernel.dysymbol_table.defext_index
|
||
|
kernel_extrn_symbols_num = self.kernel.dysymbol_table.defext_num
|
||
|
self.kernel_extrn_symbols_detail = self.kernel.symbol_table.details(kernel_extrn_symbols_index, kernel_extrn_symbols_num, self.kernel.string_table)
|
||
|
|
||
|
for key in self.kernel_extrn_symbols_detail:
|
||
|
value = self.kernel_extrn_symbols_detail[key]
|
||
|
self.import_symbols[value["n_value"]] = key.decode('ascii')
|
||
|
|
||
|
offset = 0
|
||
|
"""
|
||
|
0: 48 83 ec 08 sub rsp,0x8
|
||
|
4: c7 04 24 af be ad de mov DWORD PTR [rsp],0xdeadbeaf
|
||
|
b: c7 44 24 04 be ba fe mov DWORD PTR [rsp+0x4],0xcafebabe
|
||
|
12: ca
|
||
|
13: c3 ret
|
||
|
"""
|
||
|
|
||
|
for relocation in self.macho_file.dysymbol_table.extreloc:
|
||
|
symbol = self.macho_file.symbol_table.symbols[relocation.symbolnum]
|
||
|
symname = self.macho_file.string_table[symbol.n_strx]
|
||
|
if relocation.length == 2 and relocation.rtype == 2:
|
||
|
if symname not in self.static_symbols:
|
||
|
if symname in self.kernel_local_symbols_detail:
|
||
|
real_addr = self.kernel_local_symbols_detail[symname]["n_value"]
|
||
|
elif b"_" + symname in self.kernel_extrn_symbols_detail:
|
||
|
# ___MALLOC ftw???
|
||
|
real_addr = self.kernel_extrn_symbols_detail[b"_" + symname]["n_value"]
|
||
|
elif symname in self.kernel_extrn_symbols_detail:
|
||
|
real_addr = self.kernel_extrn_symbols_detail[symname]["n_value"]
|
||
|
else:
|
||
|
self.ql.log.info("Static symbol %s not found" % symname)
|
||
|
continue
|
||
|
self.import_symbols[real_addr] = symname.decode('ascii')
|
||
|
lo_addr = real_addr & 0xffffffff
|
||
|
hi_addr = (real_addr & 0xffffffff00000000) // 0x100000000
|
||
|
jmpcode = b"\x48\x83\xec\x08\xc7\x04\x24" + struct.pack("<I", lo_addr) + b"\xc7\x44\x24\x04" + struct.pack("<I", hi_addr) + b"\xc3"
|
||
|
|
||
|
self.ql.mem.write(self.static_addr + offset, jmpcode)
|
||
|
|
||
|
self.ql.mem.write(loadbase + relocation.address, struct.pack("<I", self.static_addr + offset - (loadbase + relocation.address + 4)))
|
||
|
self.static_symbols[symname] = self.static_addr + offset
|
||
|
offset += len(jmpcode)
|
||
|
else:
|
||
|
self.ql.mem.write(loadbase + relocation.address, struct.pack("<I", self.static_symbols[symname] - (loadbase + relocation.address + 4)))
|
||
|
|
||
|
# ql.log.info("Patching relocation (0x%x): %s at 0x%x" % (loadbase + relocation.address, symname, self.static_symbols[symname]))
|
||
|
continue
|
||
|
if relocation.extern == 0 or relocation.length != 3:
|
||
|
continue
|
||
|
|
||
|
if symname in self.kernel_local_symbols_detail:
|
||
|
# ql.log.debug("Patching relocation (0x%x): %s at 0x%x" % (loadbase + relocation.address, symname, self.kernel_local_symbols_detail[symname]["n_value"]))
|
||
|
self.ql.mem.write(loadbase + relocation.address, struct.pack("<Q", self.kernel_local_symbols_detail[symname]["n_value"]))
|
||
|
elif symname in self.kernel_extrn_symbols_detail:
|
||
|
# ql.log.debug("Patching relocation (0x%x): %s at 0x%x" % (loadbase + relocation.address, symname, self.kernel_extrn_symbols_detail[symname]["n_value"]))
|
||
|
self.ql.mem.write(loadbase + relocation.address, struct.pack("<Q", self.kernel_extrn_symbols_detail[symname]["n_value"]))
|
||
|
else:
|
||
|
self.ql.log.info("Symbol %s not found!" % symname)
|
||
|
|
||
|
# Update resolved symbols in table
|
||
|
self.loadbase = loadbase
|
||
|
index = self.macho_file.dysymbol_table.locsymbol_index
|
||
|
num = self.macho_file.dysymbol_table.locsymbol_num
|
||
|
self.kext_local_symbols = self.macho_file.symbol_table.details(index, num, self.macho_file.string_table)
|
||
|
|
||
|
index = self.macho_file.dysymbol_table.defext_index
|
||
|
num = self.macho_file.dysymbol_table.defext_num
|
||
|
self.kext_extern_symbols = self.macho_file.symbol_table.details(index, num, self.macho_file.string_table)
|
||
|
|
||
|
if self.IOKit is True:
|
||
|
# Get exported vtables
|
||
|
self.vtables = {}
|
||
|
for symbol in self.macho_file.symbol_table.symbols:
|
||
|
if symbol.n_type == 0xf and "__const".ljust(16, "\x00") == self.macho_file.sections[symbol.n_sect].name:
|
||
|
symname = self.macho_file.string_table[symbol.n_strx]
|
||
|
self.ql.log.info("Found vtable of %s at 0x%x" % (symname, loadbase + symbol.n_value))
|
||
|
self.vtables[symname] = loadbase + symbol.n_value
|
||
|
|
||
|
kext = self.plist["IOKitPersonalities"][self.kext_name]["IOClass"]
|
||
|
user = self.plist["IOKitPersonalities"][self.kext_name]["IOUserClientClass"]
|
||
|
|
||
|
self.kext_alloc = None
|
||
|
self.kext_init = None
|
||
|
self.kext_attach = None
|
||
|
self.kext_probe = None
|
||
|
self.kext_detach = None
|
||
|
self.kext_start = None
|
||
|
|
||
|
# No need to detach since we will emulate kext and user together
|
||
|
self.user_alloc = None
|
||
|
self.user_initWithTask = None
|
||
|
self.user_attach = None
|
||
|
self.user_start = None
|
||
|
|
||
|
for relocation in self.macho_file.dysymbol_table.extreloc:
|
||
|
symbol = self.macho_file.symbol_table.symbols[relocation.symbolnum]
|
||
|
symname = self.macho_file.string_table[symbol.n_strx]
|
||
|
if b"externalMethod" in symname:
|
||
|
current_value, = struct.unpack("<Q", self.ql.mem.read(loadbase + relocation.address, 8))
|
||
|
print(symname, hex(relocation.address), hex(current_value))
|
||
|
|
||
|
for symname in self.vtables:
|
||
|
# TODO: Use IDA Pro to dump offset of methods of IOService and IOUserClient objects
|
||
|
if symname.decode().endswith(str(len(kext)) + kext + "9MetaClassE"):
|
||
|
self.kext_alloc, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x98, 8))
|
||
|
elif symname.decode().endswith(str(len(kext)) + kext):
|
||
|
self.kext_init, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x258, 8))
|
||
|
self.kext_attach, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x680, 8))
|
||
|
self.kext_probe, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x5c8, 8))
|
||
|
self.kext_detach, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x688, 8))
|
||
|
self.kext_start, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x5d0, 8))
|
||
|
elif symname.decode().endswith(str(len(user)) + user + "9MetaClassE"):
|
||
|
self.user_alloc, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x98, 8))
|
||
|
elif symname.decode().endswith(str(len(user)) + user):
|
||
|
self.user_initWithTask, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x8f0, 8))
|
||
|
self.user_attach, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x680, 8))
|
||
|
self.user_start, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x5d0, 8))
|
||
|
self.user_externalMethod, = struct.unpack("<Q", self.ql.mem.read(self.vtables[symname] + 0x860, 8))
|
||
|
else:
|
||
|
# from pprint import pprint
|
||
|
# pprint(kext_local_symbols)
|
||
|
if b"__realmain" in self.kext_local_symbols:
|
||
|
realmain = loadbase + self.kext_local_symbols[b"__realmain"]["n_value"]
|
||
|
current_value, = struct.unpack("<Q", self.ql.mem.read(realmain, 8))
|
||
|
self.ql.log.info("Found entry point: 0x%x" % (current_value))
|
||
|
self.kext_start = current_value
|
||
|
else:
|
||
|
self.ql.log.info("Entry point not found")
|
||
|
self.kext_start = None
|
||
|
|
||
|
if b"__antimain" in self.kext_local_symbols:
|
||
|
antimain = loadbase + self.kext_local_symbols[b"__antimain"]["n_value"]
|
||
|
current_value, = struct.unpack("<Q", self.ql.mem.read(antimain, 8))
|
||
|
self.ql.log.info("Found exit point: 0x%x" % (current_value))
|
||
|
self.kext_stop = current_value
|
||
|
else:
|
||
|
self.ql.log.info("Exit point not found")
|
||
|
self.kext_stop = None
|
||
|
|
||
|
self.slide = loadbase
|
||
|
|
||
|
def loadMacho(self, depth=0, isdyld=False):
|
||
|
mmap_address = int(self.profile.get("OS64", "mmap_address"), 16)
|
||
|
|
||
|
# MAX load depth
|
||
|
if depth > 5:
|
||
|
return
|
||
|
|
||
|
# three pass
|
||
|
# 1: unixthread, uuid, code signature
|
||
|
# 2: segment
|
||
|
# 3: dyld
|
||
|
for pass_count in range(1, 4):
|
||
|
|
||
|
if isdyld:
|
||
|
cmds = self.dyld_file.commands
|
||
|
else:
|
||
|
cmds = self.macho_file.commands
|
||
|
|
||
|
for cmd in cmds:
|
||
|
if pass_count == 1:
|
||
|
if cmd.cmd_id == LC_UNIXTHREAD:
|
||
|
self.loadUnixThread(cmd, isdyld)
|
||
|
|
||
|
if cmd.cmd_id == LC_UUID:
|
||
|
self.loadUuid()
|
||
|
|
||
|
if cmd.cmd_id == LC_CODE_SIGNATURE:
|
||
|
self.loadCodeSignature()
|
||
|
|
||
|
if cmd.cmd_id == LC_MAIN:
|
||
|
self.loadMain(cmd)
|
||
|
|
||
|
if pass_count == 2:
|
||
|
if cmd.cmd_id == LC_SEGMENT:
|
||
|
pass
|
||
|
|
||
|
if cmd.cmd_id == LC_SEGMENT_64:
|
||
|
self.loadSegment64(cmd, isdyld)
|
||
|
|
||
|
if pass_count == 3:
|
||
|
if cmd.cmd_id == LC_LOAD_DYLINKER:
|
||
|
self.loadDylinker(cmd)
|
||
|
self.using_dyld = True
|
||
|
if not isdyld:
|
||
|
if not self.dyld_path:
|
||
|
raise QlErrorMACHOFormat("Error No Dyld path")
|
||
|
self.dyld_path = os.path.join(self.ql.rootfs + self.dyld_path)
|
||
|
self.dyld_file = MachoParser(self.ql, self.dyld_path)
|
||
|
self.loading_file = self.dyld_file
|
||
|
self.proc_entry = self.loadMacho(depth + 1, True)
|
||
|
self.loading_file = self.macho_file
|
||
|
self.using_dyld = True
|
||
|
|
||
|
if depth == 0:
|
||
|
self.mmap_address = mmap_address
|
||
|
self.stack_sp = self.loadStack()
|
||
|
if self.using_dyld:
|
||
|
self.ql.log.info("ProcEntry: {}".format(hex(self.proc_entry)))
|
||
|
self.entry_point = self.proc_entry + self.dyld_slide
|
||
|
self.ql.log.info("Dyld entry point: {}".format(hex(self.entry_point)))
|
||
|
else:
|
||
|
self.entry_point = self.proc_entry + self.slide
|
||
|
self.ql.log.info("Binary Entry Point: 0x{:X}".format(self.binary_entry))
|
||
|
self.macho_entry = self.binary_entry + self.slide
|
||
|
self.load_address = self.macho_entry
|
||
|
|
||
|
# load_commpage not wroking with ARM64, yet
|
||
|
if self.ql.arch.type == QL_ARCH.X8664:
|
||
|
load_commpage(self.ql)
|
||
|
|
||
|
return self.proc_entry
|
||
|
|
||
|
def loadSegment64(self, cmd, isdyld):
|
||
|
PAGE_SIZE = 0x1000
|
||
|
if isdyld:
|
||
|
slide = self.dyld_slide
|
||
|
else:
|
||
|
slide = self.slide
|
||
|
vaddr_start = cmd.vm_address + slide
|
||
|
vaddr_end = cmd.vm_address + cmd.vm_size + slide
|
||
|
seg_size = cmd.vm_size
|
||
|
seg_name = cmd.segment_name
|
||
|
seg_data = bytes(self.loading_file.get_segment(seg_name).content)
|
||
|
|
||
|
if seg_size == 0:
|
||
|
return -1
|
||
|
|
||
|
if seg_name[:10] == "__PAGEZERO":
|
||
|
self.ql.log.debug("Now loading {}, VM[{}:{}] for pagezero actually it only got a page size".format(seg_name, hex(vaddr_start), hex(vaddr_end)))
|
||
|
self.ql.mem.map(vaddr_start, PAGE_SIZE, info="[__PAGEZERO]")
|
||
|
self.ql.mem.write(vaddr_start, b'\x00' * PAGE_SIZE)
|
||
|
if self.vm_end_addr < vaddr_end:
|
||
|
self.vm_end_addr = vaddr_end
|
||
|
else:
|
||
|
if vaddr_end % PAGE_SIZE != 0:
|
||
|
vaddr_end = ((vaddr_end // PAGE_SIZE) + 1) * PAGE_SIZE
|
||
|
seg_size = vaddr_end - vaddr_start
|
||
|
seg_data = seg_data.ljust(seg_size, b'\0')
|
||
|
|
||
|
self.ql.log.debug("Now loading {}, VM[{}:{}]".format(seg_name, hex(vaddr_start), hex(vaddr_end)))
|
||
|
self.ql.mem.map(vaddr_start, seg_size, info="[loadSegment64]")
|
||
|
self.ql.mem.write(vaddr_start, seg_data)
|
||
|
if self.vm_end_addr < vaddr_end:
|
||
|
self.vm_end_addr = vaddr_end
|
||
|
|
||
|
return vaddr_start
|
||
|
|
||
|
def loadUnixThread(self, cmd, isdyld):
|
||
|
if not isdyld:
|
||
|
self.binary_entry = cmd.entry
|
||
|
|
||
|
self.proc_entry = cmd.entry
|
||
|
self.ql.log.debug("Binary Thread Entry: {}".format(hex(cmd.entry)))
|
||
|
|
||
|
|
||
|
def loadUuid(self):
|
||
|
# todo: WIP
|
||
|
pass
|
||
|
|
||
|
def loadCodeSignature(self):
|
||
|
# todo: WIP
|
||
|
pass
|
||
|
|
||
|
def loadMain(self, cmd, isdyld=False):
|
||
|
if self.macho_file.page_zero_size:
|
||
|
if not isdyld:
|
||
|
self.binary_entry = cmd.entry_offset + self.macho_file.page_zero_size
|
||
|
self.proc_entry = cmd.entry_offset + self.macho_file.page_zero_size
|
||
|
|
||
|
def loadDylinker(self, cmd):
|
||
|
self.dyld_path = cmd.name
|
||
|
|
||
|
def make_string(self, argvs, envs, apple_str):
|
||
|
result = bytes()
|
||
|
for item in apple_str:
|
||
|
b = bytes(item, encoding='utf8') + b'\x00'
|
||
|
result = b +result
|
||
|
for item in envs:
|
||
|
b = bytes(item, encoding='utf8') + b'\x00'
|
||
|
result = b +result
|
||
|
for item in argvs:
|
||
|
b = bytes(item, encoding='utf8') + b'\x00'
|
||
|
result = b +result
|
||
|
return result
|
||
|
|
||
|
# TODO: add size check
|
||
|
def loadStack(self):
|
||
|
|
||
|
argvs_ptr = []
|
||
|
envs_ptr = []
|
||
|
apple_ptr = []
|
||
|
|
||
|
all_str = self.make_string(self.argvs, self.envs, self.apples)
|
||
|
self.push_stack_string(all_str)
|
||
|
ptr = self.stack_sp
|
||
|
|
||
|
for item in self.argvs[::-1]:
|
||
|
argvs_ptr.append(ptr) # need pack and tostring
|
||
|
self.ql.log.debug('add argvs ptr {}'.format(hex(ptr)))
|
||
|
ptr += len(item) + 1
|
||
|
|
||
|
for item in self.envs[::-1]:
|
||
|
envs_ptr.append(ptr)
|
||
|
self.ql.log.debug('add envs ptr {}'.format(hex(ptr)))
|
||
|
ptr += len(item) + 1
|
||
|
|
||
|
for item in self.apples[::-1]:
|
||
|
apple_ptr.append(ptr)
|
||
|
self.ql.log.debug('add apple ptr {}'.format(hex(ptr)))
|
||
|
ptr += len(item) + 1
|
||
|
|
||
|
ptr = self.stack_sp
|
||
|
self.push_stack_addr(0x0)
|
||
|
ptr -= 4
|
||
|
|
||
|
for item in apple_ptr:
|
||
|
self.push_stack_addr(item)
|
||
|
ptr -= 4
|
||
|
|
||
|
self.push_stack_addr(0x0)
|
||
|
ptr -= 4
|
||
|
|
||
|
for item in envs_ptr:
|
||
|
ptr -= 4
|
||
|
self.push_stack_addr(item)
|
||
|
|
||
|
self.push_stack_addr(0x0)
|
||
|
ptr -= 4
|
||
|
|
||
|
for item in argvs_ptr:
|
||
|
ptr -= 4
|
||
|
self.push_stack_addr(item)
|
||
|
self.ql.log.debug("SP 0x%x, content 0x%x" % (self.stack_sp, item))
|
||
|
argvs_ptr_ptr = ptr
|
||
|
|
||
|
self.push_stack_addr(self.argc)
|
||
|
ptr -= 4
|
||
|
self.ql.log.debug("SP 0x%x, content 0x%x" % (self.stack_sp, self.argc))
|
||
|
|
||
|
if self.using_dyld:
|
||
|
ptr -= 4
|
||
|
#ql.log.info("Binary Dynamic Entry Point: {:X}".format(self.binary_entry))
|
||
|
self.push_stack_addr(self.macho_file.header_address)
|
||
|
# self.push_stack_addr(self.binary_entry)
|
||
|
|
||
|
return self.stack_sp
|
||
|
|
||
|
def push_stack_string(self, data):
|
||
|
align = self.string_align
|
||
|
length = len(data)
|
||
|
|
||
|
if length % align != 0:
|
||
|
for i in range(align - (length % align)):
|
||
|
data += b'\x00'
|
||
|
length = len(data)
|
||
|
|
||
|
self.stack_sp -= length
|
||
|
self.ql.mem.write(self.stack_sp, data)
|
||
|
self.ql.log.debug("SP {} write data len {}".format(hex(self.stack_sp), length))
|
||
|
|
||
|
return self.stack_sp
|
||
|
|
||
|
def push_stack_addr(self, data):
|
||
|
align = self.ptr_align
|
||
|
|
||
|
if data == 0:
|
||
|
content = b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
||
|
else:
|
||
|
content = struct.pack('<Q', data)
|
||
|
|
||
|
if len(content) != align:
|
||
|
self.ql.log.info('stack align error')
|
||
|
return
|
||
|
|
||
|
self.stack_sp -= align
|
||
|
self.ql.mem.write(self.stack_sp, content)
|
||
|
|
||
|
return self.stack_sp
|