Android逆向之旅---反編譯利器Apktool和Jadx原始碼分析以及錯誤糾正

yangxi_001發表於2016-12-02

一、前言

在之前的破解過程中可以看到我們唯一離不開的一個神器那就是apktool了,這個工具多強大就不多說了,但是如果沒有他我們沒法涉及到後面的破解工作了,這個工具是開源的,也是使用Java語言開發的,程式碼相對簡單,我們今天就來分析一下他的大體邏輯,注意是大體邏輯哦,因為如果要一行一行程式碼分析,首先覺得沒必要,其次浪費時間,有了原始碼,誰看不懂呢。至於為什麼要分析這個工具其實原因只有一個,就是我們在之前的反編譯過程中會發現,總是有那麼幾個apk應用不讓我們那麼容易的反編譯,他們就利用apktool的漏洞,對apk做了一定的混淆工作,所以我們需要通過分析原始碼來解決這些異常錯誤,從而能夠對每個apk反編譯都是如魚得水。


二、破解的國際慣例

其實在之前的破解文章中,我們在拿到一個apk進行破解之前,都會幹這兩件事:

第一件事:用壓縮軟體解壓apk,得到classes.dex,然後使用dex2jar+jd-gui工具檢視程式碼邏輯

但是這裡我們會發現如果想看資原始檔比如AndroidManifest.xml和res下面的一下xml檔案都是亂碼的,因為他們是遵循Android中的arsc檔案格式,關於這個格式不瞭解的同學可以網上搜一下,但是關於這個檔案格式我在之前的幾篇文章中做了格式解析:

Android中如何解析資原始檔樣式 其實不管是什麼檔案格式,都是有檔案格式的說明文件的,只要按照這個說明文章去做解析即可

第二件事:使用apktool工具進行反編譯apk,得到smali原始碼和資原始檔

這裡得到的是smali原始碼,破解了這麼長時間,smali語法就不做太多的介紹了,他就是Android虛擬機器識別執行的指令程式碼,他和dex檔案可以互相轉化的,使用baksmali.jar和smali.jar這兩個工具即可,後面會詳細說到,當然這裡還可以得到所有的資原始檔,即arsc格式解析之後的內容,而且這個工具可以實現回編譯,這個功能也是很強大的,不過後面分析原始碼就知道了,回編譯其實是藉助aapt這個強大的系統命令來完成的。

所以這裡我們可以看到最終如果我們想要完全的分析一個apk,apktool工具是不可或缺的,他是開啟破解大門的鑰匙,這個工具也是在逆向領域敲門磚,而且現在很多比較視覺化的破解工具,比如:apk改之理,jeb,gda等,其實這些工具核心都是使用apktool+dex2jar+jd-gui這三個工作組成的,只是後期做了一定的介面優化而已。


三、Apktool工具反編譯常見的問題

上面說了apktool工具的地位和作用,下面我們再來看一下apktool工具在反編譯的過程中會遇到哪些問題呢?

這裡我們只看BAT這三家公司的app,我們在反編譯的過程中發現了QQ和支付寶分別報了這兩個錯誤:

1、QQ報了這個錯誤:

Exception in thread "main" brut.androlib.AndrolibException: Multiple res specs: attr/name

這個主要是因為QQ利用了apktool的一個漏洞,做了屬性id的混淆


2、支付寶報了這個錯誤:

Exception in thread "main" brut.androlib.AndrolibException: Could not decode arsc file

這個錯誤其實是在使用apktool工具的時候報的錯誤最多的,這個主要是利用apktool的漏洞,修改了resource.arsc的頭部資訊


其實網上很多解決方案都是說apktool這個工具的版本太舊了,用最新版本,但是這裡可以看一下apktool.jar的版本:


這個版本是最新的了。

其實反編譯失敗很簡單,就是這些公司他們知道了apktool這個工具反編譯那麼牛逼,那肯定想辦法不讓你反編譯成功呀,所以他們也去看apktool的原始碼,分析得到漏洞,然後進行apk的一些混淆,防止反編譯,所以說防護和破解真的是無休止的戰爭,但是幸好apktool的程式碼也是更新的比較快的,所以會解決這些漏洞,但是我們在破解的時候遇到這些問題,不能一味的等待apktool的更新,既然是開源的,那麼就直接分析原始碼,發現報錯的地方修復即可。


