com.github.unidbg.ios.MachOLoader Maven / Gradle / Ivy
The newest version!
package com.github.unidbg.ios;
import com.github.unidbg.Alignment;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.Svc;
import com.github.unidbg.Symbol;
import com.github.unidbg.Utils;
import com.github.unidbg.arm.ARM;
import com.github.unidbg.arm.Arm64Hook;
import com.github.unidbg.arm.Arm64Svc;
import com.github.unidbg.arm.ArmHook;
import com.github.unidbg.arm.ArmSvc;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.BackendException;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.file.FileIO;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.ios.DarwinFileIO;
import com.github.unidbg.file.ios.IOConstants;
import com.github.unidbg.hook.HookListener;
import com.github.unidbg.ios.patch.LibDyldPatcher;
import com.github.unidbg.ios.struct.kernel.Pthread;
import com.github.unidbg.ios.struct.kernel.Pthread32;
import com.github.unidbg.ios.struct.kernel.Pthread64;
import com.github.unidbg.ios.struct.kernel.VmRemapRequest;
import com.github.unidbg.ios.struct.sysctl.DyldImageInfo32;
import com.github.unidbg.ios.struct.sysctl.DyldImageInfo64;
import com.github.unidbg.memory.MemRegion;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryAllocBlock;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.memory.MemoryBlockImpl;
import com.github.unidbg.memory.MemoryMap;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.pointer.UnidbgStructure;
import com.github.unidbg.spi.AbstractLoader;
import com.github.unidbg.spi.LibraryFile;
import com.github.unidbg.spi.Loader;
import com.github.unidbg.thread.Task;
import com.github.unidbg.unix.IO;
import com.github.unidbg.unix.UnixSyscallHandler;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import io.kaitai.MachO;
import io.kaitai.struct.ByteBufferKaitaiStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import unicorn.Arm64Const;
import unicorn.ArmConst;
import unicorn.Unicorn;
import unicorn.UnicornConst;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MachOLoader extends AbstractLoader implements Memory, Loader, com.github.unidbg.ios.MachO {
private static final Log log = LogFactory.getLog(MachOLoader.class);
private boolean objcRuntime;
MachOLoader(Emulator emulator, UnixSyscallHandler syscallHandler, String[] envs) {
super(emulator, syscallHandler);
// init stack
long stackBase = STACK_BASE;
if (emulator.is64Bit()) {
stackBase += 0xf00000000L;
}
stackSize = STACK_SIZE_OF_PAGE * emulator.getPageAlign();
backend.mem_map(stackBase - stackSize, stackSize, UnicornConst.UC_PROT_READ | UnicornConst.UC_PROT_WRITE);
setStackPoint(stackBase);
initializeTSD(envs);
}
@SuppressWarnings("unchecked")
@Override
public void setLibraryResolver(LibraryResolver libraryResolver) {
if (libraryResolver instanceof IOResolver) {
syscallHandler.addIOResolver((IOResolver) libraryResolver);
}
super.setLibraryResolver(libraryResolver);
/*
* 注意打开顺序很重要
*/
syscallHandler.open(emulator, IO.STDIN, IOConstants.O_RDONLY);
syscallHandler.open(emulator, IO.STDOUT, IOConstants.O_WRONLY);
syscallHandler.open(emulator, IO.STDERR, IOConstants.O_WRONLY);
}
@Override
protected LibraryFile createLibraryFile(File file) {
return new MachOLibraryFile(file);
}
public void setObjcRuntime(boolean objcRuntime) {
this.objcRuntime = objcRuntime;
}
private UnidbgPointer vars;
private Pointer errno;
final void setErrnoPointer(Pointer errno) {
this.errno = errno.getPointer(0);
this.setErrno(0);
}
public static final long __TSD_THREAD_SELF = 0;
public static final long __TSD_ERRNO = 1;
public static final long __TSD_MIG_REPLY = 2;
// private static final int __PTK_FRAMEWORK_OBJC_KEY5 = 0x2d;
private void initializeTSD(String[] envs) {
List envList = new ArrayList<>();
envList.add("MallocCorruptionAbort=0");
for (String env : envs) {
int index = env.indexOf('=');
if (index != -1) {
envList.add(env);
}
}
final Pointer environ = allocateStack(emulator.getPointerSize() * (envList.size() + 1));
assert environ != null;
Pointer pointer = environ;
for (String env : envList) {
Pointer envPointer = writeStackString(env);
pointer.setPointer(0, envPointer);
pointer = pointer.share(emulator.getPointerSize());
}
pointer.setPointer(0, null);
UnidbgPointer _NSGetEnviron = allocateStack(emulator.getPointerSize());
_NSGetEnviron.setPointer(0, environ);
final Pointer programName = writeStackString(emulator.getProcessName());
Pointer _NSGetProgname = allocateStack(emulator.getPointerSize());
_NSGetProgname.setPointer(0, programName);
Pointer _NSGetArgc = allocateStack(emulator.getPointerSize());
_NSGetArgc.setInt(0, 1);
Pointer args = allocateStack(emulator.getPointerSize());
args.setPointer(0, programName);
Pointer _NSGetArgv = allocateStack(emulator.getPointerSize());
_NSGetArgv.setPointer(0, args);
vars = allocateStack(emulator.getPointerSize() * 5);
vars.setPointer(0, null); // _NSGetMachExecuteHeader
vars.setPointer(emulator.getPointerSize(), _NSGetArgc);
vars.setPointer(2L * emulator.getPointerSize(), _NSGetArgv);
vars.setPointer(3L * emulator.getPointerSize(), _NSGetEnviron);
vars.setPointer(4L * emulator.getPointerSize(), _NSGetProgname);
final Pointer thread = allocateStack(UnidbgStructure.calculateSize(emulator.is64Bit() ? Pthread64.class : Pthread32.class)); // reserve space for pthread_internal_t
Pthread pthread = Pthread.create(emulator, thread);
/* 0xa4必须固定,否则初始化objc会失败 */
final UnidbgPointer tsd = pthread.getTSD(); // tsd size
assert tsd != null;
tsd.setPointer(__TSD_THREAD_SELF, thread);
tsd.setPointer(__TSD_ERRNO * emulator.getPointerSize(), errno);
tsd.setPointer(__TSD_MIG_REPLY * emulator.getPointerSize(), null);
if (emulator.is32Bit()) {
backend.reg_write(ArmConst.UC_ARM_REG_C13_C0_3, tsd.peer);
} else {
backend.reg_write(Arm64Const.UC_ARM64_REG_TPIDRRO_EL0, tsd.peer);
}
long sp = getStackPoint();
sp &= (~(emulator.is64Bit() ? 15 : 7));
setStackPoint(sp);
if (log.isDebugEnabled()) {
log.debug("initializeTSD tsd=" + tsd + ", thread=" + thread + ", environ=" + environ + ", vars=" + vars + ", sp=0x" + Long.toHexString(getStackPoint()) + ", errno=" + errno);
}
addModuleListener(new LibDyldPatcher(_NSGetArgc, _NSGetArgv, _NSGetEnviron, _NSGetProgname));
}
public final void onExecutableLoaded(String executable) {
if (callInitFunction) {
for (MachOModule m : modules.values().toArray(new MachOModule[0])) {
boolean needCallInit = m.allSymbolBound || isPayloadModule(m) || m.getPath().equals(executable);
if (needCallInit) {
m.doInitialization(emulator);
}
}
}
}
@Override
protected Module loadInternal(LibraryFile libraryFile, boolean forceCallInit) {
try {
return loadInternal(libraryFile, forceCallInit, true);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private MachOModule loadInternal(LibraryFile libraryFile, boolean forceCallInit, boolean checkBootstrap) throws IOException {
MachOModule module = loadInternalPhase(libraryFile, true, checkBootstrap, Collections.emptyList());
for (MachOModule export : modules.values().toArray(new MachOModule[0])) {
for (NeedLibrary library : export.lazyLoadNeededList.toArray(new NeedLibrary[0])) {
String neededLibrary = library.path;
if (log.isDebugEnabled()) {
log.debug(export.getPath() + " need dependency " + neededLibrary);
}
MachOModule loaded = modules.get(FilenameUtils.getName(neededLibrary));
if (loaded != null) {
if (library.upward) {
export.upwardLibraries.put(FilenameUtils.getBaseName(neededLibrary), loaded);
}
continue;
}
LibraryFile neededLibraryFile = resolveLibrary(libraryFile, neededLibrary, Collections.singletonList(FilenameUtils.getFullPath(libraryFile.getPath())));
if (neededLibraryFile != null) {
MachOModule needed = loadInternalPhase(neededLibraryFile, true, false, Collections.emptySet());
needed.addReferenceCount();
if (library.upward) {
export.upwardLibraries.put(FilenameUtils.getBaseName(needed.name), needed);
}
} else if (!library.weak) {
log.info(export.getPath() + " load dependency " + neededLibrary + " failed");
}
}
export.lazyLoadNeededList.clear();
}
for (MachOModule m : modules.values()) {
processBind(m);
m.callObjcNotifyMapped(_objcNotifyMapped);
}
notifySingle(Dyld.dyld_image_state_bound, module);
notifySingle(Dyld.dyld_image_state_dependents_initialized, module);
if (callInitFunction || forceCallInit) {
MachOModule[] modules = this.modules.values().toArray(new MachOModule[0]);
for (MachOModule m : modules) {
if (isPayloadModule(m)) {
continue;
}
if (m.allSymbolBound || forceCallInit) {
m.callObjcNotifyInit(_objcNotifyInit);
m.doInitialization(emulator);
}
}
}
for (MachOModule m : modules.values().toArray(new MachOModule[0])) {
notifySingle(Dyld.dyld_image_state_initialized, m);
}
return module;
}
private com.github.unidbg.ios.Loader loader;
public void setLoader(com.github.unidbg.ios.Loader loader) {
this.loader = loader;
}
final boolean isPayloadModule(Module module) {
return loader != null && loader.isPayloadModule(module.getPath());
}
private MachOModule loadInternalPhase(LibraryFile libraryFile, boolean loadNeeded, boolean checkBootstrap, Collection parentRpath) {
try {
ByteBuffer buffer = libraryFile.mapBuffer();
return loadInternalPhase(libraryFile, buffer, loadNeeded, checkBootstrap, parentRpath);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private MachOModule loadInternalPhase(LibraryFile libraryFile, ByteBuffer buffer,
boolean loadNeeded, boolean checkBootstrap, Collection parentRpath) throws IOException {
MachO machO = new MachO(new ByteBufferKaitaiStream(buffer));
MachO.MagicType magic = machO.magic();
switch (magic) {
case FAT_BE:
Map archMap = new HashMap<>();
for (MachO.FatArch arch : machO.fatHeader().fatArchs()) {
if ((arch.cputype() == MachO.CpuType.ARM && emulator.is32Bit()) || (arch.cputype() == MachO.CpuType.ARM64 && emulator.is64Bit())) {
archMap.put(arch.cpusubtype(), arch);
}
}
MachO.FatArch arch = archMap.get(CPU_SUBTYPE_ARM_V7); // 优先加载armv7
if (arch == null) {
Iterator iterator = archMap.values().iterator();
if (iterator.hasNext()) {
arch = iterator.next();
}
}
if (arch != null) {
buffer.limit((int) (arch.offset() + arch.size()));
buffer.position((int) arch.offset());
if (log.isDebugEnabled()) {
log.debug("loadFatArch=" + arch.cputype() + ", cpuSubType=" + arch.cpusubtype());
}
return loadInternalPhase(libraryFile, buffer.slice(), loadNeeded, checkBootstrap, parentRpath);
}
throw new IllegalArgumentException("find arch failed");
case MACHO_LE_X86: // ARM
if (machO.header().cputype() != MachO.CpuType.ARM) {
throw new UnsupportedOperationException("cpuType=" + machO.header().cputype());
}
if (emulator.is64Bit()) {
throw new UnsupportedOperationException("NOT 64 bit executable: " + libraryFile.getName());
}
break;
case MACHO_LE_X64:
if (machO.header().cputype() != MachO.CpuType.ARM64) {
throw new UnsupportedOperationException("cpuType=" + machO.header().cputype());
}
if (emulator.is32Bit()) {
throw new UnsupportedOperationException("NOT 32 bit executable: " + libraryFile.getName());
}
break;
default:
throw new UnsupportedOperationException("magic=" + magic);
}
switch (machO.header().filetype()) {
case DYLIB:
case EXECUTE:
break;
default:
throw new UnsupportedOperationException("fileType=" + machO.header().filetype());
}
final boolean isExecutable = machO.header().filetype() == MachO.FileType.EXECUTE;
final boolean isPositionIndependent = (machO.header().flags() & MH_PIE) != 0;
if (checkBootstrap && !isExecutable && executableModule == null) {
URL url = getClass().getResource(objcRuntime ? "/ios/bootstrap_objc" : "/ios/bootstrap");
String path = "unidbg_bootstrap";
if (libraryFile instanceof DarwinLibraryFile) {
path = ((DarwinLibraryFile) libraryFile).resolveBootstrapPath();
}
loadInternal(new URLibraryFile(url, path, null), false, false);
}
long start = System.currentTimeMillis();
long size = 0;
String dyId = libraryFile.getName();
MachO.DyldInfoCommand dyldInfoCommand = null;
MachO.LinkeditDataCommand chainedFixups = null;
MachOModule subModule = null;
boolean finalSegment = false;
Set rpathSet = new LinkedHashSet<>(2);
byte[] uuid = null;
String dylibPath = FilenameUtils.normalize(libraryFile.getPath(), true);
for (MachO.LoadCommand command : machO.loadCommands()) {
if (command == null) {
throw new NullPointerException();
}
switch (command.type()) {
case DYLD_INFO:
case DYLD_INFO_ONLY:
if (dyldInfoCommand != null) {
throw new IllegalStateException("dyldInfoCommand=" + dyldInfoCommand);
}
dyldInfoCommand = (MachO.DyldInfoCommand) command.body();
break;
case DYLD_CHAINED_FIXUPS:
if (chainedFixups != null) {
throw new IllegalStateException("chainedFixups=" + chainedFixups);
}
chainedFixups = (MachO.LinkeditDataCommand) command.body();
break;
case SEGMENT: {
MachO.SegmentCommand segmentCommand = (MachO.SegmentCommand) command.body();
if ("__PAGEZERO".equals(segmentCommand.segname())) {
break;
}
if (segmentCommand.filesize() > segmentCommand.vmsize()) {
throw new IllegalStateException(String.format("malformed mach-o image: segment load command %s filesize is larger than vmsize", command.type()));
}
if (finalSegment) {
throw new IllegalStateException("finalSegment");
}
if (((segmentCommand.vmaddr() + segmentCommand.vmsize()) % emulator.getPageAlign()) != 0) {
finalSegment = true;
}
if (segmentCommand.vmaddr() % emulator.getPageAlign() != 0) {
throw new IllegalArgumentException("vmaddr not page aligned");
}
if (segmentCommand.vmsize() == 0) {
break;
}
if (segmentCommand.vmsize() < segmentCommand.filesize()) {
throw new IllegalStateException(String.format("malformed mach-o image: segment %s has vmsize < filesize", command.type()));
}
long vmsize = ARM.alignSize(segmentCommand.vmsize(), emulator.getPageAlign());
long high = segmentCommand.vmaddr() + vmsize;
if (size < high) {
size = high;
}
break;
}
case SEGMENT_64: {
MachO.SegmentCommand64 segmentCommand64 = (MachO.SegmentCommand64) command.body();
if ("__PAGEZERO".equals(segmentCommand64.segname())) {
break;
}
if (segmentCommand64.filesize() > segmentCommand64.vmsize()) {
throw new IllegalStateException(String.format("malformed mach-o image: segment load command %s filesize is larger than vmsize", command.type()));
}
if (finalSegment) {
throw new IllegalStateException("finalSegment");
}
if (((segmentCommand64.vmaddr() + segmentCommand64.vmsize()) % emulator.getPageAlign()) != 0) {
finalSegment = true;
}
if (segmentCommand64.vmaddr() % emulator.getPageAlign() != 0) {
throw new IllegalArgumentException("vmaddr not page aligned");
}
if (segmentCommand64.vmsize() == 0) {
break;
}
if (segmentCommand64.vmsize() < segmentCommand64.filesize()) {
throw new IllegalStateException(String.format("malformed mach-o image: segment %s has vmsize < filesize", command.type()));
}
long vmsize = ARM.alignSize(segmentCommand64.vmsize(), emulator.getPageAlign());
long high = segmentCommand64.vmaddr() + vmsize;
if (size < high) {
size = high;
}
break;
}
case ID_DYLIB:
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
String dylibName = dylibCommand.name();
if (loader != null && loader.isPayloadModule(dylibPath)) {
dylibPath = dylibPath.replace("@executable_path/", "");
} else if (dylibName.startsWith("/")) {
dylibPath = dylibName;
}
int index = dylibPath.indexOf('/'); // unidbg build frameworks
if (index != -1) {
String first = dylibPath.substring(0, index);
String second = dylibPath.substring(index + 1);
if (first.equals(second)) {
dylibPath = "/System/Library/Frameworks/" + first + ".framework/" + first;
}
}
dyId = FilenameUtils.getName(dylibName);
break;
case LOAD_DYLIB:
// case LOAD_WEAK_DYLIB:
case REEXPORT_DYLIB:
case LOAD_UPWARD_DYLIB:
case SYMTAB:
case DYSYMTAB:
break;
case ENCRYPTION_INFO:
case ENCRYPTION_INFO_64:
MachO.EncryptionInfoCommand encryptionInfoCommand = (MachO.EncryptionInfoCommand) command.body();
if (encryptionInfoCommand.cryptid() != 0) {
throw new UnsupportedOperationException("Encrypted file: " + libraryFile.getName());
}
break;
case UUID:
MachO.UuidCommand uuidCommand = (MachO.UuidCommand) command.body();
uuid = uuidCommand.uuid();
break;
case FUNCTION_STARTS:
case DATA_IN_CODE:
case CODE_SIGNATURE:
case SOURCE_VERSION:
case SEGMENT_SPLIT_INFO:
case DYLIB_CODE_SIGN_DRS:
case SUB_FRAMEWORK:
case VERSION_MIN_IPHONEOS:
case LOAD_DYLINKER:
case MAIN:
case ROUTINES:
case ROUTINES_64:
case LOAD_WEAK_DYLIB:
case BUILD_VERSION:
case DYLD_EXPORTS_TRIE:
break;
case SUB_CLIENT:
MachO.SubCommand subCommand = (MachO.SubCommand) command.body();
String name = subCommand.name().value();
MachOModule module = (MachOModule) findModule(name);
if (module == null) {
log.debug("Find sub client failed: " + name);
} else {
subModule = module;
}
break;
case RPATH:
MachO.RpathCommand rpathCommand = (MachO.RpathCommand) command.body();
String rpath = rpathCommand.path();
if (!rpath.contains("@loader_path/")) {
rpathSet.add(rpath);
}
break;
default:
log.info("Not handle loadCommand=" + command.type() + ", dylibPath=" + dylibPath);
break;
}
}
rpathSet.addAll(parentRpath);
final long loadBase = isExecutable ? 0 : mmapBaseAddress;
long machHeader = -1;
if (isExecutable) {
long end = loadBase + size;
if (end >= mmapBaseAddress) {
setMMapBaseAddress(end);
}
} else {
setMMapBaseAddress(loadBase + size);
}
if (log.isDebugEnabled()) {
log.debug(Inspector.inspectString(uuid, "start map dyid=" + dyId + ", base=0x" + Long.toHexString(loadBase) + ", size=0x" + Long.toHexString(size) + ", rpath=" + rpathSet + ", uuid=" + Utils.toUUID(uuid)));
}
final List neededList = new ArrayList<>();
final List regions = new ArrayList<>(5);
final List exportDylibs = new ArrayList<>();
MachO.SymtabCommand symtabCommand = null;
MachO.DysymtabCommand dysymtabCommand = null;
MachO.EntryPointCommand entryPointCommand = null;
List ordinalList = new ArrayList<>();
Section fEHFrameSection = null;
Section fUnwindInfoSection = null;
Map objcSections = new HashMap<>();
List segments = new ArrayList<>(10);
for (MachO.LoadCommand command : machO.loadCommands()) {
switch (command.type()) {
case SEGMENT: {
MachO.SegmentCommand segmentCommand = (MachO.SegmentCommand) command.body();
long begin = loadBase + segmentCommand.vmaddr();
if ("__PAGEZERO".equals(segmentCommand.segname())) {
segments.add(new Segment(segmentCommand.vmaddr(), segmentCommand.vmsize(), segmentCommand.fileoff(), segmentCommand.filesize()));
regions.add(new MemRegion(begin, begin, begin + segmentCommand.vmsize(), 0, libraryFile, segmentCommand.vmaddr()));
break;
}
segments.add(new Segment(segmentCommand.vmaddr(), segmentCommand.vmsize(), segmentCommand.fileoff(), segmentCommand.filesize()));
boolean isTextSeg = "__TEXT".equals(segmentCommand.segname());
for (MachO.SegmentCommand.Section section : segmentCommand.sections()) {
String sectName = section.sectName();
checkSection(dyId, segmentCommand.segname(), sectName);
}
if (segmentCommand.vmsize() == 0) {
regions.add(new MemRegion(begin, begin, begin, 0, libraryFile, segmentCommand.vmaddr()));
break;
}
int prot = get_segment_protection(segmentCommand.initprot());
if (prot == UnicornConst.UC_PROT_NONE) {
prot = UnicornConst.UC_PROT_ALL;
}
if (machHeader == -1 && isTextSeg) {
machHeader = begin;
}
Alignment alignment = this.mem_map(begin, segmentCommand.vmsize(), prot, dyId, emulator.getPageAlign());
write_mem((int) segmentCommand.fileoff(), (int) segmentCommand.filesize(), begin, buffer);
regions.add(new MemRegion(begin, alignment.address, alignment.address + alignment.size, prot, libraryFile, segmentCommand.vmaddr()));
break;
}
case SEGMENT_64: {
MachO.SegmentCommand64 segmentCommand64 = (MachO.SegmentCommand64) command.body();
long begin = loadBase + segmentCommand64.vmaddr();
if ("__PAGEZERO".equals(segmentCommand64.segname())) {
segments.add(new Segment(segmentCommand64.vmaddr(), segmentCommand64.vmsize(), segmentCommand64.fileoff(), segmentCommand64.filesize()));
regions.add(new MemRegion(begin, begin, begin + segmentCommand64.vmsize(), 0, libraryFile, segmentCommand64.vmaddr()));
break;
}
segments.add(new Segment(segmentCommand64.vmaddr(), segmentCommand64.vmsize(), segmentCommand64.fileoff(), segmentCommand64.filesize()));
boolean isTextSeg = "__TEXT".equals(segmentCommand64.segname());
for (MachO.SegmentCommand64.Section64 section : segmentCommand64.sections()) {
String sectName = section.sectName();
if (isTextSeg && "__eh_frame".equals(sectName)) {
fEHFrameSection = new Section(section.addr(), section.size());
continue;
}
if (isTextSeg && "__unwind_info".equals(sectName)) {
fUnwindInfoSection = new Section(section.addr(), section.size());
continue;
}
if (sectName.startsWith("__objc_")) {
objcSections.put(sectName, section);
continue;
}
checkSection(dyId, segmentCommand64.segname(), sectName);
}
if (segmentCommand64.vmsize() == 0) {
regions.add(new MemRegion(begin, begin, begin, 0, libraryFile, segmentCommand64.vmaddr()));
break;
}
int prot = get_segment_protection(segmentCommand64.initprot());
if (prot == UnicornConst.UC_PROT_NONE) {
prot = UnicornConst.UC_PROT_ALL;
}
if (machHeader == -1 && isTextSeg) {
machHeader = begin;
}
Alignment alignment = this.mem_map(begin, segmentCommand64.vmsize(), prot, dyId, emulator.getPageAlign());
if (log.isDebugEnabled()) {
log.debug("mem_map address=0x" + Long.toHexString(alignment.address) + ", size=0x" + Long.toHexString(alignment.size));
}
write_mem((int) segmentCommand64.fileoff(), (int) segmentCommand64.filesize(), begin, buffer);
regions.add(new MemRegion(begin, alignment.address, alignment.address + alignment.size, prot, libraryFile, segmentCommand64.vmaddr()));
break;
}
case LOAD_DYLIB: {
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
ordinalList.add(dylibCommand.name());
neededList.add(new NeedLibrary(dylibCommand.name(), false, false));
break;
}
case LOAD_WEAK_DYLIB: {
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
ordinalList.add(dylibCommand.name());
neededList.add(new NeedLibrary(dylibCommand.name(), true, true));
break;
}
case REEXPORT_DYLIB: {
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
ordinalList.add(dylibCommand.name());
exportDylibs.add((MachO.DylibCommand) command.body());
break;
}
case LAZY_LOAD_DYLIB: {
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
ordinalList.add(dylibCommand.name());
break;
}
case LOAD_UPWARD_DYLIB:
MachO.DylibCommand dylibCommand = (MachO.DylibCommand) command.body();
ordinalList.add(dylibCommand.name());
neededList.add(new NeedLibrary(dylibCommand.name(), true, false));
break;
case SYMTAB:
symtabCommand = (MachO.SymtabCommand) command.body();
break;
case DYSYMTAB:
dysymtabCommand = (MachO.DysymtabCommand) command.body();
break;
case MAIN:
entryPointCommand = (MachO.EntryPointCommand) command.body();
break;
}
}
Log log = LogFactory.getLog("com.github.unidbg.ios." + dyId);
if (!log.isDebugEnabled()) {
log = MachOLoader.log;
}
if (log.isDebugEnabled()) {
log.debug("load dyId=" + dyId + ", base=0x" + Long.toHexString(loadBase) + ", dyldInfoCommand=" + dyldInfoCommand + ", loadNeeded=" + loadNeeded + ", regions=" + regions + ", isPositionIndependent=" + isPositionIndependent);
}
Map exportModules = new LinkedHashMap<>();
if (rpathSet.isEmpty()) {
rpathSet.add(FilenameUtils.getFullPath(dylibPath));
}
for (MachO.DylibCommand dylibCommand : exportDylibs) {
String neededLibrary = dylibCommand.name();
if (log.isDebugEnabled()) {
log.debug(dyId + " need export dependency " + neededLibrary);
}
MachOModule loaded = modules.get(FilenameUtils.getName(neededLibrary));
if (loaded != null) {
loaded.addReferenceCount();
exportModules.put(FilenameUtils.getBaseName(loaded.name), loaded);
continue;
}
LibraryFile neededLibraryFile = resolveLibrary(libraryFile, neededLibrary, rpathSet);
if (neededLibraryFile != null) {
MachOModule needed = loadInternalPhase(neededLibraryFile, false, false, rpathSet);
needed.addReferenceCount();
exportModules.put(FilenameUtils.getBaseName(needed.name), needed);
} else if(log.isDebugEnabled()) {
log.debug(dyId + " load export dependency " + neededLibrary + " failed");
}
}
Map neededLibraries = new LinkedHashMap<>();
Map upwardLibraries = new LinkedHashMap<>();
final List lazyLoadNeededList;
if (loadNeeded) {
lazyLoadNeededList = new ArrayList<>();
for (NeedLibrary library : neededList) {
String neededLibrary = library.path;
if (log.isDebugEnabled()) {
log.debug(dyId + " need dependency " + neededLibrary);
}
MachOModule loaded = modules.get(FilenameUtils.getName(neededLibrary));
if (loaded != null) {
loaded.addReferenceCount();
neededLibraries.put(FilenameUtils.getBaseName(loaded.name), loaded);
continue;
}
if (library.upward) {
lazyLoadNeededList.add(library);
continue;
}
LibraryFile neededLibraryFile = resolveLibrary(libraryFile, neededLibrary, rpathSet);
if (neededLibraryFile != null) {
MachOModule needed = loadInternalPhase(neededLibraryFile, true, false, rpathSet);
needed.addReferenceCount();
neededLibraries.put(FilenameUtils.getBaseName(needed.name), needed);
} else if(!library.weak) {
if ("/usr/lib/libnetwork.dylib".equals(neededLibrary)) {
continue;
}
log.info("Module \"" + dyId + "\" load dependency " + neededLibrary + " failed: rpath=" + rpathSet);
}
}
} else {
lazyLoadNeededList = neededList;
}
if (log.isDebugEnabled()) {
log.debug("load dyId=" + dyId + ", base=0x" + Long.toHexString(loadBase) + ", neededLibraries=" + neededLibraries + ", upwardLibraries=" + upwardLibraries);
}
final long loadSize = size;
MachOModule module = new MachOModule(machO, dyId, loadBase, loadSize, new HashMap<>(neededLibraries), regions,
symtabCommand, dysymtabCommand, buffer, lazyLoadNeededList, upwardLibraries, exportModules, dylibPath, emulator,
dyldInfoCommand, chainedFixups, null, null, vars, machHeader, isExecutable, this, hookListeners, ordinalList,
fEHFrameSection, fUnwindInfoSection, objcSections, segments.toArray(new Segment[0]), libraryFile);
if (isExecutable) {
setExecuteModule(module);
}
modules.put(dyId, module);
if (subModule != null) {
subModule.exportModules.put(FilenameUtils.getBaseName(module.name), module);
}
if (maxDylibName == null || dyId.length() > maxDylibName.length()) {
maxDylibName = dyId;
}
if (loadSize > maxSizeOfDylib) {
maxSizeOfDylib = loadSize;
}
for (MachOModule export : modules.values()) {
for (Iterator iterator = export.lazyLoadNeededList.iterator(); iterator.hasNext(); ) {
NeedLibrary library = iterator.next();
String neededLibrary = library.path;
String name = FilenameUtils.getName(neededLibrary);
MachOModule loaded = modules.get(name);
if (loaded != null) {
if (library.upward) {
export.upwardLibraries.put(name, loaded);
} else {
export.neededLibraries().put(name, loaded);
}
iterator.remove();
}
}
}
processRebase(log, module);
if ("libsystem_malloc.dylib".equals(dyId)) {
malloc = module.findSymbolByName("_malloc", false);
free = module.findSymbolByName("_free", false);
} else if ("Foundation".equals(dyId)) {
Symbol _NSSetLogCStringFunction = module.findSymbolByName("__NSSetLogCStringFunction", false);
if (_NSSetLogCStringFunction == null) {
throw new IllegalStateException("__NSSetLogCStringFunction is null");
} else {
Svc svc = emulator.is32Bit() ? new ArmHook() {
@Override
protected HookStatus hook(Emulator> emulator) {
Arm32RegisterContext context = emulator.getContext();
Pointer message = context.getR0Pointer();
int length = context.getR1Int();
boolean withSysLogBanner = context.getR2Int() != 0;
__NSSetLogCStringFunction(message, length, withSysLogBanner);
return HookStatus.LR(emulator, 0);
}
} : new Arm64Hook() {
@Override
protected HookStatus hook(Emulator> emulator) {
Arm64RegisterContext context = emulator.getContext();
Pointer message = context.getXPointer(0);
int length = context.getXInt(1);
boolean withSysLogBanner = context.getXInt(2) != 0;
__NSSetLogCStringFunction(message, length, withSysLogBanner);
return HookStatus.LR(emulator, 0);
}
};
_NSSetLogCStringFunction.call(emulator, emulator.getSvcMemory().registerSvc(svc));
}
}
if (entryPointCommand != null) {
module.setEntryPoint(entryPointCommand.entryOff());
}
if (log.isDebugEnabled()) {
log.debug("Load library " + dyId + " offset=" + (System.currentTimeMillis() - start) + "ms");
}
notifyModuleLoaded(module);
return module;
}
private static final String RPATH = "@rpath";
private LibraryFile resolveLibrary(LibraryFile libraryFile, String neededLibrary, Collection rpathSet) throws IOException {
if (rpathSet.isEmpty() || !neededLibrary.contains(RPATH)) {
LibraryFile neededLibraryFile = libraryFile.resolveLibrary(emulator, neededLibrary);
if (libraryResolver != null && neededLibraryFile == null) {
neededLibraryFile = libraryResolver.resolveLibrary(emulator, neededLibrary);
}
return neededLibraryFile;
} else {
List rpathList = new ArrayList<>(rpathSet);
Collections.reverse(rpathList);
for (String rpath : rpathList) {
String dylibName = neededLibrary.replace(RPATH, rpath);
LibraryFile neededLibraryFile = libraryFile.resolveLibrary(emulator, dylibName);
if (libraryResolver != null && neededLibraryFile == null) {
neededLibraryFile = libraryResolver.resolveLibrary(emulator, dylibName);
}
if (neededLibraryFile != null) {
return neededLibraryFile;
}
}
return null;
}
}
private void __NSSetLogCStringFunction(Pointer message, int length, boolean withSysLogBanner) {
byte[] data = message.getByteArray(0, length);
String str = new String(data, StandardCharsets.UTF_8);
if (withSysLogBanner) {
System.err.println("NSLog: " + str);
} else {
System.out.println("NSLog: " + str);
}
}
private void checkSection(String dyId, String segName, String sectName) {
// __OBJC need fNotifyObjC = true
if (log.isDebugEnabled()) {
log.debug("checkSection name=" + sectName + ", dyId=" + dyId + ", segName=" + segName);
}
}
private void processRebase(Log log, MachOModule mm) {
MachO.DyldInfoCommand dyldInfoCommand = mm.dyldInfoCommand;
if (dyldInfoCommand == null) {
MachO.LinkeditDataCommand chainedFixups = mm.chainedFixups;
if (chainedFixups == null) {
return;
}
ByteBuffer buffer = mm.buffer.duplicate();
buffer.limit((int) (chainedFixups.dataOff() + chainedFixups.dataSize()));
buffer.position((int) chainedFixups.dataOff()); // dyld_chained_fixups_header
try (ByteBufferKaitaiStream io = new ByteBufferKaitaiStream(buffer.slice())) {
fixupAllChainedFixups(io, mm, chainedFixups);
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
if (dyldInfoCommand.rebaseSize() > 0) {
ByteBuffer buffer = mm.buffer.duplicate();
buffer.limit((int) (dyldInfoCommand.rebaseOff() + dyldInfoCommand.rebaseSize()));
buffer.position((int) dyldInfoCommand.rebaseOff());
rebase(log, buffer.slice(), mm);
}
}
private void fixupAllChainedFixups(ByteBufferKaitaiStream io, MachOModule mm, MachO.LinkeditDataCommand chainedFixups) {
long fixups_version = io.readU4le();
long starts_offset = io.readU4le(); // offset of dyld_chained_starts_in_image in chain_data
long imports_offset = io.readU4le(); // offset of imports table in chain_data
long symbols_offset = io.readU4le(); // offset of symbol strings in chain_data
int imports_count = (int) io.readU4le(); // number of imported symbol names
int imports_format = (int) io.readU4le(); // DYLD_CHAINED_IMPORT*
long symbols_format = io.readU4le(); // 0 => uncompressed, 1 => zlib compressed
if (fixups_version != 0) {
throw new IllegalStateException("chained fixups, unknown header version");
}
if (starts_offset >= io.size()) {
throw new IllegalStateException("chained fixups, starts_offset exceeds LC_DYLD_CHAINED_FIXUPS size");
}
if (imports_offset >= io.size()) {
throw new IllegalStateException("chained fixups, imports_offset exceeds LC_DYLD_CHAINED_FIXUPS size");
}
ByteBuffer buffer = mm.buffer.duplicate();
buffer.limit((int) (chainedFixups.dataOff() + chainedFixups.dataSize()));
buffer.position((int) (chainedFixups.dataOff() + symbols_offset)); // symbolsPool
try (ByteBufferKaitaiStream symbolsPool = new ByteBufferKaitaiStream(buffer.slice())) {
long formatEntrySize;
/*
* http://localhost:8080/source/xref/dyld/common/MachOAnalyzer.cpp#2814
* http://localhost:8080/source/xref/dyld/common/MachOAnalyzer.cpp#5778
*/
List bindTargets = new ArrayList<>(imports_count);
switch (imports_format) {
case FixupChains.DYLD_CHAINED_IMPORT:
/*
* struct dyld_chained_import {
* uint32_t lib_ordinal : 8,
* weak_import : 1,
* name_offset : 23;
* };
*/
formatEntrySize = 4;
io.seek(imports_offset);
for (int i = 0; i < imports_count; i++) {
long raw32 = io.readU4le();
int lib_ordinal = (int) (raw32 & 0xff);
raw32 >>>= 8;
boolean weak_import = (raw32 & 1) != 0;
int name_offset = (int) (raw32 >>> 1);
if (lib_ordinal > 0xf0) {
lib_ordinal = (byte) lib_ordinal;
}
bindTargets.add(new FixupChains.dyld_chained_import_addend64(lib_ordinal, weak_import, name_offset, 0L));
}
break;
case FixupChains.DYLD_CHAINED_IMPORT_ADDEND: {
/*
* struct dyld_chained_import_addend {
* uint32_t lib_ordinal : 8,
* weak_import : 1,
* name_offset : 23;
* int32_t addend;
* };
*/
formatEntrySize = 8;
io.seek(imports_offset);
for (int i = 0; i < imports_count; i++) {
long raw32 = io.readU4le();
int lib_ordinal = (int) (raw32 & 0xff);
raw32 >>>= 8;
boolean weak_import = (raw32 & 1) != 0;
int name_offset = (int) (raw32 >>> 1);
long addend = io.readU4le();
if (lib_ordinal > 0xf0) {
lib_ordinal = (byte) lib_ordinal;
}
bindTargets.add(new FixupChains.dyld_chained_import_addend64(lib_ordinal, weak_import, name_offset, addend));
}
break;
}
case FixupChains.DYLD_CHAINED_IMPORT_ADDEND64:
/*
* struct dyld_chained_import_addend64 {
* uint64_t lib_ordinal : 16,
* weak_import : 1,
* reserved : 15,
* name_offset : 32;
* uint64_t addend;
* };
*/
formatEntrySize = 16;
io.seek(imports_offset);
for (int i = 0; i < imports_count; i++) {
long raw64 = io.readU8le();
int lib_ordinal = (int) (raw64 & 0xffff);
raw64 >>>= 16;
boolean weak_import = (raw64 & 1) != 0;
int name_offset = (int) (raw64 >>> 16);
long addend = io.readU8le();
if (lib_ordinal > 0xfff0) {
lib_ordinal = (short) lib_ordinal;
}
bindTargets.add(new FixupChains.dyld_chained_import_addend64(lib_ordinal, weak_import, name_offset, addend));
}
break;
default:
throw new IllegalStateException("chained fixups, unknown imports_format");
}
if (FixupChains.greaterThanAddOrOverflow(imports_offset, (formatEntrySize * imports_count), symbols_offset)) {
throw new IllegalStateException("chained fixups, imports array overlaps symbols");
}
if (symbols_format != 0) {
throw new IllegalStateException("chained fixups, symbols_format unknown");
}
io.seek(starts_offset); // dyld_chained_starts_in_image
int seg_count = (int) io.readU4le();
if (seg_count != mm.segments.length) {
if (seg_count > mm.segments.length) {
throw new IllegalStateException("chained fixups, seg_count exceeds number of segments");
}
// We can have fewer segments than the count, so long as those we are missing have no relocs
int numNoRelocSegments = 0;
int numExtraSegments = mm.segments.length - seg_count;
for (Segment segment : mm.segments) {
if (segment.vmSize == 0) {
++numNoRelocSegments;
}
}
if (numNoRelocSegments != numExtraSegments) {
throw new IllegalStateException("chained fixups, seg_count does not match number of segments");
}
}
long[] seg_info_offset = new long[seg_count];
for (int i = 0; i < seg_count; i++) {
seg_info_offset[i] = io.readU4le();
}
int pointer_format_for_all = -1;
long maxValidPointerSeen = 0;
for (int i = 0; i < seg_info_offset.length; i++) {
long offset = seg_info_offset[i];
if (offset == 0) {
continue;
}
io.seek(starts_offset + offset); // dyld_chained_starts_in_segment
long size = io.readU4le(); // size of this (amount kernel needs to copy)
if (offset + size > imports_offset) { // endOfStarts
throw new IllegalStateException(String.format("chained fixups, dyld_chained_starts_in_segment for segment #%d overruns imports table", i));
}
int page_size = io.readU2le(); // 0x1000 or 0x4000
if ((page_size != 0x1000) && (page_size != 0x4000)) {
throw new IllegalStateException(String.format("chained fixups, page_size not 4KB or 16KB in segment #%d", i));
}
int pointer_format = io.readU2le(); // DYLD_CHAINED_PTR_*
if (pointer_format > 12) {
throw new IllegalStateException(String.format("chained fixups, unknown pointer_format in segment #%d", i));
}
if (pointer_format_for_all == -1) {
pointer_format_for_all = pointer_format;
}
if (pointer_format != pointer_format_for_all) {
throw new IllegalStateException(String.format("chained fixups, pointer_format not same for all segments %d and %d", pointer_format, pointer_format_for_all));
}
long segment_offset = io.readU8le(); // offset in memory to start of segment
if (segment_offset != (mm.segments[i].vmAddr - mm.machHeader) && segment_offset != mm.segments[i].vmAddr) {
throw new IllegalStateException(String.format("chained fixups, segment_offset does not match vmaddr from LC_SEGMENT in segment #%d", i));
}
long max_valid_pointer = io.readU4le(); // for 32-bit OS, any value beyond this is not a pointer
if (max_valid_pointer != 0) {
if (maxValidPointerSeen == 0) {
// record max_valid_pointer values seen
maxValidPointerSeen = max_valid_pointer;
} else if (maxValidPointerSeen != max_valid_pointer) {
throw new IllegalStateException("chained fixups, different max_valid_pointer values seen in different segments");
}
}
int page_count = io.readU2le(); // how many pages are in array
if (page_count == 0) {
continue;
}
for (long pageIndex = 0; pageIndex < page_count; pageIndex++) {
int offsetInPage = io.readU2le(); // each entry is offset in each page of first element in chain or DYLD_CHAINED_PTR_START_NONE if no fixups on page
if (offsetInPage == FixupChains.DYLD_CHAINED_PTR_START_NONE) {
continue;
}
if ((offsetInPage & FixupChains.DYLD_CHAINED_PTR_START_MULTI) != 0) {
throw new UnsupportedOperationException("DYLD_CHAINED_PTR_START_MULTI");
} else if (offsetInPage > page_size) {
throw new IllegalStateException(String.format("chained fixups, in segment #%d page_start[%d]=0x%04X exceeds page size", i, pageIndex, offsetInPage));
}
// one chain per page
Pointer pageContentStart = UnidbgPointer.pointer(emulator, mm.machHeader + segment_offset + (pageIndex * page_size));
assert pageContentStart != null;
Pointer chain = pageContentStart.share(offsetInPage);
walkChain(mm, chain, pointer_format, bindTargets, symbolsPool);
}
}
if (imports_count != 0) {
int maxBindOrdinal = 0;
switch (pointer_format_for_all) {
case FixupChains.DYLD_CHAINED_PTR_32:
maxBindOrdinal = 0x0fffff; // 20-bits
break;
case FixupChains.DYLD_CHAINED_PTR_ARM64E:
case FixupChains.DYLD_CHAINED_PTR_ARM64E_USERLAND:
case FixupChains.DYLD_CHAINED_PTR_ARM64E_OFFSET:
maxBindOrdinal = 0x00ffff; // 16-bits
break;
case FixupChains.DYLD_CHAINED_PTR_64:
case FixupChains.DYLD_CHAINED_PTR_64_OFFSET:
case FixupChains.DYLD_CHAINED_PTR_ARM64E_USERLAND24:
maxBindOrdinal = 0xFFFFFF; // 24 bits
break;
}
if (imports_count >= maxBindOrdinal) {
throw new IllegalStateException(String.format("chained fixups, imports_count (%d) exceeds max of %d", imports_count, maxBindOrdinal));
}
}
if (maxValidPointerSeen != 0) {
Segment segment = mm.segments[mm.segments.length - 1];
long lastSegmentLastVMAddr = segment.vmAddr + segment.vmSize;
if (maxValidPointerSeen < lastSegmentLastVMAddr) {
throw new IllegalStateException("chained fixups, max_valid_pointer too small for image");
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 参考实现
*/
private void walkChain(MachOModule mm, Pointer chain, int pointer_format, List bindTargets, ByteBufferKaitaiStream symbolsPool) {
boolean chainEnd = false;
while (!chainEnd) {
long raw64 = chain.getLong(0);
FixupChains.handleChain(emulator, mm, hookListeners, pointer_format, chain, raw64, bindTargets, symbolsPool);
switch (pointer_format) {
case FixupChains.DYLD_CHAINED_PTR_ARM64E: {
long dyld_chained_ptr_arm64e_rebase = chain.getLong(16);
int next = (int) ((dyld_chained_ptr_arm64e_rebase >> 51) & 0x7ff);
if (next == 0) {
chainEnd = true;
} else {
chain = chain.share(next * 8);
}
break;
}
case FixupChains.DYLD_CHAINED_PTR_64:
case FixupChains.DYLD_CHAINED_PTR_64_OFFSET:
int next = (int) ((raw64 >> 51) & 0xfff);
if (next == 0) {
chainEnd = true;
} else {
chain = chain.share(next * 4);
}
break;
default:
throw new UnsupportedOperationException("pointer_format=" + pointer_format);
}
}
}
private void rebase(Log log, ByteBuffer buffer, MachOModule module) {
final List regions = module.getRegions();
int type = 0;
int segmentIndex;
long address = module.base;
long segmentEndAddress = module.base + module.size;
int count;
int skip;
boolean done = false;
while (!done && buffer.hasRemaining()) {
int b = buffer.get() & 0xff;
int immediate = b & REBASE_IMMEDIATE_MASK;
int opcode = b & REBASE_OPCODE_MASK;
switch (opcode) {
case REBASE_OPCODE_DONE:
done = true;
break;
case REBASE_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segmentIndex = immediate;
if (segmentIndex >= regions.size()) {
throw new IllegalStateException(String.format("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", segmentIndex, regions.size() - 1));
}
MemRegion region = regions.get(segmentIndex);
address = region.begin + Utils.readULEB128(buffer).longValue();
segmentEndAddress = region.end;
break;
case REBASE_OPCODE_ADD_ADDR_ULEB:
address += Utils.readULEB128(buffer).longValue();
break;
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
address += ((long) immediate * emulator.getPointerSize());
break;
case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
for (int i = 0; i < immediate; i++) {
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
rebaseAt(log, type, address, module);
address += emulator.getPointerSize();
}
break;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
count = Utils.readULEB128(buffer).intValue();
for (int i = 0; i < count; i++) {
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
rebaseAt(log, type, address, module);
address += emulator.getPointerSize();
}
break;
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
rebaseAt(log, type, address, module);
address += (Utils.readULEB128(buffer).longValue() + emulator.getPointerSize());
break;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
count = Utils.readULEB128(buffer).intValue();
skip = Utils.readULEB128(buffer).intValue();
for (int i = 0; i < count; i++) {
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
rebaseAt(log, type, address, module);
address += (skip + emulator.getPointerSize());
}
break;
default:
throw new IllegalStateException("bad rebase opcode=0x" + Integer.toHexString(opcode));
}
}
}
private void rebaseAt(Log log, int type, long address, Module module) {
Pointer pointer = UnidbgPointer.pointer(emulator, address);
if (pointer == null) {
throw new IllegalStateException();
}
Pointer newPointer = pointer.getPointer(0);
Pointer old = newPointer;
if (newPointer == null) {
newPointer = UnidbgPointer.pointer(emulator, module.base);
} else {
newPointer = newPointer.share(module.base);
}
if (log.isTraceEnabled()) {
log.trace("rebaseAt type=" + type + ", address=0x" + Long.toHexString(address - module.base) + ", module=" + module.name + ", old=" + old + ", new=" + newPointer);
}
switch (type) {
case REBASE_TYPE_POINTER:
case REBASE_TYPE_TEXT_ABSOLUTE32:
pointer.setPointer(0, newPointer);
break;
default:
throw new IllegalStateException("bad rebase type " + type);
}
}
private void bindLocalRelocations(MachOModule module) {
MachO.DysymtabCommand dysymtabCommand = module.dysymtabCommand;
if (dysymtabCommand.nLocRel() <= 0) {
return;
}
ByteBuffer buffer = module.buffer;
buffer.limit((int) (dysymtabCommand.locRelOff() + dysymtabCommand.nLocRel() * 8));
buffer.position((int) dysymtabCommand.locRelOff());
ByteBuffer slice = buffer.slice();
slice.order(ByteOrder.LITTLE_ENDIAN);
Log log = LogFactory.getLog("com.github.unidbg.ios." + module.name);
for (int i = 0; i < dysymtabCommand.nLocRel(); i++) {
Relocation relocation = Relocation.create(slice);
if (relocation.pcRel || relocation.extern || relocation.scattered ||
relocation.length != (emulator.is64Bit() ? 3 : 2) ||
relocation.type != ARM_RELOC_VANILLA) {
throw new IllegalStateException("Unexpected relocation found.");
}
buffer.limit(relocation.address + emulator.getPointerSize());
buffer.position(relocation.address);
long target = emulator.is64Bit() ? buffer.getLong() : buffer.getInt();
Pointer pointer = UnidbgPointer.pointer(emulator, module.base + relocation.address);
if (pointer == null) {
throw new IllegalStateException();
}
pointer.setPointer(0, UnidbgPointer.pointer(emulator, module.base + target));
if (log.isDebugEnabled()) {
log.debug("bindLocalRelocations address=0x" + Integer.toHexString(relocation.address) + ", symbolNum=0x" + Integer.toHexString(relocation.symbolNum) + ", target=0x" + Long.toHexString(target));
}
}
}
private boolean bindExternalRelocations(MachOModule module) {
MachO.DysymtabCommand dysymtabCommand = module.dysymtabCommand;
if (dysymtabCommand.nExtRel() <= 0) {
return true;
}
ByteBuffer buffer = module.buffer;
buffer.limit((int) (dysymtabCommand.extRelOff() + dysymtabCommand.nExtRel() * 8));
buffer.position((int) dysymtabCommand.extRelOff());
ByteBuffer slice = buffer.slice();
slice.order(ByteOrder.LITTLE_ENDIAN);
Log log = LogFactory.getLog("com.github.unidbg.ios." + module.name);
boolean ret = true;
for (int i = 0; i < dysymtabCommand.nExtRel(); i++) {
Relocation relocation = Relocation.create(slice);
if (relocation.pcRel || !relocation.extern || relocation.scattered ||
relocation.length != (emulator.is64Bit() ? 3 : 2) ||
relocation.type != ARM_RELOC_VANILLA) {
throw new IllegalStateException("Unexpected relocation found.");
}
MachOSymbol symbol = module.getSymbolByIndex(relocation.symbolNum);
Pointer pointer = UnidbgPointer.pointer(emulator, module.base + relocation.address);
if (pointer == null) {
throw new IllegalStateException();
}
boolean isWeakRef = (symbol.nlist.desc() & N_WEAK_REF) != 0;
long address = resolveSymbol(module, symbol);
if (address == 0L) {
if (isWeakRef) {
if (log.isDebugEnabled()) {
log.debug("bindExternalRelocations failed symbol=" + symbol + ", isWeakRef=true");
}
pointer.setPointer(0, null);
} else {
log.warn("bindExternalRelocations failed symbol=" + symbol + ", isWeakRef=false");
}
ret = false;
} else {
pointer.setPointer(0, UnidbgPointer.pointer(emulator, address));
if (log.isDebugEnabled()) {
log.debug("bindExternalRelocations address=0x" + Long.toHexString(relocation.address) + ", symbolNum=0x" + Integer.toHexString(relocation.symbolNum) + ", symbolName=" + symbol.getName());
}
}
}
return ret;
}
private long resolveSymbol(MachOModule module, MachOSymbol symbol) {
int libraryOrdinal = symbol.getLibraryOrdinal();
Symbol replace = null;
if (libraryOrdinal == BIND_SPECIAL_DYLIB_SELF) {
replace = module.findSymbolByName(symbol.getName(), false);
} else if (libraryOrdinal <= module.ordinalList.size()) {
String path = module.ordinalList.get(libraryOrdinal - 1);
MachOModule targetImage = this.modules.get(FilenameUtils.getName(path));
if (targetImage != null) {
replace = findSymbolInternal(targetImage, symbol.getName());
} else {
if (log.isDebugEnabled()) {
log.debug("resolveSymbol libraryOrdinal=" + libraryOrdinal + ", path=" + path);
}
}
} else {
throw new IllegalStateException(String.format("bad mach-o binary, library ordinal (%d) too big (max %d) for symbol %s in %s", libraryOrdinal, module.ordinalList.size(), symbol.getName(), module.getPath()));
}
long address = replace == null ? 0L : replace.getAddress();
for (HookListener listener : hookListeners) {
long hook = listener.hook(emulator.getSvcMemory(), replace == null ? module.name : replace.getModuleName(), symbol.getName(), address);
if (hook > 0) {
address = hook;
break;
}
}
return address;
}
private Pointer dyldLazyBinder;
private Pointer dyldFuncLookup;
private void setupLazyPointerHandler(MachOModule module) {
if (module.lazyPointerProcessed) {
return;
}
module.lazyPointerProcessed = true;
if (module.isVirtual()) { // virtual module
return;
}
for (MachO.LoadCommand command : module.machO.loadCommands()) {
switch (command.type()) {
case SEGMENT:
MachO.SegmentCommand segmentCommand = (MachO.SegmentCommand) command.body();
if ("__DATA".equals(segmentCommand.segname())) {
for (MachO.SegmentCommand.Section section : segmentCommand.sections()) {
if ("__dyld".equals(section.sectName())) {
Pointer dd = UnidbgPointer.pointer(emulator, module.base + section.addr());
if (dyldLazyBinder == null) {
dyldLazyBinder = emulator.getSvcMemory().registerSvc(new ArmSvc() {
@Override
public long handle(Emulator> emulator) {
return ((Dyld) emulator.getDlfcn())._stub_binding_helper();
}
});
}
if (dyldFuncLookup == null) {
dyldFuncLookup = emulator.getSvcMemory().registerSvc(new ArmSvc() {
@Override
public long handle(Emulator> emulator) {
String name = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R0).getString(0);
Pointer address = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
return ((Dyld) emulator.getDlfcn())._dyld_func_lookup(emulator, name, address);
}
});
}
if (dd != null) {
dd.setPointer(0, dyldLazyBinder);
dd.setPointer(emulator.getPointerSize(), dyldFuncLookup);
}
}
}
}
break;
case SEGMENT_64:
MachO.SegmentCommand64 segmentCommand64 = (MachO.SegmentCommand64) command.body();
if ("__DATA".equals(segmentCommand64.segname())) {
for (MachO.SegmentCommand64.Section64 section : segmentCommand64.sections()) {
if ("__dyld".equals(section.sectName())) {
Pointer dd = UnidbgPointer.pointer(emulator, module.base + section.addr());
if (dyldLazyBinder == null) {
dyldLazyBinder = emulator.getSvcMemory().registerSvc(new Arm64Svc() {
@Override
public long handle(Emulator> emulator) {
return ((Dyld) emulator.getDlfcn())._stub_binding_helper();
}
});
}
if (dyldFuncLookup == null) {
dyldFuncLookup = emulator.getSvcMemory().registerSvc(new Arm64Svc() {
@Override
public long handle(Emulator> emulator) {
String name = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X0).getString(0);
Pointer address = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1);
return ((Dyld) emulator.getDlfcn())._dyld_func_lookup(emulator, name, address);
}
});
}
if (dd != null) {
dd.setPointer(0, dyldLazyBinder);
dd.setPointer(emulator.getPointerSize(), dyldFuncLookup);
}
}
}
}
break;
}
}
}
private void bindIndirectSymbolPointers(MachOModule module) {
if (module.indirectSymbolBound) {
return;
}
module.indirectSymbolBound = true;
if (module.chainedFixups != null) {
return;
}
MachO.DysymtabCommand dysymtabCommand = module.dysymtabCommand;
if (dysymtabCommand == null) { // virtual module
return;
}
List indirectTable = dysymtabCommand.indirectSymbols();
MachO.DyldInfoCommand dyldInfoCommand = module.dyldInfoCommand;
if (dyldInfoCommand == null) {
bindLocalRelocations(module);
boolean ret = true;
for (MachO.LoadCommand command : module.machO.loadCommands()) {
switch (command.type()) {
case SEGMENT: {
MachO.SegmentCommand segmentCommand = (MachO.SegmentCommand) command.body();
for (MachO.SegmentCommand.Section section : segmentCommand.sections()) {
ret = processSection(module, indirectTable, ret, section.flags(), section.size(), section.addr(), section.reserved1());
}
break;
}
case SEGMENT_64: {
MachO.SegmentCommand64 segmentCommand = (MachO.SegmentCommand64) command.body();
for (MachO.SegmentCommand64.Section64 section : segmentCommand.sections()) {
ret = processSection(module, indirectTable, ret, section.flags(), section.size(), section.addr(), section.reserved1());
}
break;
}
}
}
ret &= bindExternalRelocations(module);
module.allSymbolBound = ret;
} else {
if (dyldInfoCommand.bindSize() > 0) {
ByteBuffer buffer = module.buffer.duplicate();
buffer.limit((int) (dyldInfoCommand.bindOff() + dyldInfoCommand.bindSize()));
buffer.position((int) dyldInfoCommand.bindOff());
Log log = LogFactory.getLog("com.github.unidbg.ios." + module.name);
if (!log.isDebugEnabled()) {
log = MachOLoader.log;
}
module.allSymbolBound = eachBind(log, buffer.slice(), module);
}
}
}
private boolean processSection(MachOModule module, List indirectTable, boolean allSymbolBound, long flags, long size, long addr, long reserved1) {
Log log = LogFactory.getLog("com.github.unidbg.ios." + module.name);
if (!log.isDebugEnabled()) {
log = MachOLoader.log;
}
long type = flags & SECTION_TYPE;
long elementCount = size / emulator.getPointerSize();
if (type != S_NON_LAZY_SYMBOL_POINTERS && type != S_LAZY_SYMBOL_POINTERS) {
return allSymbolBound;
}
long ptrToBind = addr;
int indirectTableOffset = (int) reserved1;
for (int i = 0; i < elementCount; i++, ptrToBind += emulator.getPointerSize()) {
long symbolIndex = indirectTable.get(indirectTableOffset + i);
if (symbolIndex == INDIRECT_SYMBOL_ABS) {
continue; // do nothing since already has absolute address
}
if (symbolIndex == INDIRECT_SYMBOL_LOCAL) {
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, ptrToBind + module.base);
if (pointer == null) {
throw new IllegalStateException("pointer is null");
}
Pointer newPointer = pointer.getPointer(0);
if (newPointer == null) {
newPointer = UnidbgPointer.pointer(emulator, module.base);
} else {
newPointer = newPointer.share(module.base);
}
if (log.isDebugEnabled()) {
log.debug("bindIndirectSymbolPointers pointer=" + pointer + ", newPointer=" + newPointer);
}
pointer.setPointer(0, newPointer);
continue;
}
MachOSymbol symbol = module.getSymbolByIndex((int) symbolIndex);
if (symbol == null) {
log.warn("bindIndirectSymbolPointers symbol is null");
allSymbolBound = false;
continue;
}
boolean isWeakRef = (symbol.nlist.desc() & N_WEAK_REF) != 0;
long address = resolveSymbol(module, symbol);
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, ptrToBind + module.base);
if (pointer == null) {
throw new IllegalStateException("pointer is null");
}
if (address == 0L) {
if (log.isDebugEnabled()) {
log.debug("bindIndirectSymbolPointers symbol=" + symbol + ", isWeakRef=" + isWeakRef);
}
pointer.setPointer(0, null);
} else {
pointer.setPointer(0, UnidbgPointer.pointer(emulator, address));
if (log.isDebugEnabled()) {
log.debug("bindIndirectSymbolPointers symbolIndex=0x" + Long.toHexString(symbolIndex) + ", symbol=" + symbol + ", ptrToBind=0x" + Long.toHexString(ptrToBind));
}
}
}
return allSymbolBound;
}
private boolean eachBind(Log log, ByteBuffer buffer, MachOModule module) {
final List regions = module.getRegions();
int type = 0;
int segmentIndex;
long address = module.base;
long segmentEndAddress = address + module.size;
String symbolName = null;
int symbolFlags = 0;
int libraryOrdinal = 0;
long addend = 0;
int count;
int skip;
boolean done = false;
boolean ret = true;
while (!done && buffer.hasRemaining()) {
int b = buffer.get() & 0xff;
int immediate = b & BIND_IMMEDIATE_MASK;
int opcode = b & BIND_OPCODE_MASK;
switch (opcode) {
case BIND_OPCODE_DONE:
done = true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
libraryOrdinal = immediate;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
libraryOrdinal = Utils.readULEB128(buffer).intValue();
break;
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
// the special ordinals are negative numbers
if ( immediate == 0 )
libraryOrdinal = 0;
else {
libraryOrdinal = BIND_OPCODE_MASK | immediate;
}
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((b = buffer.get()) != 0) {
baos.write(b);
}
symbolName = baos.toString();
symbolFlags = immediate;
break;
case BIND_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
addend = Utils.readULEB128(buffer).longValue();
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segmentIndex = immediate;
if (segmentIndex >= regions.size()) {
throw new IllegalStateException(String.format("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", segmentIndex, regions.size() - 1));
}
MemRegion region = regions.get(segmentIndex);
address = region.begin + Utils.readULEB128(buffer).longValue();
segmentEndAddress = region.end;
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
address += Utils.readULEB128(buffer).longValue();
break;
case BIND_OPCODE_DO_BIND:
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
ret &= doBindAt(log, libraryOrdinal, type, address, symbolName, symbolFlags, addend, module);
address += emulator.getPointerSize();
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
ret &= doBindAt(log, libraryOrdinal, type, address, symbolName, symbolFlags, addend, module);
address += (Utils.readULEB128(buffer).longValue() + emulator.getPointerSize());
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
ret &= doBindAt(log, libraryOrdinal, type, address, symbolName, symbolFlags, addend, module);
address += ((long) immediate *emulator.getPointerSize() + emulator.getPointerSize());
break;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
count = Utils.readULEB128(buffer).intValue();
skip = Utils.readULEB128(buffer).intValue();
for (int i = 0; i < count; i++) {
if (address >= segmentEndAddress) {
throw new IllegalStateException();
}
ret &= doBindAt(log, libraryOrdinal, type, address, symbolName, symbolFlags, addend, module);
address += (skip + emulator.getPointerSize());
}
break;
default:
throw new IllegalStateException(String.format("bad bind opcode 0x%s in bind info", Integer.toHexString(opcode)));
}
}
return ret;
}
private boolean doBindAt(Log log, int libraryOrdinal, int type, long address, String symbolName, int symbolFlags, long addend, MachOModule module) {
Pointer pointer = UnidbgPointer.pointer(emulator, address);
if (pointer == null) {
throw new IllegalStateException();
}
// libraryOrdinal = (byte) libraryOrdinal;
MachOModule targetImage;
if (libraryOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) {
targetImage = executableModule;
} else if (libraryOrdinal == BIND_SPECIAL_DYLIB_SELF) {
targetImage = module;
} else if (libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP) {
for(MachOModule mm : modules.values().toArray(new MachOModule[0])) {
if (doBindAt(type, pointer, addend, module, mm, symbolName)) {
return true;
}
}
return false;
} else if (libraryOrdinal <= 0) {
throw new IllegalStateException(String.format("bad mach-o binary, unknown special library ordinal (%d) too big for symbol %s in %s: symbolFlags=0x%x", libraryOrdinal, symbolName, module.getPath(), symbolFlags));
} else if (libraryOrdinal <= module.ordinalList.size()) {
String path = module.ordinalList.get(libraryOrdinal - 1);
targetImage = this.modules.get(FilenameUtils.getName(path));
if (targetImage == null) { // LOAD_WEAK_DYLIB
if (log.isDebugEnabled()) {
log.debug("doBindAt LOAD_WEAK_DYLIB: " + path);
}
return false;
}
} else {
throw new IllegalStateException(String.format("bad mach-o binary, library ordinal (%d) too big (max %d) for symbol %s in %s", libraryOrdinal, module.ordinalList.size(), symbolName, module.getPath()));
}
targetImage = fakeTargetImage(targetImage, symbolName);
return doBindAt(type, pointer, addend, module, targetImage, symbolName);
}
final MachOModule fakeTargetImage(MachOModule targetImage, String symbolName) {
if ("___NSArray0__".equals(symbolName) ||
"___NSDictionary0__".equals(symbolName) ||
"_OBJC_CLASS_$_NSConstantIntegerNumber".equals(symbolName) ||
"_NSProcessInfoPowerStateDidChangeNotification".equals(symbolName) ||
"_NSExtensionHostDidEnterBackgroundNotification".equals(symbolName) ||
"_NSExtensionHostDidBecomeActiveNotification".equals(symbolName)) {
targetImage = this.modules.get("UIKit");
if (targetImage == null) {
targetImage = this.modules.get("AppKit");
}
if (targetImage == null) {
throw new IllegalStateException();
}
}
return targetImage;
}
final Symbol findSymbolInternal(MachOModule targetImage, String symbolName) {
if ("CoreFoundation".equals(targetImage.name)) {
if ("___kCFBooleanFalse".equals(symbolName) ||
"___kCFBooleanTrue".equals(symbolName)) {
Symbol symbol = targetImage.findSymbolByName(symbolName.substring(2), false);
if (symbol == null) {
throw new IllegalStateException();
}
long address = symbol.getAddress();
Pointer pointer = UnidbgPointer.pointer(emulator, address);
assert pointer != null;
long value = pointer.getLong(0);
return new ExportSymbol(symbolName, value, targetImage, 0, com.github.unidbg.ios.MachO.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
}
}
Symbol symbol = targetImage.findSymbolByName(symbolName, false);
if (symbol != null) {
return symbol;
}
if ("CFNetwork".equals(targetImage.name)) {
MachOModule foundation = modules.get("Foundation");
if (foundation != null) {
symbol = findSymbolInternal(foundation, symbolName);
if (symbol != null) {
if (log.isDebugEnabled()) {
log.debug("Redirect symbol=" + symbol);
}
}
}
}
return symbol;
}
private boolean doBindAt(int type, Pointer pointer, long addend, Module module, MachOModule targetImage, String symbolName) {
Symbol symbol = this.findSymbolInternal(targetImage, symbolName);
if (symbol == null) {
if (log.isDebugEnabled()) {
log.info("doBindAt type=" + type + ", symbolName=" + symbolName + ", targetImage=" + targetImage);
}
long bindAt = 0;
for (HookListener listener : hookListeners) {
long hook = listener.hook(emulator.getSvcMemory(), module.name, symbolName, HookListener.EACH_BIND);
if (hook > 0) {
bindAt = hook;
break;
}
}
if (bindAt > 0) {
Pointer newPointer = UnidbgPointer.pointer(emulator, bindAt);
switch (type) {
case BIND_TYPE_POINTER:
pointer.setPointer(0, newPointer);
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
case BIND_TYPE_TEXT_PCREL32:
default:
throw new IllegalStateException("bad bind type " + type);
}
return true;
}
return false;
}
long bindAt = symbol.getAddress() + addend;
for (HookListener listener : hookListeners) {
long hook = listener.hook(emulator.getSvcMemory(), symbol.getModuleName(), symbol.getName(), bindAt);
if (hook > 0) {
bindAt = hook;
break;
}
}
if (log.isTraceEnabled()) {
log.trace("doBindAt 0x=" + Long.toHexString(symbol.getValue()) + ", type=" + type + ", symbolName=" + symbol.getModuleName() + ", addend=" + addend + ", lazy=" + false + ", symbol=" + symbol + ", pointer=" + pointer + ", bindAt=0x" + Long.toHexString(bindAt));
}
Pointer newPointer = UnidbgPointer.pointer(emulator, bindAt);
switch (type) {
case BIND_TYPE_POINTER:
pointer.setPointer(0, newPointer);
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
pointer.setInt(0, (int) (symbol.getAddress() + addend));
break;
case BIND_TYPE_TEXT_PCREL32:
default:
throw new IllegalStateException("bad bind type " + type);
}
return true;
}
private String maxDylibName;
private long maxSizeOfDylib;
private void write_mem(int offset, int size, long begin, ByteBuffer buffer) {
if (size > 0) {
buffer.limit(offset + size);
buffer.position(offset);
byte[] data = new byte[size];
buffer.get(data);
pointer(begin).write(data);
} else if(size < 0) {
log.warn("write_mem offset=" + offset + ", size=" + offset + ", begin=0x" + Long.toHexString(begin));
}
}
final Map modules = new LinkedHashMap<>();
private int get_segment_protection(MachO.VmProt vmProt) {
int prot = Unicorn.UC_PROT_NONE;
if (vmProt.read()) prot |= Unicorn.UC_PROT_READ;
if (vmProt.write()) prot |= Unicorn.UC_PROT_WRITE;
if (vmProt.execute()) prot |= Unicorn.UC_PROT_EXEC;
return prot;
}
@Override
public int brk(long address) {
throw new UnsupportedOperationException();
}
private Symbol malloc, free;
@Override
public MemoryBlock malloc(int length, boolean runtime) {
if (runtime) {
return MemoryBlockImpl.alloc(this, length);
} else {
return MemoryAllocBlock.malloc(emulator, malloc, free, length);
}
}
private int lastErrno;
@Override
public int getLastErrno() {
return lastErrno;
}
@Override
public void setErrno(int errno) {
this.lastErrno = errno;
Task task = emulator.get(Task.TASK_KEY);
if (task != null && task.setErrno(emulator, errno)) {
return;
}
if (this.errno != null) {
this.errno.setInt(0, errno);
}
}
@Override
public Module dlopen(String path) {
return dlopen(path, true);
}
private void processBind(MachOModule m) {
bindIndirectSymbolPointers(m);
setupLazyPointerHandler(m);
}
public boolean dlopen_preflight(String path) {
MachOModule loaded = modules.get(FilenameUtils.getName(path));
if (loaded != null) {
return true;
}
LibraryFile libraryFile = libraryResolver == null ? null : libraryResolver.resolveLibrary(emulator, path);
return libraryFile != null;
}
@Override
public Module dlopen(String path, boolean callInit) {
if ("/usr/lib/libSystem.dylib".equals(path)) {
path = "/usr/lib/libSystem.B.dylib";
}
MachOModule loadedModule = modules.get(FilenameUtils.getName(path));
if (loadedModule != null) {
loadedModule.addReferenceCount();
return loadedModule;
}
for (Module module : getLoadedModules()) {
for (MemRegion memRegion : module.getRegions()) {
if (path.equals(memRegion.getName())) {
module.addReferenceCount();
return module;
}
}
}
LibraryFile libraryFile = libraryResolver == null ? null : libraryResolver.resolveLibrary(emulator, path);
if (libraryFile == null) {
return null;
}
MachOModule module = loadInternalPhase(libraryFile, true, false, Collections.emptyList());
for (MachOModule export : modules.values().toArray(new MachOModule[0])) {
for (NeedLibrary library : export.lazyLoadNeededList.toArray(new NeedLibrary[0])) {
String neededLibrary = library.path;
if (log.isDebugEnabled()) {
log.debug(export.getPath() + " need dependency " + neededLibrary);
}
MachOModule loaded = modules.get(FilenameUtils.getName(neededLibrary));
if (loaded != null) {
if (library.upward) {
export.upwardLibraries.put(FilenameUtils.getBaseName(neededLibrary), loaded);
}
continue;
}
try {
LibraryFile neededLibraryFile = resolveLibrary(libraryFile, neededLibrary, Collections.singletonList(FilenameUtils.getFullPath(libraryFile.getPath())));
if (neededLibraryFile != null) {
MachOModule needed = loadInternalPhase(neededLibraryFile, true, false, Collections.emptySet());
needed.addReferenceCount();
if (library.upward) {
export.upwardLibraries.put(FilenameUtils.getBaseName(needed.name), needed);
}
} else if (!library.weak) {
log.info(export.getPath() + " load dependency " + neededLibrary + " failed");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
export.lazyLoadNeededList.clear();
}
for (MachOModule export : modules.values()) {
if (!export.lazyLoadNeededList.isEmpty()) {
log.info("dlopen " + path + " resolve needed library failed: " + export.name + ", neededList=" + export.lazyLoadNeededList);
}
}
for (MachOModule m : modules.values()) {
processBind(m);
}
if (!callInitFunction) { // No need call init array
for (MachOModule m : modules.values()) {
m.initFunctionList.clear();
}
}
if (callInit) {
for (MachOModule m : modules.values()) {
if (m.allSymbolBound) {
m.doInitialization(emulator);
}
}
}
module.addReferenceCount();
return module;
}
@Override
public boolean dlclose(long handle) {
throw new UnsupportedOperationException();
}
@Override
public Symbol dlsym(long handle, String symbolName) {
for (MachOModule module : modules.values()) {
if (module.machHeader == handle) {
return module.findSymbolByName(symbolName, false);
}
if (handle == Dyld.RTLD_NEXT) {
Symbol symbol = module.findSymbolByName(symbolName, false);
if (symbol != null) {
return symbol;
}
}
}
if (handle == Dyld.RTLD_DEFAULT) {
for (Module module : modules.values()) {
Symbol symbol = module.findSymbolByName(symbolName, false);
if (symbol != null) {
return symbol;
}
}
}
log.info("dlsym failed: handle=" + handle + ", symbolName=" + symbolName);
return null;
}
@Override
public Collection getLoadedModules() {
return new ArrayList<>(modules.values());
}
final List getLoadedModulesNoVirtual() {
List list = new ArrayList<>(modules.size());
for (MachOModule mm : modules.values()) {
if (!mm.isVirtual()) {
list.add(mm);
}
}
return list;
}
@Override
public String getMaxLengthLibraryName() {
return maxDylibName;
}
@Override
public long getMaxSizeOfLibrary() {
return maxSizeOfDylib;
}
final List addImageCallbacks = new ArrayList<>();
final List boundHandlers = new ArrayList<>();
final List initializedHandlers = new ArrayList<>();
UnidbgPointer _objcNotifyMapped;
UnidbgPointer _objcNotifyInit;
private UnidbgStructure createDyldImageInfo(MachOModule module) {
if (emulator.is64Bit()) {
int elementSize = UnidbgStructure.calculateSize(DyldImageInfo64.class);
Pointer pointer = emulator.getSvcMemory().allocate(elementSize, "notifySingle");
DyldImageInfo64 info = new DyldImageInfo64(pointer);
info.imageFilePath = UnidbgPointer.nativeValue(module.createPathMemory(emulator.getSvcMemory()));
info.imageLoadAddress = module.machHeader;
info.imageFileModDate = 0;
info.pack();
return info;
} else {
int elementSize = UnidbgStructure.calculateSize(DyldImageInfo32.class);
Pointer pointer = emulator.getSvcMemory().allocate(elementSize, "notifySingle");
DyldImageInfo32 info = new DyldImageInfo32(pointer);
info.imageFilePath = (int) (UnidbgPointer.nativeValue(module.createPathMemory(emulator.getSvcMemory())));
info.imageLoadAddress = (int) module.machHeader;
info.imageFileModDate = 0;
info.pack();
return info;
}
}
private void notifySingle(int state, MachOModule module) {
if (module.isVirtual()) { // virtual module
return;
}
UnidbgStructure info = createDyldImageInfo(module);
switch (state) {
case Dyld.dyld_image_state_bound:
long slide = Dyld.computeSlide(emulator, module.machHeader);
if (!module.executable) {
for (UnidbgPointer callback : addImageCallbacks) {
if (module.addImageCallSet.add(callback)) {
if (log.isDebugEnabled()) {
log.debug("notifySingle callback=" + callback + ", module=" + module.name);
}
Module.emulateFunction(emulator, callback.peer, UnidbgPointer.pointer(emulator, module.machHeader), UnidbgPointer.pointer(emulator, slide));
}
}
}
for (UnidbgPointer handler : boundHandlers) {
if (module.boundCallSet.add(handler)) {
if (log.isDebugEnabled()) {
log.debug("notifySingle state=" + state + ", handler=" + handler + ", module=" + module.name);
}
Module.emulateFunction(emulator, handler.peer, state, 1, info);
}
}
break;
case Dyld.dyld_image_state_dependents_initialized:
for (UnidbgPointer handler : initializedHandlers) {
if (module.dependentsInitializedCallSet.add(handler)) {
if (log.isDebugEnabled()) {
log.debug("notifySingle state=" + state + ", handler=" + handler + ", module=" + module.name);
}
Module.emulateFunction(emulator, handler.peer, state, 1, info);
}
}
break;
case Dyld.dyld_image_state_initialized:
for (UnidbgPointer handler : boundHandlers) {
if (module.initializedCallSet.add(handler)) {
if (log.isDebugEnabled()) {
log.debug("notifySingle state=" + state + ", handler=" + handler + ", module=" + module.name);
}
Module.emulateFunction(emulator, handler.peer, state, 1, info);
}
}
module.callObjcNotifyInit(_objcNotifyInit);
break;
default:
throw new UnsupportedOperationException("state=" + state);
}
}
private void setExecuteModule(MachOModule module) {
if (executableModule == null) {
executableModule = module;
vars.setPointer(0, UnidbgPointer.pointer(emulator, module.machHeader)); // _NSGetMachExecuteHeader
}
}
MachOModule executableModule;
final long allocate(long size, long mask) {
if (log.isDebugEnabled()) {
log.debug("allocate size=0x" + Long.toHexString(size) + ", mask=0x" + Long.toHexString(mask));
}
long address = allocateMapAddress(mask, size);
int prot = UnicornConst.UC_PROT_READ | UnicornConst.UC_PROT_WRITE;
backend.mem_map(address, size, prot);
if (mMapListener != null) {
mMapListener.onMap(address, size, prot);
}
if (memoryMap.put(address, new MemoryMap(address, size, prot)) != null) {
log.warn("Replace memory map address=0x" + Long.toHexString(address));
}
return address;
}
public Module getExecutableModule() {
return executableModule;
}
final void remap(VmRemapRequest args) {
MemoryMap memoryMap = null;
for (MemoryMap map : emulator.getMemory().getMemoryMap()) {
if (args.target_address >= map.base && args.target_address + args.size <= map.base + map.size) {
memoryMap = map;
break;
}
}
if (memoryMap != null) {
munmap(args.target_address, (int) args.size);
}
int prot = UnicornConst.UC_PROT_ALL;
try {
backend.mem_map(args.target_address, args.size, prot);
if (mMapListener != null) {
mMapListener.onMap(args.target_address, args.size, prot);
}
} catch (BackendException e) {
throw new IllegalStateException("remap target_address=0x" + Long.toHexString(args.target_address) + ", size=" + args.size, e);
}
if (this.memoryMap.put(args.target_address, new MemoryMap(args.target_address, args.size, prot)) != null) {
log.warn("remap replace exists memory map: start=" + Long.toHexString(args.target_address));
}
}
@Override
public long mmap2(long start, int length, int prot, int flags, int fd, int offset) {
int aligned = (int) ARM.alignSize(length, emulator.getPageAlign());
boolean isAnonymous = ((flags & com.github.unidbg.ios.MachO.MAP_ANONYMOUS) != 0) || (start == 0 && fd <= 0 && offset == 0);
if ((flags & MAP_FIXED) != 0 && isAnonymous) {
if (log.isDebugEnabled()) {
log.debug("mmap2 MAP_FIXED start=0x" + Long.toHexString(start) + ", length=" + length + ", prot=" + prot);
}
MemoryMap mapped = null;
for (MemoryMap map : memoryMap.values()) {
if (start >= map.base && start + aligned <= map.base + map.size) {
mapped = map;
}
}
if (mapped != null) {
munmap(start, aligned);
backend.mem_map(start, aligned, prot);
if (mMapListener != null) {
mMapListener.onMap(start, aligned, prot);
}
if (memoryMap.put(start, new MemoryMap(start, aligned, prot)) != null) {
log.warn("mmap2 replace exists memory map: start=" + Long.toHexString(start));
}
return start;
} else {
throw new IllegalStateException("mmap2 MAP_FIXED not found mapped memory: start=0x" + Long.toHexString(start));
}
}
if (isAnonymous) {
long addr = allocateMapAddress(0, aligned);
if (log.isDebugEnabled()) {
log.debug("mmap2 addr=0x" + Long.toHexString(addr) + ", mmapBaseAddress=0x" + Long.toHexString(mmapBaseAddress) + ", start=" + start + ", fd=" + fd + ", offset=" + offset + ", aligned=" + aligned);
}
backend.mem_map(addr, aligned, prot);
if (mMapListener != null) {
mMapListener.onMap(addr, aligned, prot);
}
if (memoryMap.put(addr, new MemoryMap(addr, aligned, prot)) != null) {
log.warn("mmap2 replace exists memory map: addr=" + Long.toHexString(addr));
}
return addr;
}
try {
FileIO file;
if (start == 0 && fd > 0 && (file = syscallHandler.getFileIO(fd)) != null) {
long addr = allocateMapAddress(0, aligned);
if (log.isDebugEnabled()) {
log.debug("mmap2 addr=0x" + Long.toHexString(addr) + ", mmapBaseAddress=0x" + Long.toHexString(mmapBaseAddress));
}
long ret = file.mmap2(emulator, addr, aligned, prot, offset, length);
if (mMapListener != null) {
mMapListener.onMap(addr, aligned, prot);
}
if (memoryMap.put(addr, new MemoryMap(addr, aligned, prot)) != null) {
log.warn("mmap2 replace exists memory map addr=0x" + Long.toHexString(addr));
}
return ret;
}
if ((flags & MAP_FIXED) != 0) {
if (log.isDebugEnabled()) {
log.debug("mmap2 MAP_FIXED start=0x" + Long.toHexString(start) + ", length=" + length + ", prot=" + prot + ", fd=" + fd + ", offset=0x" + Long.toHexString(offset));
}
MemoryMap mapped = null;
for (MemoryMap map : memoryMap.values()) {
if (start >= map.base && start + aligned <= map.base + map.size) {
mapped = map;
}
}
if (mapped != null) {
backend.mem_unmap(start, aligned);
if (mMapListener != null) {
mMapListener.onUnmap(start, aligned);
}
} else {
log.warn("mmap2 MAP_FIXED not found mapped memory: start=0x" + Long.toHexString(start));
}
FileIO io = syscallHandler.getFileIO(fd);
if (io != null) {
long ret = io.mmap2(emulator, start, aligned, prot, offset, length);
if (mMapListener != null) {
mMapListener.onMap(start, aligned, prot);
}
return ret;
}
}
if (flags == MAP_MY_FIXED) {
if (log.isDebugEnabled()) {
log.debug("mmap2 NOT VM_FLAGS_ANYWHERE start=0x" + Long.toHexString(start) + ", length=" + length + ", prot=" + prot + ", fd=" + fd + ", offset=0x" + Long.toHexString(offset));
}
MemoryMap mapped = null;
for (MemoryMap map : memoryMap.values()) {
if (start >= map.base && start + aligned <= map.base + map.size) {
mapped = map;
}
}
if (mapped != null) {
if (log.isDebugEnabled()) {
log.debug("mmap2 NOT VM_FLAGS_ANYWHERE found mapped memory: start=0x" + Long.toHexString(start));
}
return 0;
}
backend.mem_map(start, aligned, prot);
if (mMapListener != null) {
mMapListener.onMap(start, aligned, prot);
}
if (memoryMap.put(start, new MemoryMap(start, aligned, prot)) != null) {
log.warn("mmap2 NOT VM_FLAGS_ANYWHERE exists memory map addr=0x" + Long.toHexString(start));
}
if (start + aligned >= mmapBaseAddress) {
setMMapBaseAddress(start + aligned);
}
return start;
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
throw new AbstractMethodError("mmap2 start=0x" + Long.toHexString(start) + ", length=" + length + ", prot=0x" + Integer.toHexString(prot) + ", flags=0x" + Integer.toHexString(flags) + ", fd=" + fd + ", offset=" + offset);
}
@Override
public Module loadVirtualModule(String name, Map symbols) {
MachOModule module = MachOModule.createVirtualModule(name, symbols, emulator);
modules.put(name, module);
if (maxDylibName == null || name.length() > maxDylibName.length()) {
maxDylibName = name;
}
return module;
}
@Override
protected long getModuleBase(Module module) {
// return ((MachOModule) module).machHeader;
return super.getModuleBase(module);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy