├── README.md ├── libs ├── asm-all-5.2.jar └── commons-lang3-3.5.jar ├── obfuseJarString.jar ├── obfuseSmaliString.jar ├── obfuseStringGradle.jar ├── runJarObfuse.bat ├── runSmaliObfuse.bat └── src ├── META-INF └── MANIFEST.MF ├── ObfuseJarString.java ├── ObfuseJarStringGradle.java ├── ObfuseSmaliString.java ├── Test.java ├── com └── OooOO0OO.java ├── qtfreet00.java ├── qtfreet00.smali ├── utils └── FileUtils.java └── visitor ├── ClassStringField.java ├── ClassVisitorFactory.java ├── StringFieldClassVisitor.java ├── TextUtils.java └── WhiteLists.java /README.md: -------------------------------------------------------------------------------- 1 | # obfuseSmaliText 2 | smali字符串混淆 3 | 4 | 相关文章 http://mp.weixin.qq.com/s/SRv1Oar87w1iKuDXS4oaew 5 | 6 | 注意:配置中设置了仅会混淆包名目录下的文件 7 | 8 | ##### 2017-3-2 9 | 目前使用异或+十六进制的方式对字符串进行混淆,支持中文字符,测试未发现问题,有问题欢迎反馈 10 | 11 | ##### 2017-3-14 12 | 参考StringFog,增加对jar包的字符串混淆(使用asm),支持自定义key 13 | 14 | ##### 2017-3-21 15 | 修复了因ide字符串替换导致的错误,编译了两个可执行jar包,并写了两个bat文件,双击即可执行 16 | 17 | ##### 2017-5-5 18 | 更新ObfuseJarString方法,替换其中class字节码为java7,防止dx无法正确编译dex 19 | 20 | ##### 2017-5-10 21 | 更新jar包混淆string方式,改为每个字符串解密对应一个解密key,字符串以byte数组呈现(未更新jar包,需自行编译),一定要注意编码问题,不然会导致乱码 22 | 23 | ##### 2017-07-17 24 | 修改smali混淆的目录为手工输入,部分apk包名对应的目录下可能会没有文件,先暂时修改为这样,后期修改为按照配置文件对目录进行混淆 25 | 26 | ##### 2017-07-19 27 | 添加支持gradle的字符串混淆,在打包时进行自动混淆 28 | 29 | * 将obfuseStringGradle.jar文件放在工程根目录,在项目build.gradle下添加如下 30 | ``` 31 | android.libraryVariants.all{ variant -> //module工程则为libraryVariants,主项目则是applicationVariants 32 | variant.javaCompile.doLast{ 33 | println ("start classes obfuscation "+"${variant.javaCompile.destinationDir}") 34 | javaexec { 35 | main = "-jar"; 36 | args = [ 37 | "../obfuseStringGradle.jar", 38 | project.name, 39 | variant.javaCompile.destinationDir 40 | ] 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | 47 | 48 | ##### 使用方法 49 | * 先使用apktool.jar将apk进行反编译 50 | * java -Dfile.encoding=utf-8 -jar 执行jar 51 | * 输入当前已经反编译apk的路径(复制粘贴即可) 52 | * 等待任务完成,重新打包回去即可 53 | 54 | 注:`jar包执行时需要指定运行编码,不然会导致混淆后乱码 ,使用命令如:java -Dfile.encoding=utf-8 -jar obfuseJarString.jar` 55 | 56 | ##### 混淆版酷安网 57 | [下载](https://qtfreet.cn/com.coolapk.market_7_Mod.apk) 58 | 59 | #### smali混淆效果图 60 | ![](http://p1.bpimg.com/567571/90927a8fd19786b1.png) 61 | 62 | #### jar包混淆效果图 63 | ![](http://i4.buimg.com/588926/d9b230241ef448ea.png) 64 | -------------------------------------------------------------------------------- /libs/asm-all-5.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysrc/obfuseSmaliText/dc76c0eca0daedbfc93e95c7f422a2f2822ea587/libs/asm-all-5.2.jar -------------------------------------------------------------------------------- /libs/commons-lang3-3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysrc/obfuseSmaliText/dc76c0eca0daedbfc93e95c7f422a2f2822ea587/libs/commons-lang3-3.5.jar -------------------------------------------------------------------------------- /obfuseJarString.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysrc/obfuseSmaliText/dc76c0eca0daedbfc93e95c7f422a2f2822ea587/obfuseJarString.jar -------------------------------------------------------------------------------- /obfuseSmaliString.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysrc/obfuseSmaliText/dc76c0eca0daedbfc93e95c7f422a2f2822ea587/obfuseSmaliString.jar -------------------------------------------------------------------------------- /obfuseStringGradle.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysrc/obfuseSmaliText/dc76c0eca0daedbfc93e95c7f422a2f2822ea587/obfuseStringGradle.jar -------------------------------------------------------------------------------- /runJarObfuse.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -Dfile.encoding=utf-8 -jar obfuseJarString.jar 3 | pause -------------------------------------------------------------------------------- /runSmaliObfuse.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -Dfile.encoding=utf-8 -jar obfuseSmaliString.jar 3 | pause -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: ObfuseJarStringGradle 3 | 4 | -------------------------------------------------------------------------------- /src/ObfuseJarString.java: -------------------------------------------------------------------------------- 1 | import com.OooOO0OO; 2 | import org.objectweb.asm.ClassReader; 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.ClassWriter; 5 | import visitor.ClassVisitorFactory; 6 | 7 | import java.io.*; 8 | import java.nio.charset.Charset; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Scanner; 12 | import java.util.zip.ZipEntry; 13 | import java.util.zip.ZipInputStream; 14 | import java.util.zip.ZipOutputStream; 15 | 16 | /** 17 | * Created by qtfreet on 2017/3/14. 18 | */ 19 | public class ObfuseJarString { 20 | private static final String encryptFile = OooOO0OO.class.getName().replace(".", "/") + ".class"; 21 | 22 | public static void main(String[] args) throws IOException { 23 | byte b[] = readClass(); 24 | System.out.println("请输入jar包路径:"); 25 | Scanner scanner = new Scanner(System.in); 26 | String path = scanner.next(); 27 | if (!path.endsWith(".jar")) { 28 | System.out.println("请输入正确的jar包路径"); 29 | System.exit(0); 30 | } 31 | int index = path.lastIndexOf(".jar"); 32 | File jarIn = new File(path); 33 | File jarOut = new File(path.substring(0, index) + "obfused.jar"); 34 | try { 35 | processJar(jarIn, jarOut, Charset.forName("UTF-8"), Charset.forName("UTF-8"), b); 36 | } catch (IllegalArgumentException e) { 37 | if ("MALFORMED".equals(e.getMessage())) { 38 | processJar(jarIn, jarOut, Charset.forName("GBK"), Charset.forName("UTF-8"), b); 39 | } else { 40 | throw e; 41 | } 42 | } 43 | System.out.println("混淆完成"); 44 | } 45 | 46 | private static byte[] readClass() { 47 | InputStream in = null; 48 | try { 49 | in = OooOO0OO.class.getClassLoader().getResourceAsStream(encryptFile); 50 | int len = in.available(); 51 | byte[] b = new byte[len]; 52 | in.read(b); 53 | in.close(); 54 | return b; 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | } 58 | return null; 59 | } 60 | 61 | 62 | private static void processJar(File jarIn, File jarOut, Charset charsetIn, Charset charsetOut, byte[] out) throws IOException { 63 | ZipInputStream zis = null; 64 | ZipOutputStream zos = null; 65 | try { 66 | 67 | zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(jarIn)), charsetIn); 68 | zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(jarOut)), charsetOut); 69 | ZipEntry entryIn; 70 | Map processedEntryNamesMap = new HashMap<>(); 71 | while ((entryIn = zis.getNextEntry()) != null) { 72 | final String entryName = entryIn.getName(); 73 | if (!processedEntryNamesMap.containsKey(entryName)) { 74 | ZipEntry entryOut = new ZipEntry(entryIn); 75 | entryOut.setCompressedSize(-1); 76 | zos.putNextEntry(entryOut); 77 | if (!entryIn.isDirectory()) { 78 | if (entryName.endsWith(".class")) { 79 | processClass(zis, zos); 80 | } else { 81 | copy(zis, zos); 82 | } 83 | } 84 | zos.closeEntry(); 85 | processedEntryNamesMap.put(entryName, 1); 86 | } 87 | } 88 | ZipEntry eninject = new ZipEntry(encryptFile); 89 | zos.putNextEntry(eninject); 90 | zos.write(out); 91 | zos.closeEntry(); 92 | 93 | } finally { 94 | closeQuietly(zos); 95 | closeQuietly(zis); 96 | } 97 | } 98 | 99 | 100 | private static void processClass(InputStream classIn, OutputStream classOut) throws IOException { 101 | ClassReader cr = new ClassReader(classIn); 102 | ClassWriter cw = new ClassWriter(1); 103 | ClassVisitor aia = ClassVisitorFactory.create(cr.getClassName(), cw); 104 | // ClassVisitor aia = new TestClassVisitor("", cw); 105 | cr.accept(aia, 0); 106 | 107 | classOut.write(cw.toByteArray()); 108 | classOut.flush(); 109 | } 110 | 111 | 112 | private static void closeQuietly(Closeable target) { 113 | if (target != null) { 114 | try { 115 | target.close(); 116 | } catch (Exception e) { 117 | // Ignored. 118 | } 119 | } 120 | } 121 | 122 | private static int copy(InputStream in, OutputStream out) throws IOException { 123 | int total = 0; 124 | byte[] buffer = new byte[8192]; 125 | int c; 126 | while ((c = in.read(buffer)) != -1) { 127 | total += c; 128 | out.write(buffer, 0, c); 129 | } 130 | return total; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/ObfuseJarStringGradle.java: -------------------------------------------------------------------------------- 1 | import com.OooOO0OO; 2 | import org.objectweb.asm.ClassReader; 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.ClassWriter; 5 | import visitor.ClassVisitorFactory; 6 | 7 | import java.io.*; 8 | import java.nio.charset.Charset; 9 | import java.util.*; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipInputStream; 12 | import java.util.zip.ZipOutputStream; 13 | 14 | /** 15 | * Created by qtfreet on 2017/3/14. 16 | */ 17 | public class ObfuseJarStringGradle { 18 | private static final String encryptFile = OooOO0OO.class.getName().replace(".", "/") + ".class"; 19 | private static final String separator = File.separator; 20 | private final static String PARAMS_ORDER = "params [module] [variant]"; 21 | private static List filelist = new ArrayList(); 22 | 23 | public static void main(String[] args) throws IOException { 24 | if (args.length < 2) { 25 | System.out.println("params [module] [variant]"); 26 | System.exit(0); 27 | return; 28 | } 29 | byte b[] = readClass(); 30 | String module = args[0]; 31 | String variant = args[1]; 32 | if (variant == null) { 33 | System.out.println("variant not detected; try " + PARAMS_ORDER); 34 | System.exit(0); 35 | } 36 | System.err.println(variant); 37 | if (filelist != null && filelist.size() > 0) { 38 | filelist.clear(); 39 | } 40 | getFiles(variant); 41 | if (filelist.size() == 0) { 42 | System.out.println("no found any class files"); 43 | System.exit(0); 44 | } 45 | for (String path : filelist) { 46 | processFile(path); 47 | } 48 | String encFilePath = variant + separator + encryptFile; 49 | write(encFilePath, b); 50 | System.err.println("task completed"); 51 | } 52 | 53 | 54 | private static byte[] readClass() { 55 | InputStream in = null; 56 | try { 57 | in = OooOO0OO.class.getClassLoader().getResourceAsStream(encryptFile); 58 | int len = in.available(); 59 | byte[] b = new byte[len]; 60 | in.read(b); 61 | in.close(); 62 | return b; 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | return null; 67 | } 68 | 69 | 70 | private static void processFile(String path) throws IOException { 71 | FileInputStream fis = new FileInputStream(path); 72 | byte[] content = processClass(fis); 73 | closeQuietly(fis); 74 | if (content == null) { 75 | return; 76 | } 77 | File outFile = new File(path + ".tmp"); 78 | FileOutputStream fos = new FileOutputStream(outFile); 79 | fos.write(content); 80 | fos.flush(); 81 | closeQuietly(fos); 82 | File file = new File(path); 83 | if (file.exists()) { 84 | file.delete(); 85 | outFile.renameTo(new File(path)); 86 | } 87 | } 88 | 89 | private static void processJar(File jarIn, File jarOut, Charset charsetIn, Charset charsetOut, byte[] out) throws IOException { 90 | ZipInputStream zis = null; 91 | ZipOutputStream zos = null; 92 | try { 93 | zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(jarIn)), charsetIn); 94 | zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(jarOut)), charsetOut); 95 | ZipEntry entryIn; 96 | Map processedEntryNamesMap = new HashMap<>(); 97 | boolean flag = false; 98 | while ((entryIn = zis.getNextEntry()) != null) { 99 | final String entryName = entryIn.getName(); 100 | if (!processedEntryNamesMap.containsKey(entryName)) { 101 | ZipEntry entryOut = new ZipEntry(entryIn); 102 | entryOut.setCompressedSize(-1); 103 | zos.putNextEntry(entryOut); 104 | if (!entryIn.isDirectory()) { 105 | if (entryName.endsWith(".class")) { 106 | if (entryName.equals(encryptFile)) { 107 | flag = true; 108 | } 109 | processClass(zis, zos); 110 | } else { 111 | copy(zis, zos); 112 | } 113 | } 114 | zos.closeEntry(); 115 | processedEntryNamesMap.put(entryName, 1); 116 | } 117 | } 118 | if (!flag) { 119 | ZipEntry eninject = new ZipEntry(encryptFile); 120 | zos.putNextEntry(eninject); 121 | zos.write(out); 122 | zos.closeEntry(); 123 | } 124 | } finally { 125 | closeQuietly(zos); 126 | closeQuietly(zis); 127 | } 128 | } 129 | 130 | private static void processClass(InputStream classIn, OutputStream classOut) throws IOException { 131 | ClassReader cr = new ClassReader(classIn); 132 | ClassWriter cw = new ClassWriter(1); 133 | ClassVisitor aia = ClassVisitorFactory.create(cr.getClassName(), cw); 134 | // ClassVisitor aia = new TestClassVisitor("", cw); 135 | cr.accept(aia, 0); 136 | 137 | classOut.write(cw.toByteArray()); 138 | classOut.flush(); 139 | } 140 | 141 | 142 | private static byte[] processClass(InputStream classIn) throws IOException { 143 | if (classIn == null || classIn.available() == 0) { 144 | return null; 145 | } 146 | ClassReader cr = new ClassReader(classIn); 147 | ClassWriter cw = new ClassWriter(1); 148 | ClassVisitor aia = ClassVisitorFactory.create(cr.getClassName(), cw); 149 | // ClassVisitor aia = new TestClassVisitor("", cw); 150 | cr.accept(aia, 0); 151 | return cw.toByteArray(); 152 | } 153 | 154 | 155 | private static void closeQuietly(Closeable target) { 156 | if (target != null) { 157 | try { 158 | target.close(); 159 | } catch (Exception e) { 160 | // Ignored. 161 | } 162 | } 163 | } 164 | 165 | private static void write(String path, byte[] out) throws IOException { 166 | FileOutputStream fos = new FileOutputStream(path); 167 | fos.write(out); 168 | fos.flush(); 169 | fos.close(); 170 | } 171 | 172 | /** 173 | * 遍历所有文件,添加到list中 174 | * 175 | * @param filePath 文件路径 176 | */ 177 | private static void getFiles(String filePath) { 178 | File[] files = new File(filePath).listFiles(); 179 | if (files == null) { 180 | return; 181 | } 182 | for (File file : files) { 183 | if (file.isDirectory()) { 184 | getFiles(file.getPath()); 185 | } else { 186 | if (file.getName().endsWith(".class")) { 187 | filelist.add(file.getPath()); 188 | } 189 | } 190 | } 191 | } 192 | 193 | private static int copy(InputStream in, OutputStream out) throws IOException { 194 | int total = 0; 195 | byte[] buffer = new byte[8192]; 196 | int c; 197 | while ((c = in.read(buffer)) != -1) { 198 | total += c; 199 | out.write(buffer, 0, c); 200 | } 201 | return total; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/ObfuseSmaliString.java: -------------------------------------------------------------------------------- 1 | import org.apache.commons.lang3.StringEscapeUtils; 2 | 3 | import java.io.*; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Scanner; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Created by qtfreet on 2017/2/24. 12 | */ 13 | public class ObfuseSmaliString { 14 | 15 | private static List filelist = new ArrayList(); 16 | 17 | public static void main(String[] args) throws UnsupportedEncodingException { 18 | System.out.println("please enter the apk decompiled path:\n"); 19 | Scanner scanner = new Scanner(System.in); 20 | String path = scanner.next(); 21 | String packagePath; 22 | /* 23 | * please enter the apk decompiled path: 24 | C:\Users\qtfreet00\Desktop\signed 25 | please enter the package path which u want to string obfuscation (such as com.qtfreet.example): 26 | com.manyi.mobile.activity 27 | mission completed 28 | please repack your apk file*/ 29 | System.out.println("please enter the package path which u want to string obfuscation (such as com.qtfreet.example): \n"); 30 | packagePath = scanner.next().replace(".", "\\"); 31 | String smaliPath = path + "\\smali\\" + packagePath; 32 | getFiles(smaliPath); 33 | if (filelist == null || filelist.size() == 0) { 34 | System.out.println("smali files no found"); 35 | return; 36 | } 37 | for (String signalFile : filelist) { 38 | if (!signalFile.endsWith(".smali")) { 39 | continue; 40 | } 41 | FileTofindString(signalFile); 42 | } 43 | inject(path + "\\smali\\com"); 44 | System.out.println("mission completed"); 45 | System.out.println("please repack your apk file"); 46 | } 47 | 48 | private static void inject(String path) { 49 | try { 50 | File file = new File(path); 51 | if (!file.exists() || !file.isDirectory()) { 52 | file.mkdir(); 53 | } 54 | InputStream resourceAsStream = ObfuseSmaliString.class.getResourceAsStream("/qtfreet00.smali"); 55 | InputStreamReader read = new InputStreamReader(resourceAsStream); 56 | BufferedReader br = new BufferedReader(read); 57 | String str = ""; 58 | StringBuilder sb = new StringBuilder(); 59 | while ((str = br.readLine()) != null) { 60 | sb.append(str).append("\n"); 61 | } 62 | FileOutputStream fos = new FileOutputStream(new File(path + "\\qtfreet00.smali")); 63 | fos.write(sb.toString().getBytes("UTF-8")); 64 | fos.flush(); 65 | fos.close(); 66 | } catch (Exception e) { 67 | 68 | } 69 | 70 | } 71 | 72 | private static String getPackagePath(String path) { 73 | try { 74 | String AndroidManifestPath = path + "\\AndroidManifest.xml"; 75 | File file = new File(AndroidManifestPath); 76 | if (!file.exists()) { 77 | System.out.println("未在目录下发现AndroidManifest.xml文件"); 78 | System.exit(0); 79 | return ""; 80 | } 81 | String packgaName = ""; 82 | InputStreamReader read = new InputStreamReader(new FileInputStream(file), "UTF-8"); 83 | BufferedReader br = new BufferedReader(read); 84 | String str = ""; 85 | while ((str = br.readLine()) != null) { 86 | Matcher m = Pattern.compile("package=\"([\\w\\.]+)\"").matcher(str); 87 | if (m.find()) { 88 | packgaName = m.group(1); 89 | break; 90 | } 91 | 92 | } 93 | return packgaName.replace(".", "\\"); 94 | } catch (Exception ignored) { 95 | 96 | } 97 | return ""; 98 | } 99 | 100 | 101 | /** 102 | * 匹配文件 103 | * 104 | * @param path 每个文件对应路径 105 | */ 106 | private static void FileTofindString(String path) { 107 | // boolean hashConstructor = false; 108 | // HashMap map = new HashMap<>(); 109 | 110 | StringBuilder sb = new StringBuilder(); 111 | try { 112 | InputStreamReader read = new InputStreamReader(new FileInputStream(path), "UTF-8"); 113 | BufferedReader br = new BufferedReader(read); 114 | String str = ""; 115 | 116 | while ((str = br.readLine()) != null) { 117 | // Matcher removeFinalString = Pattern.compile("(.field (public|private) static final (\\w+):Ljava/lang/String;) = \"(\\w*)\"").matcher(str); 118 | // if (removeFinalString.find()) { 119 | // // group(1) 为.field public static final BUILD_TYPE:Ljava/lang/String; 120 | // //group(3) 为变量名 121 | // //group(4) 为变量值 122 | // str = removeFinalString.group(1); 123 | // map.put() 124 | // continue; 125 | // } 126 | //利用正则去匹配方法中定义的字符串 127 | Matcher m = Pattern.compile("const-string ([vp]\\d{1,2}), \"(.*)\"").matcher(str); 128 | if (m.find()) { 129 | String tmp = m.group(2); 130 | if (tmp.equals("")) { 131 | sb.append(str).append("\n"); 132 | continue; 133 | } 134 | //字符串转义,过滤掉\(如\",不转义时获取到的为\",但理想效果应为")以及将smali中的unicode转为中文字符 135 | tmp = StringEscapeUtils.unescapeJava(tmp); 136 | String register = m.group(1); 137 | //register代表寄存器 138 | String enc = qtfreet00.encode(tmp); 139 | //混淆字符串 140 | String sign = " const-string " + register + ", " + "\"" + enc + "\""; 141 | String dec = ""; 142 | if (Integer.parseInt(register.substring(1)) > 15 && register.startsWith("v")) { 143 | //此处考虑寄存器个数,如果v寄存器大于15时,应使用range方式传参 144 | dec = " invoke-static/range {" + register + " .. " + register + "}, Lcom/qtfreet00;->decode(Ljava/lang/String;)Ljava/lang/String;"; 145 | //添加解密方法 146 | } else if (register.startsWith("v") || (register.startsWith("p") && Integer.parseInt(register.substring(1)) < 10)) { 147 | //此处p在10以上(不清楚具体),也会出现一些问题,由于没太接触过较大p寄存器,这里直接忽略掉了10以上的,实际应用中也很少会出现 148 | //p在方法中一般代表入参,静态方法中从p0开始,非静态方法从p1开始,p0带表this 149 | dec = " invoke-static {" + register + "}, Lcom/qtfreet00;->decode(Ljava/lang/String;)Ljava/lang/String;"; 150 | } else { 151 | sb.append(str).append("\n"); 152 | continue; 153 | } 154 | String mov = " move-result-object " + register; 155 | sb.append(sign).append("\n\n"); 156 | sb.append(dec).append("\n\n"); 157 | sb.append(mov).append("\n"); 158 | } else { 159 | sb.append(str).append("\n"); 160 | } 161 | } 162 | br.close(); 163 | read.close(); 164 | //覆盖掉源文件 165 | FileOutputStream fos = new FileOutputStream(new File(path)); 166 | fos.write(sb.toString().getBytes("UTF-8")); 167 | fos.flush(); 168 | fos.close(); 169 | 170 | } catch (Exception ignored) { 171 | } 172 | } 173 | 174 | /** 175 | * 遍历所有文件,添加到list中 176 | * 177 | * @param filePath 文件路径 178 | */ 179 | private static void getFiles(String filePath) { 180 | File[] files = new File(filePath).listFiles(); 181 | if (files == null) { 182 | return; 183 | } 184 | for (File file : files) { 185 | if (file.isDirectory()) { 186 | getFiles(file.getPath()); 187 | } else { 188 | filelist.add(file.getPath()); 189 | } 190 | } 191 | } 192 | } 193 | 194 | 195 | -------------------------------------------------------------------------------- /src/Test.java: -------------------------------------------------------------------------------- 1 | import javax.crypto.KeyGenerator; 2 | import java.io.ByteArrayOutputStream; 3 | import java.lang.reflect.Method; 4 | import java.security.SecureRandom; 5 | import java.util.Arrays; 6 | 7 | /** 8 | * Created by qtfreet00 on 2017/3/14. 9 | */ 10 | public class Test { 11 | 12 | public static void main(String[] args) throws Exception { 13 | // OooOO0OO.createDecodeFunction(); 14 | // String s = OooOO0OO.OooOOoo0oo(new byte[]{(byte) 88, (byte) 89, (byte) 65, (byte) 10, (byte) 20, (byte) 83, (byte) 27, (byte) 84, (byte) 92, (byte) 1}, "685cb6"); 15 | // System.out.println(s); 16 | // System.out.println(UUID.randomUUID().toString().toUpperCase()); 17 | //System.out.println(THIS_IS_DEMO_VERSION_NOT_FOR_COMMERCIAL_USE("^RaPzYt\u0017_XpVarEzVq[vD3Xu\u0017w^uQvEvYg\u0017`^iR,\u0016,\u0017UE|Z3SzQuRaR}C3ZvC{XwD3Xa\u0017d_rC,\u0016,")); 18 | // StackTraceElement stackTraceElement = new CloneNotSupportedException().getStackTrace()[1]; 19 | //String string = new StringBuffer(stackTraceElement.getClassName()).append(stackTraceElement.getMethodName()).toString(); 20 | // System.out.println(stackTraceElement.getClassName()+stackTraceElement.getMethodName()); 21 | // test(); 22 | // System.out.println(decode2("}f\u001d\u001a\u0014s\u0000\f\u0004a\u0017\u001b\f\u0002\u001f\u0000\u0004= \rt\u0006\u0015\u000e")); 23 | // System.out.println(toString("\u0001'9>*+',94<'t#7;-<){:2,", 104)); 24 | // System.out.println(insert(83, "\u0017=&&;9 z8303>.%b/-+#g ,&;")); 25 | // FileInputStream fis = new FileInputStream("C:\\Users\\qtfreet00\\Desktop\\jadx\\jadx-gui\\src\\main\\resources\\i18n\\Messages_zh_CN2.properties"); 26 | // BufferedReader br = new BufferedReader(new InputStreamReader(fis)); 27 | // StringBuilder sb = new StringBuilder(); 28 | // String line; 29 | // while ((line = br.readLine()) != null) { 30 | // sb.append(StringEscapeUtils.escapeJava(line)).append("\n"); 31 | // } 32 | // FileOutputStream fos = new FileOutputStream("C:\\Users\\qtfreet00\\Desktop\\jadx\\jadx-gui\\src\\main\\resources\\i18n\\Messages_zh_CN.properties"); 33 | // fos.write(sb.toString().getBytes()); 34 | KeyGenerator v4 = KeyGenerator.getInstance("AES"); 35 | v4.init(128, new SecureRandom("6666".getBytes())); 36 | System.out.println(Arrays.toString(v4.generateKey().getEncoded())); 37 | } 38 | 39 | private static String z(char[] var0) { 40 | int var10000 = var0.length; 41 | int var1 = 0; 42 | char[] var10001 = var0; 43 | char[] var10002; 44 | int var10003; 45 | if (var10000 <= 1) { 46 | var10002 = var0; 47 | var10003 = var1; 48 | } else { 49 | var10001 = var0; 50 | if (var10000 <= var1) { 51 | return (new String(var0)).intern(); 52 | } 53 | 54 | var10002 = var0; 55 | var10003 = var1; 56 | } 57 | 58 | while (true) { 59 | char var10004 = var10002[var10003]; 60 | byte var10005; 61 | switch (var1 % 5) { 62 | case 0: 63 | var10005 = 55; 64 | break; 65 | case 1: 66 | var10005 = 103; 67 | break; 68 | case 2: 69 | var10005 = 95; 70 | break; 71 | case 3: 72 | var10005 = 115; 73 | break; 74 | default: 75 | var10005 = 40; 76 | } 77 | 78 | var10002[var10003] = (char) (var10004 ^ var10005); 79 | ++var1; 80 | if (var10000 == 0) { 81 | var10003 = var10000; 82 | var10002 = var10001; 83 | } else { 84 | if (var10000 <= var1) { 85 | return (new String(var10001)).intern(); 86 | } 87 | 88 | var10002 = var10001; 89 | var10003 = var1; 90 | } 91 | } 92 | } 93 | 94 | private static char[] z(String var0) { 95 | char[] var10000 = var0.toCharArray(); 96 | char[] var10001 = var10000; 97 | 98 | while (true) { 99 | int var10002 = var10001.length; 100 | var10001 = var10000; 101 | int var1 = var10002; 102 | if (var10002 >= 2) { 103 | break; 104 | } 105 | 106 | char[] var4 = var10001; 107 | int var3 = var1; 108 | var10000 = var4; 109 | char[] var10003 = var4; 110 | var10002 = var3; 111 | var10001 = var10003; 112 | if (var10002 != 0) { 113 | var10001 = var10000; 114 | boolean var2 = false; 115 | var10003[0] = (char) (var10003[0] ^ 40); 116 | break; 117 | } 118 | } 119 | 120 | return var10001; 121 | } 122 | 123 | 124 | public static String toString(String string, int n) { 125 | try { 126 | throw new Exception("findhook"); 127 | } catch (Exception e) { 128 | for (StackTraceElement stackTraceElement : e.getStackTrace()) { 129 | System.out.println(stackTraceElement.getClassName() + " " + stackTraceElement.getMethodName() + " " + stackTraceElement.getLineNumber()); 130 | } 131 | } 132 | int n2 = 2 + 2; 133 | char[] arrc = string.toCharArray(); 134 | int n3 = arrc.length; 135 | char[] arrc2 = arrc; 136 | int n4 = 0; 137 | int n5 = (n2 << 1 + n2) + -1 ^ 32; 138 | do { 139 | char[] arrc3 = arrc2; 140 | if (n4 == n3) { 141 | String string2 = String.valueOf(arrc3, 0, n3).intern(); 142 | return string2; 143 | } 144 | int n6 = n4++; 145 | int n7 = arrc3[n6] ^ n & n5; 146 | ++n; 147 | arrc3[n6] = (char) n7; 148 | } while (true); 149 | } 150 | 151 | public static String insert(int n, String string) { 152 | String string2; 153 | try { 154 | char[] arrc; 155 | int n2 = 2 + 2; 156 | char[] arrc2 = string.toCharArray(); 157 | int n3 = arrc2.length; 158 | char[] arrc3 = arrc2; 159 | int n4 = 0; 160 | int n5 = (n2 << 1 + n2) - 1 ^ 32; 161 | do { 162 | arrc = arrc3; 163 | if (n4 == n3) break; 164 | int n6 = n4++; 165 | int n7 = arrc[n6] ^ n & n5; 166 | ++n; 167 | arrc[n6] = (char) n7; 168 | } while (true); 169 | string2 = String.valueOf(arrc, 0, n3).intern(); 170 | } catch (Exception g2) { 171 | string2 = null; 172 | } 173 | String string3 = string2; 174 | return string3; 175 | } 176 | 177 | public static /* synthetic */ String decode2(String iiIiIIIIiI2) { 178 | int n; 179 | String string = "com.allatori.iIIIIIIiIITHIS_IS_DEMO_VERSION_NOT_FOR_COMMERCIAL_USE"; 180 | int n2 = iiIiIIIIiI2.length(); 181 | int n3 = n2 - 1; 182 | char[] arrc = new char[n2]; 183 | int n4 = (2 ^ 5) << 3 ^ 2; 184 | int n5 = 4 << 3 ^ (2 ^ 5); 185 | int n6 = n = string.length() - 1; 186 | String string2 = string; 187 | while (n3 >= 0) { 188 | int n7 = n3--; 189 | arrc[n7] = (char) (n5 ^ (iiIiIIIIiI2.charAt(n7) ^ string2.charAt(n))); 190 | if (n3 < 0) break; 191 | 192 | if (--n < 0) { 193 | n = n6; 194 | } 195 | 196 | } 197 | return new String(arrc); 198 | } 199 | 200 | 201 | private static void test() throws Exception { 202 | Class clazz = Class.forName("com.allatori.IiIIiiIIii"); 203 | Method rfc = clazz.getDeclaredMethod("THIS_IS_DEMO_VERSION_NOT_FOR_COMMERCIAL_USE", String.class); 204 | rfc.setAccessible(true); 205 | String res = (String) rfc.invoke(null, "#U,\u001d\b\u0013a\u0019\u0017\u001b\u0007\u0010\u000b\u0015f\u0001\u0015u\t\u0004\u0019/'\u0014\u0014\u0000t\u001b\r\t\u0003b\u0010\u001d\u0004\f\u0017\f\u0013\u001a\u001c\rc"); 206 | System.out.println(res); 207 | } 208 | 209 | public static String OooOOoo0oo(byte[] str, String key) { 210 | return ""; 211 | } 212 | 213 | public static final String DEFAULT_KEY = "qtfreet"; 214 | private static final String hexString = "0123456789ABCDEF"; 215 | 216 | public static String OooOOoo0oo(String str) { 217 | int i; 218 | ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length() / 2); 219 | for (i = 0; i < str.length(); i += 2) { 220 | baos.write((hexString.indexOf(str.charAt(i)) << 4) | hexString.indexOf(str.charAt(i + 1))); 221 | } 222 | byte[] b = baos.toByteArray(); 223 | int len = b.length; 224 | int keyLen = DEFAULT_KEY.length(); 225 | for (i = 0; i < len; i++) { 226 | b[i] = (byte) (b[i] ^ DEFAULT_KEY.charAt(i % keyLen)); 227 | } 228 | return new String(b); 229 | } 230 | 231 | 232 | public static /* synthetic */ String THIS_IS_DEMO_VERSION_NOT_FOR_COMMERCIAL_USE(String iiIiIIIIiI2) { 233 | int n = iiIiIIIIiI2.length(); 234 | int n2 = n - 1; 235 | char[] arrc = new char[n]; 236 | int n3 = (3 ^ 5) << 3 ^ (2 ^ 5); 237 | int n4 = n2; 238 | int n5 = 2 << 3 ^ 3; 239 | while (n4 >= 0) { 240 | int n6 = n2--; 241 | arrc[n6] = (char) (iiIiIIIIiI2.charAt(n6) ^ n5); 242 | if (n2 < 0) break; 243 | int n7 = n2--; 244 | arrc[n7] = (char) (iiIiIIIIiI2.charAt(n7) ^ n3); 245 | n4 = n2; 246 | } 247 | return new String(arrc); 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/com/OooOO0OO.java: -------------------------------------------------------------------------------- 1 | package com; 2 | 3 | /** 4 | * Created by qtfreet on 2017/2/24. 5 | */ 6 | 7 | 8 | public class OooOO0OO { 9 | 10 | /** 11 | * 将字符串编码成16进制数字,适用于所有字符(包括中文) 12 | */ 13 | public static byte[] encode(byte[] bytes, String key) { 14 | //根据默认编码获取字节数组 15 | 16 | int len = bytes.length; 17 | int keyLen = key.length(); 18 | for (int i = 0; i < len; i++) { 19 | //对每个字节进行异或 20 | bytes[i] = (byte) (bytes[i] ^ key.charAt(i % keyLen)); 21 | } 22 | 23 | return bytes; 24 | } 25 | 26 | /** 27 | * 将16进制数字解码成字符串,适用于所有字符(包括中文) 28 | */ 29 | public static String OooOOoo0oo(byte[] bytes, String key) { 30 | int len = bytes.length; 31 | int keyLen = key.length(); 32 | for (int i = 0; i < len; i++) { 33 | //对每个字节进行异或 34 | bytes[i] = (byte) (bytes[i] ^ key.charAt(i % keyLen)); 35 | } 36 | 37 | return new String(bytes); 38 | } 39 | } -------------------------------------------------------------------------------- /src/qtfreet00.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qtfreet on 2017/2/24. 3 | */ 4 | 5 | import java.io.ByteArrayOutputStream; 6 | 7 | public class qtfreet00 { 8 | private static final String hexString = "0123456789ABCDEF"; 9 | private static final String KEY = "qtfreet"; 10 | 11 | /** 12 | * 将字符串编码成16进制数字,适用于所有字符(包括中文) 13 | */ 14 | public static String encode(String str) { 15 | //根据默认编码获取字节数组 16 | byte[] bytes = str.getBytes(); 17 | int len = bytes.length; 18 | int keyLen = KEY.length(); 19 | for (int i = 0; i < len; i++) { 20 | //对每个字节进行异或 21 | bytes[i] = (byte) (bytes[i] ^ KEY.charAt(i % keyLen)); 22 | } 23 | StringBuilder sb = new StringBuilder(bytes.length * 2); 24 | //将字节数组中每个字节拆解成2位16进制整数 25 | for (int i = 0; i < bytes.length; i++) { 26 | sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4)); 27 | sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0)); 28 | } 29 | return sb.toString(); 30 | } 31 | 32 | public static String encode(String str, String key) { 33 | //根据默认编码获取字节数组 34 | byte[] bytes = str.getBytes(); 35 | int len = bytes.length; 36 | int keyLen = key.length(); 37 | for (int i = 0; i < len; i++) { 38 | //对每个字节进行异或 39 | bytes[i] = (byte) (bytes[i] ^ key.charAt(i % keyLen)); 40 | } 41 | StringBuilder sb = new StringBuilder(bytes.length * 2); 42 | //将字节数组中每个字节拆解成2位16进制整数 43 | for (int i = 0; i < bytes.length; i++) { 44 | sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4)); 45 | sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0)); 46 | } 47 | return sb.toString(); 48 | } 49 | 50 | /** 51 | * 将16进制数字解码成字符串,适用于所有字符(包括中文) 52 | */ 53 | public static String decode(String str) { 54 | ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length() / 2); 55 | //将每2位16进制整数组装成一个字节 56 | for (int i = 0; i < str.length(); i += 2) 57 | baos.write((hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1)))); 58 | byte[] b = baos.toByteArray(); 59 | int len = b.length; 60 | int keyLen = KEY.length(); 61 | for (int i = 0; i < len; i++) { 62 | b[i] = (byte) (b[i] ^ KEY.charAt(i % keyLen)); 63 | } 64 | return new String(b); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/qtfreet00.smali: -------------------------------------------------------------------------------- 1 | .class public Lcom/qtfreet00; 2 | .super Ljava/lang/Object; 3 | .source "qtfreet00.java" 4 | 5 | 6 | # static fields 7 | .field private static final KEY:Ljava/lang/String; = "qtfreet" 8 | 9 | .field private static final hexString:Ljava/lang/String; = "0123456789ABCDEF" 10 | 11 | 12 | # direct methods 13 | .method public constructor ()V 14 | .locals 0 15 | 16 | .prologue 17 | .line 7 18 | invoke-direct {p0}, Ljava/lang/Object;->()V 19 | 20 | return-void 21 | .end method 22 | 23 | .method public static decode(Ljava/lang/String;)Ljava/lang/String; 24 | .locals 8 25 | .param p0, "str" # Ljava/lang/String; 26 | 27 | .prologue 28 | .line 14 29 | new-instance v1, Ljava/io/ByteArrayOutputStream; 30 | 31 | invoke-virtual {p0}, Ljava/lang/String;->length()I 32 | 33 | move-result v5 34 | 35 | div-int/lit8 v5, v5, 0x2 36 | 37 | invoke-direct {v1, v5}, Ljava/io/ByteArrayOutputStream;->(I)V 38 | 39 | .line 16 40 | .local v1, "baos":Ljava/io/ByteArrayOutputStream; 41 | const/4 v2, 0x0 42 | 43 | .local v2, "i":I 44 | :goto_0 45 | invoke-virtual {p0}, Ljava/lang/String;->length()I 46 | 47 | move-result v5 48 | 49 | if-ge v2, v5, :cond_0 50 | 51 | .line 17 52 | const-string v5, "0123456789ABCDEF" 53 | 54 | invoke-virtual {p0, v2}, Ljava/lang/String;->charAt(I)C 55 | 56 | move-result v6 57 | 58 | invoke-virtual {v5, v6}, Ljava/lang/String;->indexOf(I)I 59 | 60 | move-result v5 61 | 62 | shl-int/lit8 v5, v5, 0x4 63 | 64 | const-string v6, "0123456789ABCDEF" 65 | 66 | add-int/lit8 v7, v2, 0x1 67 | 68 | invoke-virtual {p0, v7}, Ljava/lang/String;->charAt(I)C 69 | 70 | move-result v7 71 | 72 | invoke-virtual {v6, v7}, Ljava/lang/String;->indexOf(I)I 73 | 74 | move-result v6 75 | 76 | or-int/2addr v5, v6 77 | 78 | invoke-virtual {v1, v5}, Ljava/io/ByteArrayOutputStream;->write(I)V 79 | 80 | .line 16 81 | add-int/lit8 v2, v2, 0x2 82 | 83 | goto :goto_0 84 | 85 | .line 18 86 | :cond_0 87 | invoke-virtual {v1}, Ljava/io/ByteArrayOutputStream;->toByteArray()[B 88 | 89 | move-result-object v0 90 | 91 | .line 19 92 | .local v0, "b":[B 93 | array-length v4, v0 94 | 95 | .line 20 96 | .local v4, "len":I 97 | const-string v5, "qtfreet" 98 | 99 | invoke-virtual {v5}, Ljava/lang/String;->length()I 100 | 101 | move-result v3 102 | 103 | .line 21 104 | .local v3, "keyLen":I 105 | const/4 v2, 0x0 106 | 107 | :goto_1 108 | if-ge v2, v4, :cond_1 109 | 110 | .line 22 111 | aget-byte v5, v0, v2 112 | 113 | const-string v6, "qtfreet" 114 | 115 | rem-int v7, v2, v3 116 | 117 | invoke-virtual {v6, v7}, Ljava/lang/String;->charAt(I)C 118 | 119 | move-result v6 120 | 121 | xor-int/2addr v5, v6 122 | 123 | int-to-byte v5, v5 124 | 125 | aput-byte v5, v0, v2 126 | 127 | .line 21 128 | add-int/lit8 v2, v2, 0x1 129 | 130 | goto :goto_1 131 | 132 | .line 24 133 | :cond_1 134 | new-instance v5, Ljava/lang/String; 135 | 136 | invoke-direct {v5, v0}, Ljava/lang/String;->([B)V 137 | 138 | return-object v5 139 | .end method 140 | -------------------------------------------------------------------------------- /src/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | public class FileUtils { 4 | } 5 | -------------------------------------------------------------------------------- /src/visitor/ClassStringField.java: -------------------------------------------------------------------------------- 1 | package visitor; 2 | 3 | /** 4 | * The String fields in class. 5 | * 6 | * @author Megatron King 7 | * @since 2017/3/7 10:54 8 | */ 9 | 10 | public class ClassStringField { 11 | 12 | public static final String STRING_DESC = "Ljava/lang/String;"; 13 | 14 | public ClassStringField(String name, String value) { 15 | this.name = name; 16 | this.value = value; 17 | } 18 | 19 | public String name; 20 | public String value; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/visitor/ClassVisitorFactory.java: -------------------------------------------------------------------------------- 1 | package visitor; 2 | 3 | import com.OooOO0OO; 4 | import org.objectweb.asm.ClassVisitor; 5 | import org.objectweb.asm.ClassWriter; 6 | import org.objectweb.asm.Opcodes; 7 | 8 | /** 9 | * A factory creates {@link ClassVisitor}. 10 | * 11 | * @author Megatron King 12 | * @since 2017/3/7 19:56 13 | */ 14 | 15 | public final class ClassVisitorFactory { 16 | 17 | private ClassVisitorFactory() { 18 | } 19 | 20 | public static ClassVisitor create(String className, ClassWriter cw) { 21 | if (OooOO0OO.class.getName().replace('.', '/').equals(className)) { 22 | return createEmpty(cw); 23 | } 24 | if (WhiteLists.inWhiteList(className, WhiteLists.FLAG_PACKAGE) || WhiteLists.inWhiteList(className, WhiteLists.FLAG_CLASS)) { 25 | return createEmpty(cw); 26 | } 27 | return new StringFieldClassVisitor(cw); 28 | } 29 | 30 | public static ClassVisitor createEmpty(ClassWriter cw) { 31 | return new ClassVisitor(Opcodes.ASM5, cw) { 32 | }; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/visitor/StringFieldClassVisitor.java: -------------------------------------------------------------------------------- 1 | package visitor; 2 | 3 | import com.OooOO0OO; 4 | import org.objectweb.asm.*; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.UUID; 9 | 10 | /** 11 | * Visit the class to execute string fog. 12 | * 13 | * @author Megatron King 14 | * @since 2017/3/6 20:37 15 | */ 16 | 17 | public class StringFieldClassVisitor extends ClassVisitor { 18 | 19 | private static final String IGNORE_ANNOTATION = "Lcom/qtfreet/lib/annotation/StringIgnore;"; 20 | private static final String Xor_FLAG = OooOO0OO.class.getName().replace('.', '/'); 21 | 22 | private boolean isClInitExists; 23 | 24 | private List mStaticFinalFields = new ArrayList<>(); 25 | private List mStaticFields = new ArrayList<>(); 26 | private List mFinalFields = new ArrayList<>(); 27 | private List mFields = new ArrayList<>(); 28 | 29 | private String mClassName; 30 | 31 | private boolean mIgnoreClass; 32 | 33 | public StringFieldClassVisitor(ClassWriter cw) { 34 | super(Opcodes.ASM5, cw); 35 | } 36 | 37 | private void encode(MethodVisitor mv, String str) { 38 | String mKey = UUID.randomUUID().toString().replace("-", "").trim().substring(0, 6); 39 | byte[] enc = OooOO0OO.encode(str.getBytes(), mKey); 40 | int len = enc.length; 41 | mv.visitIntInsn(Opcodes.SIPUSH, len); 42 | mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); 43 | for (int i = 0; i < len; i++) { 44 | mv.visitInsn(Opcodes.DUP); 45 | mv.visitIntInsn(Opcodes.SIPUSH, i); 46 | mv.visitIntInsn(Opcodes.BIPUSH, enc[i]); 47 | mv.visitInsn(Opcodes.BASTORE); 48 | } 49 | mv.visitLdcInsn((String) mKey); 50 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, Xor_FLAG, "OooOOoo0oo", "([BLjava/lang/String;)Ljava/lang/String;", false); 51 | } 52 | 53 | @Override 54 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 55 | this.mClassName = name; 56 | // System.out.println("processClass: " + mClassName); 57 | super.visit(version, access, name, signature, superName, interfaces); 58 | } 59 | 60 | @Override 61 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 62 | mIgnoreClass = IGNORE_ANNOTATION.equals(desc); 63 | return super.visitAnnotation(desc, visible); 64 | } 65 | 66 | @Override 67 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 68 | if (ClassStringField.STRING_DESC.equals(desc) && name != null && !mIgnoreClass) { 69 | // static final, in this condition, the value is null or not null. 70 | if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) != 0) { 71 | mStaticFinalFields.add(new ClassStringField(name, (String) value)); 72 | value = null; 73 | } 74 | // static, in this condition, the value is null. 75 | if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) == 0) { 76 | mStaticFields.add(new ClassStringField(name, (String) value)); 77 | value = null; 78 | } 79 | 80 | // final, in this condition, the value is null or not null. 81 | if ((access & Opcodes.ACC_STATIC) == 0 && (access & Opcodes.ACC_FINAL) != 0) { 82 | mFinalFields.add(new ClassStringField(name, (String) value)); 83 | value = null; 84 | } 85 | 86 | // normal, in this condition, the value is null. 87 | if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) != 0) { 88 | mFields.add(new ClassStringField(name, (String) value)); 89 | value = null; 90 | } 91 | } 92 | return super.visitField(access, name, desc, signature, value); 93 | } 94 | 95 | @Override 96 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 97 | 98 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 99 | if (mv != null && !mIgnoreClass) { 100 | if ("".equals(name)) { 101 | isClInitExists = true; 102 | // If clinit exists meaning the static fields (not final) would have be inited here. 103 | mv = new MethodVisitor(Opcodes.ASM5, mv) { 104 | 105 | private String lastStashCst; 106 | 107 | @Override 108 | public void visitCode() { 109 | super.visitCode(); 110 | // Here init static final fields. 111 | for (ClassStringField field : mStaticFinalFields) { 112 | if (field.value == null) { 113 | continue; 114 | } 115 | encode(super.mv, field.value); 116 | 117 | super.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 118 | } 119 | } 120 | 121 | @Override 122 | public void visitLdcInsn(Object cst) { 123 | // Here init static or static final fields, but we must check field name int 'visitFieldInsn' 124 | if (cst != null && cst instanceof String && !TextUtils.isEmptyAfterTrim((String) cst)) { 125 | lastStashCst = (String) cst; 126 | encode(super.mv, lastStashCst); 127 | 128 | } else { 129 | lastStashCst = null; 130 | super.visitLdcInsn(cst); 131 | } 132 | } 133 | 134 | @Override 135 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 136 | if (mClassName.equals(owner) && lastStashCst != null) { 137 | boolean isContain = false; 138 | for (ClassStringField field : mStaticFields) { 139 | if (field.name.equals(name)) { 140 | isContain = true; 141 | break; 142 | } 143 | } 144 | if (!isContain) { 145 | for (ClassStringField field : mStaticFinalFields) { 146 | if (field.name.equals(name) && field.value == null) { 147 | field.value = lastStashCst; 148 | break; 149 | } 150 | } 151 | } 152 | } 153 | lastStashCst = null; 154 | super.visitFieldInsn(opcode, owner, name, desc); 155 | } 156 | }; 157 | 158 | } else if ("".equals(name)) { 159 | // Here init final(not static) and normal fields 160 | mv = new MethodVisitor(Opcodes.ASM5, mv) { 161 | @Override 162 | public void visitLdcInsn(Object cst) { 163 | // We don't care about whether the field is final or normal 164 | if (cst != null && cst instanceof String && !TextUtils.isEmptyAfterTrim((String) cst)) { 165 | encode(super.mv, (String) cst); 166 | } else { 167 | super.visitLdcInsn(cst); 168 | } 169 | } 170 | }; 171 | } else { 172 | mv = new MethodVisitor(Opcodes.ASM5, mv) { 173 | 174 | @Override 175 | public void visitLdcInsn(Object cst) { 176 | if (cst != null && cst instanceof String && !TextUtils.isEmptyAfterTrim((String) cst)) { 177 | // If the value is a static final field 178 | for (ClassStringField field : mStaticFinalFields) { 179 | if (cst.equals(field.value)) { 180 | super.visitFieldInsn(Opcodes.GETSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 181 | return; 182 | } 183 | } 184 | // If the value is a final field (not static) 185 | for (ClassStringField field : mFinalFields) { 186 | // if the value of a final field is null, we ignore it 187 | if (cst.equals(field.value)) { 188 | super.visitVarInsn(Opcodes.ALOAD, 0); 189 | super.visitFieldInsn(Opcodes.GETFIELD, mClassName, field.name, "Ljava/lang/String;"); 190 | return; 191 | } 192 | } 193 | encode(super.mv, (String) cst); 194 | return; 195 | } 196 | super.visitLdcInsn(cst); 197 | } 198 | 199 | }; 200 | } 201 | } 202 | return mv; 203 | } 204 | 205 | @Override 206 | public void visitEnd() { 207 | if (!mIgnoreClass && !isClInitExists && !mStaticFinalFields.isEmpty()) { 208 | MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); 209 | mv.visitCode(); 210 | // Here init static final fields. 211 | for (ClassStringField field : mStaticFinalFields) { 212 | if (field.value == null) { 213 | continue; // It could not be happened 214 | } 215 | encode(mv, field.value); 216 | mv.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.name, ClassStringField.STRING_DESC); 217 | } 218 | mv.visitInsn(Opcodes.RETURN); 219 | mv.visitMaxs(1, 0); 220 | mv.visitEnd(); 221 | } 222 | super.visitEnd(); 223 | } 224 | } -------------------------------------------------------------------------------- /src/visitor/TextUtils.java: -------------------------------------------------------------------------------- 1 | package visitor; 2 | 3 | public final class TextUtils { 4 | 5 | private TextUtils() { 6 | } 7 | 8 | /** 9 | * Returns true if the string is null or 0-length. 10 | * @param str the string to be examined 11 | * @return true if str is null or zero length 12 | */ 13 | public static boolean isEmpty(CharSequence str) { 14 | return str == null || str.length() == 0; 15 | } 16 | 17 | /** 18 | * Returns true if the string is null or 0-length. 19 | * @param str the string to be examined 20 | * @return true if str is null or zero length 21 | */ 22 | public static boolean isEmptyAfterTrim(String str) { 23 | return str == null || str.length() == 0 || str.trim().length() == 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/visitor/WhiteLists.java: -------------------------------------------------------------------------------- 1 | package visitor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * The white list contains some ignored levels. We defined some popular 8 | * library domains and classes which must be ignored when executing string fog. 9 | * 10 | * @author Megatron King 11 | * @since 2017/3/7 19:34 12 | */ 13 | 14 | public final class WhiteLists { 15 | 16 | public static final int FLAG_PACKAGE = 0; 17 | public static final int FLAG_CLASS = 1; 18 | 19 | private static final List PACKAGE_WHITE_LIST = new ArrayList<>(); 20 | private static final List CLASS_WHITE_LIST = new ArrayList<>(); 21 | 22 | static { 23 | // default packages in white list. 24 | addWhiteList("android.support", FLAG_PACKAGE); 25 | addWhiteList("com.google", FLAG_PACKAGE); 26 | addWhiteList("com.facebook", FLAG_PACKAGE); 27 | addWhiteList("com.baidu", FLAG_PACKAGE); 28 | addWhiteList("com.alipay", FLAG_PACKAGE); 29 | addWhiteList("com.alibaba", FLAG_PACKAGE); 30 | addWhiteList("com.tencent", FLAG_PACKAGE); 31 | addWhiteList("de.greenrobot", FLAG_PACKAGE); 32 | addWhiteList("com.qq", FLAG_PACKAGE); 33 | addWhiteList("mozilla", FLAG_PACKAGE); 34 | addWhiteList("okhttp3", FLAG_PACKAGE); 35 | addWhiteList("okio", FLAG_PACKAGE); 36 | // addWhiteList("org", FLAG_PACKAGE); 37 | 38 | // default classes short name in white list. 39 | addWhiteList("BuildConfig", FLAG_CLASS); 40 | addWhiteList("R", FLAG_CLASS); 41 | } 42 | 43 | private WhiteLists() { 44 | } 45 | 46 | public static void addWhiteList(String name, int flag) { 47 | switch (flag) { 48 | case FLAG_PACKAGE: 49 | PACKAGE_WHITE_LIST.add(name); 50 | break; 51 | case FLAG_CLASS: 52 | CLASS_WHITE_LIST.add(name); 53 | break; 54 | } 55 | } 56 | 57 | public static boolean inWhiteList(String name, int flag) { 58 | if (TextUtils.isEmpty(name)) { 59 | return false; 60 | } 61 | boolean inWhiteList = false; 62 | switch (flag) { 63 | case FLAG_PACKAGE: 64 | inWhiteList = checkPackage(trueClassName(name)); 65 | break; 66 | case FLAG_CLASS: 67 | inWhiteList = checkClass(shortClassName(name)); 68 | break; 69 | } 70 | return inWhiteList; 71 | } 72 | 73 | private static boolean checkPackage(String name) { 74 | for (String packageName : PACKAGE_WHITE_LIST) { 75 | if (name.startsWith(packageName + ".")) { 76 | return true; 77 | } 78 | } 79 | return false; 80 | } 81 | 82 | private static boolean checkClass(String name) { 83 | for (String className : CLASS_WHITE_LIST) { 84 | if (name.equals(className)) { 85 | return true; 86 | } 87 | } 88 | return false; 89 | } 90 | 91 | private static String trueClassName(String className) { 92 | return className.replace('/', '.'); 93 | } 94 | 95 | private static String shortClassName(String className) { 96 | String[] spiltArrays = className.split("/"); 97 | return spiltArrays[spiltArrays.length - 1]; 98 | } 99 | 100 | } 101 | --------------------------------------------------------------------------------