1. 程式人生 > >Android中如何修改編譯的資源ID值(預設值是0x7F...可以隨意改成0x02~0x7E)

Android中如何修改編譯的資源ID值(預設值是0x7F...可以隨意改成0x02~0x7E)

一、技術準備

今天我們來看一下如何修改Android中編譯時的資源Id的值,在講解這內容之前,我們需要先了解一下Android中的資源編譯之後的結構和編譯過程,這裡就不多說了,具體可以檢視這篇文章:

這篇文章中,介紹瞭如何解析Android中編譯之後的resource.arsc檔案,這裡就介紹了Android中資原始檔編譯之後的型別和格式,其實Android中資源編譯之後,會產生一個R檔案,所有的資源ID都是儲存在這個檔案中的的,預設我們看到所有的ID都有一個共同的特點,就是他們都是0x7F開頭的,其實這個0x7F是包的ID值,我們在在解析resource.arsc文章中提到一點,Android中的id值其實是一個int型別,他的值由三部分組成: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開始,依次遞增,他佔用四個位元組。

二、遇到的問題

既然我們瞭解了Android中的資源Id的結構,下面我們來說說我們遇到的問題:

1、在Android專案中偶爾會出現依賴第三方庫包,出現資源ID(packageId+typeId+ItemValue)發生衝突的問題(網上有很多解決方案,不一一列舉,如public 限定等)。那麼對於我們自己提供的庫包,如果能指定其包的命令空間(預設是從127=0x7F開始),特別考慮mutiDex的情況,自定義修改package ID顯得意義重大。

2、我們在開發Android中外掛技術的時候,為了防止外掛工程中的資源Id和宿主工程中的資源Id不衝突,也是需要去修改一下外掛中編譯之後的資源Id值,來減少衝突。

那麼上面就是我們遇到的問題,其實我們的解決方案很簡單,就是在編譯的時候修改資源Id值,給一個限定值。

三、解決思路

我們之前講解了資源Id的組成結構,發現高兩個位元組是代表PackageId的值,而且第三方app的預設值是0x7F,那麼我們能不能修改這個值呢?比如,外掛1中的資源Id中的PackageId為0x30,外掛2中的資源Id中的PackageId為0x31...這樣每個外掛的資源就被劃分了一定的區域值,同時保證不要和主工程中的0x7F衝突即可,那麼這些值就可以從0x02~0x7E了,這個區間值我們都是可以使用的,為什麼0x01不能用呢?因為他是系統應用的呀,所以我們就有0x7E-0x02=124個區間,哈哈,聽著好興奮,那麼我們是否可以操作了呢?答案是可以的,我們知道Android中編譯資源用的是aapt命令,那麼我們就可以檢視他的原始碼來看看是否可以。

aapt命令是Android中提供的編譯apk的一個工具,所以原始碼可以從 Android原始碼目錄/tools/... 下面檢視:


這個工具的原始碼還是不復雜的,沒多少檔案,當然入口肯定找main啥的關鍵字了,果然看到一個Main.cpp檔案,開啟檢視,找到入口函式main,這裡我們可以看到,他對輸入引數做了判斷:


這裡main函式有點長,我們直接看最後的處理函式:


這裡有一個handleCommand函式,這裡就是主要處理命令的功能:


這裡有好多個函式,但是我們這裡需要關注的是doPackage函式,他是打出包的關鍵,但是這時候我們發現全域性搜這個函式,找不到,那麼這個函式肯定是被引用的,原始碼中查詢具體函式,

腦補一下:

這裡因為是Window系統,不想是Linux系統,可以直接使用find+grep就可以快速的查詢到包含指定內容的檔案了,但是Windows中提供了視覺化的檔案搜尋,但是他預設在搜尋的時候,只是搜尋檔名,不搜尋包含的內容,所以需要設定一下,可以到資料夾選項中設定:


這時候我們可以在tools目錄下搜尋了:


這時候看到了,我們搜到了三個檔案,Main.cpp可以不用看了,因為已經看過了,那麼就在Command.cpp裡面了:


這裡我們往下面看:


這裡有一個方法,而且我們看註釋,這裡就是編譯的核心函式:buildResources,我們在全域性搜這個函式,沒找到,那麼我們還是到整個目錄下去搜:


搜到了,在Resource.cpp中:


這裡看到,一個packgeType欄位,這個就是包型別,這裡有三個型別:共享的,系統的,第三方

