某习惯app的脱壳分析与破解
某习惯app的脱壳分析与破解
参考了下面的博文学习
某习惯app的Signature分析与vip破解 | twogoat/showmakerの小站
需要app名联系我
几种常见的脱壳相关工具
反编译:
Jdax:https://github.com/skylot/jadx
同时支持命令行和图形界面,能以最简便的方式完成apk的反编译操作。
jd-gui:http://jd.benow.ca/
类似jadx
Dex2jar:http://sourceforge.net/projects/dex2jar/files/
类似jadx
ApkTool :https://bitbucket.org/iBotPeaches/apktool/downloads/
ApkTool 的最重要的两个作用是解包和打包 ;
解包 : 拿到 APK 文件 , 如果按照 zip 格式解压出来 , xml 文件都是乱码 ; APK 文件打包时 , 会将 xml 文件进行压缩转为二进制文件 , 以减小体积 ; 解包时 , 必须使用 ApkTool 解包工具 , 将二进制数据格式的 xml 文件转为 文本 xml 文件 , 才能获取刻度的 xml 文件 ;
打包 : 将使用 ApkTool 工具解包后的零散文件 , 再次打包成 APK 文件 ,
Frida脱壳工具
FRIDA-DEXDump: https://github.com/hluwa/FRIDA-DEXDump
脱壳
MT管理器可以看到是有360加固的
| 公司名称 | 对应的壳包名 |
|---|---|
| 爱加密 | libexec.so,libexecmain.so,ijiami.dat |
| 梆梆 | libsecexe.so,libsecmain.so , libDexHelper.so libSecShell.so |
| 360 | libprotectClass.so,libjiagu.so,libjiagu_art.so,libjiagu_x86.so |
| 百度 | libbaiduprotect.so |
| 腾讯 | libshellx-2.10.6.0.so,libBugly.so,libtup.so, libexec.so,libshell.so,stub_tengxun |
| 网易易盾 | libnesec.so |
用查壳工具,发现是360加固
moyuwa/ApkCheckPack: apk加固特征检查工具,汇总收集已知特征和手动收集大家提交的app加固特征,全网最全开源加固特征,支持40个厂商的加固检测,欢迎大家提交无法识别的app
frida-ps -Ua
我用的frida版本是frida16.4.2 用17的高版本 启动frida 会出现白屏
直接frida-dexdump -U -f xxxx -o E:\Desktop\dump\
或者直接frida-dexdump -FU -o E:\Desktop\dump\
1 | 指定App的应用名称:frida-dexdump -U -n 保利票务 |
全部拖入jadx
注意的是,这里需要为no
请求Signature分析
配一下小黄鸟或者burp抓包环境
小黄鸟我这配了有点问题,因此用burp
完整教程:【burp手机真机抓包】Burp Suite 在真机(Android and IOS)抓包手机APP + 微信小程序详细教程 - yangykaifa - 博客园
可以看到一些信息,测试下接口,随便点一些东西找一下post
根据几个接口定位到
跟踪到
其实就是分析activateCode
1
2
3
4
5
6
7 findViewById(R$id.sms1).setOnClickListener(new a());
findViewById(R$id.activationCodeBtn).setOnClickListener(new View.OnClickListener() { // from class: p1.w4
// android.view.View.OnClickListener
public final void onClick(View view) {
GenerateActionCodeActivity.this.O(view);
}
});调用O
1
2
3
4
5
6
7
8
9
10
11
12 /* JADX INFO: Access modifiers changed from: private */
public /* synthetic */ void O(View view) {
Q();
}
private void Q() {
new MaterialDialog.Builder(this).G("兑换会员").l("请输入激活码", "", new MaterialDialog.f() { // from class: p1.y4
// com.afollestad.materialdialogs.MaterialDialog.f
public final void onInput(MaterialDialog materialDialog, CharSequence charSequence) {
GenerateActionCodeActivity.this.P(materialDialog, charSequence);
}
}).E();
}
1
2
3
4 public /* synthetic */ void P(MaterialDialog materialDialog, CharSequence charSequence) {
if (!TextUtils.isEmpty(charSequence)) {
M(charSequence.toString().trim());
}接下来
1
2
3
4 private void M(String str) {
String str2 = "{\"activeCode\":\"" + str + "\"}";
i9.a.l(a.e.f22775v).m3292upJson(str2).execute(new c(String.class, str2));
}调用“兑换激活码”的接口
i9.a.l(a.e.f22775v)是构造请求对象
执行请求(execute),并传入回调,new 就是“创建一个处理器对象”,创建一个回调处理器,网络请求结束后由它处理结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 public class c extends DailyCallBack<String> {
c(Class cls, String str) {
super(cls, str);
}
// com.itally.base.data.DailyCallBack
public void loadSuccess(DailyResponse<String> dailyResponse) {
if (dailyResponse.isSuccessCode()) {
return;
}
q4.o.a().d(GenerateActionCodeActivity.this, !TextUtils.isEmpty(dailyResponse.getMsg()) ? dailyResponse.getMsg() : "兑换失败~");
}
// m9.a, m9.b
public void onError(u9.e<DailyResponse<String>> eVar) {
super.onError(eVar);
q4.o.a().d(GenerateActionCodeActivity.this, "兑换失败~");
}
}
因此可以跟踪到DailyCallBack
DailyCallBack 是所有网络请求的回调类,负责:
- 发请求前自动生成签名 (signature)
- 统一设置请求头(时间戳、nonce 等)
- 统一 JSON 解析 DailyResponse
- 拦截
第一部分:构造函数
1 | public DailyCallBack(Class cls, String str) { |
aClass 是返回数据的类型,例如 ActiveCode.classcontent 是你发送的 JSON(请求体)
content 会被写入签名算法中,这是重点!
第二部分:onStart() —— 请求开始前自动生成签名
1 | @Override |
做了几件重要事:
添加时间戳
1 | String valueOf = String.valueOf(z8.c.a()); |
生成随机 nonce
1 | uuid = UUID.randomUUID().toString().replace("-", ""); |
准备签名字段
签名字段包括:
- 请求体 JSON:
this.content - header 里的 channel
- deviceinfo
- platform
- clientversion
- deviceid
- timestamp
- nonce
按顺序加入 list:
1 | arrayList.add(this.content); //请求体 |
排序(按字典序)
1 | Collections.sort(arrayList); |
拼接成大字符串
1 | for (String str : arrayList) { |
用 KEY”签名”
1 | String e10 = d.e(KEY.getBytes(), stringBuffer.toString().getBytes()); |
这里 KEY 是:
1 | 06fdrlDr625oTBbW |
这就是 签名密钥。
签名算法是 d.e()
放入请求头
1 | cVar.headers("signature", e10); |
看到这里,你应该已经明白:
想伪造 API,就必须伪造 signature,因为服务器一定会验证 signature。
幸运的是:
- KEY 是硬编码的
- 签名算法 d.e() 也在 APK 中
完全可以:
- 逆向 d.e() 算法
- 自己用 Python/JS 生成 signature
- 或用 Frida hook d.e() 拿返回值
- 或直接篡改 signature 验证逻辑(patch)
第三部分:onSuccess
1 | public void onSuccess(e<DailyResponse<T>> eVar) { |
就是把解析结果交给业务层。
第四部分:convertResponse —— JSON 解析器
1 | DailyResponse<T> dailyResponse2 = |
返回结构是:
1 | { |
如果 code 是 401:
1 | EventBus.post(new Respons401(...)); |
也可以伪造成功返回
只要你 hook:
1 | DailyResponse.getCode() 返回 0 |
整个 APP 就认为激活成功。
题外话,如果不是硬编码的key有什么对抗手段?
方案 1:将签名逻辑放到服务器(完全不在客户端处理)(最安全)
客户端不做加密、不做签名,只做:
1 | - 身份凭证(token) |
方案 2:使用短时效动态秘钥(临时 key)
流程如下:
- 客户端启动时向服务器请求一个临时 key
- 服务器返回一个 5 秒有效/一次性 key
- 客户端用临时 key 生成 signature
- key 过期自动失效
方案 3:使用“服务端签名 + 前端签名混合模式”(抗伪造)
客户端只参与一部分签名,例如:
1 | signature = HMAC(server_key, server_data) + MD5(client_data) |
或者:
- 客户端参与“弱签名”
- 服务器做核心签名
算法追踪到
是一个标准 HMAC-SHA256 签名工具类
这里直接改了下某习惯app的Signature分析与vip破解 | twogoat/showmakerの小站
的代码
1 | import hmac |
Vip破解
我们不应该hook isVip(),为什么在另一篇里讲过了
1 | function hook_user(){ |
ps:frida -U -p 13937 -l xiaoxiguan.js
要用attach去hook而非spwn
because:
- Frida 启动 App 的进程
- App 还没开始加载 dex
- Frida 就执行你的脚本
- 此时 Java 层 尚未初始化
- 所以
UserInfo、com.itally.*全部都还没加载 - 因此
Java.use("xxxx")→ ClassNotFound
APP还没加载 Java 类时去 Hook,当然找不到。



























