APP逆向分析-以某music软件逆向为例
字数 1981 2025-08-22 12:23:00

某音乐APP逆向分析详细教学文档

1. 前置准备

1.1 工具清单

  • 夜神模拟器(Android 9):用于运行目标APP(照顾无真机用户)。
  • Pixel 1 真机:用于后续SO层分析(Unidbg仅支持ARM架构)。
  • Frida:动态Hook工具,用于代码注入。
  • Jadx:APK反编译工具,用于静态分析。
  • Charles/Burp Suite:抓包工具,用于分析网络请求。
  • SocksDroid/Postern:代理工具,用于模拟器抓包。
  • 目标APP(版本11.7.0):待分析的某音乐APP。

1.2 注意事项

  • 本文仅用于学习,若侵犯公司利益,请联系删除。
  • 抓包时需配置代理(IP+端口),确保流量经过Charles/Burp。

2. 抓包分析

2.1 抓包配置

  1. 获取本机IP(ipconfig或Charles帮助菜单)。
  2. 在SocksDroid/Postern中配置代理(IP+端口)。
  3. 启动目标APP,进行登录操作(账号密码登录)。
  4. 观察抓包结果,重点关注signature等关键参数。

2.2 关键发现

  • 登录时即使不验证图片验证码,仍可抓包成功。
  • signature参数出现在登录请求中,是后续分析重点。

3. 绕过Root检测

3.1 检测逻辑分析

  • 反编译APK,搜索字符串"当前处于root环境,请注意账号安全"
  • 定位到br.bu()函数,其返回值决定是否弹窗:
    if (br.bu()) {
        showToast("当前处于root环境,请注意账号安全");
    }
    
  • bu()函数内部逻辑:
    return bF() || bG();  // 任意一个为true即触发检测
    

3.2 绕过方案

方案1:Hook bu()函数

function kugousign() {
    Java.perform(function () {
        let br = Java.use("com.kugou.common.utils.br");
        br["bu"].implementation = function () {
            console.log('bu is called');
            return false;  // 强制返回false
        };
    });
}
setImmediate(kugousign);

方案2:Hook bF()bG()函数

br["bF"].implementation = function () {
    console.log('bF is called');
    return false;
};
br["bG"].implementation = function () {
    console.log('bG is called');
    return false;
};

4. 绕过Frida检测

4.1 检测原理

  • APP通过ptrace占坑检测Frida(Frida附加进程时会占用ptrace)。
  • APP启动子进程提前附加父进程,导致Frida无法附加。

4.2 绕过方法

  1. 先启动Frida,但不附加进程:
    frida -U -f com.kugou.android -l sign.js
    
  2. 手动输入%resume,恢复进程执行。

5. Signature算法分析

5.1 定位关键函数

  • 通过抓包和反编译,定位到com.kugou.common.useraccount.b.u类。
  • signaturea()方法生成:
    public String a(String str, Map map, String str2) {
        // 生成signature的逻辑
    }
    

5.2 Frida验证

let w = Java.use("com.kugou.common.network.w");
w["a"].overload('java.lang.String', 'java.util.Map', 'java.lang.String').implementation = function (str, map, str2) {
    console.log('a is called: str=' + str + ', map=' + map + ', str2=' + str2);
    let ret = this.a(str, map, str2);
    console.log('a ret value is ' + ret);
    return ret;
};
  • 对比Frida输出和Charles抓包结果,确认signature生成逻辑正确。

5.3 参数分析

  • 固定参数
    • support_thirddev(设备型号)、platsupport_multisupport_verifygitversiont3
  • 可变参数
    • paramsclienttime_ms(时间戳)、dfidpkt1t2keyusername

6. 关键参数生成逻辑

6.1 pk参数

  • 生成逻辑:
    String pk = new ba().a(br.a(Long.valueOf(br.as()), com.kugou.common.config.c.a().b(com.kugou.android.app.a.a.lp), Integer.valueOf(br.F(KGCommonApplication.getContext())), Long.valueOf(currentTimeMillis)));
    
  • 分步解析
    1. br.as():返回固定值1005
    2. com.kugou.android.app.a.a.lp:固定字符串"OIlwieks28dk2k092lksi2UIkp"
    3. br.F():返回设备相关定值11709
    4. currentTimeMillis:当前时间戳(秒级)。
  • 最终拼接
    String keyRaw = "1005" + "OIlwieks28dk2k092lksi2UIkp" + "11709" + timestamp;
    
  • MD5加密
    String pk = MD5(keyRaw).substring(0, 16);  // 取前16位
    

6.2 dfid参数

  • 生成逻辑:
    String dfid = c.o.k();
    
  • 实际来源于com.kugou.common.preferences.a.c(5, ""),可写死。

6.3 t1t2参数

  • 来源
    • t1NativeParams.getToken()
    • t2NativeParams.getMachineIdCode()
  • SO层分析
    • 使用Frida定位到libj.so中的_d()_e()方法。
    • 使用Unidbg模拟执行:
      public class videos extends AbstractJni {
          public String calls() {
              String ret = NativeApi.callJniMethodObject(emulator, "_d(Ljava/lang/Object;)Ljava/lang/String;", arg1).getValue().toString();
              return ret;
          }
      }
      
    • 补环境
      • 需实现SecretAccess.getAndroidId()SecretAccess.getSafeDeviceId()
        @Override
        public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
            switch (signature) {
                case "com/kugou/common/utils/SecretAccess->getAndroidId()Ljava/lang/String;":
                    return new StringObject(vm, "5c083d1045985cf2");
                case "com/kugou/common/utils/SecretAccess->getSafeDeviceId()Ljava/lang/String;":
                    return new StringObject(vm, "null");
            }
            return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
        }
        

