TechBridge 技術共筆部落格

var topics = ['Web前後端', '行動網路', '機器人/物聯網', '數據分析', '產品設計', 'etc.']

人人都會的 Android Apk 反編譯


對於 Android 工程師來說,了解如何反編譯可以增進自己對 Android 底層的理解,也可以思考如何保護自己的 apk 不被反編譯。

對於一般人來說,許多現成的工具可以幫助我們非常輕鬆的、只要打打幾個指令就可以反編譯 apk,看到 java source code,滿足自己的好奇心。

本篇文章只介紹一些工具的使用,適合初學者觀看。若是想了解更底層的知識,可以參考文末附上的延伸閱讀。

事前準備

首先,我們需要一個用來被破解的 apk,簡單用任何你平常熟悉的工具自己 build 一個就好了。基本架構很簡單,只要一個 MainActivity 跟兩個TextView就好:

MainActivity.java
1
2
3
4
5
6
7
8
9
10
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text = (TextView)findViewById(R.id.text);
text.setText("Taiwan No1");
}
}
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

安裝到手機上之後,會看到這樣的畫面:

device-2016-03-20-152510.png

實際動手

好,這個就是我們要拿來測試的 apk 了!
接著你需要一些非常好用的工具:

  1. apktool
  2. jd-gui
  3. dex2jar

如何安裝就不再贅述了,讀者們可以參考看看文件或是上網搜尋一下就會有一堆解答~
apktool是拿來把 apk 拆開用的,可以反編譯 apk 之後,看到 smali 檔案跟 resource
dex2jar可以把 apk 轉成 jar,再用jd-gui檢視 java code

接著我們開啟 terminal,到剛剛那個示範 apk 的目錄底下,執行apktool d APKNAME.apk
螢幕快照 2016-03-20 下午3.32.47.png

執行以後,會自動生成一個APKNAME的資料夾,裡面就是反編譯出來的東西了。

1
2
3
4
5
6
.
├── AndroidManifest.xml
├── apktool.yml
├── original
├── res
└── smali

其中比較值得講的是smali這個資料夾,其實這裡面就是你的 source code,只是格式不太一樣。
你可以在smali這資料夾裡面找到你的MainActivity.java,內容如下:
(覺得長得很奇怪是很正常的事,但是認真多看幾眼,你會發現其實沒那麼難懂)

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
.class public Lapktest/huli/com/apkdecompile/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
# direct methods
.method public constructor <init>()V
.locals 0
.prologue
.line 8
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.locals 2
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 12
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 13
const v1, 0x7f040019
invoke-virtual {p0, v1}, Lapktest/huli/com/apkdecompile/MainActivity;->setContentView(I)V
.line 14
const v1, 0x7f0c0050
invoke-virtual {p0, v1}, Lapktest/huli/com/apkdecompile/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/TextView;
.line 15
.local v0, "text":Landroid/widget/TextView;
const-string v1, "Taiwan No1"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 16
return-void
.end method

你可以仔細對照一下剛剛自己寫的 java code,會發現只是換了種格式而已:

1
setContentView(R.layout.activity_main);

其實就等於

1
2
3
.line 13
const v1, 0x7f040019
invoke-virtual {p0, v1}, Lapktest/huli/com/apkdecompile/MainActivity;->setContentView(I)V

你可能會好奇,這個0x7f040019是哪來的?
事實上,你可以在res/values/public.xml這個檔案裡面找到答案:

1
<public type="layout" name="activity_main" id="0x7f040019" />

到這裡,應該就可以大概猜出 Android 在編譯時候的流程:

  1. 把所有資源檔壓縮、處理並且包在一起,產生id與記憶體位置對照表
  2. 把程式碼裡面所有的R.xx.xxx透過剛剛產生的表,換成實際的記憶體位置
  3. 把 java code 變成 smali code(有點像把 C 變成組合語言的程式碼那樣)

修改

在剛剛的smali裡面,有這麼一段:

1
2
3
4
5
.line 15
.local v0, "text":Landroid/widget/TextView;
const-string v1, "Taiwan No1"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

讓我們把Taiwan No1換成T@iw@n n0!
還記得另一個TextView有用到R.string.hello_world嗎?
res/values/strings.xml裡面,可以找到這一串的定義:

1
<string name="hello_world">Hello world!</string>

改成:

1
<string name="hello_world">HELLO WORLD</string>

確定都有改完以後,就可以把這些程式碼再度「組裝」回去。
還記得剛剛反編譯的指令嗎?apktool d APK_NAME.apk
這邊的d就是decompile的意思,所以如果要逆向組裝回去,就是bbuild

apktool b APK_NAME

執行完之後可以在APK_NAME/dist下面找到一個 apk。
但要注意的是這個 apk 還沒有被 sign 過,因此無法安裝。
可以隨便生成一個 keystore 或是找現成的來簽署。
jarsigner -verbose -digestalg SHA1 -keystore ~/KEY.keystore APK_NAME.apk KEY_ALIAS

安裝完以後就會看到這樣的畫面:

device-2016-03-20-160501.png

沒錯!就是這麼簡單,一個 apk 就這樣被修改了!

可是smali的程式碼不好懂,能不能直接看到 java code呢?
這時候剛剛推薦的工具dex2jarjd-gui就派上用場了
前者可以把 apk 變成 jar,後者可以開啟一個 jar 並且顯示 java code
兩個組合在一起,就可以直接看到原本的程式碼了

dex2jar下載下來之後會有一堆的 shell script,dex2jar就是我們想要的那個
./d2j-dex2jar.sh app.apk
執行完之後會有一個 jar,用 jd-gui 打開,會看到你的程式碼一覽無遺:

螢幕快照 2016-03-20 下午4.10.15.png

總結

沒接觸過反編譯的人可能會很驚訝:什麼!要改掉一個 apk 居然這麼簡單!
沒錯,就是這麼簡單,而且這只是一個很基本的範例而已。事實上,你想要加入新的程式碼、加入新的資源(圖片、聲音等等)也是可以的。
也就是說,你不只可以修改,還可以擴充原本的 apk!

但也有些方法可以防止不肖人士反編譯 apk,例如說加殼、混淆、動態載入等,關於這些方法我們下次有機會再介紹給大家囉!

延伸閱讀

  1. Android 反編譯與防止被反編譯
  2. [Android] 程式碼混淆(ProGuard)與反組譯
  3. [Android] 反組譯 破解Android的apk安裝檔
  4. 反编译的常用工具与使用方法
  5. Smali–Dalvik虚拟机指令语言–>【android_smali语法学习一】
  6. android反编译-smali语法

關於作者:
@huli 野生工程師,相信分享與交流能讓世界變得更美好



TechBridge Weekly 技術週刊編輯團隊

TechBridge Weekly 技術週刊團隊是一群對用技術改變世界懷抱熱情的團隊。本技術共筆部落格初期專注於Web前後端、行動網路、機器人/物聯網、數據分析與產品設計等技術分享。This is TechBridge Weekly Team Tech Blog, which focus on web, mobile, robot, IoT, data analytics technology sharing.

關於我們 / 技術日報 / 技術週刊 / 粉絲專頁 / 訂閱RSS

Comments