吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3875|回复: 15
上一主题 下一主题
收起左侧

[Android 原创] 跳舞的线(DancingLine)游戏内购破解

[复制链接]
跳转到指定楼层
楼主
无聊的味道 发表于 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监听和弹窗定位,接着再去触发一下弹窗和购买,返回找到日志



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:}
有错误望请大佬指出

免费评分

参与人数 6吾爱币 +6 热心值 +6 收起 理由
eggcn + 1 + 1 感谢🙏🙏🙏
Wulimideruxia + 1 + 1 感谢分析,学习了
xghd + 1 + 1 我很赞同!
IcePlume + 1 + 1 我很赞同!
z14278 + 1 + 1 热心回复!
mini123 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
Kzs 发表于 2026-5-22 00:40
感谢分享,更深入的研究了一下native层,结合frida hook,在最新版本的dancing line上解锁了被隐藏的关卡。基本思路是i2lcpp解包找到对应函数偏移地址进行写入即可,同时发现了游戏的穿墙模式和一系列调试菜单
沙发
xianyuge 发表于 2026-2-22 14:47
3#
hai0079 发表于 2026-2-23 08:40
4#
牛人王老五 发表于 2026-2-23 10:23
思路很棒! 学习了!
但是通过伪造订单编号的操作,因为要传回服务器,感觉使用的账号不是很保险啊,可能分分钟就没了..
5#
IcePlume 发表于 2026-2-23 15:45
有点意思,学一学思路
6#
jyfyge 发表于 2026-2-23 23:22
感谢分享,可以作为案例以后学习了还记得以前小时候傻乎乎的自己硬看广告
7#
wh11o7 发表于 2026-2-26 18:45
其他ap也能用此法
8#
Malese 发表于 2026-2-28 18:48
思路清晰明了! 适合我个小白学习学习!
9#
Tsuki0402 发表于 2026-3-1 14:49
按照教学试试
10#
 楼主| 无聊的味道 发表于 2026-3-1 15:01 |楼主
牛人王老五 发表于 2026-2-23 10:23
思路很棒! 学习了!
但是通过伪造订单编号的操作,因为要传回服务器,感觉使用的账号不是很保险啊,可能分分钟 ...

我一开始是尝试随机字符串但是失败了,怀疑可能是只校验订单号格式或者so层发送服务端二次校验,后面尝试伪造一下然后就成功了,如果so层二次校验的话就得去逆向so层了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-9-15 20:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表