6.4 params参数

  • 生成逻辑:
    String params = a.b(str, str2);
    
  • 内部为AES加密:
    SecretKeySpec keySpec = new SecretKeySpec(str3.getBytes("UTF-8"), "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(str4.getBytes());
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
    byte[] encrypted = cipher.doFinal(str.getBytes());
    

7. 最终Signature生成

  • 步骤
    1. 将所有参数按key=value形式拼接。
    2. key升序排序。
    3. 拼接为字符串并MD5加密。
  • 示例
    String raw = "clienttime_ms=123456789&dfid=xxx&key=xxx&pk=xxx&t1=xxx&t2=xxx";
    String signature = MD5(raw);
    

8. 总结

  1. 抓包:确认关键参数(signaturepkdfid等)。
  2. 反编译:定位核心逻辑(com.kugou.common.useraccount.b.u)。
  3. Frida Hook:动态验证参数生成逻辑。
  4. Unidbg:模拟执行SO层代码(libj.so)。
  5. 补环境:处理缺失的JNI方法(如getAndroidId)。
  6. 算法还原:拼接参数 + MD5/AES加密。

通过以上步骤,可完整还原某音乐APP的签名算法,实现自动化请求。

某音乐APP逆向分析详细教学文档 1. 前置准备 1.1 工具清单 夜神模拟器(Android 9) :用于运行目标APP(照顾无真机用户)。 Pixel 1 真机 :用于后续SO层分析(Unidbg仅支持ARM架构)。 Frida :动态Hook工具,用于代码注入。 Jadx :APK反编译工具,用于静态分析。 Charles/Burp Suite :抓包工具,用于分析网络请求。 SocksDroid/Postern :代理工具,用于模拟器抓包。 目标APP(版本11.7.0) :待分析的某音乐APP。 1.2 注意事项 本文仅用于学习,若侵犯公司利益,请联系删除。 抓包时需配置代理(IP+端口),确保流量经过Charles/Burp。 2. 抓包分析 2.1 抓包配置 获取本机IP( ipconfig 或Charles帮助菜单)。 在SocksDroid/Postern中配置代理(IP+端口)。 启动目标APP,进行登录操作(账号密码登录)。 观察抓包结果,重点关注 signature 等关键参数。 2.2 关键发现 登录时即使不验证图片验证码,仍可抓包成功。 signature 参数出现在登录请求中,是后续分析重点。 3. 绕过Root检测 3.1 检测逻辑分析 反编译APK,搜索字符串 "当前处于root环境,请注意账号安全" 。 定位到 br.bu() 函数,其返回值决定是否弹窗: bu() 函数内部逻辑: 3.2 绕过方案 方案1:Hook bu() 函数 方案2:Hook bF() 和 bG() 函数 4. 绕过Frida检测 4.1 检测原理 APP通过 ptrace 占坑检测Frida(Frida附加进程时会占用 ptrace )。 APP启动子进程提前附加父进程,导致Frida无法附加。 4.2 绕过方法 先启动Frida ,但不附加进程: 手动输入 %resume ,恢复进程执行。 5. Signature算法分析 5.1 定位关键函数 通过抓包和反编译,定位到 com.kugou.common.useraccount.b.u 类。 signature 由 a() 方法生成: 5.2 Frida验证 对比Frida输出和Charles抓包结果,确认 signature 生成逻辑正确。 5.3 参数分析 固定参数 : support_third 、 dev (设备型号)、 plat 、 support_multi 、 support_verify 、 gitversion 、 t3 。 可变参数 : params 、 clienttime_ms (时间戳)、 dfid 、 pk 、 t1 、 t2 、 key 、 username 。 6. 关键参数生成逻辑 6.1 pk 参数 生成逻辑: 分步解析 : br.as() :返回固定值 1005 。 com.kugou.android.app.a.a.lp :固定字符串 "OIlwieks28dk2k092lksi2UIkp" 。 br.F() :返回设备相关定值 11709 。 currentTimeMillis :当前时间戳(秒级)。 最终拼接 : MD5加密 : 6.2 dfid 参数 生成逻辑: 实际来源于 com.kugou.common.preferences.a.c(5, "") ,可写死。 6.3 t1 和 t2 参数 来源 : t1 : NativeParams.getToken() t2 : NativeParams.getMachineIdCode() SO层分析 : 使用Frida定位到 libj.so 中的 _d() 和 _e() 方法。 使用Unidbg模拟执行: 补环境 : 需实现 SecretAccess.getAndroidId() 和 SecretAccess.getSafeDeviceId() : 6.4 params 参数 生成逻辑: 内部为AES加密: 7. 最终Signature生成 步骤 : 将所有参数按 key=value 形式拼接。 按 key 升序排序。 拼接为字符串并MD5加密。 示例 : 8. 总结 抓包 :确认关键参数( signature 、 pk 、 dfid 等)。 反编译 :定位核心逻辑( com.kugou.common.useraccount.b.u )。 Frida Hook :动态验证参数生成逻辑。 Unidbg :模拟执行SO层代码( libj.so )。 补环境 :处理缺失的JNI方法(如 getAndroidId )。 算法还原 :拼接参数 + MD5/AES加密。 通过以上步骤,可完整还原某音乐APP的签名算法,实现自动化请求。