您现在的位置是:网站首页>文章详情文章详情

逆向神器Androidkiller、JEB的使用,案例:内购游戏修改

inlike2019-09-09 原创文章 浏览(3080) 评论(0) 喜欢(37)

简介本篇是讲述两款逆向神器Androidkiller和JEB工具的使用,案例是修改一款内购的充值小游戏,内容会涉及一点smali语法和java语法,以及app逆向的一些常规思路。

本篇是讲述两款逆向神器Androidkiller和JEB工具的使用,案例是修改一款内购的充值小游戏,内容会涉及一点smali语法和java语法,以及app逆向的一些常规思路。

Androidkiller是一款工具集,通过他我们可以使用图形化界面反编译、回编译、smali分析、java分析、日志查询、安卓手机交互,而不必通过命令去执行,省了很多事。

Androidkiller是通过封装调用APPtools的命令来实现图形化操作,所以我们需要更新APPtools的版本,否则会出现app不兼容导致反编译、回编译失败的问题,更新APPtools版本操作如下:

  1. 下载APPtools放到Androidkiller目录下的\bin\apktool\apktool目录里。
  2. 打开Androidkiller,点击APPtools管理,选择刚才的目录点击添加。

image.png

这样就完成APPtools的更新,然后在主界面拖入需要逆向的app,会开始反编译得到资源目录和文件,反编译后会对smali文件分析得到java源码文件,在这一步可能会卡死,重启就可以了。

image.png

这样我们就得到smali源码,但是前提是app没被加壳,否则会出错,所以使用之前先对app查壳,然后用其他工具脱壳,这又是一个大方面了。
同时Androidkiller继承了jd-gui,这是可以将smali源码翻译成java源码的轻量级工具,但是翻译效果和容错性并不理想,翻译结果仅供参考,适用于小项目。
JEB也是一款神器,它的优势在于可以将smali源码最大准确率翻译成java源码,同时具有很高容错性和反混淆能力,即使大项目也可以使用该工具。不仅如此JEB还具有动态调试功能,完全可以不借助Androidstudio和smaliidea插件实现动态调试。正因为功能强大,所以他是商业软件需要收费,不过目前最新破解版式3.0。
了解这两款神器后,我们一手拿着Androidkiller反编译、回编译、签名、日志,一手拿着JEB看源码、动态调试。本篇的案例是游戏:西游降魔录,一款经典的内购游戏,安装包几M,要实现逆向查找内购逻辑,从而修改逻辑在不实际支付的情况下达到实际支付的效果。

通过Androidkiller逆向后,我们也难以下手,因为无任何标志性关键字,也没抓包分析,那我们就先看简单的log信息,在手机连接电脑后开启USB调试,在Androidkiller中选择连接的手机,点击日志信息。

image.png


在日志面板中选择日志级别info,填写目标进程的pid,目标进程pid通过下面方法获得:
adb shell "ps -ef | packagename"


通过记录关键点击信息,可以发现在日志中有“请求支付,计费id”等关键字样,那我们就在源码中查找该关键字。

image.png

搜索后发现在smali源码中有该关键字,但是对smali不是太熟,很难找出他的逻辑,那我们就去JEB源码中查找一下,把app拖入JEB程序中。

image.png


找到对应位置

image.png


