安卓入门逆向实战番外篇(一) 某数日软件 破解内购,过服务器验证,对抗dex混淆

本文最后更新于 2024年10月16日 下午

时间过去一周多了,我们今天就来搞个番外篇,研究一下倒数日这个app的逆向获取vip的方法

那么搞完番外篇就到去除开屏广告的教程了,那我们废话不多说,开始今天的教程

准备工作

  • 一台电脑
  • jadx
  • 一台已root的手机
  • frida


查壳

这里我们可以用appmsg这个项目点我进行软件的查壳,如果感觉使用麻烦,可以直接使用mt管理器,那我们把安装包拷贝到手机上看看

注:我这里下错了版本,这个是旧版本,不是最新版

可以看到是未加固状态的,这里我们要记住com.clover.daysmatter这个包名,等下hook要用到


反编译分析

首先是安装这个app,打开mt管理器的activity记录,记录app所在的组件

打开这个app,同时启动服务,我们尝试在没有会员的情况下,使用会员功能

接下来轮到jadx出场了,直接搜索一下相关的字符,查看是否能出东西

可以看到,是搜索不出的,包括vip这个字眼也是,我们把视角回到mt管理器上的 0oo0000,搜索一下

找到这里来了,我们直接用fridahook一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_test(){
Java.perform(
function () {
var r = Java.use("com.clover.daysmatter.OO0000O")
r.OooO0O0.implementation = function (msg) {
console.log("函数被调用")
var t = this.OooO0O0(msg)
console.log("函数返回了"+ t)
return t
}
}
)
}
setImmediate(hook_test())

函数压根没触发

那么这条线索断了,我们尝试其他方法

这里我就想开始破解内购了,首先我们先从字符串定位一下,搜索支付成功有关的字眼

购买成功对应着cs_pay_success,那我们就在源代码搜索一下cs_pay_success,发现一个监听支付事件的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    public final void handleMessage(Message message) {
// 先传入一个message参数
// 然后开始定义 str str2 str3
// str为支付状态 str2是memo str3 是订单号
String str;
String str2;
if (message.what == 1) {
Map map = (Map) message.obj;
String str3 = null;
if (map == null) {
str = null;
str2 = null;
} else {
str = null;
str2 = null;
for (String str4 : map.keySet()) {
if (TextUtils.equals(str4, "resultStatus")) {
str = (String) map.get(str4); // 获取最后的支付结果
} else if (TextUtils.equals(str4, "result")) {
str3 = (String) map.get(str4);
} else if (TextUtils.equals(str4, "memo")) {
} else if (TextUtils.equals(str4, "outTradeNumber")) {
str2 = (String) map.get(str4);
}
}
}
C0567o0 c0567o0 = C0567o0.this;
c0567o0.getClass();
if (TextUtils.equals(str, "9000")) { // 可以看到 当str为9000就判断为支付成功
Toast.makeText(c0567o0.OooO00o, c0567o0.OooO00o.getString(com.clover.clover_cloud.R$string.cs_pay_success), 1).show(); // 这里为支付成功
CSAlipaySuccessModel cSAlipaySuccessModel = new CSAlipaySuccessModel(c0567o0.OooO0Oo, c0567o0.OooO0o0, str3, str2);
Context context = c0567o0.OooO00o;
String json = new Gson().toJson(cSAlipaySuccessModel);
C2072o0OO0O0.OooOO0O = json;
C2072o0OO0O0.OooO00o(context).edit().putString("CS_PREFERENCE_KEY_ALI_RESULT_INFO", json).apply();
String sku = cSAlipaySuccessModel.getSku();
C2116o0OOo000 OooO00o = C2116o0OOo000.OooO0oO.OooO00o(c0567o0.OooO00o);
String cSAlipaySuccessModel2 = cSAlipaySuccessModel.toString();
C3065oOo00OO.OooO0o(sku, "sku");
C3065oOo00OO.OooO0o(cSAlipaySuccessModel2, "purchaseInfo");
OooO00o.OooO00o.getApplicationContext().getSharedPreferences("PREFERENCE_NAME_LOCAL_PURCHASE", 0).edit().putString(sku, cSAlipaySuccessModel2).apply();
c0567o0.OooO0O0(cSAlipaySuccessModel, c0567o0.OooO0OO);
return;
}
Toast.makeText(c0567o0.OooO00o, c0567o0.OooO00o.getString(com.clover.clover_cloud.R$string.cs_pay_failed), 1).show(); // 否则为支付失败
}
}
}

