饥饿鲨:进化去so签名验证Android内购破解

—Creaked by B.S. 6/24/2017 16:17:58 PM

无聊练手,当作学习Android优秀源码的笔记

仅供学习研究,请勿用作商业用途,如若喜欢请支持正版!

原程序:(v4.3.1.0)

移动下载v4.3.1.0(D)

百度下载v4.3.1.0(D)百度版

B.S.内购破解版:

百度下载 v4.3.1.0(E)

也可以去各大安卓市场搜索下载最新版本:(推荐支付接口比较熟悉的移动,咪咕游戏,爱游戏)

爱游戏: http://www.play.cn/

咪咕游戏: http://g.10086.cn/

中国移动应用商城: http://mm.10086.cn/android

腾讯应用宝: http://android.myapp.com/

360手机助手: http://zhushou.360.cn/

百度旗下安卓市场: http://apk.hiapk.com/

安智市场: http://www.anzhi.com/

有图有真相,动画演示:

无限内购B.S.破解版

1. 内购破解:

搜索logcat定位字符串,

支付 测试:

g+:

unity购买失败

unity购买成功

还可以通过查找\res\values\strings.xml有没有支付相关字符串可以用,

解析支付协议失败

ourpalm_tip_prasecharginfoerror

找到在下面这个函数里,

.class public Lourpalm/android/pay/Ourpalm_Entry;

.super Ljava/lang/Object;

.source “Ourpalm_Entry.java”

