从大三伊始到大四落幕,从刚开始接触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>
第二步,将导出的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)); } }
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(); } }
反编译工具下载:dex2jar & jd-gui