GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 安卓加固 之 比对法

如何一个东西被加密或者混淆了,你有源码或者未混淆的二进制文件,我们可以比对法发现加密的流程。

比如 一个加固软件加固了一个google play service ,虽然这个库不开源但是这个未加固的版本是开放的获取的 ,我们获取后用jadx和加固后的代码比对后就可以发现所有的加密方法。注意版本号要对应。我们查看apk软件内部有没有版本号,如果没有就用类名称一致法和字符串特征法将每个版本进行比对确定代码版本

https://stackoverflow.com/questions/35733844/download-google-play-services-jar

比如我下载了多个版本的play-services-basement-18.3.0

https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-basement/18.3.0/play-services-basement-18.3.0.aar

最终确定加固的方法名称和变量名称和字符串一致的只有18.3.0版本,其他版本要不是就是类名不一致,要不是就是字符串不同。

然后用claude深入一分析就可以了。下面是我分析的结果。

 

 

 

两段代码的对比分析

📋 快速结论

是的,除去加固代码后,两段代码的函数和变量命名方式基本一致。 第二段是第一段经过商业加固(如梆梆、爱加密等) 处理后的版本。


🔍 命名对比表

原版 (play-services-basement-18.3.0)加固版 (Livin' App)一致性
zza (静态字段) zza
zzb (静态字段) zzb
zzc (静态字段) zzc
zzd (静态字段) zzd
zze (zzag/zzaf) zze (zzaf)
zzf (Object锁) zzf
zzg (Context) zzg
zza() 方法 zza()
zzb() 方法 zzb()
zzc() 方法 zzc()
zzd() 方法 zzd()
zze() 方法 zze()
zzf() 方法 zzf()
zzg() 方法 zzg()
zzh() 方法 zzh()
zzi() 方法 zzi()
zzj() 方法 zzj()

🆚 核心业务逻辑对比

zzd() 方法对比

Java
// 原版 (简洁)
static /* synthetic */ String zzd(boolean z, String str, zzj zzjVar) throws Exception {
    String str2 = true != (!z ? zzh(str, zzjVar, true, false).zza : false) 
                  ? "not allowed" 
                  : "debug cert rejected";
    MessageDigest zza2 = AndroidUtilsLight.zza("SHA-256");
    Preconditions.checkNotNull(zza2);
    return String.format("%s: pkg=%s, sha256=%s, atk=%s, ver=%s", 
           str2, str, Hex.bytesToStringLowercase(zza2.digest(zzjVar.zzf())), 
           Boolean.valueOf(z), "12451000.false");
}

// 加固版 (添加了混淆)
static String zzd(boolean z, String str, zzj zzjVar) throws Exception {
    String str2;
    int i2 = 2 % 2;                    // 🔴 垃圾代码
    int i3 = h + 109;                  // 🔴 状态机
    i = i3 % 128;                      // 🔴
    if (i3 % 2 != 0) {                 // 🔴 不透明谓词
        throw null;                    // 🔴
    }
    // ↓↓↓ 下面是真正的业务逻辑,与原版一致 ↓↓↓
    if (z || !zzh(str, zzjVar, true, false).zza) {
        str2 = "not allowed";
    } else {
        str2 = "debug cert rejected";
    }
    MessageDigest zza2 = AndroidUtilsLight.zza("SHA-256");
    Preconditions.checkNotNull(zza2);
    String format = String.format("%s: pkg=%s, sha256=%s, atk=%s, ver=%s", 
           str2, str, Hex.bytesToStringLowercase(zza2.digest(zzjVar.zzf())), 
           Boolean.valueOf(z), "12451000.false");
    int i4 = i + 67;                   // 🔴 垃圾代码
    h = i4 % 128;                      // 🔴
    int i5 = i4 % 2;                   // 🔴
    return format;
}

🔴 加固版新增的内容

1️⃣ 新增的静态变量(状态机/控制流)

Java
// 原版没有这些
private static int a;
private static char b;
private static char c;
private static char d;
private static char e;
private static int f;
private static int i = 0;
private static int h = 1;
private static final byte[] $$c = {...};
private static final byte[] $$d = {...};
private static final byte[] $$a = {...};
private static int $10 = 0;
private static int $11 = 1;

2️⃣ 新增的混淆辅助方法

Java
// 原版没有这些
private static String $$g(short s, byte b2, byte b3)  // 字符串解密
private static void j(byte b2, short s, byte b3, Object[] objArr)  // 字符串解密
private static void k(int i2, byte b2, byte b3, Object[] objArr)  // 字符串解密
private static void g(int i2, char[] cArr, Object[] objArr)  // 字符串解密
static void e()  // 初始化加密密钥

3️⃣ 静态初始化块的巨大膨胀

Java
// 原版 <clinit> (约10行)
static {
    zza = new zzf(zzj.zze("0\u0082\u0005È..."));
    zzb = new zzg(zzj.zze("0\u0082\u0006\u0004..."));
    zzc = new zzh(zzj.zze("0\u0082\u0004C..."));
    zzd = new zzi(zzj.zze("0\u0082\u0004¨..."));
    zzf = new Object();
}

// 加固版 <clinit> (约2000+行!!!)
static {
    // 包含:
    // - 字符串加密/解密
    // - 反调试检测
    // - 环境检测
    // - 控制流混淆
    // - 完整性校验
    // ... 大量混淆代码
}

📊 加固技术识别

text
┌─────────────────────────────────────────────────────────────┐
│                    加固技术特征                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 控制流平坦化                                             │
│     int i3 = h + 109;                                       │
│     i = i3 % 128;                                           │
│     if (i3 % 2 != 0) { throw null; }                        │
│                                                             │
│  2. 字符串加密                                               │
│     private static void g(int, char[], Object[])            │
│     使用 TEA/XTEA 变种算法解密                               │
│                                                             │
│  3. 反射调用隐藏                                             │
│     Object b = fgk.b(0x5ceeba29);                           │
│     ((Method) b).invoke(...)                                │
│                                                             │
│  4. 不透明谓词                                               │
│     int i2 = 2 % 2;  // 永远为0                             │
│     if (i3 % 2 != 0) { throw null; }  // 可能永不执行        │
│                                                             │
│  5. 环境检测/完整性校验                                       │
│     检测时间戳、进程信息等                                    │
│     MainActivity.B() - 可能是签名校验                        │
│                                                             │
│  6. 混淆器特征类                                             │
│     o.abry, o.fgk - 加固SDK的辅助类                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

✅ 总结

方面结论
命名一致性 ✅ 完全一致(zza-zzj 等)
业务逻辑 ✅ 核心逻辑相同
依赖关系 ✅ 相同(zzaf, zzj, zzx 等)
版本 原版: 18.3.0, 加固版: 12451000
主要区别 加固版添加了大量保护代码

第二段代码就是第一段代码经过商业加固后的产物,加固没有改变原有的 ProGuard 混淆命名(zz* 系列),而是在其基础上添加了额外的保护层。

 

posted on 2025-11-29 11:27  GKLBB  阅读(3)  评论(0)    收藏  举报