巧用正则表达式,一千行代码进入混淆新境界

312 阅读4分钟

大家知道,安卓项目极易被破解,即使是用Proguard混淆后,一些关键部分比如Activitylayoutviewdrawablejni方法等等,全部都是保留原名,替逆向人员大开方便之门。

你也可以手动起难以辨认的名字,但是往往自损八百,而且没道理千日防贼。怎么办?其实可以在打包release前,用Java脚本做一个预处理,进行自定义混淆。

Java实在是太强大了!却没有成为流行的“脚本”语言,实为憾事。 API简洁如同公式,可以对比一下最流行的脚本语言——Python(不是黑,毕竟我入门语言就是Python),Python的文件API都是什么牛鬼蛇神啊,下划线、驼峰式应有尽有!而Java只需记一个 new File(),随后getName、getLength、listFiles等等随之而来,忘都忘不掉。

Java自带的正则引擎也十分强大易用。Regex入门推荐notepad++这样的编辑器,可以批量处理一些规律性的文本。但是这样的编辑器,不借助脚本的话是无法处理复杂需求的。这时候就是Java的用武之地。

比如,利用用Java的正则表达式,在一轮匹配中直接替换R.layout.布局、R.xml.菜单与设置、R.drawable.图画等资源的文件名称:

首先,用File API经行文件夹遍历,无需递归法,直接都放进一个数组就可以,所以是广度优先,先记录当前目录所有文件(夹),然后才扫描下一层

然后是准备正则表达式:匹配:R.(三到10个字母).任意名称 .单词边界

final Pattern resPattern4Src = Pattern.compile("R\\.([a-z]{3,10})\\.(.+?)\\b");

最后开启一轮匹配与替换,根据(三到10个字母)里面是什么,将原名替换成不同的hashmap映射值,里面存储了预先计算的随机映射,也就是混淆后的名称。

private String Pattern_replace_resources_for_source(StringBuffer buffer, String source) {
   if(buffer==null) buffer = new StringBuffer(); else buffer.setLength(0);
   buffer.ensureCapacity((int) (source.length()*1.2));
   Matcher m = resPattern4Src.matcher(source);
   while (m.find()) {
      String realm = m.group(1);
      String originalName = m.group(2);
      HashMap<String, String> nameMap = null;
      if ("xml".equals(realm)) {
         nameMap = xmlsMap;
      } else if ("layout".equals(realm)) {
         nameMap = layoutsMap;
      } else if ("drawable".equals(realm)) {
         nameMap = drawablesMap;
      }
      if (nameMap!=null) {
         String newName = nameMap.get(originalName);
         if (newName == null) {
            _log("fatal poison:: originalName=" + originalName + " realm=" + realm);
         } else {
            m.appendReplacement(buffer, "");
            buffer.append("R.").append(realm).append(".").append(newName);
         }
      }
   }
   if (buffer.length()>0) {
      m.appendTail(buffer);
      return buffer.toString();
   } else {
      return source;
   }
}

提炼一下,用正则表达式进行批量替换的方法就是:

  • Matcher m = resPattern4Src.matcher(source); // 开启Matcher

  • while (m.find()) { // 有什么花招,尽管放m马过来吧!

  •  m.appendReplacement(buffer, 用于替换匹配到的文本的字符串);

  •  现在对buffer想干嘛就干嘛,你可以为所欲为,m.group(n)用于获取匹配到的文本,n从零开始,先是匹配到的整体,后是正则表达式括号内的内容。m.start、m.end用于获取匹配文本在源文本中的位置。

  • }

  • m.appendTail(buffer); // 收尾工作。

用这个模式,可以替换xml中的@layout/名字、@drawable/名字,替换jni中的Java_包名下划线_方法名字(todo,我只实现了替换_包名下划线_)等等,实测用一千行代码,就可以进入混淆加固的新境界,此生筑基有望!

为何不用加固?虽然不冲突,但是加固的性能太低。Facebook研究redex提升安卓apk的性能,研究zstd进化zlib,研究元宇宙重定义网络社交与远程工作。而我们呢,苦哈哈地深陷加固之苦。

blog.csdn.net/sinat_27171…

(略水,轻喷)

private String fileToString(ByteArrayOutputStream bous, File f) throws IOException {
   if (bous==null) bous = new ByteArrayOutputStream(); else bous.reset();
   byte[] data = new byte[4096];
   FileInputStream fin = new FileInputStream(f);
   int len;
   while ((len=fin.read(data))>0) {
      bous.write(data, 0, len);
   }
   fin.close();
   return bous.toString();
}
private void SaveToFile(String string, File f) throws IOException {
   FileOutputStream fout = new FileOutputStream(f);
   fout.write(string.getBytes(StandardCharsets.UTF_8));
   fout.close();
}

……

@Test
public void testHunxiao() {
   bPerform = true;
   bLogSource = 0x3;
   projectDetails.mainFolder = "C:\\Users\\Admin\\AndroidStudioProjects\\MyApplication\\app\\src\\main\\";
   projectDetails.srcFolder = projectDetails.mainFolder+"java\\";
   projectDetails.resFolder = projectDetails.mainFolder+"res\\";
   projectDetails.manifestFile = projectDetails.mainFolder+"AndroidManifest.xml";
   projectDetails.verisonSuffix = "v1.0";
   projectDetails.mapping = new File(projectDetails.mainFolder+"mapping."+projectDetails.verisonSuffix+".txt");
   projectDetails.generated = new File(projectDetails.mainFolder+"generated.txt");
   
   projectDetails.jniFiles.add("cpp\com_knziha_myapplication_exampleact_MainActivity.h");
   projectDetails.jniFiles.add("cpp\jniMain.cpp");
   
   if (b要删除生成的混淆文件嘛) { // 还原
      deleteGenerated(projectDetails.generated);
   } else { // 混淆奥
      readPrevMapping(); // 读取之前记录的 mapping.version.txt,如果删除了那就重新计算
      parseMapping(new File(projectDetails.mainFolder, "mapping.user.txt")); // 用户定义的mapping
      
      String obscureFolder = projectDetails.srcFolder+"com\\knziha\\myapplication"; //单模块混淆,设置主目录
      
      huntun_hunxiao(projectDetails, obscureFolder); // 馄饨混淆,热乎乎!
   }
}

没错,这个脚本就叫做“馄饨混淆工具”!界面都没有,运行在IDE的test环境中,真是可怜。

这个脚本其实很简单,目前只能“混淆”单一模块。

最后记录一个weixin开源的异曲同工的项目:AndResGuard,只处理资源文件,将res/drawable/wechat 重命名为 r/d/a,这一步,对包体积的影响应该不大。所以,他们还用7-zip进行了重新打包。