四、分析Apktool原始碼

上面說了為什麼要分析apktool的原始碼,下面就真正的開始分析吧

當然第一步先得到apktool的原始碼吧,地址:https://code.google.com/p/android-apktool/

看到有google的域名是不是瞬間感覺整個人都不好了,的確國內程式猿一般開啟都是始終loading的過程,直至error,所以我們只能去萬能的github上search了,找到了這個地址:https://github.com/iBotPeaches/Apktool,可以看到這個有很多人關注,而且程式碼是有人維護和更新的,所以靠譜,clone到本地。

但是他是一個gradle專案,所以咋們就給Eclipse裝一個gradle外掛,然後匯入專案即可,這裡因為apktool是Java專案,所以還是使用Eclipse比較習慣吧。但是這裡又遇到一個蛋疼的地方,還是國內網路的問題,gradle下載失敗,因為這裡引用了一些第三方的jar

好吧,那麼我們只能無奈的手動去一個一個找這些jar包,不過在這個過程中還是比較蛋疼的,就是有些jar找的很蛋疼,不過最後還是都湊齊了,沒有報錯了,專案結構如下:


這裡Apktools這個專案是入口的專案,也是主要功能專案類,Baksmali和Smali,SmaliUtil是操作smali的工具類,BrutCommon和BrutDir,BrutUtil是一些輔助的工具類,程式碼簡單,不做太多的解釋,這裡除了Apktool之外,其他工程都是一個功能庫,他們直接的引用關係如下:

Baksmali依賴於:SmaliUtil
BrutDir依賴於:BrutCommon,BrutUtil
BrutUtil依賴於:BrutCommon
Smali依賴於:SmaliUtil
Apktool依賴於:Baksmali,BrutCommon,BrutDir,BrutUtil,Smali

這裡直接來看看主要功能Apktools專案


第一、分析Apktool的反編譯原始碼

首先我們知道,Java專案的入口方法肯定是main方法,搜一下找到這個Main類:


這個方法中得到引數,然後進行引數的分析和組裝。繼續往下看,看執行程式碼:


這裡看到了我們經常用的一些命令引數,他們的含義這裡也都可以瞭解到了,有一個ApkDecoder類,是反編譯的核心類:


最終也是呼叫他的decode方法:


這裡看到使用了Androlib這個核心類來做了一些操作,首先會判斷需不需要解析資原始檔,即arsc格式的,這裡又細分了解析resource.arsc和AndroidManifest.xml這兩個檔案的解析,下面繼續看:


這裡會解析dex檔案,得到smali原始碼,而且區分了多個dex的情況。


其實到這裡我們可以發現,apktool在反編譯的整個過程中核心點就三個:

解析resource.arsc檔案,AndroidManifest.xml檔案,dex檔案

那麼關於這三個檔案的格式解析,一定請看這篇文章:Android中解析所有檔案格式

如果看了這篇文章之後,會發現Android中的這三個檔案都有各自的格式,解析也是很簡單的。所以這裡就不在詳細介紹了具體的解析步驟了,但是這三個檔案分別對應的三個經典格式圖必須展示一下:

AndroidManifest.xml檔案格式圖



Resource.arsc檔案格式圖


Dex檔案個格式圖


這三張圖可算是經典中的經典,而且一定要看懂,因為不看懂這三張圖的話,自己在分析apktool解析原始碼的時候會非常的費勁。


下面繼續分析,Androidlib這個核心解析類其實就那麼幾個方法,下面來一一講解:

1、decodeRawFiles

這個方法主要解析原生的檔案,就是Android在編譯apk的過程中不參與編譯的檔案目錄,一般是assets和libs



2、decodeManifestWithResources

這個方法主要是解析AndroidManifest.xml的


我們知道Android在安裝一個apk的時候,肯定也是需要解析AndroidManifest.xml檔案的,而且Android中解析xml檔案採用的是Pull解析法,所以這裡直接把Android中的一些方法copy過來了。