1
.method private Ourpalm_Pay_FormSdk(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lourpalm/android/callback/Ourpalm_PaymentCallBack;Ljava/lang/String;Ljava/lang/String;)V
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
.line 718
:cond_4
sget-object v1, Lourpalm/android/pay/Ourpalm_Entry_Model;->mActivity:Landroid/app/Activity;
.line 720
sget-object v2, Lourpalm/android/pay/Ourpalm_Entry_Model;->mActivity:Landroid/app/Activity;
.line 721
const-string v3, "ourpalm_tip_prasecharginfoerror"
.line 719
invoke-static {v2, v3}, Lourpalm/android/pay/Ourpalm_GetResId;->GetString(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
.line 722
const/4 v3, 0x0
.line 718
#可以注释掉不提示,解析支付协议失败,这里注意这个函数里16个寄存器都给用了
invoke-static {v1, v2, v3}, Lourpalm/android/view/Ourpalm_Toast;->makeText(Landroid/content/Context;Ljava/lang/String;I)V
######Toast显示自定义字符串
const-string v3, "\u65e0\u9650\u5185\u8d2dB.S.\u7834\u89e3\u7248\n www.appleos.xyz \n-- by B.S."
move-object/from16 v2, v3
const/4 v3, 0x0
invoke-static {v1, v2, v3}, Lourpalm/android/view/Ourpalm_Toast;->makeText(Landroid/content/Context;Ljava/lang/String;I)V
######

在Ourpalm_Pay_FormSdk方法函数里,想要跳过支付窗口,不弹窗框的,要跳到cond_4去.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.line 714
goto :cond_4 #直接跳过下面的pay支付框 #if-eqz v1, :cond_4
.line 716
invoke-static {}, Lourpalm/android/channels/Ourpalm_Channels_Manage;->getInstance()Lourpalm/android/channels/Ourpalm_Channels_Manage;
move-result-object v1
invoke-virtual {v1}, Lourpalm/android/channels/Ourpalm_Channels_Manage;->Pay()Z
goto/16 :goto_0
.line 718
:cond_4
sget-object v1, Lourpalm/android/pay/Ourpalm_Entry_Model;->mActivity:Landroid/app/Activity;
.line 720
sget-object v2, Lourpalm/android/pay/Ourpalm_Entry_Model;->mActivity:Landroid/app/Activity;
.line 721
const-string v3, "ourpalm_tip_prasecharginfoerror"

改失败为直接成功然后,goto返回

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
.line 723
sget-object v1, Lourpalm/android/pay/Ourpalm_Statics;->mChargingResult:Lourpalm/android/callback/Ourpalm_PaymentCallBack;
.line 724
const/16 v2, 0x6a
const-string v3, ""
.line 723
move-object/from16 v0, p1
#invoke-virtual {v1, v2, v3, v0}, Lourpalm/android/callback/Ourpalm_PaymentCallBack;->Ourpalm_PaymentFail(ILjava/lang/String;Ljava/lang/String;)V
#######这里换为调用成功
invoke-virtual {v1, v2, v3, v0}, Lourpalm/android/callback/Ourpalm_PaymentCallBack;->Ourpalm_PaymentSuccess(ILjava/lang/String;Ljava/lang/String;)V
.line 725
const/4 v1, 0x0
sput-boolean v1, Lourpalm/android/pay/Ourpalm_Statics;->IsExecute:Z
goto/16 :goto_0
.line 732
:cond_5

另外一种破解方法(第2种改法),也可以搜索字符

Ourpalm_PaymentFail

支付失败

ourpalm_tip_gw_chargfail

看哪里调用引用了这个方法函数

.class public Lcom/fgol/OurpalmIAPListener;

.super Lourpalm/android/callback/Ourpalm_PaymentCallBack;

.source “OurpalmIAPListener.java”

用成功的代码替换掉失败方法里的代码

1
.method public Ourpalm_PaymentSuccess(ILjava/lang/String;Ljava/lang/String;)V
1
.method public Ourpalm_PaymentFail(ILjava/lang/String;Ljava/lang/String;)V

这里只需要修改一句调用的函数,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.method public Ourpalm_PaymentFail(ILjava/lang/String;Ljava/lang/String;)V
.locals 4
.param p1, "arg0" # I
.param p2, "arg1" # Ljava/lang/String;
.param p3, "arg2" # Ljava/lang/String;
.prologue
.line 74
if-nez p2, :cond_0
const-string v0, "000000"
.line 75
.local v0, "tid":Ljava/lang/String;
:goto_0
iget-object v1, p0, Lcom/fgol/OurpalmIAPListener;->context:Lcom/fgol/IAPInterface;
#invoke-virtual {v1, v0}, Lcom/fgol/IAPInterface;->purchaseFailed(Ljava/lang/String;)V
######这里替换为成功的方法函数,失败变成功,上面原来的语句删掉或者注释掉即可
invoke-virtual {v1, v0}, Lcom/fgol/IAPInterface;->purchaseSuccess(Ljava/lang/String;)V
.line 76

来看一下java源码,就知道改那个语句了:

1
2
3
4
5
6
7
8
9
public void Ourpalm_PaymentFail(int i, String str, String str2) {
this.context.purchaseFailed(str == null ? "000000" : str);
Log.d("HSE", new StringBuilder(String.valueOf(i)).append("支付失败##").append(str).append("##").append(str2).toString());
}
public void Ourpalm_PaymentSuccess(int i, String str, String str2) {
this.context.purchaseSuccess(str == null ? "000000" : str);
Log.d("HSE", new StringBuilder(String.valueOf(i)).append("支付成功##").append(str).append("##").append(str2).toString());
}

或者进入purchaseFailed函数方法里面去改(这是第3种改法了):

1
2
3
4
.line 246
#const-string v1, "purchaseFailed"
######这换改为传入成功的消息参数
const-string v1, "purchaseSucceeded"

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void purchaseFailed(String str) {
Log.d(TAG, "unity购买失败" + str);
if (!isCanBuy()) {
UnityPlayer.UnitySendMessage("IAPCNManager", "purchaseFailed", str);
}
}
public void purchaseSuccess(String str) {
Log.d(TAG, "unity购买成功" + str);
if (!isCanBuy()) {
UnityPlayer.UnitySendMessage("IAPCNManager", "purchaseSucceeded", str);
}
}

2. 去so签名验证:

android killer修改内购破解后直接回编译后安装游戏,进游戏闪退,是有签名验证的.

搜索定位字符串

signatures

没有什么实用价值的代码

再搜索

内购接口初始化

ShareSDKUtils.prepare

向上回溯,哪里引用到了,定位到.method protected onCreate(Landroid/os/Bundle;)V
在下面的内购界面的类里,先看看源码,关键在public void init()函数里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.mContext = this;
init();
UMGameAgent.setDebugMode(true);
UMGameAgent.init(this.mContext);
ShareSDKUtils.prepare(getApplicationContext());
}
public void init() {
Log.d(TAG, "内购接口初始化");
FlurryHelper.init(this.mContext);
getMobileType(this.mContext);
Log.d(TAG, new StringBuilder(String.valueOf(this.mContext.getPackageName())).append("++").append(this.mContext.toString()).toString());
this.mIAPIdentifier = new IAPIdentifier();
this.mListener = new OurpalmIAPListener(this);
Ourpalm_Entry.getInstance(this).Ourpalm_Init("2", "1.0", "1.0", this.mListener);
setCanBuy(true);
}