其中这段源码如下:
 public void order_internal({
        Object v9 = this.valueMap.get(Integer.valueOf(this.mOrgID));
        this.mMMID = Long.parseLong(((Map)v9).get("Id"));
        System.out.println("请求支付,计费id:" + this.mMMID);
        this.mOrderID = "orderID-" + SystemClock.elapsedRealtime();
        LSGAVirtualCurrency.onChargeRequest(this.mOrderID, ((Map)v9).get("tradeName"), ((double)((((float)Integer.parseInt(((Map)v9).get("money")))) / 1f)), "CNY", ((double)((((float)Integer.parseInt(((Map)v9).get("money")))) / 1f)), "爱游戏");
        HashMap v8 = new HashMap();
        v8.put("toolsAlias", ((Map)v9).get("alias"));
        EgamePay.pay(((Activity)this), ((Map)v8), new EgamePayListener() {
            public void payCancel(Map arg3{
                AppActivity.onResult(AppActivity.this.mOrgID, -1);
            }

            public void payFailed(Map arg3, int arg4{
                AppActivity.onResult(AppActivity.this.mOrgID, -1);
            }

            public void paySuccess(Map arg3{
                AppActivity.onResult(AppActivity.this.mOrgID, 0);
            }
        });
    }

更关键的是里面有三个关键函数:paycancel、payFailed、paySuccess,这三个根据字母意思应该是字符取消、字符失败、支付成功,并且这三个函数都调用了同一个静态方法noResult,通过传入的第二参数来区别成功还是失败。
我们再看这段java源码对应smali源码:
.method public order_internal()V
    .locals 10

    .prologue
    const/high16 v6, 0x3f800000    # 1.0f

    .line 395
    iget-object v0, p0, Lorg/cocos2dx/cpp/AppActivity;->valueMap:Ljava/util/Map;

    iget v1, p0, Lorg/cocos2dx/cpp/AppActivity;->mOrgID:I

    invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;

    move-result-object v1

    invoke-interface {v0, v1}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v9

    check-cast v9, Ljava/util/Map;

    .line 396
    .local v9, "value":Ljava/util/Map;, "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"
    const-string v0, "Id"

    invoke-interface {v9, v0}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v0

    check-cast v0, Ljava/lang/String;

    invoke-static {v0}, Ljava/lang/Long;->parseLong(Ljava/lang/String;)J

    move-result-wide v0

    iput-wide v0, p0, Lorg/cocos2dx/cpp/AppActivity;->mMMID:J

    .line 397
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    new-instance v1, Ljava/lang/StringBuilder;

    const-string v2, "\u8bf7\u6c42\u652f\u4ed8\uff0c\u8ba1\u8d39id\uff1a"

    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    iget-wide v2, p0, Lorg/cocos2dx/cpp/AppActivity;->mMMID:J

    invoke-virtual {v1, v2, v3}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 399
    new-instance v0, Ljava/lang/StringBuilder;

    const-string v1, "orderID-"

    invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    invoke-static {}, Landroid/os/SystemClock;->elapsedRealtime()J

    move-result-wide v1

    invoke-virtual {v0, v1, v2}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v0

    iput-object v0, p0, Lorg/cocos2dx/cpp/AppActivity;->mOrderID:Ljava/lang/String;

    .line 400
    iget-object v0, p0, Lorg/cocos2dx/cpp/AppActivity;->mOrderID:Ljava/lang/String;

    const-string v1, "tradeName"

    invoke-interface {v9, v1}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v1

    check-cast v1, Ljava/lang/String;

    const-string v2, "money"

    invoke-interface {v9, v2}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v2

    check-cast v2, Ljava/lang/String;

    invoke-static {v2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v2

    int-to-float v2, v2

    div-float/2addr v2, v6

    float-to-double v2, v2

    const-string v4, "CNY"

    const-string v5, "money"

    invoke-interface {v9, v5}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v5

    check-cast v5, Ljava/lang/String;

    invoke-static {v5}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I

    move-result v5

    int-to-float v5, v5

    div-float/2addr v5, v6

    float-to-double v5, v5

    const-string v7, "\u7231\u6e38\u620f"

    invoke-static/range {v0 .. v7}, Lcom/lotuseed/android/LSGAVirtualCurrency;->onChargeRequest(Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;DLjava/lang/String;)V

    .line 402
    new-instance v8, Ljava/util/HashMap;

    invoke-direct {v8}, Ljava/util/HashMap;-><init>()V

    .line 403
    .local v8, "payParams":Ljava/util/HashMap;, "Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;"
    const-string v1, "toolsAlias"

    const-string v0, "alias"

    invoke-interface {v9, v0}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;

    move-result-object v0

    check-cast v0, Ljava/lang/String;

    invoke-virtual {v8, v1, v0}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

    .line 404
    new-instance v0, Lorg/cocos2dx/cpp/AppActivity$3;

    invoke-direct {v0, p0}, Lorg/cocos2dx/cpp/AppActivity$3;-><init>(Lorg/cocos2dx/cpp/AppActivity;)V

    invoke-static {p0, v8, v0}, Lcn/egame/terminal/paysdk/EgamePay;->pay(Landroid/app/Activity;Ljava/util/Map;Lcn/egame/terminal/paysdk/EgamePayListener;)V

    .line 433
    return-void
.end method
smali源码又臭又长,还是先从java分析逻辑,要实现在不支付的情况下购买成功,那我们就就将上面三个函数第二位置传入的值都改为0,那么就是不管成功还是失败都调用字符成功的处理逻辑和方法。
那我们通过noResult函数来查找引用他的类,分别找到引用noResult的三个方法的smali源码:
.method public payCancel(Ljava/util/Map;)V
    .locals 2
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/util/Map",
            "<",
            "Ljava/lang/String;",
            "Ljava/lang/String;",
            ">;)V"
        }
    .end annotation

    .prologue
    .line 409
    .local p1, "arg0":Ljava/util/Map;, "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"
    iget-object v0, p0, Lorg/cocos2dx/cpp/AppActivity$3;->this$0:Lorg/cocos2dx/cpp/AppActivity;

    iget v0, v0, Lorg/cocos2dx/cpp/AppActivity;->mOrgID:I

    const/4 v1, -0x1

    invoke-static {v0, v1}, Lorg/cocos2dx/cpp/AppActivity;->onResult(II)V

    .line 410
    return-void
.end method

.method public payFailed(Ljava/util/Map;I)V
    .locals 2
    .param p2, "arg1"    # I
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/util/Map",
            "<",
            "Ljava/lang/String;",
            "Ljava/lang/String;",
            ">;I)V"
        }
    .end annotation

    .prologue
    .line 415
    .local p1, "arg0":Ljava/util/Map;, "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"
    iget-object v0, p0, Lorg/cocos2dx/cpp/AppActivity$3;->this$0:Lorg/cocos2dx/cpp/AppActivity;

    iget v0, v0, Lorg/cocos2dx/cpp/AppActivity;->mOrgID:I

    const/4 v1, -0x1

    invoke-static {v0, v1}, Lorg/cocos2dx/cpp/AppActivity;->onResult(II)V

    .line 416
    return-void
.end method
其中核心的是调用字符回调接口:
smali:invoke-static {v0, v1}, Lorg/cocos2dx/cpp/AppActivity;->onResult(II)V

java:AppActivity.onResult(AppActivity.this.mOrgID, -1);
我们将参数v1改为0就OK了,也就是在失败的情况下也调用成功的处理方法。
上面只是一种方法,需要修改paycancel、payFailed两个方法里面的参数,那有没有一个方法可以一步到位的呢?其实是有的,既然成功失败、取消都调用的同一个回调函数,那么是不是可以实现一次修改,永久生效呢?
我们再看他们共同调用的一个函数:onResult
 public static void onResult(int arg2, int arg3{
        if(arg3 == 0) {
            LSGAVirtualCurrency.onChargeSuccess(AppActivity.context.mOrderID);
        }

        AppActivity.context.runOnGLThread(new Runnable(arg2, arg3) {
            public void run({
                AppActivity.onResultNative(this.val$id, this.val$success);
            }
        });
    }

其实就是把状态的参数arg3传入,如果等于0那么就做成功的逻辑,我们就只需要在内部把arg3改为0,需要在smali中修改,再看对应的smali源码:
.method public static onResult(II)V
    .locals 2
    .param p0, "id"    # I
    .param p1, "success"    # I

    .prologue
    .line 437
    if-nez p1, :cond_0

    .line 438
    sget-object v0, Lorg/cocos2dx/cpp/AppActivity;->context:Lorg/cocos2dx/cpp/AppActivity;

    iget-object v0, v0, Lorg/cocos2dx/cpp/AppActivity;->mOrderID:Ljava/lang/String;

    invoke-static {v0}, Lcom/lotuseed/android/LSGAVirtualCurrency;->onChargeSuccess(Ljava/lang/String;)V

    .line 440
    :cond_0
    sget-object v0, Lorg/cocos2dx/cpp/AppActivity;->context:Lorg/cocos2dx/cpp/AppActivity;

    new-instance v1, Lorg/cocos2dx/cpp/AppActivity$4;

    invoke-direct {v1, p0, p1}, Lorg/cocos2dx/cpp/AppActivity$4;-><init>(II)V

    invoke-virtual {v0, v1}, Lorg/cocos2dx/cpp/AppActivity;->runOnGLThread(Ljava/lang/Runnable;)V

    .line 446
    return-void
.end method

smali中对应java源码if判断的语句是p1参数不等于0,其逻辑是如果p1不等于0那么就跳到cond_0标记的地方,因为smali是从上往下执行,所以有跳转。
if-nez p1, :cond_0

那我们直接将这一句注解了就行了,然后打包签名安装,最后的效果有多给力呢?攻击力每次一千万,boss最多几百万,出来的boss直接秒杀,自动刷级,因为这款app已经下架,正常支付是不通过的,所以这篇文章应该不侵犯任何人权益。


很赞哦! ( 37)
    《Python实战进阶》
    None
    None
    夏至已深

站点信息

  • 建站时间:2019-5-24
  • 网站程序:like in love
  • 主题模板《今夕何夕》
  • 文章统计:104条
  • 文章评论:***条
  • 微信公众号:扫描二维码,关注我们
  • 个人微信公众号