突然發現這個似乎和PackageId的值有關係,我們接著往下看:


在這裡,用到了packageType,而且有一個重要的型別ResourceTable,這個就是資源索引表,和ResId有對映關係的資料結構,所以我們檢視他的定義:在ResourceTable.cpp


我擦,果然,看到結果了,這裡看到了有三個值,0x00,0x01,0x7F。說明我們找到核心的地方了。接著往下看:


這裡構建了一個Package,這裡傳入了packageId值的,好了,我們分析原始碼就到這裡了,那麼下面我們來看一下原始碼流程

首先找到入口類:Main.cpp:main函式,解析引數,然後呼叫handleCommand函式處理引數對應的邏輯,我們看到了有一個函式doPackage,這裡就是處理編譯工作的。

然後就搜尋到了Command.cpp:在他內部的doPackage函式中進行編譯工具的一個函式:buildResources函式,在全域性搜尋,發現了Resource.cpp:具體檢視buildResources函式,發現這裡就是處理編譯工作,同時在這裡我們也看到了核心,構建ResourceTable的邏輯,在ResourceTable.cpp中,也是獲取PackageId的地方,到此我們就知道了大體的邏輯,那麼知道了邏輯,下面我們就來看看如何修改呢?

其實最好的方法是,能夠修改aapt原始碼,新增一個引數,把我們想要編譯的PackageId作為輸入值,傳進來最好了,其實我們在看原始碼的時候發現,有一個型別始終傳遞這,那就是Bundle型別,他是從Main.cpp中的main函式傳遞到了最後的buildResources函式中,那麼我們就可以把這個引數用Bundle進行攜帶。

四、操作實踐

既然知道了修改的思路,下面就是來修改原始碼了:

第一步:修改Main.cpp中的main函式,獲取外部傳遞的PackageId值,然後存入到Bundle中


這裡我們使用的引數是:-apk-module

第二步:我們只需要在ResourceTable.cpp中的構造方法讀取這個值即可


到此,我們就修改完了,然後編譯,這裡編譯因為環境不同,所以這裡就不列出來如何編譯的了,本人使用VC6.0進行編譯的,得到了最終的修改之後的appt命令:aapt_win.exe

五、工具使用

那麼既然上面我們那麼辛苦的修改了aapt命令,下面就可以大展生手的修改一下試一試了,用一個簡單的demo進行嘗試,不過這裡還有一個問題,就是這裡我們呀用ant指令碼來編譯apk,因為我們需要修改aapt命令的路徑,換成編譯之後的aapt_win.exe,關於如何使用Ant指令碼編譯apk,這裡就不做太多的解釋了,大家可以看這篇文章:

而且,我就是用這篇文章中的demo做案例的,就是改了一下編譯指令碼:

修改aapt命令的路徑,用我們修改之後的命令


在編譯生成R檔案的時候,新增引數:-apk-module


編譯resource.arsc也需要修改:


這裡全部修改成0x78,然後我們跑一個ant指令碼:ant release

然後看一下R檔案的內容:



首先我們可以檢視log資訊,我們在程式碼中使用反射去獲取一個外掛apk中的app_name欄位的id值。


日誌顯示的id是0x78050000,顯示的值也是正確的,這樣我們就讓外掛的ResId的範圍區分了宿主工程中的0x7F,就不會出現資源衝突的問題了,看一下執行的效果:


好了,到這裡,我們就說完了本章的內容了。

六、學習到技術點

1、學習瞭如何在Windows中查詢原始碼內容

2、學習了aapt編譯的整體流程

3、學會了修改編譯的資源id值

七、解決的問題

通過修改aapt原始碼,來達到我們可以隨意定製編譯之後的resId值,解決我們在引用第三方包或者工程以及在開發外掛化的時候遇到的資源id值衝突問題,所以這裡就記住一點,我們可以修改Android中編譯之後的資源ID值了。

八、總結

結束了這篇文章,感覺收穫還是很多的,起碼我們知道Android中編譯之後的資源ID是可以定製的,雖然有的同學可能現在用不到這個功能,但是我相信遲早有一天你一定會用到的,所以只要記住有這個技術方案就好了。

《Android應用安全防護和逆向分析》

點選立即購買:京東  天貓

更多內容:點選這裡

關注微信公眾號,最新Android技術實時推送