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

com.googlecode.dex2jar.tools.DecryptStringCmd Maven / Gradle / Ivy

package com.googlecode.dex2jar.tools;

import com.googlecode.d2j.converter.IR2JConverter;
import com.googlecode.d2j.converter.J2IRConverter;
import com.googlecode.d2j.util.Escape;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.StmtTraveler;
import com.googlecode.dex2jar.ir.expr.Constant;
import com.googlecode.dex2jar.ir.expr.Exprs;
import com.googlecode.dex2jar.ir.expr.FilledArrayExpr;
import com.googlecode.dex2jar.ir.expr.InvokeExpr;
import com.googlecode.dex2jar.ir.expr.Value;
import com.googlecode.dex2jar.ir.ts.AggTransformer;
import com.googlecode.dex2jar.ir.ts.CleanLabel;
import com.googlecode.dex2jar.ir.ts.DeadCodeTransformer;
import com.googlecode.dex2jar.ir.ts.ExceptionHandlerTrim;
import com.googlecode.dex2jar.ir.ts.Ir2JRegAssignTransformer;
import com.googlecode.dex2jar.ir.ts.NewTransformer;
import com.googlecode.dex2jar.ir.ts.NpeTransformer;
import com.googlecode.dex2jar.ir.ts.RemoveConstantFromSSA;
import com.googlecode.dex2jar.ir.ts.RemoveLocalFromSSA;
import com.googlecode.dex2jar.ir.ts.TypeTransformer;
import com.googlecode.dex2jar.ir.ts.UnSSATransformer;
import com.googlecode.dex2jar.ir.ts.VoidInvokeTransformer;
import com.googlecode.dex2jar.ir.ts.ZeroTransformer;
import com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer;
import com.googlecode.dex2jar.tools.BaseCmd.Syntax;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

@Syntax(cmd = "d2j-decrypt-string", syntax = "[options] ", desc = "Decrypt in class file", onlineHelp = "https"
        + "://sourceforge.net/p/dex2jar/wiki/DecryptStrings\nhttps://bitbucket.org/pxb1988/dex2jar/wiki/DecryptStrings")
public class DecryptStringCmd extends BaseCmd {

    public static void main(String... args) {
        new DecryptStringCmd().doMain(args);
    }

    @Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
    private boolean forceOverwrite = false;

    @Opt(opt = "o", longOpt = "output", description = "output of .jar files, default is "
            + "$current_dir/[jar-name]-decrypted.jar", argName = "out")
    private Path output;

    @Opt(opt = "m", longOpt = "methods", description = "a file contain a list of methods, each line like: La/b;"
            + "->decrypt(III)Ljava/lang/String;", argName = "cfg")
    private Path method;

    @Opt(opt = "mo", longOpt = "decrypt-method-owner", description = "the owner of the method which can decrypt the "
            + "stings, example: java.lang.String", argName = "owner")
    private String methodOwner;

    @Opt(opt = "mn", longOpt = "decrypt-method-name", description = "the owner of the method which can decrypt the "
            + "stings, the method's signature must be static (parameter-type)Ljava/lang/String;. Please use -pt,"
            + "--parameter-type to set the argument decrypt.", argName = "name")
    private String methodName;

    @Opt(opt = "cp", longOpt = "classpath", description = "add extra lib to classpath", argName = "cp")
    private String classpath;

    //extended parameter option: e.g. '-t int,byte,string' to specify a routine such as decryptionRoutine(int a, byte
    // b, String c)
    @Opt(opt = "t", longOpt = "arg-types", description = "comma-separated list of types:boolean,byte,short,char,int,"
            + "long,float,double,string. Default is string", argName = "type")
    private String parameterJTypes;

    @Opt(opt = "pd", longOpt = "parameters-descriptor", description = "the descriptor for the method which can "
            + "decrypt the stings, example1: Ljava/lang/String; example2: III, default is Ljava/lang/String;",
            argName = "type")
    private String parametersDescriptor;

    @Opt(opt = "d", longOpt = "delete", hasArg = false, description = "delete the method which can decrypt the stings")
    private boolean deleteMethod = false;

    @Opt(opt = "da", longOpt = "deep-analyze", hasArg = false, description = "use dex2jar IR to static analyze and "
            + "find more values like byte[]")
    private boolean deepAnalyze = false;

    @Opt(opt = "v", longOpt = "verbose", hasArg = false, description = "show more on output")
    private boolean verbose = false;

