申明:本文仅用于技术研究所用,请支持正版游戏!
一、APK反编译
从官网下载最新版本(2.5.1)的元气骑士apk安装包:
http://www.chillyroom.com/yqqs-2501.apk
解压并保存apk中的文件:classes.dex
使用dex2jar
获取jar文件,到这里便可以使用jd-gui
打开这个jar包并查看反编译的java代码。为了方便搜索,可以使用jd-gui
的导出源代码功能导出所有反编译的文件。
" alt="" />
二、内购功能点分析
走正常的购买流程购买任意物品,在付款前返回,得到如下的消息:
" alt="" />
" alt="" />
可以看到返回了两个消息:
订单取消user_calcelled.
null 校验订单失败:unpaid
以'订单取消'作为关键字搜索,定位到了以下点:
" alt="" />
以'校验订单失败'作为关键字搜索,定位到以下两个点:
" alt="" />
三、代码分析 + Xposed Hook
1、第一个点(订单取消)绕过
protected void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent) {
try {
if (paramInt1 == Pingpp.REQUEST_CODE_PAYMENT && paramIntent != null && paramIntent.getExtras() != null) {
String str3 = paramIntent.getExtras().getString("pay_result");
String str2 = paramIntent.getExtras().getString("error_msg");
String str1 = paramIntent.getExtras().getString("extra_msg");
if (this.payingOrderId != null && !"".equals(this.payingOrderId)) {
if ("success".equals(str3)) {
onPaySuccess(this.payingOrderId, "", "");
Toast.makeText((Context)this, "支付成功", 0).show();
} else {
StringBuilder stringBuilder;
if ("fail".equals(str3)) {
onPayFail(this.payingOrderId, "");
stringBuilder = new StringBuilder();
stringBuilder.append("支付失败:");
stringBuilder.append(str2);
stringBuilder.append(",");
stringBuilder.append(str1);
Toast.makeText((Context)this, stringBuilder.toString(), 1).show();
} else if ("cancel".equals(stringBuilder)) {
onPayCancel(this.payingOrderId, "");
stringBuilder = new StringBuilder();
stringBuilder.append("订单取消");
stringBuilder.append(str2);
stringBuilder.append(",");
stringBuilder.append(str1);
Toast.makeText((Context)this, stringBuilder.toString(), 0).show();
} else if ("invalid".equals(stringBuilder)) {
onPayFail(this.payingOrderId, "");
stringBuilder = new StringBuilder();
stringBuilder.append("支付失败:");
stringBuilder.append(str2);
stringBuilder.append(",");
stringBuilder.append(str1);
Toast.makeText((Context)this, stringBuilder.toString(), 1).show();
} else if ("unknown".equals(stringBuilder)) {
onPayUnknown(this.payingOrderId, "");
stringBuilder = new StringBuilder();
stringBuilder.append("未知错误:");
stringBuilder.append(str2);
stringBuilder.append(",");
stringBuilder.append(str1);
Toast.makeText((Context)this, stringBuilder.toString(), 1).show();
}
}
this.payingOrderId = null;
return;
}
}
} catch (Exception exception) {
Log.i("Unity", exception.toString());
}
}
可以看到只要让paramIntent.getExtras().getString("pay_result");
的值为success
便通过了此处验证。
Xposed Hook代码
" alt="" />
2、第二个点(校验订单)绕过
com.chillyroomsdk.sdkbridge.order.https_task.OrderCheckTask
protected void onPostExecute(String paramString) {
String str = TAG;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("onPostExecute: ");
stringBuilder.append(paramString);
Log.i(str, stringBuilder.toString());
if (paramString != null && !paramString.equals("")) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.orderId = this.m_orderId;
try {
String str1;
JSONObject jSONObject = new JSONObject(paramString);
if (jSONObject.getInt("retcode") == 0) {
jSONObject = jSONObject.getJSONObject("order");
if (jSONObject.getInt("can_send") == 1) {
orderInfo.productName = jSONObject.getString("item_name");
orderInfo.productId = jSONObject.getString("item_id");
float f = jSONObject.getInt("total_price") / 100.0F;
((IPayAgent)UnityPlayer.currentActivity).onPayCheckSuccess(this.m_orderId, orderInfo.productId, orderInfo.productName, f, "");
if (this.m_dialog != null) {
this.m_dialog.dismiss();
return;
}
} else {
if (this.m_count > 0) {
StartTaskOnMainThread(this.m_dialog, this.m_orderId, this.m_count, this.m_time_count);
} else {
Toast.makeText(this.m_context, "订单支付失败。若发生丢单请勿重复支付,并联系客服", 1).show();
if (this.m_dialog != null)
this.m_dialog.dismiss();
}
str1 = TAG;
stringBuilder = new StringBuilder();
stringBuilder.append("此订单支付失败:");
stringBuilder.append(orderInfo.orderId);
Log.i(str1, stringBuilder.toString());
return;
}
} else {
str1 = str1.getString("msg");
Context context = this.m_context;
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append(orderInfo.productName);
stringBuilder1.append(" 校验订单失败:");
stringBuilder1.append(str1);
Toast.makeText(context, stringBuilder1.toString(), 1).show();
if (this.m_count > 0) {
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append("count : ");
stringBuilder2.append(this.m_count);
Log.i("Unity", stringBuilder2.toString());
StartTaskOnMainThread(this.m_dialog, this.m_orderId, this.m_count, this.m_time_count);
return;
}
if (this.m_dialog != null) {
this.m_dialog.dismiss();
return;
}
}
} catch (JSONException jSONException) {
jSONException.printStackTrace();
return;
}
} else {
if (this.m_count > 0) {
StartTaskOnMainThread(this.m_dialog, this.m_orderId, this.m_count, this.m_time_count);
return;
}
Toast.makeText(this.m_context, "订单支付失败。若发生丢单请勿重复支付,并联系客服", 1).show();
if (this.m_dialog != null)
this.m_dialog.dismiss();
}
}
通过代码逻辑可以分析得出:参数paramString是一个JSON
字符串,格式如下时便可以通过校验:
{"retcode":0,"order":{"can_send":1,"item_name":"商品名称","item_id":商品编号,"total_price":0},"msg":"购买成功"}
基中的商品名称
和商品编号
暂时未知。
Xposed Hook代码
" alt="" />
3、第三个点(校验订单)绕过
com.chillyroomsdk.sdkbridge.order.https_task.OrderRestoreTask
这个点是用来恢复有问题订单的,原理同第二个点,不再重复说明。
Xposed Hook代码
" alt="" />
4、商品信息获取
上面第二和第三个点都需要商品名称和商品编号这个关键信息,通过DDMS的跟踪分析,发现了这个点:
com.chillyroomsdk.sdkbridge.order.BaseOrderAgent.requestOrder
" alt="" />
" alt="" />
可以看到商品名称、编号、价格这里都有了。因此Hook这个点,设置变量内容便可以在购买成功前设置购买商品的信息。
Xposed Hook代码
" alt="" />
四、实际效果演示
" alt="" />
" alt="" />