经验:所以在没有找着有用信息是,去onCreate瞧一瞧,也许会有惊奇的发现

.class public Lcom/fgol/IAPInterface;

.super Lcom/unity3d/player/UnityPlayerActivity;

.source “IAPInterface.java”

跟进,查看Ourpalm_Init函数方法

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
public void Ourpalm_Init(String str, String str2, String str3, Ourpalm_CallBackListener ourpalm_CallBackListener) {
if (ourpalm_CallBackListener == null) {
Logs.e(TAG, "Ourpalm_Init mCallBackListener == null");
return;
}
String string;
try {
ApplicationInfo applicationInfo = Ourpalm_Entry_Model.mActivity.getPackageManager().getApplicationInfo(Ourpalm_Entry_Model.mActivity.getPackageName(), 128);
if (applicationInfo != null) {
if ("show".equals(applicationInfo.metaData.getString("useShowLog"))) {
Logs.isShowLog_1 = true;
}
this.useImplicitLogon = applicationInfo.metaData.getString("useImplicitLogon");
Logs.i(TAG, "useImplicitLogon == " + this.useImplicitLogon);
string = applicationInfo.metaData.getString("useCheckLog");
if (string != null && "NO".equals(string)) {
this.useCheckLog = false;
}
Logs.i(TAG, "useCheckLog == " + this.useCheckLog);
}
} catch (Exception e) {
e.printStackTrace();
}
Ourpalm_Statics.mCallBackListener = ourpalm_CallBackListener;
GameInfo.GameType = str;
Ourpalm_Statics.GameRoleLoginType = 0;
Ourpalm_Statics.IsExecute = false;
Ourpalm_Statics.mSimType = Ourpalm_Statics.getSimType(Ourpalm_Entry_Model.mActivity);
initMapPhoneInfo();
this.ourpalm_jni = new ourpalm_android_SdkJni();
this.ourpalm_jni.RunSign(Ourpalm_Entry_Model.mActivity.getApplicationContext());
Ourpalm_Statics.Ourpalm_kkey = this.ourpalm_jni.getPublicKey();
Ourpalm_Statics.UpdateSecretKey();
Ourpalm_Entry_Model.getInstance().userInfo = new Ourpalm_UserInfo();
Ourpalm_Entry_Model.getInstance().mOurpalm_Account_HeartBeat = new Ourpalm_Account_HeartBeat();
Ourpalm_Entry_Model.getInstance().mGameInfo = new GameInfo();
Ourpalm_Entry_Model.getInstance().mGameInfo.setGameResVersion(str3);
string = Ourpalm_Statics.getAppVersion(Ourpalm_Entry_Model.mActivity).trim();
if (Ourpalm_Statics.IsNull(string)) {
string = str2;
}
Ourpalm_Entry_Model.getInstance().mGameInfo.setGameVersion(string);
Ourpalm_Entry_Model.getInstance().parseLocalData();
Ourpalm_CrashHandler.getInstance().init(Ourpalm_Entry_Model.mActivity);
Ourpalm_Statistics_Entry.getInstance().initStatisticsLog(Ourpalm_Entry_Model.mActivity);
Ourpalm_Entry_Model.getInstance().netInitData = new NetInitData();
this.mOurpalm_Init_Net = new Ourpalm_Init_Net(this.mInit_Net_callback);
if (DK_GetPhoneInfo.getInstance(Ourpalm_Entry_Model.mActivity).NetworkIsAvailable()) {
this.mOurpalm_Init_Net.start(Ourpalm_Init_Net.INIT_NET_INIT);
} else if (GameInfo.GameType == "2") {
new Thread(new 2(this)).start();
}
Ourpalm_Channels_Manage.getInstance().init();
if (this.useCheckLog) {
JSONObject jSONObject = new JSONObject();
try {
jSONObject.put("gameType", str);
jSONObject.put("gameVer", str2);
jSONObject.put("gameResVer", str3);
} catch (JSONException e2) {
e2.printStackTrace();
}
sendCheckLog("Ourpalm_Init", jSONObject.toString());
}
}

his.ourpalm_jni = new ourpalm_android_SdkJni();

this.ourpalm_jni.RunSign(Ourpalm_Entry_Model.mActivity.getApplicationContext());

Ourpalm_Statics.Ourpalm_kkey = this.ourpalm_jni.getPublicKey();
Ourpalm_Statics.UpdateSecretKey();

1
public native String RunSign(Context paramContext);