    static class MethodConfig {

        Method jmethod;

        /**
         * in java/lang/String format
         */
        String owner;

        String name;

        String desc;

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((desc == null) ? 0 : desc.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            result = prime * result + ((owner == null) ? 0 : owner.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            MethodConfig other = (MethodConfig) obj;
            if (desc == null) {
                if (other.desc != null) {
                    return false;
                }
            } else if (!desc.equals(other.desc)) {
                return false;
            }
            if (name == null) {
                if (other.name != null) {
                    return false;
                }
            } else if (!name.equals(other.name)) {
                return false;
            }
            if (owner == null) {
                return other.owner == null;
            } else {
                return owner.equals(other.owner);
            }
        }
    }

    MethodConfig build(String line) {
        int idx = line.indexOf("->");
        if (idx < 0) {
            throw new RuntimeException("Can't read line:" + line);
        }
        String owner = line.substring(0, idx);

        if (owner.startsWith("L") && owner.endsWith(";")) {
            owner = owner.substring(1, owner.length() - 1);
        }

        int idx2 = line.indexOf('(', idx);
        if (idx2 < 0) {
            throw new RuntimeException("Can't read line:" + line);
        }

        String name = line.substring(idx + 2, idx2);

        String desc = line.substring(idx2);
        if (desc.endsWith(")")) {
            desc = desc + "Ljava/lang/String;";
        }

        MethodConfig config = new MethodConfig();
        config.owner = owner;
        config.desc = desc;
        config.name = name;
        return config;

    }

    @Override
    protected void doCommandLine() throws Exception {
        if (remainingArgs.length == 0) {
            throw new HelpException("One  file is required");
        } else if (remainingArgs.length > 1) {
            throw new HelpException("Only one  file is required, But we found " + remainingArgs.length);
        }

        final Path jar = new File(remainingArgs[0]).toPath();
        if (!Files.exists(jar)) {
            System.err.println(jar + " doesn't exist");
            return;
        }
        if (output == null) {
            if (Files.isDirectory(jar)) {
                output = new File(jar.getFileName() + "-decrypted.jar").toPath();
            } else {
                output = new File(getBaseName(jar.getFileName().toString()) + "-decrypted.jar").toPath();
            }
        }

        if (Files.exists(output) && !forceOverwrite) {
            System.err.println(output + " exists, use --force to overwrite");
            return;
        }

        System.err.println(jar + " -> " + output);

        List methodConfigs = collectMethodConfigs();
        if (methodConfigs == null || methodConfigs.isEmpty()) {
            System.err.println("No method selected !");
            return;
        }

        final Map map = loadMethods(jar, methodConfigs);
        try (FileSystem outputFileSystem = createZip(output)) {
            final Path outputBase = outputFileSystem.getPath("/");
            walkJarOrDir(jar, (file, relative) -> {
                if (file.getFileName().toString().endsWith(".class")) {
                    Path dist1 = outputBase.resolve(relative);
                    createParentDirectories(dist1);
                    byte[] data = Files.readAllBytes(file);
                    ClassNode cn = readClassNode(data);

                    if (decrypt(cn, map)) {
                        byte[] data2 = toByteArray(cn);
                        Files.write(dist1, data2);
                    } else {
                        Files.write(dist1, data);
                    }
                } else {
                    Path dist1 = outputBase.resolve(relative);
                    createParentDirectories(dist1);
                    Files.copy(file, dist1);
                }
            });
        }
    }

    private byte[] toByteArray(ClassNode cn) {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cn.accept(cw);
        return cw.toByteArray();
    }

    private ClassNode readClassNode(byte[] data) {
        ClassReader cr = new ClassReader(data);
        ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_FRAMES);
        return cn;
    }

    private boolean decrypt(ClassNode cn, Map map) {
        if (deepAnalyze) {
            return decryptByIr(cn, map);
        } else {
            return decryptByStack(cn, map);
        }
    }

