本帖最后由 Bu弃 于 2018-8-16 13:36 编辑
Hello Everyone~ 在论坛看到Rooking表哥发的“多开分身最新版7.2永久VIP版[size=10.5000pt]”(https://www.chinapyg.com/thread-120623-1-1.html)一时手痒,自己也抽空分析了下,使用Xposed实现永久VIP。下面是分析全过程~~ 另:特别感谢周年群中的大表哥们提供脱壳后的dex文件。
一、观察
在着手分析APK前,我们先装上app,观察它的一些特征。
在“个人中心”页面,有显示是否为会员,以及到期时间。如果我们第一次运行的时候是断网的,会提示请连接网络什么的。在这里,我们可以确定一件事:app在打开时会请求服务器,获取到期时间以及是否为会员,还有广告等等。
二、分析
[size=14.0000pt] 上面说到了,应用会联网获取到期时间等信息,那么我们可以通过抓包的方式,来确定app发送的请求。然后通过该url进行全局搜索。更加容易定位到关键处。这里我使用Fiddler来抓包。
Fiddler配置我就不说了,百度一大堆。抓包结果如下:
为什么确定是这些?因为该app的官网就是91xxx.cn.接下来一个个看,于是发现该连接最可疑..
Jadx打开脱壳后的dex(由于某些原因,这里就不传dex了),Navigation->Text Search
搜索链接“/ServerV60?fn=it”结果有很多。如下
我们点开第一个观察先。代码如下:
[Java] 纯文本查看 复制代码
private void a() {
this.g = true;
try {
String str;
CoreEntity a = new a(this).a();
Object obj = "测试";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("maxCore = ");
stringBuilder.append(a == null ? 0 : a.getCode());
j.a(obj, stringBuilder.toString());
GetBuilder getBuilder = (GetBuilder) OkHttpUtils.get().url("http://chaos.91ishare.cn/ServerV60?fn=it"); //这就是请求的连接
String str2 = "o";
if (a == null) {
str = "0";
} else {
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append(a.getCode());
stringBuilder2.append("");
str = stringBuilder2.toString();
}
getBuilder.addParams(str2, str).build().execute(new b(this) { //拼接参数并执行请求
final /* synthetic */ Splash a;
{
this.a = r1;
}
public /* synthetic */ void onResponse(Object obj, int i) { //服务器响应后回调,传回一个Json
a((JSONObject) obj, i);
}
public void onError(Call call, Exception exception, int i) { //连接失败时回调
u.a(this.a, "网络连接失败");
this.a.g = false;
this.a.c.setVisibility(8);
this.a.ll_no_networks.setVisibility(0);
}
public void a(JSONObject jSONObject, int i) { //如果服务器有响应,就会在onResponse中执行改方法
if (jSONObject == null) {
return;
}
if (com.bly.dkplat.a.a.a().a(jSONObject) != null) {
this.a.b();
return;
}
this.a.c.setVisibility(8);
u.a(this.a, "网络连接失败");
this.a.g = false;
this.a.ll_no_networks.setVisibility(0);
}
});
} catch (Exception e) {
e.printStackTrace();
u.a(this, "初始化失败");
this.g = false;
this.ll_no_networks.setVisibility(0);
}
}
我们点开com.bly.dkplat.a.a.a().a(jSONObject)这个方法进去看看。很遗憾,jadx没有反编译出这个方法体。所以我们只能打开JEB,找到这个类。代码如下:
[Java] 纯文本查看 复制代码
public boolean a(JSONObject arg9) {
__monitor_enter(this);
try {
j.a("initCacheByApiResult", arg9);
if(!StringUtils.isBlank(i.a(arg9, "err"))) {
goto label_127;
}
//看到这里就很熟悉了,这就是抓包抓到的返回,arg9就是传递过来的json。i.a()就是读取该json中的对应的数据
a.a().a(i.a(arg9, "et", 0)); //这里是读取过期时间,为什么这么说呢,可以去看抓到的内容,et的值是一个时间戳,转换下就是到期时间。可以自己去尝试下
a.a().a(i.a(arg9, "iv", 0)); //这个就是是否为vip
a v0 = a.a();
boolean v2 = i.a(arg9, "sa", 0) == 1 ? true : false;
v0.a(v2);
Log.e("广告测试", "sa = " + a.a().c());
boolean v0_1 = a.a().c();
String v2_2 = Application.IMEI;
boolean v4 = a.a().g() == 1 ? true : false;
com.bly.dkplat.utils.b.i.a(v0_1, v2_2, v4, a.a().f());
a.a().a(i.a(arg9, "m"));
a.a().b(i.a(arg9, "kf"));
a.a().b(i.a(arg9, "qt", 0));
a.a().c(i.a(arg9, "yz"));
a.a().d(i.a(arg9, "au"));
a.a().f(i.a(arg9, "adp"));
h.a(i.e(arg9, "os"));
a.a().c(i.a(arg9, "fk", 0));
v0 = a.a();
v2 = i.b(arg9, "fc") > 0 ? true : false;
v0.c(v2);
a.a().d(i.a(arg9, "ud", -1));
a.a().e(i.a(arg9, "wk", 0));
String v9_1 = i.a(arg9, "rgps");
if(StringUtils.isNotBlank(v9_1)) {
a.a().e(v9_1);
}
this.t();
a.a().b(true);
if(this.j != 1) {
long v6 = this.j - 86400000;
if(System.currentTimeMillis() < v6) {
com.bly.dkplat.utils.a.a(Application.getInstance(), v6);
}
else {
com.bly.dkplat.utils.a.a(Application.getInstance());
}
}
else {
com.bly.dkplat.utils.a.a(Application.getInstance());
}
if(!this.d()) {
d.a();
c.c();
}
}
catch(Throwable v9) {
__monitor_exit(this);
throw v9;
}
__monitor_exit(this);
return 1;
label_127:
__monitor_exit(this);
return 0;
}
其他的我们就不分析了,就把vip和到期时间无限制先实现。其余的有空再慢慢研究~~。我们可以看到调用a.a().a(),把从json读取的值传过去。我们点进去看看。代码如下:
[Java] 纯文本查看 复制代码
public void a(String arg1) {
this.l = arg1; //赋值给l。
}
从这大概可以看出来了大致流程了。即,从服务器读取数据,然后解析json,给类中的属性赋值。而后就是通过该类中的属性进行判断和显示。
到这,我们已经有了Xposed的思路了。只要Hook了给类中属性赋值的方法就可以达到目的了。
哦,对了,还有个东西忘了说了。既然我们知道只要Hook了属性赋值的地方就可以了,那么这个值是多少合适?既然我们已经知道过期时间是通过值“l”判断的,那么我们看看有什么地方调用了它。在JEB中选中该属性,右键->Cross references 查看调用
这里获取值,按道理应该是调用get方法,所以该方法应该没有参数。于是锁定了最后一个f()方法。同样,选中该方法,右键->Cross references 查看调用.然后这里又有个问题,我的JEB是没有反编译该方法的。所以只能在jadx中看了。在jadx中找到f()方法,选中,右键->Find Usage
我们发现第三个有点可疑,点过去看下。代码如下:
[Java] 纯文本查看 复制代码
public void f() {
k.a(getTag(), "UserFragment initDatas run");
this.tvExpired.setText(Html.fromHtml(StringUtils.getExpiredString(a.a().f()))); //这里就是调用f()方法的地方
if (a.a().f() == 1) {
this.tvBtnBuyVip.setVisibility(8);
} else {
this.tvBtnBuyVip.setVisibility(0);
}
.... //中间的代码省略
if (a.a().g() == 2) {
this.tvMemberName.setText("体验会员");
this.tvMemberName.setTextColor(getResources().getColor(R.color.userTiYan));
this.ivMemberIcon.setImageResource(R.drawable.icon_v_2);
return;
}
点进StringUtils.getExpiredString()方法中看下,他做了些什么
[Java] 纯文本查看 复制代码
public static String getExpiredString(long j) {
if (!a.a().d()) { //a.a().d() 这个就是判断是否为会员
return "<font color='#fe022b'>已到期</font>";
}
if (j == 1) { //j就是我们的过期时间。如果是1 就是无限制
return "<font color='#ffb335'>无限制</font>";
}
if (j == 0) {
return "<font color='#fe022b'>已到期</font>";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<font color='#ffb335'>");
stringBuilder.append(d.a(new Date(j), "yyyy-MM-dd"));
stringBuilder.append("</font>");
return stringBuilder.toString();
}
那么到期时间的值解决了,那是否为会员的呢?我们点进a.a().d()中去看看(注意:我在jadx中点不进去,所以又去了JEB...)
[Java] 纯文本查看 复制代码
public boolean d() {
boolean v0 = this.k > 0 ? true : false; //this.k 的值就是json中iv的值。这里也就是说,只要iv>0就是会员。所以我们iv的值就设置>0的数
return v0;
}
我们看看“体验会员”是怎么判断的。
a.a().g()方法的代码如下:
[Java] 纯文本查看 复制代码
public int g() {
return this.k;
}
到这里我们会员的取值也出来了,必须>0 。并且不能为2 所以我们就设置成1吧~~ 其他的值可自试~~
三、编写插件
[size=14.0000pt] Xposed的编写就不示范了,百度一大把。我的代码如下(代码写的很垃圾,勿喷。只为实现功能):
[size=14.0000pt] [Java] 纯文本查看 复制代码
public class Hook implements IXposedHookLoadPackage{ @Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if("com.bly.dkplat".equals(lpparam.packageName)){
XposedBridge.log("多开分身 开始Hook...");
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader classLoader = ((Context) param.args[0]).getClassLoader();
Class<?> aClass = classLoader.loadClass("com.bly.dkplat.a.a");
if(aClass!=null){
//过期时间
XposedHelpers.findAndHookMethod(aClass, "a", long.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { //在方法执行前执行
param.args[0] = 1L; //设置第一个参数值
}
});
//是否为vip
XposedHelpers.findAndHookMethod(aClass, "a", int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {//在方法执行前执行
param.args[0] = 1; //设置第一个参数值
}
});
}else{
XposedBridge.log("多开分身 class not found please restart app ...");
}
XposedBridge.log("多开分身 Hook结束...");
}
});
}
}
}
四、结果
[size=14.0000pt]
最后,希望大家多多评分~~ 最后还是得问问,论坛啥时候上MarkDown插件~~不然帖子好丑。。。再多说一句,本文仅做知识交流,如果大家喜欢这款App,希望大家能够支持正版。
感谢各位表哥观看~~~
[size=14.0000pt]
|