逆向破解Android版贪吃蛇

2018-05-11  本文已影响303人  andev009

逆向就像破案,不断找线索,等最后破案了才发现原来如此简单。
先说下破解目标,在无尽模式下让蛇不死。
首先神器apktool反编译,顺利得到混淆后的代码和资源。


1.png

可以看到进行dex分包了。
装APK跑一下,蛇碰撞死后,出现游戏结束对话框。在这步我们取得无尽模式的Activity是com.wepie.snake.app.activity.GameActivity,layout是activity_main.xml。布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">
    <com.wepie.snake.module.game.GameViewContainer android:layout_width="match_parent" android:layout_height="match_parent">
        <com.wepie.snake.lib.plugin.z android:id="@+id/main_snake_surface_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
        <com.wepie.snake.module.game.SpeedUpView android:id="@+id/main_speedup_bt" android:paddingLeft="80dp" android:paddingTop="80dp" android:paddingRight="40dp" android:paddingBottom="40dp" android:layout_width="200dp" android:layout_height="200dp" android:src="@drawable/shape_ffffff_corners_bl4_br4" android:scaleType="centerInside" android:layout_alignParentRight="true" android:layout_alignParentBottom="true"/>
    </com.wepie.snake.module.game.GameViewContainer>
    <com.wepie.snake.module.game.g android:id="@+id/main_snake_game_info_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
    <com.wepie.snake.module.game.RouletteView android:id="@+id/main_roulette_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentBottom="true"/>
</RelativeLayout>

有兴趣的可以研究下这些View,这里只谈破解分析结果。按正常逻辑,如果我们写代码,就是蛇死的时候,在布局里弹框。破解就是要找到蛇死的时机,然后hack掉。
对布局文件分析后,得知com.wepie.snake.lib.plugin.z,这个类才是游戏真正的核心,很有迷惑性。
z的定义如下:

public class z extends GLSurfaceView {
class c implements Renderer {
public void onDrawFrame(GL10 gl10) {
            GLES20.glClear(16640);
            if (this.d.q && this.d.t.b.a) {
                this.d.t.d();
            }
            if (this.a) {
                this.d.q = false;
                this.a = false;
                b();
            }
            this.d.m.a();
            this.d.l.a();
            this.d.t.a(this.d.k);
            z zVar = this.d;
            zVar.f += this.d.t.c.c * ((float) this.d.t.b.d);
            zVar = this.d;
            zVar.g += this.d.t.c.d * ((float) this.d.t.b.d);
            com.wepie.snake.module.game.i.b.a(this.d.f * e.k, this.d.g * e.k, 20.0f, this.d.f * e.k, this.d.g * e.k, 0.0f, 0.0f, 1.0f, 0.0f);
            this.d.e.c();
            this.d.j.b();
            this.d.k.c();
            this.d.l.c();
            this.d.w.a(this.d.v);
            this.d.u.b(this.d.t);
            this.d.u.a(this.d.m, this.d.k);
            this.d.t.a();
            this.d.j.a(this.d.m);
            this.d.k.a();
            this.d.l.a(this.d.m);
            this.d.t.d.a(this.d.m);
            this.d.d.a(this.d.t);
            this.d.u.a(this.d.d);
            this.b++;
            if (this.b >= 60) {
                this.b = 0;
                this.d.p.a(this.d.v);
            }
        }
}
}

代码被混淆过,考验耐心,细心和想象力的时候到了。
熟悉GLSurfaceView的都知道,游戏循环逻辑在Renderer的onDrawFrame,猜想就是在这里面每次调用时检测蛇死了没。
这里先说下一个误导,之前说蛇死了,弹框游戏结束,先找到弹框控件

public class g extends FrameLayout {
    public GameRankView a;
    private Context b;
    private TextView c;
    private TextView d;
    private TextView e;
    private c f;
    private GameOverView g;
    private int h;
    private int i;
    private int j;
     public void a(boolean z, OffGameScoreInfo offGameScoreInfo) {
        b.a();
        this.g.setVisibility(0);//View.visible
        this.g.a(z, this.i, this.j, this.h);
        this.g.a(offGameScoreInfo);
        this.f.a();
    }

}

控件有a这个方法,里面正好有setVisibility(0),蛇死,调用这个a方法,看起来逻辑正确。于是去z (GLSurfaceView)里找哪里调用了g的a方法。找到下面几个地方

this.d.u.a(this.d.m, this.d.k);//AiManager 
this.d.j.a(this.d.m);//food
this.d.l.a(this.d.m);//KillFactory
this.d.t.d.a(this.d.m)//SnakeInfo

上面的m就是g类。研究后发现四个方法没有想要的逻辑,四个方法作用后面注释了。
仔细研究onDrawFrame里的方法,每次循环都在做什么?发现了这个方法

this.d.d.a(this.d.t);

第一个d是z,第二个d是com.wepie.snake.module.game.h.d,

public class d {
 public void a(m mVar) {
        if (mVar.b.a) {
            this.k = mVar;
            i a = mVar.d.a();
            this.i = a.a;
            this.j = a.b;
            if (a()) {//重点
                this.l = mVar.d.d + this.g;
                int a2 = g.a(this.i, this.j);
                this.t = 10000.0f;
                this.u = 10000.0f;
                e(a2);
                a(a2);
                b(a2);
                if (a2 % 16 != 0) {
                    c(a2);
                }
                if ((a2 + 1) % 16 != 0) {
                    d(a2);
                }
                b();
            }
        }
    }

  private boolean a() {
        float f = this.k.d.d * 0.6f;
        if (this.i >= a + f && this.i <= c - f && this.j <= b - f && this.j >= f + d) {
            return true;
        }
        a(this.k, null);
        return false;
    }

}

这里做碰撞检测,如果a()为true,执行下面的代码,false呢?game over!
好,找到这个位置,然后怎么修改?找到反编译后的smali代码,

.line 61
invoke-direct {p0}, Lcom/wepie/snake/module/game/h/d;->a()Z
move-result v0

我们想要的效果是是if(true),这样碰撞检测始终返回true,就不会死了,所以把smali代码这段判断直接删除。

最后,回编译打包,跑跑看。


2.png

蛇撞墙后果然不会死了,但是蛇撞蛇还是会死,推测蛇撞蛇用了另外的碰撞检测。
时间关系就不找了。

当然最后只是删了两行代码而已,看起来很简单,过程其实没有那么容易,比如最后回编译打包,也许你会碰到问题,打不了包,仔细分析去解决吧。这里只是给个思路,仅供学习参考,勿做他用。
这次分析纯粹是分析代码找重点,还有动态调试破解的方法,以后再来。

上一篇下一篇

猜你喜欢

热点阅读