    private boolean decryptByIr(ClassNode cn, Map map) {
        MethodConfig key = this.key;
        boolean changed = false;
        Iterator it = cn.methods.iterator();
        while (it.hasNext()) {
            MethodNode m = it.next();
            if (m.instructions == null) {
                continue;
            }
            key.owner = cn.name;
            key.name = m.name;
            key.desc = m.desc;
            if (map.containsKey(key)) {
                if (deleteMethod) {
                    it.remove();
                }
                continue;
            }


            /*if (verbose) {
                System.out.println();
                System.out.println("===============");
                System.out.println("on method " + cn.name + ";->" + m.name + m.desc);
            }*/

            boolean find = false;
            // search for the decrypt method
            for (AbstractInsnNode p = m.instructions.getFirst(); p != null; p = p.getNext()) {
                if (p.getOpcode() == Opcodes.INVOKESTATIC) {
                    MethodInsnNode mn = (MethodInsnNode) p;
                    key.owner = mn.owner;
                    key.name = mn.name;
                    key.desc = mn.desc;
                    MethodConfig config = map.get(key);
                    if (config != null) {
                        find = true;
                    }
                }
            }
            if (find) {
                try {
                    // copy m to m2 for cleanup debug info
                    MethodNode m2 = new MethodNode();
                    m2.tryCatchBlocks = new ArrayList<>();
                    m2.name = m.name;
                    m2.access = m.access;
                    m2.desc = m.desc;
                    m.accept(m2);
                    cleanDebug(m2);
                    // convert m2 to ir
                    IrMethod irMethod = J2IRConverter.convert(cn.name, m2);
                    // opt and decrypt
                    optAndDecrypt(irMethod, map);

                    // convert ir to m3
                    MethodNode m3 = new MethodNode();
                    m3.tryCatchBlocks = new ArrayList<>();
                    new IR2JConverter()
                            .ir(irMethod)
                            .asm(m3)
                            .convert();

                    // copy back m3 to m
                    m.maxLocals = -1;
                    m.instructions = m3.instructions;
                    m.tryCatchBlocks = m3.tryCatchBlocks;
                    m.localVariables = null;
                    changed = true;
                } catch (Exception ex) {
                    if (verbose) {
                        ex.printStackTrace();
                    }
                }
            }
        }
        return changed;
    }

    MethodConfig key = new MethodConfig();

    private boolean decryptByStack(ClassNode cn, Map map) {
        MethodConfig key = this.key;
        boolean changed = false;
        Iterator it = cn.methods.iterator();
        while (it.hasNext()) {
            MethodNode m = it.next();
            if (m.instructions == null) {
                continue;
            }
            key.owner = cn.name;
            key.name = m.name;
            key.desc = m.desc;
            if (map.containsKey(key)) {
                if (deleteMethod) {
                    it.remove();
                }
                continue;
            }

            /*if (verbose) {
                System.out.println();
                System.out.println("===============");
                System.out.println("on method " + cn.name + ";->" + m.name + m.desc);
            }*/

            AbstractInsnNode p = m.instructions.getFirst();
            while (p != null) {
                if (p.getOpcode() == Opcodes.INVOKESTATIC) {
                    MethodInsnNode mn = (MethodInsnNode) p;
                    key.owner = mn.owner;
                    key.name = mn.name;
                    key.desc = mn.desc;
                    MethodConfig config = map.get(key);
                    if (config != null) {
                        //here we are, given that the decryption method is successfully recognised
                        Method jmethod = config.jmethod;
                        try {
                            int pSize = jmethod.getParameterTypes().length;
                            // arguments' list. each parameter's value is retrieved by reading bytecode backwards,
                            // starting from the INVOKESTATIC statement
                            Object[] as = readArgumentValues(mn, jmethod, pSize);
                            if (verbose) {
                                System.out.println(" > calling " + jmethod + " with arguments " + v(as));
                            }
                            //decryption routine invocation
                            String newValue = (String) jmethod.invoke(null, as);
                            if (verbose) {
                                System.out.println("  -> " + Escape.v(newValue));
                            }
                            //LDC statement generation
                            LdcInsnNode nLdc = new LdcInsnNode(newValue);
                            //insertion of the decrypted string's LDC statement, after INVOKESTATIC statement
                            m.instructions.insert(mn, nLdc);
                            //removal of INVOKESTATIC and previous push statements
                            removeInsts(m, mn, pSize);
                            p = nLdc;
                            changed = true;
                        } catch (InvocationTargetException ex) {
                            if (verbose) {
                                ex.getTargetException().printStackTrace();
                            }
                        } catch (Exception ex) {
                            if (verbose) {
                                ex.printStackTrace();
                            }
                        }
                    }
                }
                p = p.getNext();
            }
        }
        return changed;
    }

