mirror of
https://github.com/pxb1988/dex2jar.git
synced 2024-11-23 13:19:46 +00:00
merge DecryptStringCmd.java@43d692ab4917 to 2.x
--HG-- branch : 2.x
This commit is contained in:
parent
f3ed76cca0
commit
f48faf2567
@ -16,24 +16,23 @@
|
||||
*/
|
||||
package com.googlecode.dex2jar.tools;
|
||||
|
||||
import com.googlecode.dex2jar.tools.BaseCmd.Syntax;
|
||||
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.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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.List;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import com.googlecode.dex2jar.tools.BaseCmd.Syntax;
|
||||
import java.util.*;
|
||||
|
||||
@Syntax(cmd = "d2j-decrypt-string", syntax = "[options] <jar>", desc = "Decrypt in class file", onlineHelp = "https://code.google.com/p/dex2jar/wiki/DecryptStrings")
|
||||
public class DecryptStringCmd extends BaseCmd {
|
||||
@ -45,19 +44,106 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
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 = "mo", longOpt = "decrypt-method-owner", description = "the owner of the mothed which can decrypt the stings, example: java.lang.String", argName = "owner")
|
||||
@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 mothed which can decrypt the stings, the method's signature must be static (Ljava/lang/String;)Ljava/lang/String;", argName = "name")
|
||||
@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 descrypt.", argName = "name")
|
||||
private String methodName;
|
||||
@Opt(opt = "cp", longOpt = "classpath", description = "", argName = "cp")
|
||||
@Opt(opt = "cp", longOpt = "classpath", description = "add extra lib to classpath", argName = "cp")
|
||||
private String classpath;
|
||||
@Opt(opt = "t", longOpt = "arg-type", description = "ignored")
|
||||
private String type = null;
|
||||
|
||||
@Opt(opt = "pt", longOpt = "parameter-type", description = "the descript for the method which can decrypt the stings, example1: Ljava/lang/String; example2:III, default is Ljava/lang/String;", argName = "type")
|
||||
private String parameterType = "Ljava/lang/String;";
|
||||
@Opt(opt = "d", longOpt = "delete", hasArg = false, description = "delete the method which can decrypt the stings")
|
||||
private boolean deleteMethod = 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) {
|
||||
if (other.owner != null)
|
||||
return false;
|
||||
} else if (!owner.equals(other.owner))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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 != 1) {
|
||||
usage();
|
||||
return;
|
||||
if (remainingArgs.length == 0) {
|
||||
throw new HelpException("One <jar> file is required");
|
||||
} else if (remainingArgs.length > 1) {
|
||||
throw new HelpException("Only one <jar> file is required, But we found " + remainingArgs.length);
|
||||
}
|
||||
|
||||
final Path jar = new File(remainingArgs[0]).toPath();
|
||||
@ -65,11 +151,6 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
System.err.println(jar + " is not exists");
|
||||
return;
|
||||
}
|
||||
if (methodName == null || methodOwner == null) {
|
||||
System.err.println("Please set --decrypt-method-owner and --decrypt-method-name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (output == null) {
|
||||
if (Files.isDirectory(jar)) {
|
||||
output = new File(jar.getFileName() + "-decrypted.jar").toPath();
|
||||
@ -85,6 +166,25 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
|
||||
System.err.println(jar + " -> " + output);
|
||||
|
||||
List<MethodConfig> methodConfigs = new ArrayList<MethodConfig>();
|
||||
if (this.method != null) {
|
||||
for (String line : Files.readAllLines(this.method, StandardCharsets.UTF_8)) {
|
||||
if (line.length() == 0 || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
methodConfigs.add(this.build(line));
|
||||
}
|
||||
} else {
|
||||
if (methodOwner == null || methodName == null) {
|
||||
System.err.println("-mo/--decrypt-method-owner or -mn/decrypt-method-name is null");
|
||||
return;
|
||||
}
|
||||
methodConfigs.add(this.build("L" + methodOwner.replace('.', '/') + ";->" + methodName + "("
|
||||
+ this.parameterType + ")Ljava/lang/String;"));
|
||||
}
|
||||
|
||||
final Map<MethodConfig, MethodConfig> map = new HashMap<MethodConfig, MethodConfig>();
|
||||
{
|
||||
List<String> list = new ArrayList<String>();
|
||||
if (classpath != null) {
|
||||
list.addAll(Arrays.asList(classpath.split(";|:")));
|
||||
@ -94,15 +194,25 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
urls[i] = new File(list.get(i)).toURI().toURL();
|
||||
}
|
||||
final Method jmethod;
|
||||
try {
|
||||
|
||||
URLClassLoader cl = new URLClassLoader(urls);
|
||||
jmethod = cl.loadClass(methodOwner).getMethod(methodName, String.class);
|
||||
for (MethodConfig config : methodConfigs) {
|
||||
try {
|
||||
Class<?> clz = cl.loadClass(config.owner.replace('/', '.'));
|
||||
if (clz == null) {
|
||||
System.err.println("clz is null:" + config.owner);
|
||||
}
|
||||
Method jmethod = findAnyMethodMatch(clz, config.name,
|
||||
toJavaType(Type.getArgumentTypes(config.desc)));
|
||||
jmethod.setAccessible(true);
|
||||
config.jmethod = jmethod;
|
||||
map.put(config, config);
|
||||
} catch (Exception ex) {
|
||||
System.err.println("can't load method: String " + methodOwner + "." + methodName + "(String), message:"
|
||||
+ ex.getMessage());
|
||||
System.err.println("can't load method: L" + config.owner + ";->" + config.name + config.desc);
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
final String methodOwnerInternalType = this.methodOwner.replace('.', '/');
|
||||
try (FileSystem outputFileSystem = createZip(output)) {
|
||||
@ -111,36 +221,54 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
@Override
|
||||
public void visitFile(Path file, Path relative) throws IOException {
|
||||
if (file.getFileName().toString().endsWith(".class")) {
|
||||
|
||||
MethodConfig key = new MethodConfig();
|
||||
ClassReader cr = new ClassReader(Files.readAllBytes(file));
|
||||
ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
cr.accept(cn, ClassReader.EXPAND_FRAMES);
|
||||
|
||||
for (Object m0 : cn.methods) {
|
||||
MethodNode m = (MethodNode) m0;
|
||||
for (MethodNode m : new ArrayList<MethodNode>(cn.methods)) {
|
||||
if (m.instructions == null) {
|
||||
continue;
|
||||
}
|
||||
key.owner = cn.name;
|
||||
key.name = m.name;
|
||||
key.desc = m.desc;
|
||||
if (map.containsKey(key)) {
|
||||
if (deleteMethod) {
|
||||
cn.methods.remove(m);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
AbstractInsnNode p = m.instructions.getFirst();
|
||||
while (p != null) {
|
||||
if (p.getOpcode() == Opcodes.LDC) {
|
||||
LdcInsnNode ldc = (LdcInsnNode) p;
|
||||
if (ldc.cst instanceof String) {
|
||||
String v = (String) ldc.cst;
|
||||
AbstractInsnNode q = p.getNext();
|
||||
if (q.getOpcode() == Opcodes.INVOKESTATIC) {
|
||||
MethodInsnNode mn = (MethodInsnNode) q;
|
||||
if (mn.name.equals(methodName)
|
||||
&& mn.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")
|
||||
&& mn.owner.equals(methodOwnerInternalType)) {
|
||||
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) {
|
||||
Method jmethod = config.jmethod;
|
||||
try {
|
||||
Object newValue = jmethod.invoke(null, v);
|
||||
ldc.cst = newValue;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
AbstractInsnNode q = p;
|
||||
int pSize = jmethod.getParameterTypes().length;
|
||||
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]);
|
||||
}
|
||||
String newValue = (String) jmethod.invoke(null, as);
|
||||
LdcInsnNode nLdc = new LdcInsnNode(newValue);
|
||||
m.instructions.insert(p, nLdc);
|
||||
q = p;
|
||||
for (int i = 0; i <= pSize; i++) {
|
||||
AbstractInsnNode z = q.getPrevious();
|
||||
m.instructions.remove(q);
|
||||
q = z;
|
||||
}
|
||||
p = nLdc;
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,7 +283,112 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
Files.copy(file, outputBase.resolve(relative));
|
||||
}
|
||||
}
|
||||
|
||||
private 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();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fix for issue 216, travel all the parent of class and use getDeclaredMethod to find methods
|
||||
*/
|
||||
private Method findAnyMethodMatch(Class<?> clz, String name, Class<?>[] classes) {
|
||||
try {
|
||||
Method m = clz.getDeclaredMethod(name, classes);
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Class<?> sup = clz.getSuperclass();
|
||||
if (sup != null) {
|
||||
Method m = findAnyMethodMatch(sup, name, classes);
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
Class<?>[] itfs = clz.getInterfaces();
|
||||
if (itfs != null && itfs.length > 0) {
|
||||
for (Class<?> itf : itfs) {
|
||||
Method m = findAnyMethodMatch(itf, name, classes);
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Object readCst(AbstractInsnNode q) {
|
||||
if (q.getOpcode() == Opcodes.LDC) {
|
||||
LdcInsnNode ldc = (LdcInsnNode) q;
|
||||
return ldc.cst;
|
||||
} else if (q.getType() == AbstractInsnNode.INT_INSN) {
|
||||
IntInsnNode in = (IntInsnNode) q;
|
||||
return in.operand;
|
||||
} else {
|
||||
switch (q.getOpcode()) {
|
||||
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:
|
||||
int x = ((InsnNode) q).getOpcode() - Opcodes.ICONST_0;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private 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:
|
||||
case Type.ARRAY:
|
||||
return Class.forName(t.getClassName());
|
||||
case Type.VOID:
|
||||
return void.class;
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user