从大三伊始到大四落幕,从刚开始接触Android到辞掉第一份实习工作,我接触Android应用层开发也快接近两年了。越来越发觉Android的应用层已经没什么挑战性了,想当初刚开始学习Android的时候,弄了一个Activity出来显示在手机的那份喜悦,真是~哈哈~,应用层的开发无非也就调用JDK,SDK而已,现在感觉有点小儿科啊,实习期间,每当工作项目之余,基本都泡到自定义View的绘制去,那也是我所能解闷的工作了。可是,这并不符合的职业规划,我想往前发展,比如framework层或者其他深层的技术等。
因为实习将近一年,在Android开发方面有一定的经验,再加上自己是应届生,所以有家稍微有点大但名气比较低(起码叫我面试的时候我都没听过这公司,额。。)的上市公司给我伸出了橄榄枝,从白纸开始培养人才。又因为提供的岗位叫Android逆向分析工程师,以前就听过这霸气的名字了,实际就是白帽子的工作,所以我也签了这公司,来实习了,毕竟还有一个月才能拿到毕业证。
说实话,逆向分析已经和Android应用开发不是一个level了,也和应用层开发没什么关系了,只是逆向分析需要熟悉应用层开发中的内容而已,比如反编译后要找到某个Activity或着fragment,总之,你要定位到关键代码,那你就必须得熟悉Android应用层的开发内容,尤其是混淆过,那就更需要熟悉开发的结构了,不然要在一大堆反编译的文件中找到你要的代码简直就令人发怵。
嗯,废话不多说,既然没有什么经验,那么就好好学习。先来个入门的工作,就是利用dex2jar反编译一个APK,并用jd-gui.exe查看jar包内容。
第一步,先写个简单的工程,并签名打包导出APK。
LoginActivity.java:
public class LoginActivity extends Activity {
private final String ACCOUNT="samuel";
private final String PASSWORD="123456";
private EditText etAccount, etPassword;
private Button btnLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
etAccount=(EditText)findViewById(R.id.et_account);
etPassword=(EditText)findViewById(R.id.et_password);
btnLogin=(Button)findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isOK(etAccount.getText().toString(), etPassword.getText().toString())) {
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
}
});
}
private boolean isOK(String account, String password){
return account.equals(ACCOUNT) && password.equals(PASSWORD);
}
}
布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="帐号:"/>
<EditText
android:id="@+id/et_account"
android:layout_width="100dp"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码:"/>
<EditText
android:id="@+id/et_password"
android:layout_width="100dp"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="登录"/>
</LinearLayout>
</RelativeLayout>
签名打包 Build>Generate Signed APK:
第二步,将导出的xxx.apk文件的后缀.apk改为.zip,即压缩文件后缀,然后解压。(高版本的dex2jar貌似是不需要2,3步的,直接把APK扔到dos里,直接输入命令dex2jar xxx.apk即可)
第三步,下载dex2jar,用cmd打开dos系统,并在dex2jar所在窗口的dos位置输入 dex2jar 路径名+classes.dex,即在上图的文件夹中(不带路径,则默认在当前文件夹)生成一个.jar后缀的名字的文件。
第四步,jar文件是不可以直接看的,要用到配套的工具jd-gui.exe打开。
至此,反编译的入门工作也差不多了。可以看到,由于APK打包时没有作混淆处理,被反编译过来后,其代码的类名和成员变量名字都是没有变化的,这很容易让反编译的人看到源码,并实施恶意行为,所以在打包的时候必须要作混淆处理。
最后,我们比较一下不做和做了混淆处理后,同样经过以上步骤反编译出来的jar文件是怎样的:
不做混淆的反编译代码:
LoginActivity.java:
public class LoginActivity extends Activity
{
private final String ACCOUNT = "samuel";
private final String PASSWORD = "123456";
private Button btnLogin;
private EditText etAccount;
private EditText etPassword;
private boolean isOK(String paramString1, String paramString2)
{
return (paramString1.equals("samuel")) && (paramString2.equals("123456"));
}
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130968601);
this.etAccount = ((EditText)findViewById(2131492944));
this.etPassword = ((EditText)findViewById(2131492945));
this.btnLogin = ((Button)findViewById(2131492946));
this.btnLogin.setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
if (LoginActivity.this.isOK(LoginActivity.this.etAccount.getText().toString(), LoginActivity.this.etPassword.getText().toString()))
{
Toast.makeText(LoginActivity.this, "登录成功", 0).show();
return;
}
Toast.makeText(LoginActivity.this, "登录失败", 0).show();
}
});
}
}
经过混淆处理后的反编译代码:
LoginActivity.java:
public class LoginActivity extends Activity
{
private final String a = "samuel";
private final String b = "123456";
private EditText c;
private EditText d;
private Button e;
private boolean a(String paramString1, String paramString2)
{
return (paramString1.equals("samuel")) && (paramString2.equals("123456"));
}
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130968601);
this.c = ((EditText)findViewById(2131492944));
this.d = ((EditText)findViewById(2131492945));
this.e = ((Button)findViewById(2131492946));
this.e.setOnClickListener(new a(this));
}
}
a.java:
class a
implements View.OnClickListener
{
a(LoginActivity paramLoginActivity)
{
}
public void onClick(View paramView)
{
if (LoginActivity.a(this.a, LoginActivity.a(this.a).getText().toString(), LoginActivity.b(this.a).getText().toString()))
{
Toast.makeText(this.a, "登录成功", 0).show();
return;
}
Toast.makeText(this.a, "登录失败", 0).show();
}
}
比较可看到,混淆过的代码中,关键的变量名都被a, b, c, d等字母代替了,其中button的匿名内部监听类由View.OnClickListener类变成了a类。这样一来,混淆过的代码就没有那么容易受到恶意攻击了。但貌似还是有办法反混淆的,所以也没有百分百的安全吧。
反编译工具下载:dex2jar & jd-gui