從大三伊始到大四落幕,從剛開始接觸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