Android逆向之旅---動態方式破解apk前奏篇(Eclipse動態除錯smail原始碼)

yangxi_001發表於2016-12-02

一、前言

今天我們開始apk破解的另外一種方式:動態程式碼除錯破解,之前其實已經在一篇文章中說到如何破解apk了:

Android中使用靜態方式破解Apk  主要採用的是靜態方式,步驟也很簡單,首先使用apktool來反編譯apk,得到smail原始碼,然後分析smail程式碼,採用程式碼注入技術來跟蹤程式碼,然後找到關鍵方法進行修改,進而破解,同時還可以使用一些開源的hook框架,比如:Xposed和Cydia Substrate,來進行關鍵方法的hook。所以這裡我們可以看到我們破解的第一步是使用apktool來進行成功的反編譯,然後是需要了解smali語法,不過關於smali語法其實很簡單,網上有很多教程。


二、知識概要分析

那麼今天我們就用另外一種方式來破解apk:動態方式,關於動態方式其實很廣義的,因為動態方式相對於靜態方式來說,難度大一點,但是他比靜態方式高效點,能夠針對更過的破解範圍。當然動態方式很多,所以這裡就分為三篇文章來講解這塊:

1、動態方式破解apk前奏篇(Eclipse動態除錯smail原始碼)

2、動態方式破解apk升級篇(IDA動態除錯so原始碼)

3、動態方式破解apk終極篇(應對加固的apk破解方法)

從這三篇文章能夠讓我們破解一般的apk沒有任何問題,不過不能代表能夠破解所有的apk,因為沒有絕對的安全,也是沒有絕對的破解,兩方都在進步,我們只能具體問題具體分析。好了,下面我們就來看第一篇文章,也是今天的重點:Eclipse動態除錯smali原始碼

首先需要解釋一下,這裡為什麼說是除錯smali原始碼,不是Java原始碼,因為我們弄過反編譯的人知道,使用apktool反編譯apk之後,會有一個smali資料夾,這裡就存放了apk對應的smali原始碼,關於smali原始碼這裡不解釋了,網上有介紹。


三、案例分析

因為這一篇是一個教程篇,所以不能光說,那樣會很枯燥的,所以這裡用一個例子來介紹一下:

我們就用阿里2014年安全挑戰賽的第一題:AliCrack_one.apk


看到這張圖了,阿里還挺會製造氛圍的,那麼其實很簡單,我們輸入密碼就可以破解了,下面我們就來看看如何獲取這個密碼。

第一步:使用apktool來破解apk

java -jar apktool_2.0.0rc4.jar d -d AliCraceme_1.apk -o out


這裡的命令不做解釋了,但是有一個引數必須帶上,那就是:-d

因為這個引數代表我們反編譯得到的smali是java檔案,這裡說的檔案是字尾名是java,如果不帶這個引數的話,字尾名是smali的,但是Eclipse中是不會識別smali的,而是識別java檔案的,所以這裡一定要記得加上這個引數。

反編譯成功之後,我們得到了一個out目錄,如下:


原始碼都放在smali資料夾中,我們進入檢視一下檔案:


看到了,這裡全是Java檔案的,其實只是字尾名為java了,內容還是smali的:



2、修改AndroidManifest.xml中的debug屬性和在入口程式碼中新增waitDebug

上面我們反編譯成功了,下面我們為了後續的除錯工作,所以還是需要做兩件事:

1》修改AndroidManifest.xml中的android:debuggable="true"


關於這個屬性,我們前面介紹run-as命令的時候,也提到了,他標識這個應用是否是debug版本,這個將會影響到這個應用是否可以被除錯,所以這裡必須設定成true。

2》在入口處新增waitForDebugger程式碼進行除錯等待。

這裡說的入口處,就是程式啟動的地方,就是我們一般的入口Activity,查詢這個Activity的話,方法太多了,比如我們這裡直接從上面得到的AndroidManifest.xml中找到,因為入口Activity的action和category是固定的。


當然還有其他方式,比如aapt檢視apk的內容方式,或者是安裝apk之後用adb dumpsys activity top命令檢視都是可以的。

找到入口Activity之後,我們直接在他的onCreate方法的第一行加上waitForDebugger程式碼即可,找到對應的MainActivity的smali原始碼:

然後新增一行程式碼:

invoke-static {}, Landroid/os/Debug;->waitForDebugger()V

這個是smali語法的,其實對應的Java程式碼就是:android.os.Debug.waitForDebugger();


這裡把Java語言翻譯成smali語法的,不難,網上有smali的語法解析,這裡不想再解釋了。