然後在弄一個xmlPull的解析jar包即可:



3、decodeResourcesFull

這個方法用來解析resource.arsc檔案的,這個檔案我們知道他主要包含了所有資原始檔的一種格式,Android中資原始檔都有相應的型別,以及唯一的一個整型id值,那麼這個檔案就包含這些內容


這個方法其實用到的解析類和AndroidManifest.xml的解析類是一樣的,因為他們都屬於arsc格式,而且資原始檔也是xml格式的,這裡值得注意的是,會產生一個反編譯中最關鍵的一個檔案:public.xml,這個檔案是在反編譯之後的res\values\public.xml


這裡可以看到,一個id欄位,都有對應的型別,名稱,和id值的

而這裡的id值是一個整型值,8個位元組;由三部分組成的:

PackageId+TypeId+EntryId
PackageId:是包的Id值,Android中如果是第三方應用的話,這個值預設就是0x7F,系統應用的話就是0x01,具體我們可以後面看aapt原始碼得知,他佔用兩個位元組。
TypeId:是資源的型別Id值,一般Android中有這幾個型別:attr,drawable,layout,dimen,string,style等,而且這些型別的值是從1開始逐漸遞增的,而且順序不能改變,attr=0x01,drawable=0x02....他佔用兩個位元組。
EntryId:是在具體的型別下資源實體的id值,從0開始,依次遞增,他佔用四個位元組。


4、decodeSourcesSmali

這個方法主要是將dex檔案解析成smali原始碼


這裡使用了SmaliDecoder的decode方法:


這裡需要藉助一個工具包dexlib,他是用來處理dex檔案的,處理完dex檔案之後,在交給baksmali這個工具類生成smali檔案即可。


到這裡我們可以看到上面就大致分析完了apktool在反編譯的時候做的主要三件事:

解析resource.arsc,解析AndroidManifest.xml,解析dex檔案

原始碼分析完了,下面就開始測試執行一下,這裡用使用了BAT三家的主要app做實驗:


這裡為了執行簡單,我們在入口的main方法中,去手動構造一個引數:


這裡用了BAT三家的幾個app測試,發現,只有qq和支付寶有問題,所以這裡就直接看著兩個app反編譯會出現什麼錯誤,然後來分析解決這個問題:

1、分析QQ應用反編譯的問題


這裡報錯了,錯誤和我們開始使用apktool工具的時候是一樣的,看看崩潰程式碼:


這裡可以發現,使用一個Map結構存放ResResSpec格式資料的,而且key是spec的name值,那麼我們知道資源id的值是唯一的,這裡不可能會出現相同的name值的,是騰訊做了混淆機制,這種混淆機制只對apktool工具有效,對Android系統解析apk執行是不影響的,那麼問題就好辦了,我們知道了崩潰的原因,修復也就簡單了,直接加一個判斷,判斷這個key是否存在map中,存在的話就直接返回即可:


再次執行:


看到了,過濾了重複的資源值,反編譯成功了,這裡因為反編譯過程中會用到系統的資源id,需要需要系統資源包framework.apk參與解析工作,這裡因為QQ程式包比較大,反編譯時間會長點。我們可以去看看反編譯之後的目錄:


我們可以看看他的AndroidManifest.xml內容:


解析成功,可以正常檢視了。

這裡我們就解決了QQ反編譯的問題了,


2、分析支付寶應用反編譯的錯誤問題


看到了,這個錯誤和我們開始看到的錯誤是一樣的,下面我們看看崩潰的地方是什麼原因導致的:


這裡是讀取一個字串常量池Chunk頭部資訊報錯的,關於Chunk的頭部資訊可以參考這篇文章:Android中解析resource.arsc檔案

StringChunk的頭部資訊包括這些內容:

header:標準的Chunk頭部資訊結構
stringCount:字串的個數
styleCount:字串樣式的個數
flags:字串的屬性,可取值包括0x000(UTF-16),0x001(字串經過排序)、0X100(UTF-8)和他們的組合值
stringStart:字串內容塊相對於其頭部的距離
stylesStart:字串樣式塊相對於其頭部的距離

其中header是一個標準的Chunk頭部資訊:

type:是當前這個chunk的型別(兩個位元組)
headerSize:是當前這個chunk的頭部大小(兩個位元組)
size:是當前這個chunk的大小(四個位元組)

就是八個位元組。

我們繼續分析錯誤程式碼:


這裡會檢查已給Chunk結構的完整性,出入的StringPool值是:


看到了,這裡是字串常量池Chunk的頭部資訊,而且值是固定的:0x001C0001

在進入看看程式碼:


這裡如果發現格式不正確就丟擲一個異常,也就是格式不是0x001C0001的話。

那麼問題差不多清楚了,這裡崩潰的原因很可能是支付寶應用的resource.arsc的StringPool的Chunk的頭部資訊被混淆了,導致這個錯誤的,我們通過上面的分析知道,StringPool這個Chunk的頭部標準格式是:0x001C0001,我們來看看支付寶應用的resource.arsc檔案的二進位制資料:


發現了,有這個值,那麼為何還報錯呢?到這裡我們或許不知道該怎麼辦了,其實很簡單,我們再去弄一個能夠反編譯的apk的resource.arsc檔案看看:


擦,發現果然不一樣,支付寶頭部資訊多了8個位元組,0x000001000,那麼我們再看上面的檢查Chunk頭部型別資料的程式碼:


其實,這裡可以看到,apktool其實已經做了一個頭部資訊的檢查,但是這裡只是檢查0x001C0001這個正確資訊之前的值只有四個位元組,而且是0的情況,這裡讀取int整型值,四個位元組,發現如果等於傳遞進來的possible的話即0,就繼續執行這個方法,但是這裡的possible值是-1了,也就是這裡只會檢查四個位元組,但是我們分析了支付寶的resource.arsc檔案,發現他是8個位元組,而且還不全是0,是前四個位元組是0,後四個位元組是1:


所以這裡檢測也是失敗的,丟擲異常了。


好了,到這裡我們就分析完了支付寶的資原始檔混淆的機制了,下面我們修改就簡單了,首先。我們把上面的8個位元組全部改成0:


然後替換之前的resource.arsc檔案,直接用壓縮軟體替換即可,

然後在修改上面的檢測程式碼:


這裡,修改程式碼,就是會做一直檢測,直到遇到正確的值為止,我們修復完成之後,執行:


哈哈,不報錯了,看看反編譯之後的目錄:


反編譯也成功啦啦~~


我們通過上面的分析就知道了,現在使用apktool反編譯的主要兩個錯誤就是:

1、Exception in thread "main" brut.androlib.AndrolibException: Multiple res specs: attr/name

異常原因:通過分析原始碼知道,這個錯誤主要是因為apk做了混淆操作,導致在反編譯的過程中存入了重複的id值,錯誤程式碼:

ResTypeSpec.java的addResSpec方法78行

修復:在這個方法存入map資料之前做一個判斷操作即可

2、Exception in thread "main" brut.androlib.AndrolibException: Could not decode arsc file

異常原因:通過分析原始碼知道,這個錯誤主要是因為apk了做了resource.arsc頭部資訊的修改,導致在分析頭部資料結構的時候出錯,錯誤程式碼:ExtDataInput.java的skipCheckChunkTypeInt方法 73行

修復:修復resource.arsc頭部資料,修改skipCheckChunkTypeInt檢測方法邏輯


第二、Apktool的回編譯原始碼分析

到這裡我們就分析完了apktool的反編譯功能原始碼,也解決了QQ和支付寶應用反編譯的失敗問題,下面再繼續分析一下apktool的回編譯功能,關於回編譯功能的話,這裡可以先看這篇文章:Android中編譯Apk的步驟分析 這裡我們可以知道aapt命令的功能:


使用aapt命令編譯資原始檔
aapt package -f -m -J gen -S res -I D:/android-sdk-windows/platforms/android-16/android.jar -M AndroidManifest.xml 
這裡的命令引數有點多就不全部介紹了,就說明幾個:
-J 後面跟著是gen目錄,也就是編譯之後產生的R類,存放的資源Id
-S 後面跟著是res目錄,也就是需要編譯的資源目錄
-l 後面跟著是系統的庫,因為我們在專案資源中會用到系統的一些資原始檔,所以這裡需要連結一下
-M 後面跟著是工程的清單檔案,需要從這個檔案中得到應用的包名,然後產生對應的R檔案和包名。

