查看原文
其他

Android 字符串及字典混淆开源实现

2017-03-06 YSRC 同程安全应急响应中心

Android上对App进行混淆是常见的事情,目的就在于一定程序上保护app的逻辑不被逆向,同时还可以减少app的体积。

在开发中常用的混淆工具首先就要提ProGuard了,这是一款免费工具,默认已被Android IDE集成,开发者只需要适当的进行配置混淆规则就可以使用proguard来混淆app,但既然是免费软件,局限性自然也比较大,首先就是不支持字符串混淆,如果要使用字符串混淆,就需要使用它的商业版本DexGuard。

既然没有免费的字符串混淆工具,那就自己先实现一个,分析了JEB(Android反编译工具),Zelix(JAVA混淆器),BurpSuite(网络代理工具)等几款混淆效果比较好的软件,了解了它们的实现思路后,自己写了个简单的字符串混淆工具,先上效果:

可以看到,这里的字符串已经被处理成16进制了,每次执行时,会调用decode方法对字符串进行还原,decode方法也很简单

public static String decode(String str) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length() / 2);
//将每216进制整数组装成一个字节
   for (int i = 0; i < str.length(); i += 2)
baos.write((hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1))));
byte[] b = baos.toByteArray();
int len = b.length;
int keyLen = KEY.length();
for (int i = 0; i < len; i++) {
b[i] = (byte) (b[i] ^ KEY.charAt(i % keyLen));
}
return new String(b);
}

原理很简单,那是如何实现的呢,这里的实现思路是在smali层进行处理,即在App编译生成apk后再进行处理,使用apktool将apk进行反编译,然后再对smali中字符串进行混淆。

既然是混淆,就需要遍历每个smali文件,这个很简单

private static void getFiles(String filePath) {
File[] files = new File(filePath).listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
getFiles(file.getPath());
} else {
filelist.add(file.getPath());
}
}
}

使用迭代方式将每个文件添加到list中,之后就是对遍历到的文件进行处理了,直接上代码,注释写的很清楚

private static void FileTofindString(String path) {
StringBuilder sb = new StringBuilder();
try {
InputStreamReader read = new InputStreamReader(new FileInputStream(path), "UTF-8");
BufferedReader br = new BufferedReader(read);
String str = "";
while ((str = br.readLine()) != null) {
//利用正则去匹配方法中定义的字符串
           Matcher m = Pattern.compile("const-string ([vp]\\d{1,2}), \"(.*)\"").matcher(str);
if (m.find()) {
String tmp = m.group(2);
if (tmp.equals("")) {
sb.append(str + "\n");
continue;
}
//字符串转义,过滤掉\(如\",不转义时获取到的为\",但理想效果应为")以及将smali中的unicode转为中文字符
               tmp = StringEscapeUtils.unescapeJava(tmp);
String register = m.group(1);
//register代表寄存器
               String enc = qtfreet00.encode(tmp);
//混淆字符串
               String sign = "    const-string " + register + ", " + "\"" + enc + "\"";
String dec = "";
if (Integer.parseInt(register.substring(1)) > 15 && register.startsWith("v")) {
//此处考虑寄存器个数,如果v寄存器大于15时,应使用range方式传参
                   dec = "    invoke-static/range {" + register + " .. " + register + "}, Lcom/qtfreet00;->decode(Ljava/lang/String;)Ljava/lang/String;";
//添加解密方法
               } else if (register.startsWith("v") || (register.startsWith("p") && Integer.parseInt(register.substring(1)) < 10)) {
//此处p10以上(不清楚具体),也会出现一些问题,由于没太接触过较大p寄存器,这里直接忽略掉了10以上的,实际应用中也很少会出现
                   //p在方法中一般代表入参,静态方法中从p0开始,非静态方法从p1开始,p0带表this
                   dec = "    invoke-static {" + register + "}, Lcom/qtfreet00;->decode(Ljava/lang/String;)Ljava/lang/String;";
} else {
sb.append(str + "\n");
continue;
}
String mov = "    move-result-object " + register;
sb.append(sign + "\n\n");
sb.append(dec + "\n\n");
sb.append(mov + "\n");
} else {
sb.append(str + "\n");
}
}
br.close();
read.close();
//覆盖掉源文件
       FileOutputStream fos = new FileOutputStream(new File(path));
fos.write(sb.toString().getBytes("UTF-8"));
fos.flush();
fos.close();

} catch (Exception e) {
}
}

这里没有去考虑全局变量,有兴趣的可以一起交流下

既然要使用字符串加密,那加密方法自然不能漏:

public static String encode(String str) {
//根据默认编码获取字节数组
   byte[] bytes = str.getBytes();
int len = bytes.length;
int keyLen = KEY.length();
for (int i = 0; i < len; i++) {
//对每个字节进行异或
       bytes[i] = (byte) (bytes[i] ^ KEY.charAt(i % keyLen));
}
StringBuilder sb = new StringBuilder(bytes.length * 2);
//将字节数组中每个字节拆解成216进制整数
   for (int i = 0; i < bytes.length; i++) {
sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
}
return sb.toString();
}

插入解密smali文件,这里可以自己写个Android工程,将解密方法写入,然后打包反编译成smali即可,注意插入代码的路径要和方法中对应上,最后再使用apktool打包签名即可。

使用字符串混淆,可以更大程度上保护一些敏感信息的安全性,如加密方式,JAVA中一般使用原生加密api时会在字符串中注明类型,如”AES/CBC/PKCS5Padding”,以及一些密钥硬编码,当然想还原这种字符串混淆也并不难。

说完了字符串混淆,那字典混淆又是什么,在ProGuard中默认混淆过的效果如下:

可以看到变量名,方法名以及目录名都已经以abcd等字母替换,但这种混淆只能一定程度上提高逆向难度,可以语意还是可以慢慢对变量名等进行人工恢复的,那如何大幅度提高难度呢,就需要借助字典了,在ProGuard中,允许用户使用自定义字典,并提供了三个命令

-obfuscationdictionary dic.txt -classobfuscationdictionary dic.txt -packageobfuscationdictionary dic.txt

那这个自定义字典的效果如何呢?如下:

正常使用中需要注意proguard-rules.pro中的配置,以免app无法运行或者运行异常。

关注公众号后发送“混淆”获取文中的代码实现。

搜索“同程安全”或者扫描下方二维码关注YSRC公众号。


往期文章

我可能是用了假的隐身模式

同程旅游 Hadoop 安全实践

点我的链接就能弹你一脸计算器

点我的链接我就能知道你用了哪些chrome插件

YSRC诚意之作,巡风-企业安全漏洞快速应急、巡航系统

基于文件特征的Android模拟器检测

Android逆向与病毒分析

F-Scrack 弱口令检测脚本

unsafe 模式下的 CSP Bypass

被动扫描器 GourdScan v2.0 发布!

Android App常见逆向工具和使用技巧

XSS Trap - XSS DNS防护的简单尝试

A BLACK PATH TOWARD THE SUN - HTTP Tunnel 工具简介

初探Windows Fuzzing神器----Winafl


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存