    protected final CleanLabel tCleanLabel = new CleanLabel();

    protected final Ir2JRegAssignTransformer tIr2JRegAssign = new Ir2JRegAssignTransformer();

    protected final NewTransformer tNew = new NewTransformer();

    protected final RemoveConstantFromSSA tRemoveConst = new RemoveConstantFromSSA();

    protected final RemoveLocalFromSSA tRemoveLocal = new RemoveLocalFromSSA();

    protected final ExceptionHandlerTrim tTrimEx = new ExceptionHandlerTrim();

    protected final TypeTransformer tType = new TypeTransformer();

    protected final DeadCodeTransformer tDeadCode = new DeadCodeTransformer();

    protected final FillArrayTransformer tFillArray = new FillArrayTransformer();

    protected final AggTransformer tAgg = new AggTransformer();

    protected final UnSSATransformer tUnssa = new UnSSATransformer();

    protected final ZeroTransformer tZero = new ZeroTransformer();

    protected final VoidInvokeTransformer tVoidInvoke = new VoidInvokeTransformer();

    protected final NpeTransformer tNpe = new NpeTransformer();

    public void optAndDecrypt(IrMethod irMethod, final Map map) {
        tDeadCode.transform(irMethod);
        tCleanLabel.transform(irMethod);
        tRemoveLocal.transform(irMethod);
        tRemoveConst.transform(irMethod);
        tZero.transform(irMethod);
        if (tNpe.transformReportChanged(irMethod)) {
            tDeadCode.transform(irMethod);
            tRemoveLocal.transform(irMethod);
            tRemoveConst.transform(irMethod);
        }
        tNew.transform(irMethod);
        tFillArray.transform(irMethod);
        tAgg.transform(irMethod);
        tVoidInvoke.transform(irMethod);

        new StmtTraveler() {
            @Override
            public Value travel(Value op) {
                op = super.travel(op);
                if (op.vt == Value.VT.INVOKE_STATIC) {
                    InvokeExpr ie = (InvokeExpr) op;
                    MethodConfig key = DecryptStringCmd.this.key;
                    key.owner = ie.getOwner().substring(1, ie.getOwner().length() - 1);
                    key.name = ie.getName();
                    key.desc = buildMethodDesc(ie.getArgs(), ie.getRet());

                    MethodConfig c = map.get(key);
                    if (c != null) {
                        try {
                            Method jmethod = c.jmethod;
                            if (ie.getArgs().length != jmethod.getParameterTypes().length) {
                                throw new RuntimeException();
                            }

                            Object[] args = new Object[ie.getArgs().length];
                            for (int i = 0; i < args.length; i++) {
                                args[i] = convertIr2Jobj(ie.getOps()[i], ie.getArgs()[i]);
                            }
                            if (verbose) {
                                System.out.println(" > calling " + jmethod + " with arguments " + v(args));
                            }
                            String str = (String) jmethod.invoke(null, args);
                            if (verbose) {
                                System.out.println("  -> " + Escape.v(str));
                            }
                            return Exprs.nString(str);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }
                return op;
            }
        }.travel(irMethod.stmts);

        tType.transform(irMethod);
        tUnssa.transform(irMethod);
        tTrimEx.transform(irMethod);
        tIr2JRegAssign.transform(irMethod);
    }

    public static String v(Object[] vs) {
        StringBuilder sb = new StringBuilder("[");
        boolean first = true;
        for (Object obj : vs) {
            if (first) {
                first = false;
            } else {
                sb.append(",");
            }
            if (obj instanceof String) {
                sb.append(Escape.v(obj));
            } else {
                sb.append(obj);
            }
        }
        return sb.append("]").toString();
    }

    private Object convertIr2Jobj(Value value, String type) {
        if (value instanceof Constant) {
            if (Constant.NULL.equals(((Constant) value).value)) {
                return null;
            }
        }
        switch (type) {
        case "Z": {
            Object obj = ((Constant) value).value;
            return obj instanceof Boolean ? obj : ((Number) obj).intValue() != 0;
        }
        case "B": {
            Object obj = ((Constant) value).value;
            return ((Number) obj).byteValue();
        }
        case "S": {
            Object obj = ((Constant) value).value;
            return ((Number) obj).shortValue();
        }
        case "C": {
            Object obj = ((Constant) value).value;
            return obj instanceof Character ? obj : (char) ((Number) obj).intValue();
        }
        case "I": {
            Object obj = ((Constant) value).value;
            return ((Number) obj).intValue();
        }
        case "J": {
            Object obj = ((Constant) value).value;
            return ((Number) obj).longValue();
        }
        case "F": {
            Object obj = ((Constant) value).value;
            return obj instanceof Float ? obj : Float.intBitsToFloat(((Number) obj).intValue());
        }
        case "D": {
            Object obj = ((Constant) value).value;
            return obj instanceof Double ? obj : Double.longBitsToDouble(((Number) obj).longValue());
        }
        case "Ljava/lang/String;":
            return ((Constant) value).value;
        case "[Z":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof boolean[]) {
                    return obj;
                } else {

                    boolean[] b = new boolean[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = ((Number) Array.get(obj, i)).intValue() != 0;
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                boolean[] b = new boolean[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    if (obj instanceof Boolean) {
                        b[i] = (Boolean) obj;
                    } else {
                        b[i] = ((Number) obj).intValue() != 0;
                    }
                }
                return b;
            }
            throw new RuntimeException();
        case "[B":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof byte[]) {
                    return obj;
                } else {
                    byte[] b = new byte[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = ((Number) Array.get(obj, i)).byteValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                byte[] b = new byte[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = ((Number) obj).byteValue();
                }
                return b;
            }
            throw new RuntimeException();
        case "[S":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof short[]) {
                    return obj;
                } else {
                    short[] b = new short[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = ((Number) Array.get(obj, i)).shortValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                short[] b = new short[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = ((Number) obj).shortValue();
                }
                return b;
            }
            throw new RuntimeException();
        case "[C":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof char[]) {
                    return obj;
                } else {
                    char[] b = new char[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = (char) ((Number) Array.get(obj, i)).intValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                char[] b = new char[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = obj instanceof Character ? (Character) obj : (char) ((Number) obj).intValue();
                }
                return b;
            }
            throw new RuntimeException();
        case "[I":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof int[]) {
                    return obj;
                } else {
                    int[] b = new int[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = ((Number) Array.get(obj, i)).intValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                int[] b = new int[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = ((Number) obj).intValue();
                }
                return b;
            }
            throw new RuntimeException();
        case "[J":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof long[]) {
                    return obj;
                } else {
                    long[] b = new long[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = ((Number) Array.get(obj, i)).longValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                long[] b = new long[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = ((Number) obj).longValue();
                }
                return b;
            }
            throw new RuntimeException();
        case "[F":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof float[]) {
                    return obj;
                } else {
                    float[] b = new float[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = (char) ((Number) Array.get(obj, i)).intValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                float[] b = new float[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = obj instanceof Float
                            ? (Float) obj
                            : Float.intBitsToFloat(((Number) obj).intValue());
                }
                return b;
            }
            throw new RuntimeException();
        case "[D":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof double[]) {
                    return obj;
                } else {
                    double[] b = new double[Array.getLength(obj)];
                    for (int i = 0; i < b.length; i++) {
                        b[i] = (char) ((Number) Array.get(obj, i)).intValue();
                    }
                    return b;
                }
            } else if (value instanceof FilledArrayExpr) {
                double[] b = new double[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    b[i] = obj instanceof Double
                            ? (Double) obj
                            : Double.longBitsToDouble(((Number) obj).longValue());
                }
                return b;
            }
            throw new RuntimeException();
        case "[Ljava/lang/String;":
            if (value instanceof Constant) {
                Object obj = ((Constant) value).value;
                if (obj instanceof String[]) {
                    return obj;
                }
            } else if (value instanceof FilledArrayExpr) {
                String[] b = new String[value.getOps().length];
                for (int i = 0; i < b.length; i++) {
                    Object obj = ((Constant) value.getOps()[i]).value;
                    if (obj instanceof String) {
                        b[i] = (String) obj;
                    } else if (Constant.NULL.equals(obj)) {
                        b[i] = null;
                    } else {
                        throw new RuntimeException();
                    }
                }
                return b;
            }
            throw new RuntimeException();
        default:
            break;
        }
        throw new RuntimeException();
    }

