吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1315|回复: 11
收起左侧

[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监听和弹窗定位,接着再去触发一下弹窗和购买,返回找到日志

算法助手Pro

算法助手Pro



3.回调类是com.cmplay.dancingline.ui.PayConfirmDialog,用jadx打开,定位到onClick,这里的C0588R.id.payconfirm_btn_payonlaythird(2131231494)就是确认按钮的资源id,所以我们直接进入payThird看看
203337ehr4usl3s9shas5e.png



4.payThird有联网校验和防沉迷,而且isBoolAntiAddiction()是强制返回true的,不会走下面的payAgent.pay,但是这样就很麻烦了,因为checkPayLimit会从服务端获取最高充值金额并且有很多校验,绕了一大圈才会回来,所以需要Hook isBoolAntiAddiction()强制返回false从而更轻松地Hook
204301he8saie8onfsf2rz.png


进到payAgent.pay方法,发现是空方法,用mt管理器搜一下方法重写,找到一个:com.cmcm.cmplay.pay.TaptapPay类里的pay方法,点进去,有一个支付中...的弹窗,最后一行就是调起微信支付
205403tdeeozhpnbky0oeb.png


再追进去就是发送服务器请求和调起微信支付了,具体后面的回调逻辑和流程如下
210130wgns1hhzxx1ht5zs.png


支付成功后紧接着调用SendProductBuy方法与Unity通信分发道具,传入参数为
100(成功),
s1:商品 ID(mProductId),
s2:oid + "##" + 16,

其中oid为订单号,参与后续分发函数,感觉需要找出来,万一后续会再次校验格式且是Native方法,幸运的是wechatsdk有打印日志,通过算法助手的logcat捕获,抓到了订单号格式:177158*******-***************(13位时间戳+15位随机数字)
1.png 534f3d6440b4a1420dd7e1b401782353.jpg


最后道具分发函数,参数s3格式:[商品ID]#SEPARATOR#[oid]##16##[商品ID]

PixPin_2026-02-20_21-45-39.png



逻辑搞清了,接下来只用写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:}
有错误望请大佬指出

免费评分

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

查看全部评分

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

 楼主| 无聊的味道 发表于 2026-3-1 15:01
牛人王老五 发表于 2026-2-23 10:23
思路很棒! 学习了!
但是通过伪造订单编号的操作,因为要传回服务器,感觉使用的账号不是很保险啊,可能分分钟 ...

我一开始是尝试随机字符串但是失败了,怀疑可能是只校验订单号格式或者so层发送服务端二次校验,后面尝试伪造一下然后就成功了,如果so层二次校验的话就得去逆向so层了
牛人王老五 发表于 2026-2-23 10:23
思路很棒! 学习了!
但是通过伪造订单编号的操作,因为要传回服务器,感觉使用的账号不是很保险啊,可能分分钟就没了..
xianyuge 发表于 2026-2-22 14:47
hai0079 发表于 2026-2-23 08:40
支持下,努力就有回报。
IcePlume 发表于 2026-2-23 15:45
有点意思,学一学思路
jyfyge 发表于 2026-2-23 23:22
感谢分享,可以作为案例以后学习了还记得以前小时候傻乎乎的自己硬看广告
wh11o7 发表于 2026-2-26 18:45
其他ap也能用此法
Malese 发表于 2026-2-28 18:48
思路清晰明了! 适合我个小白学习学习!
Tsuki0402 发表于 2026-3-1 14:49
按照教学试试
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-3-18 17:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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