第三步:回編譯apk並且進行簽名安裝

java -jar apktool_2.0.0rc4.jar b -d out -o debug.apk

還是使用apktool進行回編譯


編譯完成之後,將得到debug.apk檔案,但是這個apk是沒有簽名的,所以是不能安裝的,那麼下面我們需要在進行簽名,這裡我們使用Android中的測試程式的簽名檔案和sign.jar工具進行簽名:


關於簽名的相關知識,可以看這篇文章:Android中的簽名機制詳解

java -jar .\sign\signapk.jar .\sign\testkey.x509.pem .\sign\testkey.pk8 debug.apk debug.sig.apk


簽名之後,我們就可以進行安裝了。


第四步:在Eclipse中新建一個Java工程,匯入smali原始碼

這裡我們新建一個Java工程,記住不是Android工程,因為我們最後除錯其實是藉助於Java的偵錯程式,然後勾選掉Use default location選項,選擇我們的smali原始碼目錄,也就是我們上面反編譯之後的out目錄,點選完成


我們匯入原始碼之後的專案工程結構:


主要看MainActivity類:



第五步:找到關鍵點,然後打斷點

這一步我們看到,其實說的比較廣義了,這個要具體問題具體分析了,比如這個例子中,我們知道當我們輸入密碼之後,肯定要點選按鈕,然後觸發密碼的校驗過程,那麼這裡我們知道找到這個button的定義的地方,然後進入他的點選事件中就可以了。這裡分為三步走:

1》使用Eclipse自帶的View分析工具找到Button的ResId


點選之後,需要等待一會,分析View之後的結果:


看到了,這裡我們能夠看到整個當前的頁面的全部佈局,已經每個控制元件的屬性值,我們需要找到button的resource-id

這裡我們看到定義是@+id/button這個值。

2》我們得到這個resId之後,能否在smali工程中全域性搜尋這個值,就可以定位到這個button的定義的地方呢?


然後我們看看搜到的結果:


這時候我們其實是在資原始檔中搜到了這個id的定義,這個id值對應的是0x7F05003E。

當然除了這種方式,我們還有一種方式能快速找到這個id對應的整型值,那就是在反編譯之後的values/public.xml檔案中:


這個檔案很有用的,他是真個apk中所有資原始檔定義的對映內容,比如drawable/string/anim/attr/id 等這些資原始檔定義的值,名字和整型值對應的地方:


這個檔案很重要,是我們在尋找突破口的重要關鍵,比如我們有時候需要通過字串內容來定位到關鍵點,這裡就可以通過string的定義來找到對應的整型值即可。

當我們找到了button對應的id值了之後,我們就可以用這個id值在一次全域性搜尋一下,因為我們知道,Android中編譯之後的apk,在程式碼中用到的resId都是用一個整型值代替的,這個整型值就是在R檔案中做了定義,將資源的id和一個值對應起來,然後程式碼裡面一般使用R.id.button這樣的值,在編譯出apk的時候,這個值就會被替換成對應的整型值,所以在全域性搜尋0x7F05003E


搜尋的結果如下:


看到了,這裡就定位到了程式碼中用到的這個button,我們進入程式碼看看:


在這裡,看到了,使用了findViewById的方式定義Button,我們在往下面簡單分析一下smali語法,下面是給button新增一個按鈕事件,這裡用的是內部類MainActivity$1,我們到這個類看看,他肯定實現了OnClickListener介面,那麼直接搜onClick方法:


在這裡我們就可以下個斷點了,這裡就是觸發密碼校驗過程。


第六步:執行程式,設定遠端除錯工程

在第五步中,我們找到了關鍵點,然後打上斷點,下面我們就來執行程式,然後在Eclipse中設定遠端除錯的工程

首先我們執行程式,因為我們加入了waitForDebug的程式碼,所以啟動的時候會出現一個Wait debug的對話方塊。不過,我測試的時候,我的手機沒有出現這個對話方塊,而是一個白屏,不過這個不影響,程式執行起來之後,我們看看如何在Eclipse中設定遠端除錯工程,首先我們找到需要除錯的程式對應遠端除錯服務端對應的埠:


這裡我們看到有幾個點:

1》在程式等待遠端除錯伺服器的時候,前面會出現一個紅色的小蜘蛛

2》在除錯服務端這裡我們會看到兩個埠號:8600/8700,這裡需要解釋一下,為什麼會有兩個埠號呢?

