逆向破解Android版贪吃蛇

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

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


有兴趣的可以研究下这些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; }}

【逆向破解Android版贪吃蛇】这里做碰撞检测,如果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代码这段判断直接删除。
最后,回编译打包,跑跑看。

逆向破解Android版贪吃蛇
文章图片
2.png 蛇撞墙后果然不会死了,但是蛇撞蛇还是会死,推测蛇撞蛇用了另外的碰撞检测。
时间关系就不找了。
当然最后只是删了两行代码而已,看起来很简单,过程其实没有那么容易,比如最后回编译打包,也许你会碰到问题,打不了包,仔细分析去解决吧。这里只是给个思路,仅供学习参考,勿做他用。
这次分析纯粹是分析代码找重点,还有动态调试破解的方法,以后再来。

    推荐阅读