最近在在学习绘画人体,从网上找到了个还不错的人体结构模型的app,但是里面就一个骨骼模型免费,其他的都需要会员,于是乎想着直接逆向一下,看看能不能白嫖
经过三分钟的分析,发现虽然代码有混淆,但是难度还是比较简单的
应用名称:3D速写
包名:com.model.sketch3d
版本:1.6.0
下载:小米商店
所需工具:MT管理器
打开应用先注册,没有任何验证码,输入手机号密码就注册成功了
打开首页随便点击一个付费模型,跳到了支付页面并弹出toast “您还不是会员,或者会员已过期,请先购买会员!”
有弹窗就很好办了,我们可以用算法助手这类工具直接定位,但是也不知道为什么,这里算法助手pro并没有抓到这个toast
不过既然是字符串,我们也可以走另一条路子,直接去资源表里找,所以直接打开resources.arsc,搜索“过期”,直接就找到了那个toast的内容及其名称
紧接着我们可以直接去dex里搜索这个名称,有三个结果
第一个R类是负责资源索引的,app里的所有字符串,图片,布局等资源都会被分配一个id并写道R类里,我们看第二个和第三个
进入第二个,反编译一下
[Java] 纯文本查看 复制代码 package com.model.sketch3d.adapter;
import android.content.Intent;
import android.view.View;
import com.hjq.toast.Toaster;
import com.model.sketch3d.R;
import com.model.sketch3d.data.ArCategoryBean;
import com.model.sketch3d.ui.ar.ArCameraActivity;
import com.model.sketch3d.ui.mine.BuyVipActivity;
import kotlin.jvm.internal.k;
import p7.a;
import q7.l;
public final class c extends k implements l {
final /* synthetic */ ArCategoryBean $item;
final /* synthetic */ d this$0;
public final void invoke(View view) {
a.x(view, "it");
if (!t5.c.e()) {
Toaster.show("请先登录");
} else if (!this.$item.isVipContent() || t5.c.f()) {
ArCameraActivity.s.g(this.this$0.a, this.$item.getCover(), String.valueOf(this.$item.getId()));
} else {
Toaster.show(this.this$0.a.getString(R.string.vip_overdue_hint));
this.this$0.a.startActivity(new Intent(this.this$0.a, BuyVipActivity.class));
}
}
public c(ArCategoryBean arCategoryBean, d dVar) {
this.$item = arCategoryBean;
this.this$0 = dVar;
super(1);
}
}
一个if判断,然后负责toast弹窗的代码在else里,中间那个else if很可疑,看他的判断条件
!this.$item.isVipContent() || t5.c.f()
一个或运算,前面是判断是不是会员内容然后取反,后面是t5.c.f()方法,如果运算结果为真就加载arcamera,也就是进入模型详细页面,如果为假就弹出过期toast然后跳转购买页面
所以我们可以很大程度上猜测,t5.c.f()这个方法就是判断是否是会员的关键方法,我们追进去看看
[Java] 纯文本查看 复制代码 package t5;
import com.google.common.primitives.a;
import com.google.gson.n;
import com.model.sketch3d.data.UserInfoBean;
import com.tencent.mmkv.MMKV;
import h7.m;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import kotlin.text.v;
import org.json.JSONException;
public abstract class c {
public static final m a = a.W(b.INSTANCE);
public static final n b = new n();
public static boolean f() {
UserInfoBean d = d();
boolean z = false;
if (d == null) {
return false;
}
Integer accountType = d.getAccountType();
if (accountType != null) {
if (accountType.intValue() == 1) {
String becomeEndTime = d.getBecomeEndTime();
if (becomeEndTime != null) {
if (becomeEndTime.length() != 0) {
Date parse;
String becomeEndTime2 = d.getBecomeEndTime();
becomeEndTime = "yyyy-MM-dd HH:mm:ss";
p7.a.x(becomeEndTime2, "strTime");
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(becomeEndTime);
TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
p7.a.w(timeZone, "getTimeZone(...)");
simpleDateFormat.setTimeZone(timeZone);
parse = simpleDateFormat.parse(becomeEndTime2);
} catch (ParseException e) {
e.printStackTrace();
parse = null;
}
if ((parse == null ? 0 : parse.getTime()) > System.currentTimeMillis()) {
z = true;
}
}
}
}
}
return z;
}
果然是关键代码,具体逻辑如下
1.获取当前登录用户信息对象,如果用户对象为null,直接返回false
2.读取账号类型,只有 accountType == 1 才会走会员过期判断逻辑,其他账号类型直接返回 false
3.获取用户vip到期时间和当前时间并比较判断,大于的话返回z=true
进入第三个结果,大差不差,只是这个不是模型的判断,而是参考图片的判断,和模型的逻辑一样。
[Java] 纯文本查看 复制代码 package com.model.sketch3d.ui.ar;
import android.content.Intent;
import android.view.View;
import com.hjq.toast.Toaster;
import com.luck.picture.lib.basic.PictureSelector;
import com.luck.picture.lib.config.SelectMimeType;
import com.luck.picture.lib.style.BottomNavBarStyle;
import com.luck.picture.lib.style.PictureSelectorStyle;
import com.luck.picture.lib.style.SelectMainStyle;
import com.luck.picture.lib.style.TitleBarStyle;
import com.model.sketch3d.R;
import com.model.sketch3d.ui.login.LoginActivity;
import com.model.sketch3d.ui.mine.BuyVipActivity;
import p7.a;
import q7.l;
import t5.c;
public final class k extends kotlin.jvm.internal.k implements l {
final /* synthetic */ ArDrawingActivity this$0;
public final void invoke(View view) {
a.x(view, "it");
if (!c.e()) {
this.this$0.startActivity(new Intent(this.this$0, LoginActivity.class));
} else if (c.f()) {
ArDrawingActivity arDrawingActivity = this.this$0;
int i = ArDrawingActivity.o;
arDrawingActivity.getClass();
PictureSelectorStyle pictureSelectorStyle = new PictureSelectorStyle();
pictureSelectorStyle.setTitleBarStyle(new TitleBarStyle());
pictureSelectorStyle.setBottomBarStyle(new BottomNavBarStyle());
SelectMainStyle selectMainStyle = new SelectMainStyle();
selectMainStyle.setSelectNormalTextSize(14);
pictureSelectorStyle.setSelectMainStyle(selectMainStyle);
PictureSelector.create(arDrawingActivity).openGallery(SelectMimeType.ofImage()).setSelectorUIStyle(new PictureSelectorStyle()).setImageEngine(t5.a.a).setSelectionMode(1).setCameraImageFormat(".jpeg").isDirectReturnSingle(true).forResult(new o(arDrawingActivity));
} else {
Toaster.show(this.this$0.getString(R.string.vip_overdue_hint));
this.this$0.startActivity(new Intent(this.this$0, BuyVipActivity.class));
}
}
public k(ArDrawingActivity arDrawingActivity) {
this.this$0 = arDrawingActivity;
super(1);
}
}
最后,我们直接对t5.c.f()这个方法进行hook,强制返回true,重新打开软件,再次点击会员模型,成功白嫖
|