首先在這裡的埠號,代表的是,遠端除錯伺服器端的埠,下面在簡單來看一下,Java中的除錯系統:


這裡我們看到,這裡有三個角色:

111》JDB Client端(被除錯的客戶端),這裡我們可以認為我們需要破解的程式就是客戶端,如果一個程式可以被除錯,當啟動的時候,會有一個jdwp執行緒用來和遠端除錯服務端進行通訊


這裡我們看到,我們需要破解的程式啟動了JDWP執行緒,注意這個執行緒也只有當程式是debug模式下才有的,也就是AndroidManifest.xml中的debug屬性值必須是true的時候,也就是一開始為什麼我們要修改這個值的原因。

222》JDWP協議(用於傳輸除錯資訊的,比如除錯的行號,當前的區域性變數的資訊等),這個就可以說明,為什麼我們在一開始的時候,反編譯成java檔案,因為為了Eclipse匯入能夠識別的Java檔案,然後為什麼能夠除錯呢?因為smali檔案中有程式碼的行號和區域性變數等資訊,所以可以進行除錯的。

333》JDB Server端(遠端除錯的服務端,一般是有JVM端),就是開啟一個JVM程式來監聽除錯端,這裡就可以認為是本地的PC機,當然這裡必須有埠用來監聽,那麼上面的8600埠就是這個作用,而且這裡埠是從8600開始,後續的程式埠後都是依次加1的,比如其他除錯程式:


那麼有了8600埠,為什麼還有一個8700埠呢?他是幹什麼的?

其實他的作用就是遠端除錯端備用的基本埠,也就是說比如這裡的破解程式,我們用8600埠可以連線除錯,8700也是可以的,但是其他程式,比如demo.systemapi他的8607埠可以連線除錯,8700也是可以的:


所以呀,可以把8700埠想象成大家都可以用於連線除錯的一個埠,不過,在實際過程中,還是建議使用程式獨有的埠號8600,我們可以檢視8600和8700埠在遠端除錯端(本地pc機)的佔用情況:


看到了,這裡的8600埠和8700埠號都是對應的javaw程式,其實javaw程式就是啟動一個JVM來進行監聽的。

好了,到這裡我們就弄清楚了,Java中的除錯系統以及遠端除錯的埠號。

注意:

其實我們可以使用adb jdwp命令檢視,當前裝置中可以被除錯的程式的程式號資訊:



下面繼續,我們知道了遠端除錯服務端的埠:8600,以及ip地址,這裡就是本地ip:localhost/127.0.0.1

我們可以在Eclipse中新建一個遠端除錯專案,將我們的smali原始碼工程和裝置中需要除錯的程式關聯起來:

右擊被除錯的專案=》選擇Debug Configurations:


然後開始設定除錯專案


選擇Romote Java Application,在Project中選擇被除錯的smali專案,在Connection Type中選擇SocketAttach方式,其實還有一種方式是Listener的,關於這兩種方式其實很好理解:

Listner方式:是除錯客戶端啟動就準備好一個埠,當除錯服務端準備好了,就連線這個埠進行除錯

Attach方式:是除錯服務端開始就啟動一個埠,等待除錯端來連線這個埠


我們一般都是選擇Attach方式來進行操作的。

好了,我們設定完遠端除錯的工程之後,開始執行,擦發現,裝置上的程式還是白屏,這是為什麼呢?看看DDMS中除錯程式的狀態:


擦,關聯到了這個程式,原因也很簡單,我們是上面使用的是8700埠號,這時候我們選中了這個程式,所以就把smali除錯工程關聯到了這個程式,所以破解的程式沒反應了,我們立馬改一下,用8600埠:


好了,這下成功了,我們看到紅色的小蜘蛛變成綠色的了,說明除錯端已經連線上遠端除錯服務端了。

注意:

我們在設定遠端除錯專案的時候,一定要注意埠號的設定,不然沒有將除錯專案原始碼和除錯程式關聯起來,是沒有任何效果的


第七步:開始執行除錯程式,進入除錯

下面我們就開始操作了,在程式的文字框中輸入:gggg內容,點選開始:


好了,到這裡我們看到期待已久的除錯介面出來了,到了我們開始的時候加的斷點處,這時候我們就可以開始除錯了,使用F6單步除錯,F5單步跳入,F7單步跳出進行操作:


看到了,這裡使用v3變數儲存了我們輸入的內容


這裡有一個關鍵的地方,就是呼叫MainActivity的getTableFromPic方法,獲取一個String字串,從變數的值來看,貌似不是規則的字串內容,這裡先不用管了,繼續往下走:


