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 抓包配置
- 获取本机IP(
ipconfig或Charles帮助菜单)。 - 在SocksDroid/Postern中配置代理(IP+端口)。
- 启动目标APP,进行登录操作(账号密码登录)。
- 观察抓包结果,重点关注
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 绕过方法
- 先启动Frida,但不附加进程:
frida -U -f com.kugou.android -l sign.js - 手动输入
%resume,恢复进程执行。
5. Signature算法分析
5.1 定位关键函数
- 通过抓包和反编译,定位到
com.kugou.common.useraccount.b.u类。 signature由a()方法生成: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_third、dev(设备型号)、plat、support_multi、support_verify、gitversion、t3。
- 可变参数:
params、clienttime_ms(时间戳)、dfid、pk、t1、t2、key、username。
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))); - 分步解析:
br.as():返回固定值1005。com.kugou.android.app.a.a.lp:固定字符串"OIlwieks28dk2k092lksi2UIkp"。br.F():返回设备相关定值11709。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 t1和t2参数
- 来源:
t1:NativeParams.getToken()t2:NativeParams.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); }
- 需实现
- 使用Frida定位到
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生成
- 步骤:
- 将所有参数按
key=value形式拼接。 - 按
key升序排序。 - 拼接为字符串并MD5加密。
- 将所有参数按
- 示例:
String raw = "clienttime_ms=123456789&dfid=xxx&key=xxx&pk=xxx&t1=xxx&t2=xxx"; String signature = MD5(raw);
8. 总结
- 抓包:确认关键参数(
signature、pk、dfid等)。 - 反编译:定位核心逻辑(
com.kugou.common.useraccount.b.u)。 - Frida Hook:动态验证参数生成逻辑。
- Unidbg:模拟执行SO层代码(
libj.so)。 - 补环境:处理缺失的JNI方法(如
getAndroidId)。 - 算法还原:拼接参数 + MD5/AES加密。
通过以上步骤,可完整还原某音乐APP的签名算法,实现自动化请求。