APK逆向-以某赛车小游戏为例
这一款小游戏以前刚有手机的时候玩过,还不错,挺经典的。也算是少年回忆,不能说是童年回忆。
环境:Android 12 已Root;Jadx:1.4.4 apktool:2.5.0
安装
- 安装后打开,可以正常打开
- 遇到了两个问题,一是这个游戏要位置和存储权限,一个单机游戏,存储权限可能是为了要保存一些游戏资料,位置权限用来干什么?还好拒绝之后可以正常进入游戏,不像有的软件不给权限就无法正常使用。
- 第二个问题是实名制的问题,这里先看一下实名制具体的逻辑代码,正好也对这一块的验证比较好奇。
- 经过对实名界面的分析,可以得知代码逻辑在
com.mygamez.common.antiaddiction.``IDCheckDialog
。其中this.guest.setVisibility(8);
中的数字8代表了视图的可见性状态,0表示可见,4表示视图不可见,但仍然占据布局空间.8表示视图不可见,但不占据布局空间,从上图看出8让button不可见,而且不占据空间。
- 测试一下张伟这个身份信息能不能成功验证。尝试了发现不行,接着看代码吧。找一下这个Dialog的调用,还好只有一个。
- 我们可以尝试将这个判断取反,来暂时不进行实名,但是我们不能这样做,要遵守相关的法律法规,这里只是在技术层面上饶过一个东西。
- 在上面的修改过程中,发现修改一个if后会走向下面没有游戏剩余时间的if,还要改一下这里的判断语句。并且点击确认后会直接退出。
- 修改了之后发现可以进游戏,但是是以游客模式进行游戏,然后还会跳出另外一个提示,提示实名。挺好的,对实名要求挺严格的。
- 继续分析实名认证的逻辑。之前输入信息点击实名后,会进入
AntiAddictionManager.INSTANCE.get().doRidCheck(upperCase, obj, **new** AntiAddictionManager.AntiAddictionManagerInstance.RidCheckCallback()
- 通过调用
AntiAddictionManager.INSTANCE.get().doRidCheck()
方法进行实名认证检查。该方法接受一个参数RidCheckCallback
,用于处理实名认证检查的结果。如果认证检查结果为有效(z
为true
),则保存验证信息并设置用户数据。如果认证检查结果为无效,则将游客模式设置为true
。这里要任意信息实名通过,就将Z的判断取反,注意要把之前的修改改回原来的代码,否则不会进入实名的阶段。- 继续跟进
doRidCheck()
方法,this.service.requestRidCheck()
是一个请求实名认证检查的网络请求方法,
- 继续跟进
this.service.requestRidCheck(this.sessionId, new RidCheckRequest(str2, str), new ResponseCallback<PlayerDataResponse>() { // from class: com.mygamez.common.antiaddiction.AntiAddictionManager.AntiAddictionManagerInstance.12
@Override // com.mygamez.common.antiaddiction.api.ResponseCallback
public void onResponse(PlayerDataResponse playerDataResponse) {
AntiAddictionManagerInstance.logger.i("MySDK_AA", "RidCheck response: " + playerDataResponse.toString());
ridCheckCallback.onResult(playerDataResponse.getAge() != -1, playerDataResponse.getAge());
}
@Override // com.mygamez.common.antiaddiction.api.ResponseCallback
public void onFailure(int i, String str3) {
AntiAddictionManagerInstance.logger.e("MySDK_AA", "RidCheck failed: " + i + " " + str3);
if (i == 403) {
ridCheckCallback.onError(i, "Out of time");
return;
}
ridCheckCallback.onError(i, str3);
if (i != 401) {
return;
}
EventBus.getDefault().post(new SessionExpiredEvent());
}
});
return;
}
ridCheckCallback.onError(-1, "AntiAddiction system disabled");
}
- 继续看实名认证的网络请求,这里只定义了个接口,具体实现要去Service中去看。
public void requestRidCheck(final String str, final RidCheckRequest ridCheckRequest, final ResponseCallback<PlayerDataResponse> responseCallback) {
this.executor.execute(new Runnable() { // from class: com.mygamez.common.antiaddiction.api.MyGamezAntiAddictionApiService.6
@Override // java.lang.Runnable
public void run() {
String str2 = "https://antiaddiction.******.cn/api/v1" + EndPoint.IDENTIFY;
try {
HttpCaller2 httpCaller2 = HttpCaller2.getInstance(str2, new Gson().toJson(ridCheckRequest), HttpCaller2.RequestMethod.POST);
HashMap hashMap = new HashMap();
hashMap.put("x-session-id", str);
httpCaller2.setHeaderFields(hashMap);
HttpResponse makeRequest = httpCaller2.makeRequest();
int statusCode = makeRequest.getStatusCode();
if (statusCode == 200) {
MyGamezAntiAddictionApiService.this.notifyResult(MyGamezAntiAddictionApiService.this.parsePlayerDataResponseJson(makeRequest.getResponseBody()), responseCallback);
} else if (statusCode != 422) {
MyGamezAntiAddictionApiService.this.notifyError(makeRequest.getStatusCode(), makeRequest.getMessage(), responseCallback);
} else {
MyGamezAntiAddictionApiService.this.notifyError(makeRequest.getStatusCode(), MyGamezAntiAddictionApiService.this.parseErrorMessageJson(makeRequest.getMessage()).toString(), responseCallback);
}
} catch (JsonSyntaxException e) {
MyGamezAntiAddictionApiService.this.notifyError(-1, "Invalid JSON: " + e.getMessage(), responseCallback);
} catch (MalformedURLException unused) {
MyGamezAntiAddictionApiService.this.notifyError(-1, "Invalid URL: " + str2, responseCallback);
} catch (IOException e2) {
MyGamezAntiAddictionApiService.this.notifyError(-1, "HTTP request failed: " + e2.getMessage(), responseCallback);
} catch (Exception e3) {
MyGamezAntiAddictionApiService.this.notifyError(-1, e3.getMessage(), responseCallback);
}
}
});
}
- 查了一下相关资料,实名应该是向有关部门的实名认证发送请求验证。这里就不深究了,支持实名,遵纪守法!。
解锁关卡,金币充值
- 进入游戏会发现有的车辆是无法解锁的。解锁车辆需要金币或者砖石,而这需要充值。
- 点击充值会直接显示充值失败,然后提示安装微信,然后还提示不让卸载或者清除数据,根据这一提示我们可以猜的游戏的数据都是本地保存的,不会做一些线上的认证。这样也给了我们之间修改本地数据的余地。
- 分析游戏MainActivity的代码逻辑,根据Activity记录来看,主要的逻辑全在MainActivity,包括充值逻辑。
- 通过关键字Coin找到下面一个关于获取金币的函数,发现调用了
InAppPurchaseStore.getCoins
- 通过关键字Coin找到下面一个关于获取金币的函数,发现调用了
- 进入类
InAppPurchaseStore
,可以看到应该是关于金币和砖石的加减操作都是在这个类中完成的。下面的代码用到了SharedPreferences,是Android平台提供的一种轻量级的数据存储机制,用于存储和检索键值对数据。可以看到,游戏是将一些数据直接通过SharedPreferences存放在本地。
public static void loadStore(Context context) {
mProcessedOrders = context.getSharedPreferences(PREFS_PROCESSED, 0).getString("processed_orders", "");
mCoins = context.getSharedPreferences(PREFS_NAME, 0).getInt("numCoins", 0);
mGems = context.getSharedPreferences(PREFS_NAME, 0).getInt(NUM_GEMS, 0);
mAdFree = context.getSharedPreferences(PREFS_NAME, 0).getInt("adfree", 0);
mBundle = context.getSharedPreferences(PREFS_NAME, 0).getInt(BUNDLE, 0);
mLoaded = true;
}
public static void saveStore(Context context) {
SharedPreferences.Editor edit = context.getSharedPreferences(PREFS_NAME, 0).edit();
edit.putInt("numCoins", mCoins);
edit.putInt(NUM_GEMS, mGems);
edit.putInt("adFree", mAdFree);
edit.putInt(BUNDLE, mBundle);
edit.commit();
}
- 先尝试修改getcoins的返回值,就是将i改为固定的一个数,看会不会开始就有金币。
- 第一个修改是将const/4 v0,0x0改为const v0,66666,第二个是删除`sget v0, Lcom/fingersoft/game/InAppPurchaseStore;->mCoins:I` ,直接const v0, 66666.
-
发现是可以通过此方法获取金币,同理,砖石的逻辑也是一样的。
- 方法二:直接修改loadStore函数,因为loadStore函数是从本地读取相关信息。
- 下面是从获取值对金币和砖石数量进行赋值的smali代码。对其进行一定的修改。
const-string v1, "numCoins" invoke-interface {v0, v1, v3}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result v0 ***const v0, 888888#先对v0赋值再给mCoins赋值,下面的砖石逻辑同理。*** sput v0, Lcom/fingersoft/game/InAppPurchaseStore;->mCoins:I .line 28 const-string v0, "iap_db" invoke-virtual {p0, v0, v3}, Landroid/content/Context;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences; move-result-object v0 const-string v1, "numGems" invoke-interface {v0, v1, v3}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result v0 ***const v0, 999999*** sput v0, Lcom/fingersoft/game/InAppPurchaseStore;->mGems:I
- 修改后重新打包签名,查看具体效果。发现最开始启动游戏就会获得888888个金币和999999个砖石,和起初的预想的效果一样。并且金币和砖石都可以用于游戏道具的购买。
反抓包
- 在之前打算抓包的时候,发现一旦开了代理,游戏就会提示信息确认中,然后就无法进入游戏。
- 先尝试用算法助手,屏蔽这个弹窗,然后能否继续游戏。发现可以取消弹窗,但是游戏一直在进入页面.
- 这里再推荐一下算法助手这个强大的辅助工具,接下来介绍利用这个工具隐藏VPN状态。
- 首先在LSPosed或者Xposed中的算法助手模块中对要操作的APP打勾,然后进入算法助手APP对该应用开启辅助,然后打开该应用的其他选项,找到网络环境,将网络环境中的三个选项全打勾。
- 接着从算法助手应用中启动该APP(切记),最后观察,app打开不再提示刚才的Dialog,可以正常进入实名认证阶段。
- 利用编的信息进行实名认证,利用小黄鸟抓包。由于会先在本地检查身份证的格式,这里就没再编,直接用了一个符合正常格式的号码。而且认证时请求的URL和之前分析的一致,都是https://antiaddiction.******.cn/api/v1。
0 条评论
可输入 255 字