mirror of
https://github.com/pxb1988/dex2jar.git
synced 2024-11-23 13:19:46 +00:00
decrypt multi method at once
--HG-- branch : 0.0.9.x
This commit is contained in:
parent
6ad1e44d59
commit
2a6eca13ff
@ -23,8 +23,11 @@ import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
@ -58,15 +61,99 @@ 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 File output;
|
||||
|
||||
@Opt(opt = "mo", required = true, longOpt = "decrypt-method-owner", description = "the owner of the method 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 File 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", required = true, longOpt = "decrypt-method-name", description = "the owner of the method which can decrypt the stings, the method's signature must be static (type)Ljava/lang/String;. Please use -t,--arg-type to set the argument type.", 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 = "add extra lib to classpath", argName = "cp")
|
||||
private String classpath;
|
||||
@Opt(opt = "t", longOpt = "arg-type", description = "the type of the method's argument, int,string. default is string", argName = "type")
|
||||
private String type = "string";
|
||||
@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 {
|
||||
@ -96,29 +183,55 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
|
||||
System.err.println(jar + " -> " + output);
|
||||
|
||||
List<String> list = new ArrayList<String>();
|
||||
if (classpath != null) {
|
||||
list.addAll(Arrays.asList(classpath.split(";|:")));
|
||||
List<MethodConfig> methodConfigs = new ArrayList<MethodConfig>();
|
||||
if (this.method != null) {
|
||||
for (String line : FileUtils.readLines(this.method, "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;"));
|
||||
}
|
||||
list.add(jar.getAbsolutePath());
|
||||
URL[] urls = new URL[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
urls[i] = new File(list.get(i)).toURI().toURL();
|
||||
}
|
||||
final Method jmethod;
|
||||
final String targetMethodDesc;
|
||||
try {
|
||||
Class<?> argType = "string".equals(type) ? String.class : int.class;
|
||||
|
||||
final Map<MethodConfig, MethodConfig> map = new HashMap<MethodConfig, MethodConfig>();
|
||||
{
|
||||
List<String> list = new ArrayList<String>();
|
||||
if (classpath != null) {
|
||||
list.addAll(Arrays.asList(classpath.split(";|:")));
|
||||
}
|
||||
list.add(jar.getAbsolutePath());
|
||||
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);
|
||||
jmethod = cl.loadClass(methodOwner).getDeclaredMethod(methodName, argType);
|
||||
jmethod.setAccessible(true);
|
||||
targetMethodDesc = Type.getMethodDescriptor(jmethod);
|
||||
} catch (Exception ex) {
|
||||
System.err.println("can't load method: String " + methodOwner + "." + methodName + "(" + type + ")");
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
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 = clz.getDeclaredMethod(config.name,
|
||||
this.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: L" + config.owner + ";->" + config.name + config.desc);
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
final String methodOwnerInternalType = this.methodOwner.replace('.', '/');
|
||||
|
||||
final OutHandler fo = FileOut.create(output, true);
|
||||
try {
|
||||
new FileWalker().withStreamHandler(new StreamHandler() {
|
||||
@ -130,46 +243,56 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
fo.write(isDir, name, current == null ? null : current.get(), nameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
MethodConfig key = new MethodConfig();
|
||||
ClassReader cr = new ClassReader(current.get());
|
||||
ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, ClassReader.EXPAND_FRAMES);
|
||||
|
||||
for (Object m0 : cn.methods) {
|
||||
for (Object m0 : new ArrayList(cn.methods)) {
|
||||
MethodNode m = (MethodNode) m0;
|
||||
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.INVOKESTATIC) {
|
||||
MethodInsnNode mn = (MethodInsnNode) p;
|
||||
if (mn.name.equals(methodName) && mn.desc.equals(targetMethodDesc)
|
||||
&& mn.owner.equals(methodOwnerInternalType)) {
|
||||
AbstractInsnNode q = p.getPrevious();
|
||||
AbstractInsnNode next = p.getNext();
|
||||
if (q.getOpcode() == Opcodes.LDC) {
|
||||
LdcInsnNode ldc = (LdcInsnNode) q;
|
||||
tryReplace(m.instructions, p, q, jmethod, ldc.cst);
|
||||
} else if (q.getType() == AbstractInsnNode.INT_INSN) {
|
||||
IntInsnNode in = (IntInsnNode) q;
|
||||
tryReplace(m.instructions, p, q, jmethod, 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;
|
||||
tryReplace(m.instructions, p, q, jmethod, x);
|
||||
break;
|
||||
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 {
|
||||
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) {
|
||||
}
|
||||
p = next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
p = p.getNext();
|
||||
@ -180,21 +303,90 @@ public class DecryptStringCmd extends BaseCmd {
|
||||
cn.accept(cw);
|
||||
fo.write(false, cr.getClassName() + ".class", cw.toByteArray(), null);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}).walk(jar);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fo);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static AbstractInsnNode tryReplace(InsnList instructions, AbstractInsnNode p, AbstractInsnNode q,
|
||||
Method jmethod, Object arg) {
|
||||
try {
|
||||
String newValue = (String) jmethod.invoke(null, arg);
|
||||
LdcInsnNode nLdc = new LdcInsnNode(newValue);
|
||||
instructions.insertBefore(p, nLdc);
|
||||
instructions.remove(p);
|
||||
instructions.remove(q);
|
||||
return nLdc.getNext();
|
||||
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user