decrypt multi method at once

--HG--
branch : 0.0.9.x
This commit is contained in:
Bob Pan 2013-07-06 00:13:05 +08:00
parent 6ad1e44d59
commit 2a6eca13ff

View File

@ -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
}