原文章:https://www.52pojie.cn/thread-2092521-1-1.html
每个人的思路和基础知识不一样,破解时的思路也不一样,这次复现学到了很多特地记录。
1、破解背景
APP的界面是这样的(雷电模拟器运行)
需求:绕过购买机制实现0元购
2、弹窗定位
通过算法助手定位点击时触发的类路径
通过算法助手定位点击时触发的按钮信息 / 开发助手定位界面ID
3、代码分析
上面捕获到的是触发的(类、弹窗信息),点击触发的逻辑,要进入代码中找 onclick
补充:case 穿透(fall-through)payconfirm_btn_payonlaythird 和 payconfirm_btn_payother 两个 ID 共享同一段代码逻辑
进入 payThird 查看
其中 AntiAddictionKitUtil.isBoolAntiAddiction() 固定返回 true,即默认走 AntiAddictionKitUtil.checkPayLimit(),这部分逻辑跟下去&通过方法名能够知道,是防沉迷机制(从服务器端获取更新个人信息,判断是否超过支付限制等),这部分的分析有点长,在最后补充。
业务逻辑分支太多的话,可以用AI帮忙分析,对于识别业务方法名和业务逻辑,AI做得挺好,但仅限做参考。
如果未开启防沉迷的话,走的是下面的逻辑
逻辑跟进看到抽象函数
全局搜下具体实现
在 TaptapPay 文件下找到具体实现,逻辑比较明确:支付弹窗后调用支付逻辑
逻辑比较明确,判断初始化、微信SDK是否可用等
继续跟进 requestLocalOrder,拼接参数并发起请求
通过 mResponseHandler 命名判断为响应处理器,并是由 ResponseHandler 实例化
查看 ResponseHandler,明显得根据支付响应码调用对应的处理逻辑,把这段代码丢给AI分析,能快速判断出各个分支用于干嘛的,当然仅限参考。
上面发起请求只有参数和相应回调方法,猜测URL是自封装好,毕竟是微信SDK,这是我第一次接触微信支付,一些前提的知识也不清楚。这时候AI的好处来了,以往遇到这种问题,要去自己看微信支付的官方文档,查看整个调用流程,问AI更快而且有参考链接
看下AI参考了哪些文档,直接跳转去对应的官网看。
原文章直接就说后续的调用方法是这样这样的,突然有点懵逼,微信支付也挺常用的,学习一波了
因为官方规定的包路径要固定,直接找过去就是:包名.wxapi.WXPayEntryActivity
继续跟进到微信SDK里的 onPayResp,errcode == 0猜测是正常支付的时走的逻辑,走的 sendPayCallBack(100, this.oid)
继续跟进,第一个判断安卓设备和非空,猜测走第一个if,其实第二个也是回调同样的方法
又是抽象类,继续全局搜找实现逻辑
还记得之前的传入errcode == 0传入的参数是100,调用的也是 callbackSucceed
跟进 callbackSucceed
跟进 onPayCallbackSucced,又是抽象类,继续全局搜找实现逻辑
ChannelNativeUtility.SendProductBuy(100, str2, str3 + OrderIdUtil.ORDERID_SPLIT + i)
第一个判断固定返回 true,跟之前的一样,判断防沉迷的,通过下面的方法名判断下面是发送充值的产品,直接看下面的方法
到这就差不多了,继续下去是通过 Unity 发送购买成功信息 onChannelBuySucessed,通知送达商品
整体从获取支付状态到确认支付成功通知发放商品流程大概这样
按顺序整理下关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public static final String ORDERID_SPLIT = "##"; public void onPayResp(BaseResp baseResp) throws JSONException { sendPayCallBack(100, this.oid); }
public void sendPayCallBack(int i, String str) throws JSONException { this.mPayCallBack.onPurchaseCallBack(i, str); }
public void onPurchaseCallBack(int i, String str) throws JSONException { TaptapPay.this.callbackSucceed(TaptapPay.this.mProducid, str); }
public void callbackSucceed(String str, String str2) { getPayCallBack().onPayCallbackSucced(this.mUserid, str, str2, 16); }
public void onPayCallbackSucced(String str, String str2, String str3, int i) { if (AntiAddictionKitUtil.isBoolAntiAddiction()) { AntiAddictionKitUtil.paySuccess(PayConfirmDialog.price); } ChannelNativeUtility.SendProductBuy(100, str2, str3 + OrderIdUtil.ORDERID_SPLIT + i); }
public static void SendProductBuy(int i, String str, String str2) { String str3 = str2 + OrderIdUtil.ORDERID_SPLIT + str; String str4 = str + "#SEPARATOR#" + str3; UnityPlayer.UnitySendMessage("Nativeutil", "onChannelBuySucessed", str4); }
|
最终的目标是要在支付的时候,关闭支付弹窗,直接去调用支付成功发送商品的代码,分析下参数 str4
1
| str4 = mProducid + "#SEPARATOR#" + oid + "##" + 16 + "##" + mProducid
|
那就只需要分析和构造 mProducid、oid 这两个参数了,在 pay 函数中参数已经有 mProducid,需要构造 oid
全局搜下 .oid、”oid”
这里从 jSONObject 获取 oid,而 jSONObject 来源于 str,上面又打印了 str,所以在正常付款后直接抓 log 就行
这里我不想去付款,就不放图了,只是给个思路怎么去找 oid 的格式,如果实在找不到,估计就要修改 smail 代码,加一行打印了。
requestWeixinPay 的调用在这,根据回调码往下处理的
像这种可以让 AI 初步判断每一步是做什么的,供参考
Hook 代码,参考原文章:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| 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){ 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() { long timestamp = System.currentTimeMillis(); long randomPart = (long) (Math.random() * 900_000_000_000_000L) + 100_000_000_000_000L; return timestamp + "-" + randomPart; } }
|