好友
阅读权限10
听众
最后登录1970-1-1
|
无聊的味道
发表于 2026-2-20 21:36
本帖最后由 无聊的味道 于 2026-2-20 21:54 编辑
写在前面:
本项目的初衷是研究Hook技术与游戏引擎的交互逻辑,请勿将其用于非法盈利等任何商业用途,所有下载的附件请在的24小时内自行删除。
应用名称:跳舞的线 Dancing Line
包名:com.cmplay.dancingline
版本:2.7.74.00
下载:https://www.taptap.cn/app/31429
所需工具:jadx/JEB,算法助手Pro,MT管理器
开始前的碎碎念:
虽然已经看了很多教程和实战案例,但是无奈lz还是太菜了+只学过一点点java(≈0),不是在各种方法和java机制里跳来跳去被绕晕,就是混淆的看都不想看,今天心血来潮分析了下童年游戏,发现不是很复杂而且没有混淆,最后成功破解
正式流程
1.打开游戏,随便点击一个需要付费的道具,发现有一个确认弹窗和购买按钮。
购买弹窗
2.打开算法助手Pro,勾选跳舞的线,启用onClick监听和弹窗定位,接着再去触发一下弹窗和购买,返回找到日志
算法助手Pro
3.回调类是com.cmplay.dancingline.ui.PayConfirmDialog,用jadx打开,定位到onClick,这里的C0588R.id.payconfirm_btn_payonlaythird(2131231494)就是确认按钮的资源id,所以我们直接进入payThird看看
4.payThird有联网校验和防沉迷,而且isBoolAntiAddiction()是强制返回true的,不会走下面的payAgent.pay,但是这样就很麻烦了,因为checkPayLimit会从服务端获取最高充值金额并且有很多校验,绕了一大圈才会回来,所以需要Hook isBoolAntiAddiction()强制返回false从而更轻松地Hook
进到payAgent.pay方法,发现是空方法,用mt管理器搜一下方法重写,找到一个:com.cmcm.cmplay.pay.TaptapPay类里的pay方法,点进去,有一个支付中...的弹窗,最后一行就是调起微信支付
再追进去就是发送服务器请求和调起微信支付了,具体后面的回调逻辑和流程如下
支付成功后紧接着调用SendProductBuy方法与Unity通信分发道具,传入参数为
100(成功),
s1:商品 ID(mProductId),
s2:oid + "##" + 16,
其中oid为订单号,参与后续分发函数,感觉需要找出来,万一后续会再次校验格式且是Native方法,幸运的是wechatsdk有打印日志,通过算法助手的logcat捕获,抓到了订单号格式:177158*******-***************(13位时间戳+15位随机数字)
最后道具分发函数,参数s3格式:[商品ID]#SEPARATOR#[oid]##16##[商品ID]
逻辑搞清了,接下来只用写Hook就行,需要Hook isBoolAntiAddiction() 防-防沉迷 和 重写 TapTapPay 里的 pay 方法,把最后一行换成支付成功的回调,oid伪造一下就行
PS:感觉可以通过查询商品ID然后构造s3,强行调用UnityPlayer.UnitySendMessage("Nativeutil", "onChannelBuySucessed", s3)从而实现任意道具补给
Hook代码:
[Java] 纯文本查看 复制代码 package com.dancingline.Hook;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class MainHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.cmplay.dancingline")) return;
XposedHelpers.findAndHookMethod( //防-防沉迷
"com.cmplay.dancingline.util.AntiAddictionKitUtil",
lpparam.classLoader,
"isBoolAntiAddiction",
XC_MethodReplacement.returnConstant(false)
);
XposedHelpers.findAndHookMethod(
"com.cmcm.cmplay.pay.TaptapPay",
lpparam.classLoader,
"pay",
String.class,
String.class,
"com.cmcm.cmplay.pay.PayCallBack",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
processPaySurgery(param);
return null;
}
}
);
}
private void processPaySurgery(XC_MethodHook.MethodHookParam param){ //pay方法重写
XposedBridge.log("[DL-Hook] 开始执行支付重定向...");
Object thisObject = param.thisObject;
String userid = (String) param.args[0];
String productid = (String) param.args[1];
Object callback = param.args[2];
try {
XposedHelpers.setObjectField(thisObject,"mUserid", userid);
XposedHelpers.callMethod(thisObject,"setPayCallBack",callback);
XposedHelpers.setObjectField(thisObject,"mProducid", productid);
XposedBridge.log("[DL-Hook] 已设置变量");
String oid = generateFakeOid();
XposedBridge.log("[DL-Hook] 订单号已伪造: " + oid);
try {
Object progressDialog = XposedHelpers.getObjectField(thisObject, "mProgressDialog");
if (progressDialog != null) {
XposedHelpers.callMethod(progressDialog, "dismiss");
XposedBridge.log("[DL-Hook] 等待弹窗已关闭");
}
} catch (Throwable t) {
}
XposedHelpers.callMethod(thisObject, "callbackSucceed", productid, oid); //支付成功回调
XposedBridge.log("[DL-Hook] 重定向完成:)");
} catch (Throwable e){
XposedBridge.log("[DL-Hook] ERROR: " + e);
}
}
private String generateFakeOid() { //伪造oid
long timestamp = System.currentTimeMillis();
long randomPart = (long) (Math.random() * 900_000_000_000_000L) + 100_000_000_000_000L;
return timestamp + "-" + randomPart;
}
}
安装测试,点击确认购买按钮后直接成功,跳过弹窗和支付
总结:
第一次真正成功逆向了一个应用(虽然代码问了问ai,XD),学到了很多东西和工具的使用方法,果然在论坛里没白学习{:301_987:}
有错误望请大佬指出
|
免费评分
-
查看全部评分
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|
|