逆向破解Android版贪吃蛇
逆向就像破案,不断找线索,等最后破案了才发现原来如此简单。
先说下破解目标,在无尽模式下让蛇不死。
首先神器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
蛇撞墙后果然不会死了,但是蛇撞蛇还是会死,推测蛇撞蛇用了另外的碰撞检测。
时间关系就不找了。
当然最后只是删了两行代码而已,看起来很简单,过程其实没有那么容易,比如最后回编译打包,也许你会碰到问题,打不了包,仔细分析去解决吧。这里只是给个思路,仅供学习参考,勿做他用。
这次分析纯粹是分析代码找重点,还有动态调试破解的方法,以后再来。