    private String buildMethodDesc(String[] args, String ret) {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        for (String s : args) {
            sb.append(s);
        }
        return sb.append(')').append(ret).toString();
    }

    private void cleanDebug(MethodNode mn) {
        AbstractInsnNode p = mn.instructions.getFirst();
        while (p != null) {
            if (p.getType() == AbstractInsnNode.LINE) {
                AbstractInsnNode q = p.getNext();
                mn.instructions.remove(p);
                p = q;
            } else {
                p = p.getNext();
            }
        }
        mn.localVariables = null;
    }

    void removeInsts(MethodNode m, MethodInsnNode mn, int pSize) {
        // remove args
        for (int i = 0; i < pSize; i++) {
            m.instructions.remove(mn.getPrevious());
        }
        // remove INVOKESTATIC
        m.instructions.remove(mn);
    }

    Object[] readArgumentValues(MethodInsnNode mn, Method jmethod, int pSize) {
        AbstractInsnNode q = mn;
        Object[] as = new Object[pSize];
        for (int i = pSize - 1; i >= 0; i--) {
            q = q.getPrevious();
            Object object = readCst(q);
            as[i] = convert(object, jmethod.getParameterTypes()[i]);
        }
        return as;
    }

    Object convert(Object object, Class type) {
        if (int.class.equals(type)) {
            return ((Number) object).intValue();
        }
        if (byte.class.equals(type)) {
            return ((Number) object).byteValue();
        }
        if (short.class.equals(type)) {
            return ((Number) object).shortValue();
        }
        if (char.class.equals(type)) {
            return (char) ((Number) object).intValue();
        }
        if (boolean.class.equals(type)) {
            return (char) ((Number) object).intValue() != 0;
        }
        if (long.class.equals(type)) {
            return (char) ((Number) object).longValue();
        }
        if (float.class.equals(type)) {
            return (char) ((Number) object).floatValue();
        }
        if (double.class.equals(type)) {
            return (char) ((Number) object).doubleValue();
        }
        return object;
    }

