merge DecryptStringCmd.java@43d692ab4917 to 2.x

--HG--
branch : 2.x
This commit is contained in:
Bob Pan 2014-10-29 21:07:21 +08:00
parent f3ed76cca0
commit f48faf2567

View File

@ -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();
}
}