因此我们可以使用android.os.Messagejava.util.HashMap重构对象,以下是frida代码

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_test(){
Java.perform(function () {
var OooO00o = Java.use("com.clover.daysmatter.o0$OooO00o");
var Message = Java.use("android.os.Message");
var k = Java.use("java.util.HashMap")
OooO00o["handleMessage"].implementation = function (msg) {
var t = Java.cast(msg, Message)
var u = Java.cast(t.obj.value, k)
u.put("resultStatus", "9000");
this["handleMessage"](msg);
};
});
}

测试一下,vip到手了

但是还没完,发现这个app还有二次验证,因此我们继续逆向


网络验证

对于这种情况,我们打开抓包工具看看,配合ssl关闭验证模块,查看一下网络请求,得知了他请求了一个获取订单信息的api

返回内容是

1
2
3
4
5
{
"products_info": {},
"unified_receipts_info": [],
"success": 0
}

通过定位字符串,但是这里混淆太厉害了,而且验证成功后无返回结果

注:这个app里的资源文件 反编译出来的验证成功字符串与支付无关,不要被混乱了


换思路

由于app在启动的时候,就会检查内购支付完成,因此我们可以hook启动的代码,搜索一下网络验证失败的字符串,定位到

以下是源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void OooO0O0(CSMarkPaidEntity cSMarkPaidEntity) {
final C2647oO0Oo0oo c2647oO0Oo0oo = (C2647oO0Oo0oo) this.OooO0O0;
C3065oOo00OO.OooO0o(c2647oO0Oo0oo, "$onPaymentFinishedListener");
final C2116o0OOo000 c2116o0OOo000 = (C2116o0OOo000) this.OooO0OO;
C3065oOo00OO.OooO0o(c2116o0OOo000, "this$0");
final C4044oooo000 c4044oooo000 = new C4044oooo000();
final ?? obj = new Object();
if (cSMarkPaidEntity != null && cSMarkPaidEntity.getSuccess()) { // 这里有个get_success 很可疑 我们进去hook一下
c4044oooo000.OooO = String.valueOf(cSMarkPaidEntity.getProducts_info());
obj.OooO = true;
}
o0OO0O0O.OooO0O0().execute(new Runnable() { // from class: com.clover.daysmatter.o0OOOooo
/* JADX WARN: Multi-variable type inference failed */
/* JADX WARN: Type inference failed for: r1v6, types: [java.lang.Object, java.lang.Runnable] */
@Override // java.lang.Runnable
public final void run() {
C2647oO0Oo0oo c2647oO0Oo0oo2 = C2647oO0Oo0oo.this;
C3065oOo00OO.OooO0o(c2647oO0Oo0oo2, "$onPaymentFinishedListener");
C3642ooO00ooO c3642ooO00ooO = obj;
C3065oOo00OO.OooO0o(c3642ooO00ooO, "$success");
C4044oooo000 c4044oooo0002 = c4044oooo000;
C3065oOo00OO.OooO0o(c4044oooo0002, "$text");
C2116o0OOo000 c2116o0OOo0002 = c2116o0OOo000;
C3065oOo00OO.OooO0o(c2116o0OOo0002, "this$0");
boolean z = c3642ooO00ooO.OooO;
Looper.getMainLooper();
Looper.myLooper();
Application application = (Application) c2647oO0Oo0oo2.OooO0O0;
if (z) {
C3854ooOoO00.OooO0oO(application, true);
} else {
Toast.makeText(application, application.getString(R.string.cs_check_ali_pay_failed), 1).show();
}
C0580p0.OooOOO0("pay_consumed", "alipay_success", BuildConfig.FLAVOR); // 支付成功
c2116o0OOo0002.OooO0O0();
C2447o0ooOOOO.OooO0O0().execute(new Object());
}
});
}

1
2
3
4
5
6
7
8
9
10
11
function main(){
Java.perfrom(function () {
let CSMarkPaidEntity =Java.use("com.clover.clover_cloud.models.user_entities.CSMarkPaidEntity");
CSMarkPaidEntity["getSuccess"].implementation = function () {
console.log(`CSMarkPaidEntity.getSuccess is called`);
let result = this["getSuccess"]();
console.log(`CSMarkPaidEntity.getSuccess result=${result}`);
return true;
};
})
}

最后vip验证成功,各项功能正常


安卓入门逆向实战番外篇(一) 某数日软件 破解内购,过服务器验证,对抗dex混淆
http://blog.bingyue.top/2024/10/16/an_zhuo_ni_xiang_shi_zhan_fan_wai_1/
作者
bingyue
发布于
2024年10月16日
许可协议