大家知道,安卓项目极易被破解,即使是用Proguard混淆后,一些关键部分比如Activity、layout、view、drawable、jni方法等等,全部都是保留原名,替逆向人员大开方便之门。
你也可以手动起难以辨认的名字,但是往往自损八百,而且没道理千日防贼。怎么办?其实可以在打包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,研究元宇宙重定义网络社交与远程工作。而我们呢,苦哈哈地深陷加固之苦。
(略水,轻喷)
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进行了重新打包。