下次维护,定位可以搜索

RunSign

UpdateSecretKey

getSimType

native函数,去初始化里,看看ourpalm_jni.RunSign是在哪里导入的,这里可以用IDEA调试smali代码在初始化下段然后单步跟踪,确定出这里是退出前的地方.

1
2
3
4
5
6
7
8
public ourpalm_android_SdkJni ourpalm_jni;
private boolean useCheckLog = true;
private String useImplicitLogon;
static {
System.loadLibrary("ourpalm_sdk_a");
System.loadLibrary("dumpcrash");
}

在lib文件夹里,找到libourpalm_sdk_a.so文件

\lib\armeabi-v7a\libourpalm_sdk_a.so

IDA载入,在导出函数表里Alt-T或者Ctrl-F搜索RunSign

EXPORT Java_ourpalm_android_sdkjni_ourpalm_1android_1SdkJni_RunSign

.text:000244B4 Java_ourpalm_android_sdkjni_ourpalm_1android_1SdkJni_RunSign

1
2
3
.text:000244C4 FF F7 3E FF BL checksign
.text:000244CE FF F7 55 FD BL checkdexex

用16进制工具C32Asm去掉,这两个函数call调用的地方

改为”悙悙” 即是,填充为0或者90

1
2
3
.text:000244C4 90 90 90 90 aRrrr_0 DCB "悙悙"
.text:000244CE 90 90 90 90 aRrrr DCB "悙悙"

\lib\x86\libourpalm_sdk_a.so

x86的也顺便一起处理了

1
2
3
.text:000302DA 90 90 90 90 90
.text:000302EA 90 90 90 90 90

3. 添加版权自定义提示等信息

自己定义版权提示Unicode字符串:

\u65e0\u9650\u5185\u8d2dB.S.\u7834\u89e3\u7248\n www.appleos.xyz \n \u7231\u6deb\u8361 \n – by B.S.

通常在启动页面或者闪屏页面的onCreate里插入log和弹出消息,又或者是利用程序自己的封装的消息机制.
比如,内购破解改法1里的:

1
2
3
4
5
6
7
8
9
10
11
12
13
.line 722
const/4 v3, 0x0
.line 718
#可以注释掉不提示,解析支付协议失败,这里注意这个函数里16个寄存器都给用了
invoke-static {v1, v2, v3}, Lourpalm/android/view/Ourpalm_Toast;->makeText(Landroid/content/Context;Ljava/lang/String;I)V
######Toast显示自定义字符串
const-string v3, "\u65e0\u9650\u5185\u8d2dB.S.\u7834\u89e3\u7248\n www.appleos.xyz \n-- by B.S."
move-object/from16 v2, v3
const/4 v3, 0x0
invoke-static {v1, v2, v3}, Lourpalm/android/view/Ourpalm_Toast;->makeText(Landroid/content/Context;Ljava/lang/String;I)V
######

4. 统一支付接口

只需要让getMobileType返回你想要伪装的类型值就行了,返回值为3是电信,1是移动,2是联通.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static int getMobileType(Context context) {
int i;
String simOperator = ((TelephonyManager) context.getSystemService("phone")).getSimOperator();
if (simOperator.length() <= 0) {
Log.d(TAG, "未找到sim卡");
i = 0;
} else if (simOperator.equals("46000") || simOperator.equals("46002") || simOperator.equals("46007")) {
i = 1;
Log.d(TAG, "中国移动");
} else if (simOperator.equals("46001") || simOperator.equals("46006")) {
i = 2;
Log.d(TAG, "中国联通");
} else if (simOperator.equals("46003") || simOperator.equals("46005")) {
i = 3;
Log.d(TAG, "中国电信");
} else {
Log.d(TAG, "中国未知");
i = 0;
}
mobileType = i;
return i;
}

要修改的类

.class public Lcom/fgol/IAPInterface;

.super Lcom/unity3d/player/UnityPlayerActivity;

.source “IAPInterface.java”

下面函数修改地方:

.method public static getMobileType(Landroid/content/Context;)I

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.line 130
:goto_0
###### 让返回值v2等于3电信,1移动,2联通都可以
const/4 v2, 0x3
sput v2, Lcom/fgol/IAPInterface;->mobileType:I
.line 132
###### 自带日志输出 mobileType
const-string v3, "---B.S.--- mobileType= "
invoke-static {v2}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
######
return v2
您觉得好,您就随意打赏点吧(*^__^*)您的鼓励,是我坚持的动力!