而且,這個命令不僅可以進行編譯,可以反編譯,就是上面我們提到的解析AndroidManifest.xml和resource.arsc的時候,使用它可以做到的,解析dex檔案可以使用dumpdex這個命令的。這些命令都是在androidsdk目錄的build-tools目錄下。

知道了這個編譯過程,其實回編譯就是按照這個步驟來的,而這裡重要的就是使用aapt命令:


這裡我們把命令放到了專案的framework目錄下:


然後開始構造命令引數,主要需要引用系統的jar包:android.jar



這裡就差不多分析完了回編譯的功能,我們修改一下入口程式碼,新增回編譯執行引數:


執行程式:


回編譯成功,得到apk檔案:


不過這個檔案是沒有簽名的,需要簽名,這裡不繼續了,不是本文的講解的知識點了。


到這裡我們就分析完了apktool工具所有的原始碼了,其實他的功能很簡單:

1、反編譯的過程中主要是解析AndroidManifest.xml,resource.arsc,dex檔案

2、回編譯的時候藉助aapt命令完成編譯操作


五、分析Jadx原始碼

下面繼續來看反編譯的另外一個神器:Jadx

這個工具也是開源的,可以直接去github上去搜尋:https://github.com/skylot/jadx

這個工具其實和apktool反編譯的功能差不多,但是有一個特色,就是他的視覺化功能,能夠高效的分析apk的結構,下面來看一個例子:


看到了吧,這裡感覺結構很清晰,而且是視覺化的,分析起來會比較方便,感覺他是整合了apktool+jd-gui的功能,但是他和apktool相比的話,還是有點缺陷的,首先他反編譯會比較耗時,這個後面說,其次是他不能修改程式碼,進行回編譯的,這個是很蛋疼的,所以他和apktool相比較的話,還是差了點,但是他反編譯還是很靠譜的,這裡為什麼分析它呢?其實是因為他是開源的,其次是藉助了asm這個工具來生成class檔案,實現Java程式碼的視覺化。

下面就來說說asm這個工具類的用途:

這裡寫了一個demo來看看效果:


執行結果:


這裡沒有列印Hello world!的程式碼,但是結果卻列印了,這個就是asm功能了:能夠手動的構造一個class檔案


這個功能,大家是否聯想到了,動態代理模式,會產生一個動態代理類,而且還會生成一個Proxy.class檔案,而且在JavaWeb中的Spring框架中的Cglib也是採用了這個功能來實現AOP程式設計的,他可以通過輸入一個字串來定義類,給這個類新增方法,欄位等資訊,然後生成類的位元組碼陣列,可以儲存成class檔案,同時也可以使用ClassLoader來載入位元組碼資料,然後在反射呼叫指定的方法。

那麼其實Jadx的視覺化功能就是藉助於這個功,同時著名的dex2jar工具也是的,可以去dex2jar工具的lib目錄看看:



那麼Jadx的反編譯步驟是這樣的:

解析dex檔案=》smali原始碼=》解析smali指令=》藉助asm生成class檔案=》解析class檔案得到Java原始碼


Apktool+Jadx原始碼下載:http://download.csdn.net/detail/jiangwei0910410003/9655578


六、總結

好了,到這裡我們就分析完了Apktool和jadx的原始碼了,我們這裡主要還是分析了Apktool的原始碼,因為我們在反編譯的過程中還是需要藉助這個神器的,其實他內部沒什麼神祕的,就是解析三個檔案,因為apktool是開源的,所以一些公司就會去找他的漏洞,然後通過這個漏洞來給自己的apk加固,增加反編譯的困難,但是我們也是可以分析apktool原始碼的,知道了反編譯的錯誤資訊,也是可以去分析錯誤,然後修復錯誤,最終還是可以反編譯成功的,所以這種資源加固來抵抗apktool的方案其實效率並沒有那麼高,因為只要有apktool原始碼,都不是問題。

相關文章