這裡又遇到一個重要的方法:getPwdFromPic,從字面意義上看,應該是獲取正確的密碼,用於後面的密碼字串比對。


檢視一下密碼的內容,貌似也是一個不規則的字串,但是我們可以看到和上面獲取的table字串內容格式很像,接著往下走:


這裡還有一個資訊就是,呼叫了系統的Log列印,log的tag就是v6儲存的值:lil


這時候,我們看到v3是儲存的我們輸入的密碼內容,這裡使用utf-8獲取他的位元組陣列,然後傳遞給access$0方法,我們使用F5進入這個方法:


在這個方法中,還有一個bytesToAliSmsCode方法,使用F5進入:


那麼這個方法其實看上去還是很簡單的,就是把傳遞進來的位元組陣列,迴圈遍歷,取出位元組值,然後轉化成int型別,然後在呼叫上面獲取到的table字串的chatAt來獲取指定的字元,使用StringBuilder進行拼接,然後返回即可。


按F7跳出,檢視,我們返回來加密的內容是:日日日日,也就是說gggg=>日日日日


最後再往下走,可以看到是進行程式碼比對的工作了。


那麼上面我們就分析完了所有的程式碼邏輯,還不算複雜,我們來梳理一下流程:

A>呼叫MainActivity中的getTableFromPic方法,獲取一個table字串

我們可以進入看看這個方法的實現:


這裡可以大體瞭解了,他是讀取asset目錄下的一個logo.png圖片,然後獲取圖片的位元組碼,在進行操作,得到一個字串,那麼我們從上面的分析可以知道,其實這裡的table字串類似於一個金鑰庫。

B>通過MainActivity中的getPwdFromPic方法,獲取正確的密碼內容

C>獲取我們輸入內容的utf-8的位元組碼,然後呼叫access$0方法,獲取加密之後的內容

D>access$0方法中在呼叫bytesToAliSmsCode方法,獲取加密之後的內容

這個方法是最核心的,我們通過分析知道,他的邏輯是,通過傳遞進來的位元組陣列,迴圈遍歷陣列,拿到位元組轉化成int型別,然後在呼叫金鑰庫字串table的charAt得到字元,使用StringBuilder進行拼接。

通過上面的分析之後,我們知道獲取加密之後的輸入內容和正確的密碼內容做比較,那麼我們現在有的資源是:

金鑰庫字串和正確的加密之後的密碼,以及加密的邏輯

那麼我們的破解思路其實很簡單了,相當於,我們知道了金鑰庫字串,也知道了,加密之後的字元組成的字串,那麼可以通過遍歷加密之後的字串,迴圈遍歷,獲取字元,然後再去金鑰庫找到指定的index,然後在轉成byte,儲存到位元組陣列,然後用utf-8獲取一個字串,那麼這個字串就是我們要的密碼。

下面我們就用程式碼來實現這個功能:


程式碼邏輯,很簡單吧,其實這個函式相當於上面加密函式的bytesToAliSmsCode的反向實現,執行結果:


OK,得到了正確的密碼,下面來驗證一下:


哈哈,不要太激動,成功啦啦~~。破解成功。


補充:

剛剛我們在斷點除錯的時候,看到了程式碼中用了Log來列印日誌,tag是lil,那麼我們可以列印這個log看看結果:


看到了,這裡table是金鑰庫,pw是正確的加密之後的密碼,enPassword是我們輸入之後加密的密碼。

所以從這裡可以看到,這個例子,其實我們在破解apk的時候,有時候日誌也是一個非常重要的資訊。


破解需要的資料,我已經上傳了,下載地址:http://download.csdn.net/detail/jiangwei0910410003/9526113


四、思路整理

1、我們通過apktool工具進行apk的反編譯,得到smali原始碼和AndroidManifest.xml,然後修改AndroidManifest.xml中的debug屬性為true,同時在入口處加上waitForDebug程式碼,進行debug等待,一般入口都是先找到入口Activity,然後在onCreate方法中的第一行這裡需要注意的是,apktool工具一定要加上-d引數,這樣反編譯得到的檔案是java檔案,這樣才能夠被Eclipse識別,進行除錯

2、修改完成AndroidManifest.xml和新增waitForDebug之後,我們需要在使用apktool進行回編譯,回編譯之後得到的是一個沒有簽名的apk,我們還需要使用signapk.jar來進行簽名,簽名檔案直接使用測試程式的簽名檔案就可以,最後在進行安裝。