    /**
     * load java methods from jar and --classpath
     */
    private Map loadMethods(Path jar, List methodConfigs) throws Exception {
        final Map map = new HashMap<>();
        List list = new ArrayList<>();
        if (classpath != null) {
            list.addAll(Arrays.asList(classpath.split("[;:]")));
        }
        list.add(jar.toAbsolutePath().toString());
        URL[] urls = new URL[list.size()];
        for (int i = 0; i < list.size(); i++) {
            urls[i] = new File(list.get(i)).toURI().toURL();
        }

        URLClassLoader cl = new URLClassLoader(urls);
        for (MethodConfig config : methodConfigs) {
            Method jmethod;
            try {
                Class clz = cl.loadClass(config.owner.replace('/', '.'));
                if (clz == null) {
                    System.err.println("clz is null:" + config.owner);
                }
                jmethod = findAnyMethodMatch(clz, config.name,
                        toJavaType(Type.getArgumentTypes(config.desc)));
            } catch (Exception ex) {
                System.err.println("can't load method: L" + config.owner + ";->" + config.name + config.desc);
                throw ex;
            }
            if (jmethod != null) {
                jmethod.setAccessible(true);
                config.jmethod = jmethod;
                map.put(config, config);
            } else {
                throw new NoSuchMethodException("Can't find method " + config.name + config.desc
                        + " on " + config.owner + " or its parent");
            }
        }
        return map;
    }

    /**
     * collect methods from --methods and --method-owner,--method-name
     */
    private List collectMethodConfigs() throws IOException {
        List methodConfigs = new ArrayList<>();
        if (this.method != null) {
            for (String line : Files.readAllLines(this.method, StandardCharsets.UTF_8)) {
                if (line.isEmpty() || line.startsWith("#")) {
                    continue;
                }
                methodConfigs.add(this.build(line));
            }
        }
        if (methodOwner != null && methodName != null) {
            if (this.parametersDescriptor != null) {
                methodConfigs.add(this.build("L" + methodOwner.replace('.', '/') + ";->" + methodName + "("
                        + this.parametersDescriptor + ")Ljava/lang/String;"));
            } else if (this.parameterJTypes != null) {

                //parameterJTypes is a comma-separated list of the decryption method's parameters
                String[] typeList = parameterJTypes.split("[,;:]");
                //switch for all the supported types. String is default
                StringBuilder sb = new StringBuilder();
                for (String s : typeList) {
                    switch (s) {
                    case "boolean":
                        sb.append("Z");
                        break;
                    case "byte":
                        sb.append("B");
                        break;
                    case "short":
                        sb.append("S");
                        break;
                    case "char":
                        sb.append("C");
                        break;
                    case "int":
                        sb.append("I");
                        break;
                    case "long":
                        sb.append("J");
                        break;
                    case "float":
                        sb.append("F");
                        break;
                    case "double":
                        sb.append("D");
                        break;
                    case "string":
                        sb.append("Ljava/lang/String;");
                        break;

                    default:
                        throw new RuntimeException("not support type " + s + " on -t/--arg-types");
                    }
                }
                methodConfigs.add(this.build("L" + methodOwner.replace('.', '/') + ";->" + methodName + "("
                        + sb + ")Ljava/lang/String;"));
            } else {
                methodConfigs.add(this.build("L" + methodOwner.replace('.', '/') + ";->" + methodName + "(Ljava/lang"
                        + "/String;)Ljava/lang/String;"));
            }
        }
        return methodConfigs;
    }

