diff --git a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/KeepConfig.java b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/KeepConfig.java deleted file mode 100644 index 18fecc2..0000000 --- a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/KeepConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.nmmedit.apkprotect.dex2c.filters; - -import com.nmmedit.apkprotect.deobfus.MappingReader; -import org.jf.dexlib2.iface.ClassDef; -import org.jf.dexlib2.iface.Method; - -import java.io.IOException; - -/** - * 自定义规则文件用于过滤需要处理的class和方法 - */ -public class KeepConfig extends ProguardMappingConfig { - - public KeepConfig(ClassAndMethodFilter filter, MappingReader mappingReader) throws IOException { - super(filter, mappingReader); - } - - @Override - protected boolean keepClass(ClassDef classDef) { - //需要保留的class - return true; - } - - @Override - protected boolean keepMethod(Method method) { - return false; - } -} \ No newline at end of file diff --git a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/ProguardMappingConfig.java b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/ProguardMappingConfig.java index 1af629c..443ce12 100644 --- a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/ProguardMappingConfig.java +++ b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/ProguardMappingConfig.java @@ -1,27 +1,71 @@ package com.nmmedit.apkprotect.dex2c.filters; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.nmmedit.apkprotect.deobfus.MappingProcessor; import com.nmmedit.apkprotect.deobfus.MappingReader; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.immutable.reference.ImmutableMethodReference; +import javax.annotation.Nonnull; import java.io.IOException; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; /** * 读取proguard的mapping.txt文件,根据它得到class和方法名混淆前后映射关系,然后再执行过滤规则 */ -public abstract class ProguardMappingConfig implements ClassAndMethodFilter, MappingProcessor { +public class ProguardMappingConfig implements ClassAndMethodFilter, MappingProcessor { private final ClassAndMethodFilter filter; - private final Map newTypeOldTypeMap = new HashMap<>(); - - public ProguardMappingConfig(ClassAndMethodFilter filter, MappingReader mappingReader) throws IOException { + private final Map newTypeOldTypeMap = Maps.newHashMap(); + private final Map oldTypeNewTypeMap = Maps.newHashMap(); + private final Set methodSet = Sets.newHashSet(); + private final HashMultimap newMethodRefMap = HashMultimap.create(); + private final SimpleRule simpleRule; + + public ProguardMappingConfig(ClassAndMethodFilter filter, + MappingReader mappingReader, + SimpleRule simpleRule) throws IOException { this.filter = filter; + this.simpleRule = simpleRule; mappingReader.parse(this); + + for (MethodMapping methodMapping : methodSet) { + final List args = parseArgs(methodMapping.args); + final ImmutableMethodReference oldMethodRef = new ImmutableMethodReference( + javaType2jvm(methodMapping.className), + methodMapping.methodName, args, + javaType2jvm(methodMapping.returnType)); + + final List newArgs = getNewArgs(args); + String newRetType = oldTypeNewTypeMap.get(methodMapping.returnType); + if (newRetType == null) { + newRetType = methodMapping.returnType; + } + final ImmutableMethodReference newMethodRef = new ImmutableMethodReference(javaType2jvm( + methodMapping.newClassName), + methodMapping.newMethodName, newArgs, + javaType2jvm(newRetType)); + newMethodRefMap.put(newMethodRef, oldMethodRef); + } } + private List getNewArgs(List args) { + final ArrayList newArgs = new ArrayList<>(); + for (String arg : args) { + final String newType = oldTypeNewTypeMap.get(arg); + newArgs.add(newType == null ? arg : newType); + } + return newArgs; + } + + @Override public final boolean acceptClass(ClassDef classDef) { //先处理上游的过滤规则,如果上游不通过则直接返回不再处理,如果上游通过再处理当前的过滤规则 @@ -29,18 +73,28 @@ public final boolean acceptClass(ClassDef classDef) { return false; } //得到混淆之前的className - final String oldName = getOriginClassName(classDef.getType()); - if (oldName == null) { + final String oldType = getOriginClassType(classDef.getType()); + if (oldType == null) { return false; } - //需要保留的class返回false, - return !keepClass(classDef); + final ArrayList ifacs = new ArrayList<>(); + for (String ifac : classDef.getInterfaces()) { + ifacs.add(getOriginClassType(ifac)); + } + + return simpleRule != null && simpleRule.matchClass( + oldType, + getOriginClassType(classDef.getSuperclass()), + ifacs); } - protected abstract boolean keepClass(ClassDef classDef); - protected String getOriginClassName(String type) { - return newTypeOldTypeMap.get(type); + private String getOriginClassType(String type) { + final String oldType = newTypeOldTypeMap.get(type); + if (oldType == null) { + return type; + } + return oldType; } @Override @@ -48,10 +102,27 @@ public final boolean acceptMethod(Method method) { if (filter != null && !filter.acceptMethod(method)) { return false; } - return !keepMethod(method); - } + final String oldType = getOriginClassType(method.getDefiningClass()); + if (oldType == null) { + return false; + } + final Set oldMethodRefSet = newMethodRefMap.get(method); + - protected abstract boolean keepMethod(Method method); + if (oldMethodRefSet != null) { + + for (MethodReference reference : oldMethodRefSet) { + if (oldType.equals(reference.getDefiningClass())) { + if (simpleRule != null && simpleRule.matchMethod(reference.getName())) { + return true; + } + } + } + + } + + return simpleRule != null && simpleRule.matchMethod(method.getName()); + } private static String classNameToType(String className) { return "L" + className.replace('.', '/') + ";"; @@ -59,7 +130,8 @@ private static String classNameToType(String className) { @Override public final void processClassMapping(String className, String newClassName) { - newTypeOldTypeMap.put(classNameToType(newClassName), className); + newTypeOldTypeMap.put(classNameToType(newClassName), classNameToType(className)); + oldTypeNewTypeMap.put(classNameToType(className), classNameToType(newClassName)); } @Override @@ -67,8 +139,114 @@ public final void processFieldMapping(String className, String fieldType, String } + @Nonnull + private static String javaType2jvm(@Nonnull String type) { + switch (type.trim()) { + case "boolean": + return "Z"; + case "byte": + return "B"; + case "char": + return "C"; + case "short": + return "S"; + case "int": + return "I"; + case "float": + return "F"; + case "long": + return "J"; + case "double": + return "D"; + case "void": + return "V"; + default: + int i = type.indexOf('['); + if (i != -1) { + String t = type.substring(0, i); + StringBuilder arr = new StringBuilder("["); + while ((i = type.indexOf('[', i + 1)) != -1) { + arr.append('['); + } + arr.append(javaType2jvm(t)); + return arr.toString(); + } else { + return classNameToType(type); + } + + } + } + + @Nonnull + private List parseArgs(String methodArgs) { + final ArrayList args = new ArrayList<>(); + if ("".equals(methodArgs)) { + return args; + } + final String[] split = methodArgs.split(","); + for (String type : split) { + args.add(javaType2jvm(type)); + } + return args; + } + @Override - public final void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName) { + public final void processMethodMapping(String className, + int firstLineNumber, int lastLineNumber, + String methodReturnType, + String methodName, + String methodArguments, + String newClassName, + int newFirstLineNumber, int newLastLineNumber, + String newMethodName) { + final MethodMapping mapping = new MethodMapping(className, methodName, newClassName, newMethodName, methodArguments, methodReturnType); + methodSet.add(mapping); + } + + private static class MethodMapping { + private final String className; + private final String methodName; + + private final String newClassName; + private final String newMethodName; + private final String args; + private final String returnType; + + public MethodMapping(String className, String methodName, + String newClassName, String newMethodName, + String args, String returnType) { + this.className = className; + this.methodName = methodName; + this.newClassName = newClassName; + this.newMethodName = newMethodName; + this.args = args; + this.returnType = returnType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MethodMapping that = (MethodMapping) o; + + if (!className.equals(that.className)) return false; + if (!methodName.equals(that.methodName)) return false; + if (!newClassName.equals(that.newClassName)) return false; + if (!newMethodName.equals(that.newMethodName)) return false; + if (!args.equals(that.args)) return false; + return returnType.equals(that.returnType); + } + + @Override + public int hashCode() { + int result = className.hashCode(); + result = 31 * result + methodName.hashCode(); + result = 31 * result + newClassName.hashCode(); + result = 31 * result + newMethodName.hashCode(); + result = 31 * result + args.hashCode(); + result = 31 * result + returnType.hashCode(); + return result; + } } } \ No newline at end of file diff --git a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/RegexKeepConfig.java b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/RegexKeepConfig.java deleted file mode 100644 index dcca500..0000000 --- a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/RegexKeepConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.nmmedit.apkprotect.dex2c.filters; - -import org.jf.dexlib2.iface.ClassDef; -import org.jf.dexlib2.iface.Method; - -import java.io.File; - -/** - * class * extends android.app.Activity - * class * implements java.io.Serializable - */ -public class RegexKeepConfig implements ClassAndMethodFilter { - private ClassAndMethodFilter filter; - - public RegexKeepConfig(ClassAndMethodFilter filter, File ruleFile) { - this.filter = filter; - } - - @Override - public boolean acceptClass(ClassDef classDef) { - if (filter != null && !filter.acceptClass(classDef)) { - return false; - } - //todo - return true; - } - - @Override - public boolean acceptMethod(Method method) { - if (filter != null && !filter.acceptMethod(method)) { - return false; - } - //todo - return false; - } -} diff --git a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleConvertConfig.java b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleConvertConfig.java new file mode 100644 index 0000000..ab8c271 --- /dev/null +++ b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleConvertConfig.java @@ -0,0 +1,33 @@ +package com.nmmedit.apkprotect.dex2c.filters; + +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Method; + +public class SimpleConvertConfig implements ClassAndMethodFilter { + private final ClassAndMethodFilter filter; + private final SimpleRule simpleRule; + + public SimpleConvertConfig(ClassAndMethodFilter filter, SimpleRule simpleRule) { + this.filter = filter; + this.simpleRule = simpleRule; + } + + @Override + public boolean acceptClass(ClassDef classDef) { + if (filter != null && !filter.acceptClass(classDef)) { + return false; + } + return simpleRule != null && simpleRule.matchClass( + classDef.getType(), + classDef.getSuperclass(), + classDef.getInterfaces()); + } + + @Override + public boolean acceptMethod(Method method) { + if (filter != null && !filter.acceptMethod(method)) { + return false; + } + return simpleRule != null && simpleRule.matchMethod(method.getName()); + } +} diff --git a/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRule.java b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRule.java new file mode 100644 index 0000000..ea5c563 --- /dev/null +++ b/nmm-protect/apkprotect/src/main/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRule.java @@ -0,0 +1,230 @@ +package com.nmmedit.apkprotect.dex2c.filters; + +import com.google.common.collect.HashMultimap; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * class * extends android.app.Activity + * class * implements java.io.Serializable + * class my.package.AClass + * class my.package.* { *; } + * class * extends java.util.ArrayList { + * if*; + * } + * class A { + * } + * class B extends A { + * } + * class C extends B { + * } + * The rule 'class * extends A' only match B + */ +public class SimpleRule { + private final HashMultimap convertRules = HashMultimap.create(); + + + public SimpleRule() { + } + + public void parse(Reader ruleReader) throws IOException { + try (BufferedReader reader = new BufferedReader(ruleReader)) { + ClassRule classRule = null; + final ArrayList methodNameList = new ArrayList<>(); + boolean methodParsing = false; + int lineNumb = 0; + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if ("".equals(line)) {//empty line + lineNumb++; + continue; + } + if (line.startsWith("class")) { + final String[] split = line.split(" +"); + final int length = split.length; + if (length < 2) { + throw new RemoteException("Error rule " + lineNumb + ": " + line); + } + String className = split[1]; + String supperName = ""; + String interfaceName = ""; + if (length >= 4) { + if ("extends".equals(split[2])) {//class * extends A + supperName = split[3]; + } else if ("implements".equals(split[2])) {//class * implements I + interfaceName = split[3]; + } + } + classRule = new ClassRule(className, supperName, interfaceName); + int mstart; + if ((mstart = line.indexOf('{')) != -1) { // my.pkg.A { methodA;methodB;} + int mend; + if ((mend = line.indexOf('}')) != -1) { + final String[] methodNames = line.substring(mstart + 1, mend).trim().split(";"); + if (methodNames.length == 0) { + throw new RemoteException("Error rule " + lineNumb + ": " + line); + } + for (String name : methodNames) { + convertRules.put(classRule, new MethodRule(name)); + } + } else { + methodNameList.clear(); + methodParsing = true; + } + } else { + //any methods + convertRules.put(classRule, new MethodRule("*")); + } + } else if (methodParsing) { + // my.pkg.A { + // methodA; + // methodB; + // } + if (line.indexOf('}') != -1) { + if (methodNameList.isEmpty()) { + throw new RemoteException("Error rule " + lineNumb + ": " + line); + } + for (String methodName : methodNameList) { + if ("".equals(methodName)) { + continue; + } + convertRules.put(classRule, new MethodRule(methodName)); + } + methodParsing = false; + } else { + methodNameList.add(line.replace(";", "")); + } + } else { + throw new RemoteException("Error rule " + lineNumb + ": " + line); + } + + lineNumb++; + } + } + } + + private Set methodRules; + + public boolean matchClass(@Nonnull String classType, @Nullable String supperType, @Nonnull List ifacTypes) { + for (ClassRule rule : convertRules.keySet()) { + final String typeRegex = toRegex(classNameToType(rule.className)); + if (classType.matches(typeRegex)) {// match classType + if (!"".equals(rule.supperName)) {//supper name not empty + if (supperType != null) { + final String type = classNameToType(rule.supperName); + if (supperType.equals(type)) { + methodRules = convertRules.get(rule); + return true; + } + } + continue; + } + if (!"".equals(rule.interfaceName)) {//interface name not empty + for (String iface : ifacTypes) { + if (iface.equals(classNameToType(rule.interfaceName))) { + methodRules = convertRules.get(rule); + return true; + } + } + continue; + } + methodRules = convertRules.get(rule); + return true; + } + } + methodRules = null; + return false; + } + + public boolean matchMethod(String methodName) { + if (methodRules == null || methodName == null) { + return false; + } + for (MethodRule methodRule : methodRules) { + if (methodName.matches(toRegex(methodRule.methodName))) { + return true; + } + } + return false; + } + + private static String classNameToType(String className) { + return "L" + className.replace('.', '/') + ";"; + } + + @Nonnull + private static String toRegex(String s) { + final StringBuilder sb = new StringBuilder(s.length() + 3); + for (int i = 0; i < s.length(); i++) { + final char c = s.charAt(i); + switch (c) { + case '*': + sb.append('.'); + default: + sb.append(c); + } + } + return sb.toString(); + } + + private static class ClassRule { + @Nonnull + private final String className; + //supper class + @Nonnull + private final String supperName; + //interface + @Nonnull + private final String interfaceName; + + public ClassRule(@Nonnull String className) { + this(className, "", ""); + } + + public ClassRule(@Nonnull String className, @Nonnull String supperName, @Nonnull String interfaceName) { + this.className = className; + this.supperName = supperName; + this.interfaceName = interfaceName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ClassRule classRule = (ClassRule) o; + + if (!className.equals(classRule.className)) return false; + if (!supperName.equals(classRule.supperName)) return false; + return interfaceName.equals(classRule.interfaceName); + } + + @Override + public int hashCode() { + int result = className.hashCode(); + result = 31 * result + supperName.hashCode(); + result = 31 * result + interfaceName.hashCode(); + return result; + } + } + + private static class MethodRule { + @Nonnull + private final String methodName; + // args ? + // private final List args; + + public MethodRule(@Nonnull String methodName) { + this.methodName = methodName; + } + } +} diff --git a/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/deobfus/MappingReaderTest.java b/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/deobfus/MappingReaderTest.java index 1d1b996..08c6a0b 100644 --- a/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/deobfus/MappingReaderTest.java +++ b/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/deobfus/MappingReaderTest.java @@ -20,7 +20,7 @@ public void textParse() throws IOException { reader.parse(new MappingProcessor() { @Override public void processClassMapping(String className, String newClassName) { - System.out.println("className "+className+" "+newClassName); +// System.out.println("className "+className+" "+newClassName); } @Override @@ -30,7 +30,7 @@ public void processFieldMapping(String className, String fieldType, String field @Override public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newClassName, int newFirstLineNumber, int newLastLineNumber, String newMethodName) { - + System.out.println(methodArguments); } }); tempFile.delete(); diff --git a/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRuleTest.java b/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRuleTest.java new file mode 100644 index 0000000..f0a2981 --- /dev/null +++ b/nmm-protect/apkprotect/src/test/java/com/nmmedit/apkprotect/dex2c/filters/SimpleRuleTest.java @@ -0,0 +1,39 @@ +package com.nmmedit.apkprotect.dex2c.filters; + +import junit.framework.TestCase; +import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.dexbacked.DexBackedClassDef; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.dexbacked.DexBackedMethod; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.StringReader; + +public class SimpleRuleTest extends TestCase { + + public void testParse() throws IOException { + final SimpleRule ruleReader = new SimpleRule(); + final DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(Opcodes.getDefault(), + new BufferedInputStream(SimpleRuleTest.class.getResourceAsStream("/classes2.dex"))); + ruleReader.parse(new StringReader( + "class * extends android.app.Activity\n" + + "class * implements java.io.Serializable\n" + + "class *\n" + + "class * extends java.util.ArrayList {\n" + + "if*;\n" + + "}")); + for (DexBackedClassDef classDef : dexFile.getClasses()) { + if (ruleReader.matchClass(classDef.getType(), classDef.getSuperclass(), classDef.getInterfaces())) { + System.out.println(classDef); + for (DexBackedMethod method : classDef.getMethods()) { + if (ruleReader.matchMethod(method.getName())) { + System.out.println(classDef + " " + method); + } + } + + } + } + + } +} \ No newline at end of file diff --git a/nmm-protect/src/main/java/com/nmmedit/protect/Main.java b/nmm-protect/src/main/java/com/nmmedit/protect/Main.java index bbadc19..ba55fb5 100644 --- a/nmm-protect/src/main/java/com/nmmedit/protect/Main.java +++ b/nmm-protect/src/main/java/com/nmmedit/protect/Main.java @@ -2,49 +2,43 @@ import com.nmmedit.apkprotect.ApkFolders; import com.nmmedit.apkprotect.ApkProtect; -import com.nmmedit.apkprotect.dex2c.converter.instructionrewriter.RandomInstructionRewriter; -import com.nmmedit.apkprotect.dex2c.filters.BasicKeepConfig; -import com.nmmedit.apkprotect.dex2c.filters.ClassAndMethodFilter; -import com.nmmedit.apkprotect.dex2c.filters.ProguardMappingConfig; import com.nmmedit.apkprotect.deobfus.MappingReader; +import com.nmmedit.apkprotect.dex2c.converter.instructionrewriter.RandomInstructionRewriter; +import com.nmmedit.apkprotect.dex2c.filters.*; import com.nmmedit.apkprotect.sign.ApkVerifyCodeGenerator; -import org.jf.dexlib2.iface.ClassDef; -import org.jf.dexlib2.iface.Method; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.nio.charset.StandardCharsets; public class Main { public static void main(String[] args) throws IOException { if (args.length < 1) { System.err.println("No Input apk."); - System.err.println(" [ [mapping.txt]]"); + System.err.println(" [ [mapping.txt]]"); System.exit(-1); } final File apk = new File(args[0]); final File outDir = new File(apk.getParentFile(), "build"); - ClassAndMethodFilter filterConfig = new BasicKeepConfig(); - if (args.length == 2) {//根据proguard的mapping.txt得到混淆前的类名和方法名,然后判断是否要保留 - final MappingReader mappingReader = new MappingReader(new File(args[1])); - filterConfig = new ProguardMappingConfig(new BasicKeepConfig(), mappingReader) { - @Override - protected boolean keepClass(ClassDef classDef) { - final String className = getOriginClassName(classDef.getType()); - //todo 自定义规则 - return false; - } - - - @Override - protected boolean keepMethod(Method method) { - return false; - } - }; + final SimpleRule simpleRule = new SimpleRule(); + if (args.length > 1) { + simpleRule.parse(new InputStreamReader(new FileInputStream(args[1]), StandardCharsets.UTF_8)); + } else { + //all classes + simpleRule.parse(new StringReader("class *")); } + if (args.length > 2) { + final MappingReader mappingReader = new MappingReader(new File(args[2])); + filterConfig = new ProguardMappingConfig(filterConfig, mappingReader, simpleRule); + } else { + filterConfig = new SimpleConvertConfig(new BasicKeepConfig(), simpleRule); + } + + + final ApkFolders apkFolders = new ApkFolders(apk, outDir); //apk签名验证相关,不使用