3、然後我們將反編譯之後的smali原始碼匯入到Eclipse工程中,找到關鍵點,進行下斷點,這裡的關鍵點,一般是我們先大致瞭解程式執行的結構,然後找到我們需要破解的地方,使用View分析工具,或者是使用jd-gui工具直接檢視apk原始碼(使用dex2jar將dex檔案轉化成jar檔案,然後用jd-gui進行檢視),找到程式碼的大體位置。然後下斷點,這裡我們可以藉助Eclipse的DDMS自帶的View分析工具找到對應控制元件的resid,然後在全域性搜尋這個控制元件的resid,或者直接在values/public.xml中查詢,最終定位到這個控制元件位置,在檢視他的點選事件即可。

4、設定遠端除錯工程,首先執行需要除錯程式,然後在DDMS中找到對應的除錯服務端的埠號,然後在Debug Configurations中設定遠端除錯專案,設定對應的除錯埠和ip地址(一般都是本機pc,那就是localhost),然後紅色小蜘蛛變成綠色的,表示我們的遠端除錯專案連線關聯上了除錯程式,這裡需要注意的是,一定需要關聯正確,不然是沒有任何效果的,關聯成功之後,就可以進行操作。

5、操作的過程中,會進入到關鍵的斷點處,通過F6單步,F5單步進入,F7單步跳出,來進行除錯,找到關鍵方法,然後通過分析smali語法,瞭解邏輯,如果邏輯複雜的,可以通過檢視具體的環境變數的值來觀察,這裡也是最重要的,也是最複雜的,同時這裡也是沒有規章可尋的,這個和每個人的邏輯思維以及破解能力有關係,分析關鍵的加密方法是需要功底的,當然這裡還需要注意一個資訊,就是Log日誌,有時候也是很重要的一個資訊。

6、最後一般當我們知道了核心方法的邏輯,要想得到正確的密碼,還是需要自己用語言去實現邏輯的,比如本文中的加密方法,我們需要手動的code一下加密的逆向方法,才能得到正確的密碼。


五、遺留問題

1、使用apktool工具進行反編譯有時候並不是那麼順利,比如像這樣的報錯:


這個一般都是apktool中解析出現了錯誤,其實這個都是現在apk為了抵抗apktool,做的apk加固策略,這個後面會寫一篇文章如何應對這些加固策略,如何進行apk修復,其實原理就是分析apktool原始碼,找到指定的報錯位置,進行apktool程式碼修復即可。

2、本文中說到了Java的除錯系統,但是為了篇幅限制,沒有詳細的講解了整個內容,後面會寫一篇文章具體介紹Java中的除錯系統以及Android的除錯系統。

3、有時候我們還會遇到回編譯成功了,然後遇到執行不起來的錯誤,這個就需要使用靜態方式先去分析程式啟動的邏輯,看看是不是程式做了什麼執行限制,比如我們在靜態分析那篇文章中,提到了應用為了防止反編譯在回編譯執行,在程式的入口處作了簽名校驗,如果校驗失敗,直接kill掉自己的程式,退出程式了,所以這時候我們還是需要使用靜態方式去分析apk。

4、如何做到不修改AndroidManifest.xml中的debug屬性就可以進行除錯:

1》 修改boot.img,從而開啟系統除錯,這樣就可以省去給app新增android:debuggable="true",再重打包的步驟了。
2.》直接修改系統屬性,使用setpropex工具在已經root的裝置上修改只讀的系統屬性。使用此工具來修改ro.secure和ro.debuggable的值。

這個也會在後面詳細介紹這兩種方法


六、總結

這篇文章我們就介紹瞭如何使用Eclipse去動態除錯反編譯之後的smali原始碼,這種方式比靜態方式高效很多的,比如本文中的這個例子,其實我們也可以使用靜態方式進行破解的,但是肯定效率沒有動態方式高效,所以以後我們又學會了一個技能,就是動態的除錯smali原始碼來跟蹤程式的核心點,但是現在市場上的大部分應用沒有這麼簡單就破解了,比如核心的加密演算法放到了native層去做,那麼這時候就需要我們去動態除錯so檔案跟蹤,這個是我們下一篇文章的內容,也有的時候,apk進行加固了,直接在apktool進行反編譯就失敗了,這時候我們就需要先進行apk修復,然後才能後續的操作,這個是我們下下篇的文章,如何應對apk的加固策略。通過這篇文章我們可以看到動態方式破解比靜態方式高效的多,但是有時候我們還需要使用靜態方式先做一些準備工作,所以在破解apk的時候,動靜結合,才能做到完美的破解。

相關文章