    /**
     * fix for issue 216, travel all the parent of classes and use getDeclaredMethod
     * to find methods
     */
    private Method findAnyMethodMatch(Class clz, String name, Class[] classes) {
        try {
            return clz.getDeclaredMethod(name, classes);
        } catch (NoSuchMethodException ignored) {
            // https://github.com/pxb1988/dex2jar/issues/51
            // mute exception stack
        }
        Class sup = clz.getSuperclass();
        if (sup != null) {
            Method m = findAnyMethodMatch(sup, name, classes);
            if (m != null) {
                return m;
            }
        }
        Class[] itfs = clz.getInterfaces();
        for (Class itf : itfs) {
            Method m = findAnyMethodMatch(itf, name, classes);
            if (m != null) {
                return m;
            }
        }
        return null;
    }

    Object readCst(AbstractInsnNode q) {

        switch (q.getOpcode()) {
        case Opcodes.LDC:
            // LDC: String, integer, long and double cases (Opcodes.LDC comprehends LDC_W and LDC2_W)
            // push 32bit or 64bit int/float
            // push string/type
            LdcInsnNode ldc = (LdcInsnNode) q;
            if (ldc.cst instanceof Type) {
                throw new RuntimeException("not support .class value yet!");
            }
            return ldc.cst;

        case Opcodes.BIPUSH:
        case Opcodes.SIPUSH:
            // INT_INSN ("instruction with a single int operand")
            // push 8bit or 16bit int
            IntInsnNode in = (IntInsnNode) q;
            return in.operand;

        case Opcodes.ICONST_M1:
        case Opcodes.ICONST_0:
        case Opcodes.ICONST_1:
        case Opcodes.ICONST_2:
        case Opcodes.ICONST_3:
        case Opcodes.ICONST_4:
        case Opcodes.ICONST_5:
            // ICONST_*: push a tiny int, -1 <= value <= 5
            return q.getOpcode() - Opcodes.ICONST_0;
        case Opcodes.LCONST_0:
        case Opcodes.LCONST_1:
            return (long) (q.getOpcode() - Opcodes.LCONST_0);
        case Opcodes.FCONST_0:
        case Opcodes.FCONST_1:
        case Opcodes.FCONST_2:
            return (float) (q.getOpcode() - Opcodes.FCONST_0);
        case Opcodes.DCONST_0:
        case Opcodes.DCONST_1:
            return (double) (q.getOpcode() - Opcodes.DCONST_0);
        case Opcodes.ACONST_NULL:
            return null;
        default:
            break;
        }

        throw new RuntimeException();
    }

    Class[] toJavaType(Type[] pt) throws ClassNotFoundException {
        Class[] jt = new Class[pt.length];
        for (int i = 0; i < pt.length; i++) {
            jt[i] = toJavaType(pt[i]);
        }
        return jt;
    }

    Class toJavaType(Type t) throws ClassNotFoundException {
        switch (t.getSort()) {
        case Type.BOOLEAN:
            return boolean.class;
        case Type.BYTE:
            return byte.class;
        case Type.SHORT:
            return short.class;
        case Type.CHAR:
            return char.class;
        case Type.INT:
            return int.class;
        case Type.FLOAT:
            return float.class;
        case Type.LONG:
            return long.class;
        case Type.DOUBLE:
            return double.class;
        case Type.OBJECT:
            return Class.forName(t.getClassName());
        case Type.ARRAY:
            return Class.forName(t.getDescriptor());
        case Type.VOID:
            return void.class;
        default:
            break;
        }
        throw new RuntimeException();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy