All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.unidbg.ios.MachOModule Maven / Gradle / Ivy

The newest version!
package com.github.unidbg.ios;

import com.github.unidbg.AbstractEmulator;
import com.github.unidbg.Alignment;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.Utils;
import com.github.unidbg.arm.ARM;
import com.github.unidbg.hook.HookListener;
import com.github.unidbg.ios.objc.ObjectiveCProcessor;
import com.github.unidbg.ios.objc.processor.CDObjectiveC2Processor;
import com.github.unidbg.ios.struct.DyldUnwindSections;
import com.github.unidbg.memory.MemRegion;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.spi.InitFunction;
import com.github.unidbg.spi.LibraryFile;
import com.github.unidbg.utils.Inspector;
import com.github.unidbg.virtualmodule.VirtualSymbol;
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MachOModule extends Module implements com.github.unidbg.ios.MachO {

    final Emulator emulator;
    final MachO machO;
    final MachO.SymtabCommand symtabCommand;
    final MachO.DysymtabCommand dysymtabCommand;
    final ByteBuffer buffer;
    final List lazyLoadNeededList;
    final Map upwardLibraries;
    final Map exportModules;
    private final String path;
    final MachO.DyldInfoCommand dyldInfoCommand;
    final MachO.LinkeditDataCommand chainedFixups;

    final List routines;
    final List initFunctionList;

    public final long machHeader;

    boolean indirectSymbolBound;
    boolean lazyPointerProcessed;

    private final Map symbolMap = new HashMap<>();
    final Map otherSymbols = new HashMap<>();

    private final Log log;

    final boolean executable;
    final MachOLoader loader;
    private final List hookListeners;
    final List ordinalList;

    private final Section fEHFrameSection;
    private final Section fUnwindInfoSection;
    public final Map objcSections;

    private final Map exportSymbols;

    final Segment[] segments;

    private static final long ARM64E_MASK = 0x7ffffffffffL;

    private long offset2Virtual(long address) {
        for (Segment ph : segments) {
            if (address >= ph.fileOffset && address < (ph.fileOffset + ph.vmSize)) {
                return address + ph.vmAddr - ph.fileOffset;
            }
        }
        throw new UnsupportedOperationException("offset2Virtual address=0x" + Long.toHexString(address));
    }

    public final boolean validAddress(long address) {
        address &= ARM64E_MASK;
        for (Segment ph : segments) {
            if (ph.fileSize == 0) {
                continue;
            }
            if (address >= ph.vmAddr && address < (ph.vmAddr + ph.vmSize)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public final int virtualMemoryAddressToFileOffset(long address) {
        if (segments == null) {
            throw new UnsupportedOperationException();
        }
        boolean isPACCodePointer = (address & (3L << 62)) != 0;
        address &= ARM64E_MASK;

        if (isPACCodePointer) {
            long lower32Mask = -1L >>> 32;
            address = offset2Virtual(lower32Mask & address);
        }

        for (Segment ph : segments) {
            if (address >= ph.vmAddr && address < (ph.vmAddr + ph.vmSize)) {
                long relativeOffset = address - ph.vmAddr;
                if (relativeOffset >= ph.fileSize)
                    throw new IllegalStateException("Can not convert virtual memory address 0x" + Long.toHexString(address) + " to file offset -" + " found segment " + ph
                            + " but address maps to memory outside file range");
                long ret = ph.fileOffset + relativeOffset;
                if ((ret >> 33L) != 0) {
                    throw new IllegalStateException("ret=0x" + Long.toHexString(ret));
                }
                return (int) ret;
            }
        }
        throw new IllegalStateException("Cannot find segment for address: 0x" + Long.toHexString(address));
    }

    private final List allInitFunctionList;

    MachOModule(MachO machO, String name, long base, long size, Map neededLibraries, List regions,
                MachO.SymtabCommand symtabCommand, MachO.DysymtabCommand dysymtabCommand, ByteBuffer buffer,
                List lazyLoadNeededList, Map upwardLibraries, Map exportModules,
                String path, Emulator emulator, MachO.DyldInfoCommand dyldInfoCommand, MachO.LinkeditDataCommand chainedFixups, UnidbgPointer envp, UnidbgPointer apple, UnidbgPointer vars,
                long machHeader, boolean executable, MachOLoader loader, List hookListeners, List ordinalList,
                Section fEHFrameSection, Section fUnwindInfoSection,
                Map objcSections,
                Segment[] segments, LibraryFile libraryFile) {
        super(name, base, size, neededLibraries, regions, libraryFile);
        this.emulator = emulator;
        this.machO = machO;
        this.symtabCommand = symtabCommand;
        this.dysymtabCommand = dysymtabCommand;
        this.buffer = buffer;
        this.lazyLoadNeededList = lazyLoadNeededList;
        this.upwardLibraries = upwardLibraries;
        this.exportModules = exportModules;
        this.path = path;
        this.dyldInfoCommand = dyldInfoCommand;
        this.chainedFixups = chainedFixups;
        this.envp = envp;
        this.apple = apple;
        this.vars = vars;
        this.machHeader = machHeader;
        this.executable = executable;
        this.loader = loader;
        this.hookListeners = hookListeners;
        this.ordinalList = ordinalList;
        this.fEHFrameSection = fEHFrameSection;
        this.fUnwindInfoSection = fUnwindInfoSection;
        this.objcSections = objcSections;
        this.segments = segments;

        this.log = LogFactory.getLog("com.github.unidbg.ios." + name);
        this.routines = machO == null ? Collections.emptyList() : parseRoutines(machO);
        this.initFunctionList = machO == null ? Collections.emptyList() : parseInitFunction(machO, buffer.duplicate(), name, emulator);
        List allInitFunctionList = new ArrayList<>(routines.size() + initFunctionList.size());
        allInitFunctionList.addAll(routines);
        allInitFunctionList.addAll(initFunctionList);
        this.allInitFunctionList = Collections.unmodifiableList(allInitFunctionList);

        if (machO == null) {
            exportSymbols = Collections.emptyMap();
            return;
        }

        exportSymbols = processExportNode(log, dyldInfoCommand, buffer);

        if (symtabCommand != null) {
            buffer.limit((int) (symtabCommand.strOff() + symtabCommand.strSize()));
            buffer.position((int) symtabCommand.strOff());
            ByteBuffer strBuffer = buffer.slice();
            try (ByteBufferKaitaiStream io = new ByteBufferKaitaiStream(strBuffer)) {
                for (MachO.SymtabCommand.Nlist nlist : symtabCommand.symbols()) {
                    int type = nlist.type() & N_TYPE;
                    if (nlist.un() == 0) {
                        continue;
                    }

                    boolean isWeakDef = (nlist.desc() & N_WEAK_DEF) != 0;
                    boolean isThumb = (nlist.desc() & N_ARM_THUMB_DEF) != 0;
                    strBuffer.position((int) nlist.un());
                    String symbolName = new String(io.readBytesTerm(0, false, true, true), StandardCharsets.US_ASCII);
                    MachOSymbol symbol = new MachOSymbol(this, nlist, symbolName);
                    if ((type == N_SECT || type == N_ABS) && (nlist.type() & N_STAB) == 0) {
                        ExportSymbol exportSymbol = null;
                        if (exportSymbols.isEmpty() || (exportSymbol = exportSymbols.remove(symbolName)) != null) {
                            if (log.isDebugEnabled()) {
                                log.debug("nlist un=0x" + Long.toHexString(nlist.un()) + ", symbolName=" + symbolName + ", type=0x" + Long.toHexString(nlist.type()) + ", isWeakDef=" + isWeakDef + ", isThumb=" + isThumb + ", value=0x" + Long.toHexString(nlist.value()));
                            }

                            if (exportSymbol != null && symbol.getAddress() == exportSymbol.getOtherWithBase()) {
                                if (log.isDebugEnabled()) {
                                    log.debug("nlist un=0x" + Long.toHexString(nlist.un()) + ", symbolName=" + symbolName + ", value=0x" + Long.toHexString(nlist.value()) + ", address=0x" + Long.toHexString(exportSymbol.getValue()) + ", other=0x" + Long.toHexString(exportSymbol.getOtherWithBase()));
                                }
                                if (symbolMap.put(symbolName, exportSymbol) != null) {
                                    log.warn("Replace exist symbol: " + symbolName);
                                }
                            } else {
                                if (symbolMap.put(symbolName, symbol) != null) {
                                    log.warn("Replace exist symbol: " + symbolName);
                                }
                            }
                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("nlist FILTER un=0x" + Long.toHexString(nlist.un()) + ", symbolName=" + symbolName + ", type=0x" + Long.toHexString(nlist.type()) + ", isWeakDef=" + isWeakDef + ", isThumb=" + isThumb + ", value=0x" + Long.toHexString(nlist.value()));
                            }
                        }
                    } else if (type == N_INDR) {
                        strBuffer.position(nlist.value().intValue());
                        String indirectSymbol = new String(io.readBytesTerm(0, false, true, true), StandardCharsets.US_ASCII);
                        if (!symbolName.equals(indirectSymbol)) {
                            if (log.isDebugEnabled()) {
                                log.debug("nlist indirect symbolName=" + symbolName + ", indirectSymbol=" + indirectSymbol);
                            }
                            if (symbolMap.put(symbolName, new IndirectSymbol(symbolName, this, indirectSymbol)) != null) {
                                log.warn("Replace exist symbol: " + symbolName);
                            }
                        }
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("nlist isWeakDef=" + isWeakDef + ", isThumb=" + isThumb + ", type=" + type + ", symbolName=" + symbolName);
                        }
                        otherSymbols.put(symbolName, symbol);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public boolean hasWeakDefines() {
        return machO != null && (machO.header().flags() & com.github.unidbg.ios.MachO.MH_WEAK_DEFINES) != 0;
    }

    @Override
    public int callEntry(Emulator emulator, String... args) {
        if (entryPoint <= 0) {
            throw new IllegalStateException("Invalid entry point");
        }

        Memory memory = emulator.getMemory();
        final UnidbgPointer stack = memory.allocateStack(0);

        int argc = 0;
        List argv = new ArrayList<>();

        argv.add(memory.writeStackString(emulator.getProcessName()));
        argc++;

        for (int i = 0; args != null && i < args.length; i++) {
            String arg = args[i];
            argv.add(memory.writeStackString(arg));
            argc++;
        }

        Pointer auxvPointer = memory.allocateStack(emulator.getPointerSize());
        assert auxvPointer != null;
        auxvPointer.setPointer(0, null);

        Pointer envPointer = memory.allocateStack(emulator.getPointerSize());
        assert envPointer != null;
        envPointer.setPointer(0, null);

        Pointer pointer = memory.allocateStack(emulator.getPointerSize());
        assert pointer != null;
        pointer.setPointer(0, null); // NULL-terminated argv

        Collections.reverse(argv);
        for (Pointer arg : argv) {
            pointer = memory.allocateStack(emulator.getPointerSize());
            assert pointer != null;
            pointer.setPointer(0, arg);
        }

        if (log.isDebugEnabled()) {
            UnidbgPointer sp = memory.allocateStack(0);
            byte[] data = sp.getByteArray(0, (int) (stack.peer - sp.peer));
            Inspector.inspect(data, "callEntry sp=0x" + Long.toHexString(memory.getStackPoint()) + ", envPointer=" + envPointer + ", auxvPointer=" + auxvPointer);
        }

        Pointer argvPointer = memory.allocateStack(0);
        return emulateFunction(emulator, machHeader + entryPoint, argc, argvPointer, envPointer, auxvPointer).intValue();
    }

    private boolean initialized;

    final void doInitialization(Emulator emulator) {
        try {
            if (initialized) {
                return;
            }

            if (loader.executableModule == null) {
                vars.setPointer(0, UnidbgPointer.pointer(emulator, machHeader)); // _NSGetMachExecuteHeader
            }

            callRoutines(emulator);
            for (Module module : neededLibraries.values()) {
                MachOModule mm = (MachOModule) module;
                mm.doInitialization(emulator);
            }
            callInitFunction(emulator);
        } finally {
            initialized = true;
        }
    }

    final void callRoutines(Emulator emulator) {
        Log log = LogFactory.getLog(MachOModule.class);
        if (log.isDebugEnabled() && !routines.isEmpty()) {
            log.debug("callRoutines name=" + name);
        }
        while (!routines.isEmpty()) {
            InitFunction routine = routines.remove(0);
            routine.call(emulator);
        }
    }

    final void callInitFunction(Emulator emulator) {
        Log log = LogFactory.getLog(MachOModule.class);
        if (log.isDebugEnabled() && !initFunctionList.isEmpty()) {
            log.debug("callInitFunction name=" + name);
        }

        while (!initFunctionList.isEmpty()) {
            InitFunction initFunction = initFunctionList.remove(0);
            initFunction.call(emulator);
        }
    }

    private void processExportNode(Log log, ByteBuffer buffer, byte[] cummulativeString, int curStrOffset, Map map) {
        int terminalSize = Utils.readULEB128(buffer).intValue();

        if (terminalSize != 0) {
            buffer.mark();
            int flags = Utils.readULEB128(buffer).intValue();
            long address;
            long other;
            String importName;
            if ((flags & EXPORT_SYMBOL_FLAGS_REEXPORT) != 0) {
                address = 0;
                other = Utils.readULEB128(buffer).longValue();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte b;
                while ((b = buffer.get()) != 0) {
                    baos.write(b);
                }
                importName = baos.toString();
            } else {
                address = Utils.readULEB128(buffer).longValue();
                if((flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0) {
                    other = Utils.readULEB128(buffer).longValue();
                } else {
                    other = 0;
                }
                importName = null;
            }
            String symbolName = new String(cummulativeString, 0, curStrOffset);
            map.put(symbolName, new ExportSymbol(symbolName, address, this, other, flags));
            if (log.isDebugEnabled()) {
                log.debug("exportNode symbolName=" + symbolName + ", address=0x" + Long.toHexString(address) + ", other=0x" + Long.toHexString(other) + ", importName=" + importName + ", flags=0x" + Integer.toHexString(flags));
            }
            buffer.reset();
            buffer.position(buffer.position() + terminalSize);
        }

        int childrenCount = buffer.get() & 0xff;
        for (int i = 0; i < childrenCount; i++) {
            int edgeStrLen = 0;
            byte b;
            while ((b = buffer.get()) != 0) {
                cummulativeString[curStrOffset+edgeStrLen] = b;
                ++edgeStrLen;
            }
            cummulativeString[curStrOffset+edgeStrLen] = 0;

            int childNodeOffset = Utils.readULEB128(buffer).intValue();

            ByteBuffer duplicate = buffer.duplicate();
            duplicate.position(childNodeOffset);
            processExportNode(log, duplicate, cummulativeString, curStrOffset+edgeStrLen, map);
        }
    }

    private Map processExportNode(Log log, MachO.DyldInfoCommand dyldInfoCommand, ByteBuffer buffer) {
        if (dyldInfoCommand == null) {
            return Collections.emptyMap();
        }

        Map map = new HashMap<>();
        if (dyldInfoCommand.exportSize() > 0) {
            buffer = buffer.duplicate();
            buffer.limit((int) (dyldInfoCommand.exportOff() + dyldInfoCommand.exportSize()));
            buffer.position((int) dyldInfoCommand.exportOff());
            processExportNode(log, buffer.slice(), new byte[4000], 0, map);
        }
        return map;
    }

    public final String findSymbolNameByAddress(long address) {
        if (dyldInfoCommand.bindSize() > 0) {
            ByteBuffer buffer = this.buffer.duplicate();
            buffer.limit((int) (dyldInfoCommand.bindOff() + dyldInfoCommand.bindSize()));
            buffer.position((int) dyldInfoCommand.bindOff());
            return findSymbol(buffer.slice(), address);
        } else {
            return null;
        }
    }

    private String findSymbol(ByteBuffer buffer, long findAddress) {
        final List regions = this.getRegions();
        int segmentIndex;
        long address = this.base;
        long segmentEndAddress = address + this.size;
        String symbolName = null;
        int count;
        int skip;
        boolean done = false;
        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:
                case BIND_OPCODE_SET_TYPE_IMM:
                    break;
                case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
                case BIND_OPCODE_SET_ADDEND_SLEB:
                    Utils.readULEB128(buffer);
                    break;
                case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
                    // the special ordinals are negative numbers
                    break;
                case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    while ((b = buffer.get()) != 0) {
                        baos.write(b);
                    }
                    symbolName = baos.toString();
                    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();
                    }
                    if (address == findAddress) {
                        return symbolName;
                    }
                    address += emulator.getPointerSize();
                    break;
                case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
                    if (address >= segmentEndAddress) {
                        throw new IllegalStateException();
                    }
                    if (address == findAddress) {
                        return symbolName;
                    }
                    address += (Utils.readULEB128(buffer).longValue() + emulator.getPointerSize());
                    break;
                case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
                    if (address >= segmentEndAddress) {
                        throw new IllegalStateException();
                    }
                    if (address == findAddress) {
                        return symbolName;
                    }
                    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();
                        }
                        if (address == findAddress) {
                            return symbolName;
                        }
                        address += (skip + emulator.getPointerSize());
                    }
                    break;
                default:
                    throw new IllegalStateException(String.format("bad bind opcode 0x%s in bind info", Integer.toHexString(opcode)));
            }
        }
        return null;
    }

    private List parseRoutines(MachO machO) {
        List routines = new ArrayList<>();
        for (MachO.LoadCommand command : machO.loadCommands()) {
            switch (command.type()) {
                case ROUTINES: {
                    MachO.RoutinesCommand routinesCommand = (MachO.RoutinesCommand) command.body();
                    long address = routinesCommand.initAddress();
                    if (log.isDebugEnabled()) {
                        log.debug("parseRoutines address=0x" + Long.toHexString(address));
                    }
                    routines.add(new MachOModuleInit(this, envp, apple, vars, false, address));
                    break;
                }
                case ROUTINES_64: {
                    MachO.RoutinesCommand64 routinesCommand64 = (MachO.RoutinesCommand64) command.body();
                    long address = routinesCommand64.initAddress();
                    if (log.isDebugEnabled()) {
                        log.debug("parseRoutines64 address=0x" + Long.toHexString(address));
                    }
                    routines.add(new MachOModuleInit(this, envp, apple, vars, false, address));
                    break;
                }
            }
        }
        return routines;
    }

    private List parseInitFunction(MachO machO, ByteBuffer buffer, String libName, Emulator emulator) {
        List initFunctionList = new ArrayList<>();
        for (MachO.LoadCommand command : machO.loadCommands()) {
            switch (command.type()) {
                case SEGMENT:
                    MachO.SegmentCommand segmentCommand = (MachO.SegmentCommand) command.body();
                    for (MachO.SegmentCommand.Section section : segmentCommand.sections()) {
                        parseInitFunction(buffer, libName, emulator, initFunctionList, section.flags(), section.size(), section.offset());
                    }
                    break;
                case SEGMENT_64:
                    MachO.SegmentCommand64 segmentCommand64 = (MachO.SegmentCommand64) command.body();
                    for (MachO.SegmentCommand64.Section64 section : segmentCommand64.sections()) {
                        parseInitFunction(buffer, libName, emulator, initFunctionList, section.flags(), section.size(), section.offset());
                    }
            }
        }
        return initFunctionList;
    }

    private void parseInitFunction(ByteBuffer buffer, String libName, Emulator emulator, List initFunctionList, long flags, long size, long offset) {
        long type = flags & SECTION_TYPE;
        if (type != S_MOD_INIT_FUNC_POINTERS) {
            return;
        }

        long elementCount = size / emulator.getPointerSize();
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.limit((int) (offset + size));
        buffer.position((int) offset);
        for (int i = 0; i < elementCount; i++) {
            long address = emulator.is32Bit() ? buffer.getInt() : buffer.getLong();
            if (log.isDebugEnabled()) {
                log.debug("parseInitFunction libName=" + libName + ", address=0x" + Long.toHexString(address) + ", offset=0x" + Long.toHexString(offset) + ", elementCount=" + elementCount);
            }
            initFunctionList.add(new MachOModuleInit(this, envp, apple, vars, true, address));
        }
    }

    private final UnidbgPointer envp;
    private final UnidbgPointer apple;
    private final UnidbgPointer vars;

    final Map neededLibraries() {
        return neededLibraries;
    }

    @Override
    public Number callFunction(Emulator emulator, long offset, Object... args) {
        return emulateFunction(emulator, base + offset, args);
    }

    MachOSymbol getSymbolByIndex(int index) {
        buffer.limit((int) (symtabCommand.strOff() + symtabCommand.strSize()));
        buffer.position((int) symtabCommand.strOff());
        ByteBuffer strBuffer = buffer.slice();

        try (ByteBufferKaitaiStream io = new ByteBufferKaitaiStream(strBuffer)) {
            MachO.SymtabCommand.Nlist nlist = symtabCommand.symbols().get(index);
            strBuffer.position((int) nlist.un());
            String symbolName = new String(io.readBytesTerm(0, false, true, true), StandardCharsets.US_ASCII);
            return new MachOSymbol(this, nlist, symbolName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Symbol findSymbolByName(String name, boolean withDependencies) {
        Symbol symbol = findSymbolByNameInternal(name, withDependencies);
        if (symbol != null) {
            if (symbol instanceof IndirectSymbol) {
                IndirectSymbol indirectSymbol = (IndirectSymbol) symbol;
                symbol = indirectSymbol.resolveSymbol();
                if (symbol == null) {
                    log.warn("Resolve indirect symbol failed: name=" + this.name + ", symbolName=" + name + ", indirectSymbol=" + indirectSymbol.symbol + ", neededLibraries=" + neededLibraries.values());
                }
            }
            return symbol;
        } else {
            return null;
        }
    }

    private Symbol findSymbolByNameInternal(String name, boolean withDependencies) {
        Symbol symbol = symbolMap.get(name);
        if (symbol == null) {
            ExportSymbol es = exportSymbols.get(name);
            if (es != null) {
                if (es.isReExport()) {
                    int ordinal = (int) es.getOther();
                    if (ordinal <= ordinalList.size()) {
                        String path = ordinalList.get(ordinal - 1);
                        MachOModule reexportedFrom = loader.modules.get(FilenameUtils.getName(path));
                        if (reexportedFrom != null) {
                            symbol = reexportedFrom.findSymbolByName(name, false);
                        }
                    } else {
                        throw new IllegalStateException("ordinal=" + ordinal);
                    }
                } else {
                    symbol = es;
                }
            }
        }
        if (symbol != null) {
            return symbol;
        }

        if (withDependencies) {
            Set findSet = new LinkedHashSet<>(loader.getLoadedModules().size());
            findSet.addAll(exportModules.values());
            findSet.addAll(upwardLibraries.values());
            findSet.addAll(neededLibraries.values());
//            findSet.addAll(loader.getLoadedModules());
            for (Module module : findSet) {
                symbol = module.findSymbolByName(name, false);
                if (symbol != null) {
                    return symbol;
                }
            }
        } else {
            for (Module module : exportModules.values()) {
                symbol = module.findSymbolByName(name, false);
                if (symbol != null) {
                    return symbol;
                }
            }
        }

        return null;
    }

    private ObjectiveCProcessor objectiveCProcessor;

    @Override
    public Symbol findClosestSymbolByAddress(long addr, boolean fast) {
        long targetAddress = addr - base;
        if (targetAddress == 0) {
            return new ExportSymbol("__dso_handle", addr, this, 0, EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
        }
        if (targetAddress < 0) {
            return null;
        }

        List symbols = symtabCommand.symbols();
        MachO.SymtabCommand.Nlist bestSymbol = null;

        // first walk all global symbols
        for (long i = dysymtabCommand.iExtDefSym(); i < dysymtabCommand.iExtDefSym() + dysymtabCommand.nExtDefSym(); i++) {
            MachO.SymtabCommand.Nlist nlist = symbols.get((int) i);
            if ((nlist.type() & N_TYPE) == N_SECT) {
                if ( bestSymbol == null ) {
                    if ( nlist.value() <= targetAddress ) {
                        bestSymbol = nlist;
                    }
                } else if ( (nlist.value() <= targetAddress) && (bestSymbol.value() < nlist.value()) ) {
                    bestSymbol = nlist;
                }
            }
        }

        // next walk all local symbols
        for (long i = dysymtabCommand.iLocalSym(); i < dysymtabCommand.iLocalSym() + dysymtabCommand.nLocalSym(); i++) {
            MachO.SymtabCommand.Nlist nlist = symbols.get((int) i);
            if ((nlist.type() & N_TYPE) == N_SECT && ((nlist.type() & N_STAB) == 0)) {
                if ( bestSymbol == null ) {
                    if ( nlist.value() <= targetAddress ) {
                        bestSymbol = nlist;
                    }
                } else if ( (nlist.value() <= targetAddress) && (bestSymbol.value() < nlist.value()) ) {
                    bestSymbol = nlist;
                }
            }
        }

        Symbol symbol = null;
        if (bestSymbol != null) {
            buffer.limit((int) (symtabCommand.strOff() + symtabCommand.strSize()));
            buffer.position((int) symtabCommand.strOff());
            ByteBuffer strBuffer = buffer.slice();
            strBuffer.position((int) bestSymbol.un());

            try (ByteBufferKaitaiStream io = new ByteBufferKaitaiStream(strBuffer)) {
                String symbolName = new String(io.readBytesTerm(0, false, true, true), StandardCharsets.US_ASCII);
                // strip off leading underscore
                if (symbolName.startsWith("_")) {
                    symbolName = symbolName.substring(1);
                }
                symbol = new MachOSymbol(this, bestSymbol, symbolName);
                // never return the mach_header symbol
                if ((symbol.getAddress() & ~1) == base) {
                    return null;
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            if (!fast && objectiveCProcessor == null && objcSections != null && !objcSections.isEmpty()) {
                objectiveCProcessor = new CDObjectiveC2Processor(this, emulator, buffer);
            }
            if (!fast && objectiveCProcessor != null) {
                if (executable) {
                    long entry = machHeader + entryPoint;
                    if (addr >= entry && (symbol == null || entry > symbol.getAddress())) {
                        symbol = new ExportSymbol("main", entry, this, 0, com.github.unidbg.ios.MachO.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
                    }
                }
                for (int i = 0; i < allInitFunctionList.size(); i++) {
                    InitFunction initFunction = allInitFunctionList.get(i);
                    long address = initFunction.getAddress();
                    if (addr >= address && (symbol == null || address > symbol.getAddress())) {
                        symbol = new ExportSymbol("InitFunc_" + i, address, this, 0, com.github.unidbg.ios.MachO.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
                    }
                }

                symbol = objectiveCProcessor.findObjcSymbol(symbol, targetAddress, this);
            }
        } catch (Exception e) {
            if (LogFactory.getLog(AbstractEmulator.class).isDebugEnabled()) {
                e.printStackTrace();
            }
        }

        return symbol;
    }

    @Override
    public String toString() {
        return path;
    }

    boolean hasUnresolvedSymbol() {
        return !allSymbolBound || !allLazySymbolBound;
    }

    boolean allSymbolBound;
    boolean allLazySymbolBound;

    final Set addImageCallSet = new HashSet<>();
    final Set boundCallSet = new HashSet<>();
    final Set dependentsInitializedCallSet = new HashSet<>();
    final Set initializedCallSet = new HashSet<>();

    boolean objcNotifyMapped;
    boolean objcNotifyInit;

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public void registerSymbol(String symbolName, long address) {
        throw new UnsupportedOperationException();
    }

    static MachOModule createVirtualModule(String name, final Map symbols, Emulator emulator) {
        if (symbols.isEmpty()) {
            throw new IllegalArgumentException("symbols is empty");
        }

        List list = new ArrayList<>(symbols.values());
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(UnidbgPointer o1, UnidbgPointer o2) {
                return (int) (o1.peer - o2.peer);
            }
        });
        UnidbgPointer first = list.get(0);
        UnidbgPointer last = list.get(list.size() - 1);
        Alignment alignment = ARM.align(first.peer, last.peer - first.peer, emulator.getPageAlign());
        final long base = alignment.address;
        final long size = alignment.size;

        Log log = LogFactory.getLog(MachOModule.class);
        if (log.isDebugEnabled()) {
            log.debug("createVirtualModule first=0x" + Long.toHexString(first.peer) + ", last=0x" + Long.toHexString(last.peer) + ", base=0x" + Long.toHexString(base) + ", size=0x" + Long.toHexString(size));
        }

        MachOModule module = new MachOModule(null, name, base, size, Collections.emptyMap(),
                Collections.emptyList(),
                null, null, null,
                Collections.emptyList(),
                Collections.emptyMap(),
                Collections.emptyMap(),
                name, emulator, null, null, null, null, null, 0L, false, null,
                Collections.emptyList(), Collections.emptyList(), null, null, null, null, null) {
            @Override
            public Symbol findSymbolByName(String name, boolean withDependencies) {
                UnidbgPointer pointer = symbols.get(name);
                if (pointer != null) {
                    return new VirtualSymbol(name, this, pointer.peer);
                } else {
                    return null;
                }
            }
            @Override
            public void registerSymbol(String symbolName, long address) {
            }
            @Override
            public boolean isVirtual() {
                return true;
            }
        };
        for (Map.Entry entry : symbols.entrySet()) {
            module.registerSymbol(entry.getKey(), entry.getValue().peer);
        }
        return module;
    }

    public long doBindFastLazySymbol(Emulator emulator, int lazyBindingInfoOffset) {
        ByteBuffer buffer = this.buffer.duplicate();
        buffer.limit((int) (dyldInfoCommand.lazyBindOff() + dyldInfoCommand.lazyBindSize()));
        buffer.position((int) dyldInfoCommand.lazyBindOff());
        return doBindFastLazySymbol(emulator, buffer.slice(), lazyBindingInfoOffset);
    }

    private long doBindFastLazySymbol(Emulator emulator, ByteBuffer buffer, int lazyBindingInfoOffset) {
        final List regions = this.getRegions();
        int type = BIND_TYPE_POINTER;
        long address = 0;
        String symbolName = null;
        int libraryOrdinal = 0;
        boolean done = false;
        long result = 0;
        buffer.position(lazyBindingInfoOffset);
        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();
                    break;
                case BIND_OPCODE_SET_TYPE_IMM:
                    type = immediate;
                    break;
                case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
                    MemRegion region = regions.get(immediate);
                    address = region.begin + Utils.readULEB128(buffer).longValue();
                    break;
                case BIND_OPCODE_DO_BIND:
                    result = bindAt(emulator, libraryOrdinal, type, address, symbolName);
                    break;
                case BIND_OPCODE_SET_ADDEND_SLEB:
                case BIND_OPCODE_ADD_ADDR_ULEB:
                case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
                case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
                case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
                default:
                    throw new IllegalStateException("bad lazy bind opcode " + opcode);
            }
        }
        return result;
    }

    private long bindAt(Emulator emulator, int libraryOrdinal, int type, long address, String symbolName) {
//        libraryOrdinal = (byte) libraryOrdinal;
        Pointer pointer = UnidbgPointer.pointer(emulator, address);
        if (pointer == null) {
            throw new IllegalStateException();
        }

        final MachOModule targetImage;
        if (libraryOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) {
            targetImage = (MachOModule) loader.getExecutableModule();
        } else if (libraryOrdinal == BIND_SPECIAL_DYLIB_SELF) {
            targetImage = this;
        } else if (libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP) {
            for(MachOModule mm : loader.modules.values().toArray(new MachOModule[0])) {
                long at = bindAt(type, pointer, mm, symbolName);
                if (at != 0) {
                    return at;
                }
            }
            return 0;
        } 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", libraryOrdinal, symbolName, getPath()));
        } else if (libraryOrdinal <= ordinalList.size()) {
            String path = ordinalList.get(libraryOrdinal - 1);
            targetImage = loader.modules.get(FilenameUtils.getName(path));
            if (targetImage == null) {
                throw new IllegalStateException("targetImage is null: path=" + path + ", module=" + getPath() + ", symbolName=" + symbolName);
            }
        } else {
            throw new IllegalStateException(String.format("bad mach-o binary, library ordinal (%d) too big (max %d) for symbol %s in %s", libraryOrdinal, ordinalList.size(), symbolName, getPath()));
        }

        if ("_dispatch_queue_create_with_target$V2".equals(symbolName)) {
            symbolName = "_dispatch_queue_create";
        }

        return bindAt(type, pointer, targetImage, symbolName);
    }

    private long bindAt(int type, Pointer pointer, MachOModule targetImage, String symbolName) {
        Symbol symbol = loader.findSymbolInternal(targetImage, symbolName);
        if (symbol == null) {
            long bindAt = 0;
            for (HookListener listener : hookListeners) {
                long hook = listener.hook(emulator.getSvcMemory(), this.name, symbolName, HookListener.WEAK_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 bindAt;
            }
            return 0;
        }

        long bindAt = symbol.getAddress();
        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("bindAt 0x=" + Long.toHexString(symbol.getValue()) + ", type=" + type + ", symbolName=" + symbol.getModuleName() + ", symbol=" + symbol + ", pointer=" + pointer + ", bindAt=0x" + Long.toHexString(bindAt));
        }

        switch (type) {
            case BIND_TYPE_POINTER:
                Pointer newPointer = UnidbgPointer.pointer(emulator, bindAt);
                pointer.setPointer(0, newPointer);
                break;
            case BIND_TYPE_TEXT_ABSOLUTE32:
                pointer.setInt(0, (int) (bindAt));
                break;
            case BIND_TYPE_TEXT_PCREL32:
            default:
                throw new IllegalStateException("bad bind type " + type);
        }
        return bindAt;
    }

    public final void getUnwindInfo(DyldUnwindSections info) {
        info.mach_header = machHeader;
        info.dwarf_section = 0;
        info.dwarf_section_length = 0;
        info.compact_unwind_section = 0;
        info.compact_unwind_section_length = 0;
        if (fEHFrameSection != null) {
            info.dwarf_section = base + fEHFrameSection.addr;
            info.dwarf_section_length = fEHFrameSection.size;
        }
        if (fUnwindInfoSection != null) {
            info.compact_unwind_section = base + fUnwindInfoSection.addr;
            info.compact_unwind_section_length = fUnwindInfoSection.size;
        }
        info.pack();
    }

    @Override
    public long getBaseHeader() {
        return machHeader;
    }

    final void callObjcNotifyMapped(UnidbgPointer _objcNotifyMapped) {
        if (_objcNotifyMapped != null && !objcNotifyMapped) {
            SvcMemory svcMemory = emulator.getSvcMemory();
            MemoryBlock block = emulator.getMemory().malloc(emulator.getPointerSize() * 2, true);
            try {
                Pointer paths = block.getPointer();
                Pointer mh = paths.share(emulator.getPointerSize());
                paths.setPointer(0, createPathMemory(svcMemory));
                mh.setPointer(0, UnidbgPointer.pointer(emulator, machHeader));
                Module.emulateFunction(emulator, _objcNotifyMapped.peer, 1, paths, mh);
                objcNotifyMapped = true;
            } finally {
                block.free();
            }
        }
    }

    final void callObjcNotifyInit(UnidbgPointer _objcNotifyInit) {
        if (_objcNotifyInit != null && !objcNotifyInit && objcNotifyMapped) {
            SvcMemory svcMemory = emulator.getSvcMemory();
            Module.emulateFunction(emulator, _objcNotifyInit.peer, createPathMemory(svcMemory), machHeader);
            objcNotifyInit = true;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy