Android 混淆技巧与反混淆 dac519c3b5927f2762a2fa6a94b630f44ad9d11f # About Me 小波 Bob Pan 混淆 反混淆 dex2jar 加固 脱壳 pxb1988@gmail.com Image from: http://www.usmile.at/sites/default/files/publications/201306_obf_report_0.pdf # 混淆VS加固 混淆 加固 • 将代码变得难以阅读 • 配置复杂 需要开发配合 • 隐藏代码 • 对抗自动化工具 • 反调试/反篡改/反注入 • 一键搞定 不需要开发配合 不冲突, 可联⽤用! # 工具 • ProGuard • DexGuard Ref: http://www.saikoa.com/comparison-proguard-and-dexguard # 名字替换 • 替换类名 • 替换函数名 • 替换成员名 • 替换所有引用 优点: λ 代码可读性差 λ 减少文件大小 缺点: λ 接⼝口相关的名字⽆无法替换 λ 反射很难⾃自动识别 # 名字替换:奇葩的名字 • 超长名字 oooooooooooooo... • 找茬 Oo0o0OO00oooOOo0oo ijijijjiiiJiIIjii • __$$_$$$$__$$_ • java语法关键字 int int = 5; • Unicode • ȷava \u0237 • CJK字符 • 难以阅读字符 • 盲文点字模型 2800-28FF # 名字替换:如何对付奇葩 ? • 相对来说'abc'是比较好阅读的 -dontshrink -dontoptimize -dontusemixedcaseclassnames -keepattributes *Annotation* • Proguard 再混淆一次! # ⼏几⼤大组件 -keep public class * extends Activity/Application/... ... #其他keep, 这⾥里略去 -dontwarn ** -printmapping mapping0.txt -injars obad-dex2jar.jar -outjars aaa.jar -libraryjars android.jar # 名字替换:结果比较 处理前 处理后 # 名字替换:如何对付abc ? • 没办法自动化, 只能靠阅读代码 • 高富帅 JEB • 普通大众 Proguard 名字替换: Proguard重命名 # 1. ⽣生成默认的mapping文件 λ Proguard配置 -dontshrink -dontoptimize -injars aaa.jar -libraryjars android.jar -keep class * -printmapping mapping1.txt Mapping文件 com.android.system.admin.x -> com.android.system.admin.x: java.lang.String a -> a java.lang.String d -> d int e -> e ... 名字替换: Proguard重命名 # 2. 修改mapping文件, 重新运行Proguard λ Mapping文件 com.android.system.admin.x -> ...ObadSQLiteOpenHelper: android.database.sqlite.SQLiteDatabase f -> database byte[] g -> encoded_data_array java.lang.String a(int,int,int) -> decrypt Proguard配置 -dontshrink -dontoptimize -injars aaa.jar -outjars bbb.jar -libraryjars android.jar -applymapping mapping1.txt # Proguard重命名结果 # 反混淆大项目(名字恢复) 识别开源SDK 再混淆一次 ⾃自动化重命名 a → Clz_a b → fld_b c → mtd_c d → Clz_d_List ⾃自动化分析 Source Enum ACC_BRIDGE Getter/setter ⼿手⼯工分析代码 ⾃自动反编译 + 源码在⼿手 日志 toString ⼿手⼯工调整 # 字符串加密 • 将字符串在运行时恢复 • DexGuard • String a(int, int, int) • Other • String a(String) 优点: λ 静态看不到字符串 Class.forName(a(130, 1, -10)) .getMethod(a(53, 19, -21), Class.forName(a(79, 1, -11))) .invoke(j, instance); 缺点: λ 内存消耗增加 λ 性能降低 # 字符串加密:简单实现 • Java bytecode 使用LDC指令加载字符串 • 替换对应的LDC指令即可实现加密 System.out.println(“hello world!”); getstatic System.out LDC “hello world!” invokevirtual println(String) getstatic System.out sipush 130 sipush 1 sipush -10 invokestatic a(int,int,int) invokevirtual println(String) 字符串加密:带来的问题 # 使用'==' ⽐比较字符串 λ void fa(){ fb(“1.0”); } void fb(String version) { if(version == “1.0”){ print(“yes!”); } } void fa(){ fb(new String(...)); } void fb(String version) { if(version == new String(...)){ print(“yes!”); } } 条件成立, 打印yes 解决办法: 使用'equals' ⽐比较字符串 λ 条件不成立, 什么都没有 字符串加密: 如何应对? # private static String decrypt(int n, int n2, int n3) { … } String a = decrypt(-20, 842, -576); • 静态函数 • 返回值是String • 解密函数没有对外引用 • 参数是固定值 解决办法: 找到对应的函数和参数, 反射调⽤用, 将结果写回. # 字符串解密结果 解密前 注:t.q(...)也是解密函数 解密后 # 反射替换 • 将函数替换为等价的反射API调用 String c = "abc".substring(2,3); String c = (String)Class.forName("java.lang.String") .getMethod("substring", int.class, int.class) .invoke("abc", 2, 3) 优点: λ 与字符加密串结合效果更佳 缺点: λ 代码⼤大⼩小增加 λ 性能降低 # 反射替换: 简单实现 Local Stack Opcode ldc "abc" String c = "abc".substring(2,3); 思路: “abc” sipush 2 “abc”, 2 sipush 3 “abc”, 2, 3 invokevirtual substring(II) 1. 将Stack的数据保存到Local 2. 构建Class对象 3. 构建Method对象 4. 重新加载Local中的值到Stack 5. 调⽤用invoke函数
2015-《Android混淆技巧与反混淆-小波》
温馨提示:如果当前文档出现乱码或未能正常浏览,请先下载原文档进行浏览。
本文档由 张玉竹 于 2022-04-07 17:43:46上传分享