轉載請註明出處: http://www.blogjava.net/zh-weir/arch...11/352099.html
APK Crack
寫這篇文章之前,有些猶豫,我會不會因此而開啟了一個潘多拉魔盒呢?後來一想,Android類似的惡意軟體早已問世,說明這世上已經有不少軟體安全專家或者駭客們對此並不陌生了。而我,僅僅是作為一個傳道者,將這個少數人知道的秘密告訴大家。於是我心安多了,我想我是在為Android軟體安全行業早日成熟起來做貢獻吧~!
所謂APK指的是Android作業系統的應用程式安裝檔案。所謂Crack,簡單地理解為“破解”。我具體指的是反編譯APK檔案進行彙編級的程式碼分析,並修改或插入自己的代碼,重新簽名打包為APK檔案,以達到改變程式原有行為的目的。
由以上的說明可知,我們要Crack一個APK檔案,主要流程有三步:反編譯、程式碼分析、重新打包簽名。
基本準備
我們需要一些基本的工具進行一些主要的工作。如果你是一個會做Android APK漢化的朋友,那麼你應該對這些工具非常熟悉:
第一個工具是android-apktool,A tool for reengineering Android apk files 。這個工具是我們完成APK Crack的核心,利用它實現APK檔案的反編譯和重新打包。它是Google Code上一個非常著名的開源項目,大家可以在Google Code的網頁上擷取它和它的Wiki、源碼及其他相關資訊。網址是:http://code.google.com/p/android-apktool/ 。
第二個工具是Auto-sign。這個工具實現的是APK打包後的簽名工作,屬於一個小工具。
除了這些基本工具外,為了更好的分析代碼,你可能還需要用到一些其他工具,例如:dex2jar和jd-gui等,這裡不做詳述。
反編譯
如果你是一個經常漢化APK程式的朋友,那麼反編譯這一步你肯定不會陌生。不過,既然這篇文章側重於基本流程講解,那麼這一步想來是不能省掉的。所以,覺得羅嗦的朋友,請跳過。首先我們需要有一個待反編譯的APK。這裡我自己寫了一個HelloWorld的APK,代碼如下:
package com.zh_weir.helloworld;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
我們通過android-apktool對這個APK進行反編譯。對於android-apktool的使用,我就不做太多翻譯的工作,直接給出說明文檔吧。簡單一句話,就是命令列執行。
Apktool v1.3.2 - a tool for reengineering Android apk files
Copyright 2010 Ryszard Wi?niewski <brut.alll@gmail.com>
Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
Usage: apktool [-v|--verbose] COMMAND [...]
COMMANDs are:
d[ecode] [OPTS] <file.apk> [<dir>]
Decode <file.apk> to <dir>.
OPTS:
-s, --no-src
Do not decode sources.
-r, --no-res
Do not decode resources.
-d, --debug
Decode in debug mode. Check project page for more info.
-f, --force
Force delete destination directory.
-t <tag>, --frame-tag <tag>
Try to use framework files tagged by <tag>.
--keep-broken-res
Use if there was an error and some resources were dropped, e.g.:
"Invalid config flags detected. Dropping resources", but you
want to decode them anyway, even with errors. You will have to
fix them manually before building.
b[uild] [OPTS] [<app_path>] [<out_file>]
Build an apk from already decoded application located in <app_path>.
It will automatically detect, whether files was changed and perform
needed steps only.
If you omit <app_path> then current directory will be used.
If you omit <out_file> then <app_path>/dist/<name_of_original.apk>
will be used.
OPTS:
-f, --force-all
Skip changes detection and build all files.
-d, --debug
Build in debug mode. Check project page for more info.
if|install-framework <framework.apk> [<tag>]
Install framework file to your system.
For additional info, see: http://code.google.com/p/android-apktool/
通過apktool d HelloWorld.apk的命令,我們就完成了一個簡單的APK的反編譯工作。得到了一個叫做“HelloWorld”的檔案夾。你可以看見檔案夾下有Manifest檔案,有反編譯出的res資源檔。這些東西都是平時漢化特別關心的,而不是我們要注意的重點。我們需要注意的是一個叫做“smali”的檔案夾。
仔細觀察,你會發現這個檔案夾下的檔案組織圖和我們的Android工程中java源碼的組織圖幾乎一致。只不過Java檔案被.smali的檔案取而代之了。我們用文字編輯器開啟這些.smali檔案,你會發現它們都是可識別的、並且非常“整齊”的文字檔,大致如下:
.class public Lcom/zh_weir/helloworld/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
# direct methods
.method public constructor <init>()V
.locals 0
.prologue
.line 6
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
.parameter "savedInstanceState"
.prologue
.line 10
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 11
const/high16 v0, 0x7f03
invoke-virtual {p0, v0}, Lcom/zh_weir/helloworld/MainActivity;->setContentView(I)V
.line 12
return-void
.end method
Smali檔案其實就是dalvik虛擬機器啟動並執行dex位元組碼檔案對應的彙編檔案了。如果你瞭解Java虛擬機器的組合語言Jasmin的話,你會發現兩者的文法非常相似。關於smali的文法等問題就不深入下去了,如果你想瞭解更多,可以訪問Google Code上Smali項目首頁:http://code.google.com/p/smali/ 。
程式碼分析與修改
即使你不會Jasmin文法,你也能很容易看明白上面的彙編代碼。需要指出的是,apktool反編譯出來的彙編代碼同樣也是物件導向的,而不是面向過程的。這點和C++的反組譯碼可能有所不同。
根據上面的代碼,我們可以看出,這個MainActivity的類有兩個成員方法。一個是預設的建構函式;另一個就是我們重載的OnCreate方法了。
在java彙編中,每個成員方法需要首先申明自己所使用的局部變數的個數,以便實現分配儲存空間。例如OnCreate使用了一個局部變數,就聲明:.locals 1 。後面則使用v0表示。
在一個非靜態成員方法中,p0代表的是這個類本身的引用,相當於this,p1開始才是函數的參數;而對於靜態方法,由於沒有this指標,所以p0就是函數的第一個參數。(其實本身this指標就是作為一個隱含的參數傳遞給非靜態成員函數的)。
通過分析上面Oncreate的彙編代碼,我們可以知道,它首先是調用super類的onCreate方法,然後再setContentView設定顯示。其中I、V等表示的是函數的參數和返回變數的類型,這是通用做法,這裡就不多做說明了。
分析到這一步,你是否發現一個問題?那就是如果我們按照同樣的文法修改或者增刪一個語句,是否就可以實現對程式的修改了呢?答案是肯定的。
例如,我們希望這個APK程式在運行時會彈出一個Toast,提示它被破解了。用Java的話,應該這樣表述:
Toast.makeText(this, "I'm Cracked!", Toast.LENGTH_LONG).show();
而用Java彙編的話,則應該表述為這樣:
const-string v0, "I\'m Cracked!"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
OK,只要我們將這段代碼插入到原來程式的OnCreate中,再重新打包程式,我們就能實現在這個程式運行時彈出Toast了。
改之後的代碼,大致如下:
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.locals 2
.parameter "savedInstanceState"
.prologue
.line 11
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 12
const/high16 v0, 0x7f03
invoke-virtual {p0, v0}, Lcom/zh_weir/helloworld/MainActivity;->setContentView(I)V
.line 14
const-string v0, "I\'m Cracked!"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 15
return-void
.end method
重新編譯打包簽名
修改完成後,我們就可以對這個檔案夾進行編譯打包了。同樣,我們使用的工具是apktool。通過命令apktool b HelloWorld,就可以實現程式編譯打包了。這時會在這個檔案夾下產生兩個檔案夾:存放中間檔案的檔案夾build和存放最後的apk檔案的檔案夾dist。
如果一切順利的話,你就可以在dist檔案夾中看到我們修改後的HelloWorld.apk了。不過需要注意的是,這個APK檔案是還沒有簽名的,所以無法安裝運行。我們還需要進行最後一步,那就是對這個APK進行簽名。
簽名我們需要用到的工具是Auto-sign。它主要是利用批處理命令,使用signapk.jar對APK檔案進行簽名的。你可以用記事本開啟Sign.bat,看看它的具體調用關係。關鍵如下:
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
OK!到此,我們對這個APK的Crack就結束了。現在在模擬器或者Android機器上安裝上籤好名的APK程式,運行試試。一切與我們的預期相同~
如何反破解
這篇文章本來到此就結束了。不過,對於軟體安全來說,有攻就要有防才對。不然,Android整個產業鏈就會被這樣的Crack給毀掉。由於篇幅有限,反破解方面就不做詳細描述了,僅僅做一個思路上的說明,希望大家諒解。
第一種辦法:將核心代碼用JNI寫進so庫中。由於so庫的反編譯和破解的難度加大,所以這種方式防止反編譯效果不錯。缺點是,對於Java層的代碼沒有保護作用,同樣可以被篡改。
第二種辦法:線上簽名比較。在程式初始化時,連網將啟動並執行程式的簽名與伺服器上的官方標準簽名進行比較,從而達到讓反編譯後的程式無法正常啟動並執行效果。缺點是,如果此部分連網檢驗的代碼被篡改跳過,則整套機制失效。
第三種辦法:代碼混淆。為了加大反編譯後程式碼分析的難度,對代碼進行混淆。缺點是,治標不治本,同樣可以修改(甚至據說還有反混淆工具,沒用過,不多做評論)。
這三種辦法都有各自的缺點,所以單單靠某一項要實現完美的軟體保護都是不可能的。不過,我們可以採用聯合幾種辦法的方式,來增強軟體保護的力度。曾經反編譯過Android版的卡巴斯基,它的保護思路似乎是這樣的:
代碼混淆,那是必須的,不過不指望它能有很好的效果。在程式初始化時,就直接通過JNI的so庫初始化程式。程式啟用部分也是通過JNI連網下載檔案,然後在JNI層讀檔案並做相應啟用與否的判斷的。
卡巴斯基是將大部分功能模組都放在JNI層來實現的,如果我們的程式都這樣處理,耗費的精力必然很大。所以,我們只是借鑒它的思路而已。具體操作思路如下:
代碼混淆。初始化時JNI層連網驗證簽名。驗證失敗則直接在JNI層退出程式。值得注意的是需要保證如果繞過JNI層的初始化,則程式無法正常啟動。這點不保證的話,破解還是很容易……