mirror of
https://github.com/pxb1988/dex2jar.git
synced 2025-02-17 03:37:38 +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.net.URLClassLoader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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.FilenameUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
@ -58,15 +61,99 @@ public class DecryptStringCmd extends BaseCmd {
|
|||||||
private boolean forceOverwrite = false;
|
private boolean forceOverwrite = false;
|
||||||
@Opt(opt = "o", longOpt = "output", description = "output of .jar files, default is $current_dir/[jar-name]-decrypted.jar", argName = "out")
|
@Opt(opt = "o", longOpt = "output", description = "output of .jar files, default is $current_dir/[jar-name]-decrypted.jar", argName = "out")
|
||||||
private File output;
|
private File 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")
|
||||||
@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")
|
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;
|
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;
|
private String methodName;
|
||||||
@Opt(opt = "cp", longOpt = "classpath", description = "add extra lib to classpath", argName = "cp")
|
@Opt(opt = "cp", longOpt = "classpath", description = "add extra lib to classpath", argName = "cp")
|
||||||
private String classpath;
|
private String classpath;
|
||||||
@Opt(opt = "t", longOpt = "arg-type", description = "the type of the method's argument, int,string. default is string", argName = "type")
|
@Opt(opt = "t", longOpt = "arg-type", description = "ignored")
|
||||||
private String type = "string";
|
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
|
@Override
|
||||||
protected void doCommandLine() throws Exception {
|
protected void doCommandLine() throws Exception {
|
||||||
@ -96,29 +183,55 @@ public class DecryptStringCmd extends BaseCmd {
|
|||||||
|
|
||||||
System.err.println(jar + " -> " + output);
|
System.err.println(jar + " -> " + output);
|
||||||
|
|
||||||
List<String> list = new ArrayList<String>();
|
List<MethodConfig> methodConfigs = new ArrayList<MethodConfig>();
|
||||||
if (classpath != null) {
|
if (this.method != null) {
|
||||||
list.addAll(Arrays.asList(classpath.split(";|:")));
|
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()];
|
final Map<MethodConfig, MethodConfig> map = new HashMap<MethodConfig, MethodConfig>();
|
||||||
for (int i = 0; i < list.size(); i++) {
|
{
|
||||||
urls[i] = new File(list.get(i)).toURI().toURL();
|
List<String> list = new ArrayList<String>();
|
||||||
}
|
if (classpath != null) {
|
||||||
final Method jmethod;
|
list.addAll(Arrays.asList(classpath.split(";|:")));
|
||||||
final String targetMethodDesc;
|
}
|
||||||
try {
|
list.add(jar.getAbsolutePath());
|
||||||
Class<?> argType = "string".equals(type) ? String.class : int.class;
|
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);
|
URLClassLoader cl = new URLClassLoader(urls);
|
||||||
jmethod = cl.loadClass(methodOwner).getDeclaredMethod(methodName, argType);
|
for (MethodConfig config : methodConfigs) {
|
||||||
jmethod.setAccessible(true);
|
try {
|
||||||
targetMethodDesc = Type.getMethodDescriptor(jmethod);
|
Class<?> clz = cl.loadClass(config.owner.replace('/', '.'));
|
||||||
} catch (Exception ex) {
|
if (clz == null) {
|
||||||
System.err.println("can't load method: String " + methodOwner + "." + methodName + "(" + type + ")");
|
System.err.println("clz is null:" + config.owner);
|
||||||
ex.printStackTrace();
|
}
|
||||||
return;
|
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);
|
final OutHandler fo = FileOut.create(output, true);
|
||||||
try {
|
try {
|
||||||
new FileWalker().withStreamHandler(new StreamHandler() {
|
new FileWalker().withStreamHandler(new StreamHandler() {
|
||||||
@ -130,46 +243,56 @@ public class DecryptStringCmd extends BaseCmd {
|
|||||||
fo.write(isDir, name, current == null ? null : current.get(), nameObject);
|
fo.write(isDir, name, current == null ? null : current.get(), nameObject);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
MethodConfig key = new MethodConfig();
|
||||||
ClassReader cr = new ClassReader(current.get());
|
ClassReader cr = new ClassReader(current.get());
|
||||||
ClassNode cn = new ClassNode();
|
ClassNode cn = new ClassNode();
|
||||||
cr.accept(cn, ClassReader.EXPAND_FRAMES);
|
cr.accept(cn, ClassReader.EXPAND_FRAMES);
|
||||||
|
|
||||||
for (Object m0 : cn.methods) {
|
for (Object m0 : new ArrayList(cn.methods)) {
|
||||||
MethodNode m = (MethodNode) m0;
|
MethodNode m = (MethodNode) m0;
|
||||||
if (m.instructions == null) {
|
if (m.instructions == null) {
|
||||||
continue;
|
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();
|
AbstractInsnNode p = m.instructions.getFirst();
|
||||||
while (p != null) {
|
while (p != null) {
|
||||||
if (p.getOpcode() == Opcodes.INVOKESTATIC) {
|
if (p.getOpcode() == Opcodes.INVOKESTATIC) {
|
||||||
MethodInsnNode mn = (MethodInsnNode) p;
|
MethodInsnNode mn = (MethodInsnNode) p;
|
||||||
if (mn.name.equals(methodName) && mn.desc.equals(targetMethodDesc)
|
key.owner = mn.owner;
|
||||||
&& mn.owner.equals(methodOwnerInternalType)) {
|
key.name = mn.name;
|
||||||
AbstractInsnNode q = p.getPrevious();
|
key.desc = mn.desc;
|
||||||
AbstractInsnNode next = p.getNext();
|
MethodConfig config = map.get(key);
|
||||||
if (q.getOpcode() == Opcodes.LDC) {
|
if (config != null) {
|
||||||
LdcInsnNode ldc = (LdcInsnNode) q;
|
Method jmethod = config.jmethod;
|
||||||
tryReplace(m.instructions, p, q, jmethod, ldc.cst);
|
try {
|
||||||
} else if (q.getType() == AbstractInsnNode.INT_INSN) {
|
AbstractInsnNode q = p;
|
||||||
IntInsnNode in = (IntInsnNode) q;
|
int pSize = jmethod.getParameterTypes().length;
|
||||||
tryReplace(m.instructions, p, q, jmethod, in.operand);
|
Object[] as = new Object[pSize];
|
||||||
} else {
|
for (int i = pSize - 1; i >= 0; i--) {
|
||||||
switch (q.getOpcode()) {
|
q = q.getPrevious();
|
||||||
case Opcodes.ICONST_M1:
|
Object object = readCst(q);
|
||||||
case Opcodes.ICONST_0:
|
as[i] = convert(object, jmethod.getParameterTypes()[i]);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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();
|
p = p.getNext();
|
||||||
@ -180,21 +303,90 @@ public class DecryptStringCmd extends BaseCmd {
|
|||||||
cn.accept(cw);
|
cn.accept(cw);
|
||||||
fo.write(false, cr.getClassName() + ".class", cw.toByteArray(), null);
|
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);
|
}).walk(jar);
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(fo);
|
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,
|
public static AbstractInsnNode tryReplace(InsnList instructions, AbstractInsnNode p, AbstractInsnNode q,
|
||||||
Method jmethod, Object arg) {
|
Method jmethod, Object arg) {
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user