接上一篇
分析一下这个apk的代码结构:google.gson肯定是google 解析json的gson库,nd.dianjin初步看一下,推断为 获取积分的第三方sdk,首先重点分析android.a这个包。
贴出Aa这个class的源代码,发现这个就是第一个activity的代码了,看来这个app并没有使用proguard进行混淆 =,= ,纯粹是命名方式让我产生了错觉。继续分析这个类的代码:
package com.android.a;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.nd.dianjin.DianJinPlatform;
import com.nd.dianjin.DianJinPlatform.OfferWallStyle;
import com.nd.dianjin.DianJinPlatform.Oriention;
import com.nd.dianjin.listener.AppActivatedListener;
import com.nd.dianjin.webservice.WebServiceListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Aa extends Activity
{
String Pname;
boolean isRomHave = false;
final Handler mHan = new Handler();
Runnable sxMB = new Runnable()
{
public void run()
{
TextView localTextView = (TextView)Aa.this.findViewById(1);
if (Ac.point == null)
Aa.this.getPoint();
localTextView.setText("当前M币:" + Ac.point);
Button localButton = (Button)Aa.this.findViewById(11);
if ((Aa.this.isRomHave) && (Ac.point != null))
localButton.setEnabled(true);
while (true)
{
Aa.this.mHan.postDelayed(Aa.this.sxMB, 500L);
return;
localButton.setEnabled(false);
}
}
};
private void CopyAssetsRom()
{
if (new File("/data/data/" + this.Pname + "/rom_zse.zip").exists())
{
this.isRomHave = true;
return;
}
AssetManager localAssetManager = getAssets();
try
{
localInputStream = localAssetManager.open("rom_zse.zip");
localFileOutputStream = new FileOutputStream("/data/data/" + this.Pname + "/rom_zse.zip");
}
catch (Exception localException1)
{
try
{
InputStream localInputStream;
FileOutputStream localFileOutputStream;
copyFile(localInputStream, localFileOutputStream);
localInputStream.close();
localFileOutputStream.flush();
localFileOutputStream.close();
while (true)
{
label109: this.isRomHave = true;
return;
localException1 = localException1;
}
}
catch (Exception localException2)
{
break label109;
}
}
}
private void bujuset()
{
LinearLayout localLinearLayout = new LinearLayout(this);
localLinearLayout.setGravity(17);
localLinearLayout.setOrientation(1);
TextView localTextView = new TextView(this);
localTextView.setId(1);
localTextView.setWidth(-2);
localTextView.setGravity(17);
Button localButton1 = new Button(this);
localButton1.setId(11);
Button localButton2 = new Button(this);
Button localButton3 = new Button(this);
localTextView.setText("当前M币:0");
localButton1.setText("开始游戏");
localButton2.setText("去除广告");
localButton3.setText("退出");
localLinearLayout.addView(localTextView);
localLinearLayout.addView(localButton1);
localLinearLayout.addView(localButton2);
localLinearLayout.addView(localButton3);
setContentView(localLinearLayout);
localButton1.setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
String str = "/data/data/" + Aa.this.Pname + File.separator;
if (Ac.point.floatValue() >= 5.0F)
{
Aa.this.subPoint(5.0F);
FRW.saveFile("1", str, "261905");
}
while (true)
{
Ac.GameCanRun = true;
Aa.this.startNewAct();
return;
FRW.saveFile("0", str, "261905");
}
}
});
localButton2.setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
Aa.this.dialog();
}
});
localButton3.setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
Ac.GameCanRun = false;
Aa.this.finish();
}
});
}
private void chushihua()
{
DianJinPlatform.initialize(this, 18283, "866f016094916c9709b9ed998e214c6b");
DianJinPlatform.setAppActivatedListener(new AppActivatedListener()
{
public void onAppActivatedResponse(int paramAnonymousInt, Float paramAnonymousFloat)
{
switch (paramAnonymousInt)
{
default:
Toast.makeText(Aa.this, "奖励M币:ERROR", 0).show();
return;
case 7:
Toast.makeText(Aa.this, "奖励M币:" + String.valueOf(paramAnonymousFloat), 0).show();
return;
case 8:
}
Toast.makeText(Aa.this, "奖励M币:0", 0).show();
}
});
}
private void copyFile(InputStream paramInputStream, OutputStream paramOutputStream)
throws IOException
{
byte[] arrayOfByte = new byte[1024];
while (true)
{
int i = paramInputStream.read(arrayOfByte);
if (i == -1)
return;
paramOutputStream.write(arrayOfByte, 0, i);
}
}
private Float getPoint()
{
DianJinPlatform.getBalance(this, new WebServiceListener()
{
public void onResponse(int paramAnonymousInt, Float paramAnonymousFloat)
{
switch (paramAnonymousInt)
{
default:
Toast.makeText(Aa.this, "未知错误,错误码为:" + paramAnonymousInt, 0).show();
Ac.point = Float.valueOf(0.0F);
return;
case 0:
Ac.point = paramAnonymousFloat;
return;
case -1:
}
Toast.makeText(Aa.this, "获取余额失败", 0).show();
Ac.point = Float.valueOf(0.0F);
}
});
return Ac.point;
}
private void startNewAct()
{
Intent localIntent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
localIntent.addFlags(67108864);
startActivity(localIntent);
}
private float subPoint(float paramFloat)
{
DianJinPlatform.consume(this, paramFloat, new WebServiceListener()
{
public void onResponse(int paramAnonymousInt, Integer paramAnonymousInteger)
{
switch (paramAnonymousInt)
{
default:
Toast.makeText(Aa.this, "未知错误,错误码为:" + paramAnonymousInt, 0).show();
return;
case 0:
Toast.makeText(Aa.this, "成功去掉广告", 0).show();
return;
case 6001:
Toast.makeText(Aa.this, "去广告失败请重启游戏", 0).show();
return;
case 6002:
Toast.makeText(Aa.this, "余额不足", 0).show();
return;
case 6003:
Toast.makeText(Aa.this, "账号不存在", 0).show();
return;
case 6004:
Toast.makeText(Aa.this, "订单号重复", 0).show();
return;
case 6005:
Toast.makeText(Aa.this, "一次性交易金额超过最大限定金额", 0).show();
return;
case 6006:
}
Toast.makeText(Aa.this, "不存在该类型的消费动作ID", 0).show();
}
});
return getPoint().floatValue();
}
protected void dialog()
{
AlertDialog.Builder localBuilder = new AlertDialog.Builder(this);
localBuilder.setMessage("只需5个M币即可无广告运行");
localBuilder.setIcon(2130837514);
localBuilder.setTitle("好消息");
localBuilder.setPositiveButton("免费获取M币", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
paramAnonymousDialogInterface.dismiss();
DianJinPlatform.showOfferWall(Aa.this, DianJinPlatform.Oriention.SENSOR, DianJinPlatform.OfferWallStyle.BLUE);
}
});
localBuilder.setNegativeButton("精品推荐", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
paramAnonymousDialogInterface.dismiss();
DianJinPlatform.showOfferWall(Aa.this, DianJinPlatform.Oriention.SENSOR, DianJinPlatform.OfferWallStyle.BROWN);
}
});
localBuilder.create().show();
}
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
this.Pname = getPackageName();
CopyAssetsRom();
chushihua();
bujuset();
this.mHan.post(this.sxMB);
}
protected void onDestroy()
{
DianJinPlatform.destroy();
Ac.GameCanRun = false;
super.onDestroy();
}
public boolean onKeyDown(int paramInt, KeyEvent paramKeyEvent)
{
if (paramInt == 4)
new AlertDialog.Builder(this).setTitle("提示").setMessage("是否确认退出?").setCancelable(false).setPositiveButton("确定", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
Ac.GameCanRun = false;
Aa.this.finish();
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface paramAnonymousDialogInterface, int paramAnonymousInt)
{
paramAnonymousDialogInterface.cancel();
}
}).show();
return true;
}
protected void onResume()
{
getPoint();
super.onResume();
}
}
还有个类,Ac.class,看来关键就是这2个类了
package com.android.a;
public class Ac
{
public static boolean GameCanRun = false;
public static Float point;
}
再简单的看一下,这个Ac.point应该就是当前的M币.
观察到sxMB这个runnable对象:
public void run()
{
TextView localTextView = (TextView)Aa.this.findViewById(1);
if (Ac.point == null)
Aa.this.getPoint();
localTextView.setText("当前M币:" + Ac.point);
以及在Ac 类中 Float point ==null ,所以可以直接考虑为将Ac.point 赋予一个足够大的初值。
以及:
Button localButton = (Button)Aa.this.findViewById(11);
if ((Aa.this.isRomHave) && (Ac.point != null)) //将isRomHave置1,使localButton为enable
localButton.setEnabled(true);
while (true)
{
Aa.this.mHan.postDelayed(Aa.this.sxMB, 500L); //表示500ms运行一次sxMB ,直接删除掉
return;
localButton.setEnabled(false);
下面将对此文件进行修改:
步骤四:修改
使用apktool 对此apk进行反汇编并修改,命令如下: apktool d sanguo2.apk ,对于apktool的使用,这里就不进行详细说明了
在apk的安装目录下,得到sanguo2这样一个文件夹,这里的smali 文件夹下就是前一篇文章所介绍的源文件的汇编代码。
打开Ac.smali文件: 我这里用的编辑器是sumblime text2
.class public Lcom/android/a/Ac;
.super Ljava/lang/Object;
.source "Ac.java"
# static fields
.field public static GameCanRun:Z
.field public static point:Ljava/lang/Float;
# direct methods
.method static constructor <clinit>()V
.locals 1
.prologue
.line 4
const/4 v0, 0x0
sput-boolean v0, Lcom/android/a/Ac;->GameCanRun:Z
.line 3
return-void
.end method
.method public constructor <init>()V
.locals 0
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
这个就是Ac.class对应的 汇编文件,smali的语法可参照google code上的smali项目主页: http://code.google.com/p/smali/
我们将point值赋予5000,修改后的smali文件如下图所示:
.class public Lcom/android/a/Ac;
.super Ljava/lang/Object;
.source "Ac.java"
# static fields
.field public static GameCanRun:Z
.field public static point:Ljava/lang/Float;
# direct methods
.method static constructor <clinit>()V
.locals 1
.prologue
.line 4
const/4 v0, 0x0
sput-boolean v0, Lcom/android/a/Ac;->GameCanRun:Z
.line 5
const v0, 0x459c4000
invoke-static {v0}, Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;
move-result-object v0
sput-object v0, Lcom/android/a/Ac;->point:Ljava/lang/Float;
return-void
.end method
.method public constructor <init>()V
.locals 0
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.line 5
//表示代码在原java文件中的行数,它只是为了方便调试,不是必须的,去掉也没关系
const v0, 0x459c4000
//表示将0x459c400的值放入v0寄存器
invoke-static {v0}, Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;
//表示调用静态方法valueof(f),L表示对象,java/lang/Float 表示包/类名, Ljava/lang/Float表示返回的类型
move-result-object v0
//将得到的值放入寄存器v0中
sput-object v0, Lcom/android/a/Ac;->point:Ljava/lang/Float;
//和上面类似,表示将v0中的值放入point这个变量中
在Aa.smali 中:
删除line53:
invoke-virtual {v0, v1}, Landroid/os/Handler;->post(Ljava/lang/Runnable;)Z
修改line30:
const/4 v0, 0x0
iput-boolean v0, p0, Lcom/android/a/Aa;->isRomHave:Z
为:
const/4 v0, 0x1
iput-boolean v0, p0, Lcom/android/a/Aa;->isRomHave:Z
修改完之后保存,并重新打包:
在cmd下输入apktool b sanguo2,如图所示:
这时会在这个文件夹下生成两个文件夹:存放中间文件的文件夹build和存放最后的apk文件的文件夹dist。
如果一切顺利的话,你就可以在dist文件夹中看到我们修改后的sanguo2.apk了。不过需要注意的是,这个APK文件是还没有签名的,所以无法安装运行。我们还需要进行最后一步,那就是对这个APK进行签名。
步骤五:重新签名
这里使用的工具是autosign,下载地址( http://pan.baidu.com/s/1o6I4Z9o ),双击sign.bat 即可使用。需要注意的是,要把签名文件改为update.zip , 然后才可以使用。
原因我们打开sign.bat 这个批处理文件就可以知道:
@ECHO OFF
Echo Auto-sign Created By Dave Da illest 1
Echo Update.zip is now being signed and will be renamed to update_signed.zip
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.zip update_signed.zip
Echo Signing Complete
Pause
EXIT
这样,这次小小的破解就算完成了,让我们试试效果吧!
安装,运行游戏:
好了,开始游戏吧!