愤怒小鸟时空之旅破解内购思路:
看游戏封面就知道是4399的游戏,按照惯例,如果没有加壳的话,androidkiller反编译之后直接搜索关键方法
notifyDeliverGoods
说不定就直接通杀了。androidkiller反编译之后的notifyDeliverGoods的smali代码:
.class Lcom/talkweb/twOfflineSdk/GameManager$1; .super Ljava/lang/Object; .source "GameManager.java" # interfaces .implements Lcn/m4399/operate/SingleOperateCenter$SingleRechargeListener; # annotations .annotation system Ldalvik/annotation/EnclosingMethod; value = Lcom/talkweb/twOfflineSdk/GameManager;->initGame(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)V .end annotation .annotation system Ldalvik/annotation/InnerClass; accessFlags = 0x0 name = null .end annotation # instance fields .field private final synthetic val$mContext:Landroid/app/Activity; # direct methods .method constructor <init>(Landroid/app/Activity;)V .locals 0 .prologue .line 1 iput-object p1, p0, Lcom/talkweb/twOfflineSdk/GameManager$1;->val$mContext:Landroid/app/Activity; .line 55 invoke-direct {p0}, Ljava/lang/Object;-><init>()V return-void .end method # virtual methods .method public notifyDeliverGoods(ZLcn/m4399/recharge/RechargeOrder;)Z .locals 6 .param p1, "shouldDeliver" # Z .param p2, "o" # Lcn/m4399/recharge/RechargeOrder; .prologue const/4 v0, 0x1 .line 116 sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; new-instance v2, Ljava/lang/StringBuilder; const-string v3, "shouldDeliver:" invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V invoke-virtual {v2, p1}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder; move-result-object v2 invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v2 invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V .line 118 #if-eqz p1, :cond_1 #这里的跳转是关键点 .line 119 sput-boolean v0, Lcom/talkweb/twOfflineSdk/GameManager;->isCallback:Z .line 120 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$1()Landroid/app/ProgressDialog; move-result-object v1 if-eqz v1, :cond_0 .line 121 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$1()Landroid/app/ProgressDialog; move-result-object v1 invoke-virtual {v1}, Landroid/app/ProgressDialog;->dismiss()V .line 123 :cond_0 const-string v1, "\u652f\u4ed8\u6210\u529f!" #支付成功 invoke-static {v1}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 124 const/16 v1, 0x270f const-string v2, "\u652f\u4ed8\u6210\u529f" .line 125 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v3 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v4 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$3()Ljava/lang/String; move-result-object v5 .line 124 invoke-static {v1, v2, v3, v4, v5}, Lcom/talkweb/twOfflineSdk/callback/CallbackManager;->onPayCallBack(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V .line 135 :goto_0 return v0 .line 128 :cond_1 sput-boolean v0, Lcom/talkweb/twOfflineSdk/GameManager;->isCallback:Z .line 129 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$1()Landroid/app/ProgressDialog; move-result-object v0 if-eqz v0, :cond_2 .line 130 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$1()Landroid/app/ProgressDialog; move-result-object v0 invoke-virtual {v0}, Landroid/app/ProgressDialog;->dismiss()V .line 132 :cond_2 const-string v0, "\u652f\u4ed8\u5931\u8d25!" invoke-static {v0}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 133 const/16 v0, 0x3e8 const-string v1, "\u652f\u4ed8\u5931\u8d25" .line 134 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v2 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v3 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$3()Ljava/lang/String; move-result-object v4 .line 133 invoke-static {v0, v1, v2, v3, v4}, Lcom/talkweb/twOfflineSdk/callback/CallbackManager;->onPayCallBack(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V .line 135 const/4 v0, 0x0 goto :goto_0 .end method .method public onRechargeFinished(ZLjava/lang/String;)V .locals 5 .param p1, "success" # Z .param p2, "msg" # Ljava/lang/String; .prologue .line 66 new-instance v0, Ljava/lang/StringBuilder; const-string v1, "Pay: [" invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder; move-result-object v0 const-string v1, ", " invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 .line 67 const-string v1, "]" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 .line 66 invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 invoke-static {v0}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 68 #if-eqz p1, :cond_0 .line 69 #new-instance v0, Ljava/lang/Thread; #new-instance v1, Lcom/talkweb/twOfflineSdk/GameManager$1$1; #iget-object v2, p0, Lcom/talkweb/twOfflineSdk/GameManager$1;->val$mContext:Landroid/app/Activity; #invoke-direct {v1, p0, v2}, Lcom/talkweb/twOfflineSdk/GameManager$1$1;-><init>(Lcom/talkweb/twOfflineSdk/GameManager$1;Landroid/app/Activity;)V #invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V .line 105 #invoke-virtual {v0}, Ljava/lang/Thread;->start()V .line 112 :goto_0 return-void .line 107 :cond_0 const-string v0, "\u652f\u4ed8\u5931\u8d25" invoke-static {v0}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 108 const/4 v0, 0x1 sput-boolean v0, Lcom/talkweb/twOfflineSdk/GameManager;->isCallback:Z .line 109 const/16 v0, 0x3e8 const-string v1, "\u652f\u4ed8\u5931\u8d25" .line 110 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v2 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v3 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$3()Ljava/lang/String; move-result-object v4 .line 109 invoke-static {v0, v1, v2, v3, v4}, Lcom/talkweb/twOfflineSdk/callback/CallbackManager;->onPayCallBack(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V goto :goto_0 .end method
通过分析代码,本以为只要把
if-eqz p1, :cond_1
注释掉,就可以直接内购了,但是天不遂人意,我打开支付页面,然后点击右上角的取消按钮,但是当支付完成之后,并没有购买成功,这下翻车了,我只好再想其他的思路。![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F64186672-3857-4bca-8e5a-4cdd0bd4cfff%2F2022-10-04_134544.png?table=block&id=d6526eec-7130-4ac4-9271-f0231f45e0e0&cache=v2)
思路一:
通过动态调试的方式来找出其他的关键点,通过jde下断点:
先是通过MT管理器的activity记录,找到支付页面的activity的记录,然后找到对应代码下断点:
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fcbb11759-0a0b-407a-874c-268cbbed174e%2F2022-09-30_194226.png?table=block&id=e7e9b12c-0fa3-4ee2-b792-e988196ebf97&cache=v2)
充值页面应该是recharge.ui的activity页面:
我在充值页面点击取消之后,断在了at()函数,跟踪进入之后我发现V0定义的代码就是6001就是支付失败的代码,按tab键显示java代码后,我发现如果改成9000就可以显示支付成功。
notifyDeliverGoods
方法的跳转逻辑:![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F53a8e6ef-237d-4a82-8958-b0d987a494f2%2F2022-10-02_121645.png?table=block&id=2c111a80-8ccc-4a10-be85-4c5f9db376db&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fff4be75e-c9c1-4b95-ae8e-0cd66f454758%2F2022-10-01_204256.png?table=block&id=18018761-add2-4dbc-a1fe-2438cb94e23d&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fdad6505e-3b27-436d-a115-77016cca2c07%2F2022-10-01_204147.png?table=block&id=90d1a3eb-3928-42c8-b606-3340355390a1&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fef47f87c-b55d-4f1a-b28c-3d421d725c6e%2F2022-10-01_204147.png?table=block&id=799151af-9a0c-42fb-bff4-cad12555a4f7&cache=v2)
但是改完之后重新打包安装之后,很可惜并没有成功,但是我觉得我已经找到了重点,我又搜索了一下returncode,发现0x270f所代表的即是支付成功,于是我替换了所有的0x3e8也即是6000为0x270f,但是依旧没有奏效。
思路二:
我虽然看到支付成功的字符串,但是当我点击支付取消的时候,发现已经检测到了支付失败,我看到有些位置调用了日志输出的位置,抱着试一试的心理我打开了DDMS:
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa6931c19-c9f3-4a3c-aceb-24dfb06654c8%2F2022-10-03_113018.png?table=block&id=58d04b87-6c54-4884-95e9-3b31ff434a11&cache=v2)
果真找到了其他的关键点,在
onRechargeFinished
方法中打印除了支付失败的结果,并且也打印出了取消支付操作,我看到在at()方法调用了onPayFinished
方法,而这个方法调onRechargeFinished
方法:![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7125b73f-ca90-4c5d-af33-8509abb69d6a%2F2022-10-02_213903.png?table=block&id=17c417dc-3601-4a20-8ec8-fa3e4ca95810&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9d998476-4afb-4e81-b50d-bd34e7b64a6d%2F2022-10-02_21221.png?table=block&id=f1e87234-df81-4a93-8fae-0c80340e7a5d&cache=v2)
我本来想在at函数中更改输入的第一个参数,但是不知道为什么会app会闪退,就放弃了,我又想在点击支付取消之后找到第一次调用这个函数的地方,但是通DDMS进行方法跟踪实在太麻烦了,既然不能修改方法运行时的参数 ,那只能修改这个函数,只要去掉判断分支就可以了,非常凑巧的时,这个函数和
notifyDeliverGoods
在同一个smali文件中,只要去掉分支就可以了,看来我这兜兜转转又回到最初的起点。.method public onRechargeFinished(ZLjava/lang/String;)V .locals 5 .param p1, "success" # Z .param p2, "msg" # Ljava/lang/String; .prologue .line 66 new-instance v0, Ljava/lang/StringBuilder; const-string v1, "Pay: [" invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder; move-result-object v0 const-string v1, ", " invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 .line 67 const-string v1, "]" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v0 .line 66 invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 invoke-static {v0}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 68 #if-eqz p1, :cond_0 #去掉这个判断分支就破解成功了 .line 69 #new-instance v0, Ljava/lang/Thread; #这是一个启动线程的代码,删除即可,否则会一直转圈圈 #new-instance v1, Lcom/talkweb/twOfflineSdk/GameManager$1$1; #iget-object v2, p0, Lcom/talkweb/twOfflineSdk/GameManager$1;->val$mContext:Landroid/app/Activity; #invoke-direct {v1, p0, v2}, Lcom/talkweb/twOfflineSdk/GameManager$1$1;-><init>(Lcom/talkweb/twOfflineSdk/GameManager$1;Landroid/app/Activity;)V #invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V .line 105 #invoke-virtual {v0}, Ljava/lang/Thread;->start()V .line 112 :goto_0 return-void .line 107 :cond_0 const-string v0, "\u652f\u4ed8\u5931\u8d25" invoke-static {v0}, Lcom/talkweb/twOfflineSdk/tools/LogUtils;->i(Ljava/lang/String;)V .line 108 const/4 v0, 0x1 sput-boolean v0, Lcom/talkweb/twOfflineSdk/GameManager;->isCallback:Z .line 109 const/16 v0, 0x3e8 const-string v1, "\u652f\u4ed8\u5931\u8d25" .line 110 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v2 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$2()Ljava/lang/String; move-result-object v3 invoke-static {}, Lcom/talkweb/twOfflineSdk/GameManager;->access$3()Ljava/lang/String; move-result-object v4 .line 109 invoke-static {v0, v1, v2, v3, v4}, Lcom/talkweb/twOfflineSdk/callback/CallbackManager;->onPayCallBack(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V goto :goto_0 .end method
如果一开始在MT管理器不是找关键方法而是寻找与支付有关的变量,就直接成功了,根本不用那么麻烦,这是一个值得注意的。
![notion image](https://www.notion.so/image/https%3A%2F%2Fs2.loli.net%2F2022%2F11%2F29%2FoJgbxe81YPjUSNM.png?table=block&id=7c64e2a3-b45b-4cd9-99f4-32fd7eb66fc7&cache=v2)