1簡述
前段時間在bluebox的一份android安全pdf中看到一個AndroidManifest Ambiguity方案。該方案基於android系統解析AXML的一個特點:android在解析AXML的屬性的時候,是通過該屬性的res id號而非屬性名定位的。所謂的AXML就是AndroidManifest.xml對應的二進制文件,APK包中存儲的就是AXML。比如屬性:
<public type="attr" name="name" id="0x01010003" />
它的屬性名為name,id號為0x01010003。
該方案的大致原理如下圖所示:
我簡要概括一下:
我們在axml(注意是axml不是AndroidManifest.xml)中添加一個屬性,該屬性的屬性名是name,屬性的值是some.class,屬性的ID號為0。根據前文所述,android系統對於非法的res ID號是不會解析的。所以我們添加這個無用的屬性后,並不影響該APK的正常工作(上圖左下角所示),但是對於apktool之類的逆向工具而言,他們卻會對這個無用的屬性進行解析(上圖右下角所示)。所以,如果我們進行重打包的話,apktool就會將該屬性變更為一個ID號0x01010003的可以被系統解析的屬性。這樣造成的后果就是:由於我們的APK中並沒有實現trap.class類,所以APK啟動時會報錯“there is no trap.class~~”。
2 實現方案
該PDF雖然提出了這個方案,但並沒有給出實現的代碼(其實它就給了上面那張圖~其它什么都木有了~),google也是空白。所以當我看懂原理之后,就想自己將它實現出來。哪知事情並沒有我想的那么簡單~~
2.1 AXML文件格式
遇到的第一個挑戰就是:網上竟然搜不到AXML文件的格式!!!當時差點就放棄了,不過后來一想,既然apktool能解析AXML那就說明它是了解AXML的文件格式的,所以就上網搜索了一下解析AXML的各種解析代碼,綜合過后覺得Claud大大的AXML Parser代碼比較利於總結AXML的文件格式。所以就以該代碼問藍本總結了一下文件格式,如下表所示:
0x0~0x3 magic: 0x03000800固定值 |
0x4~0x7 filesize: 文件整體大小 |
0x8~0xb StringTag: 字符串塊開始標志,0x01001c00固定值 |
0xc~0xf StringChunkSize:字符串塊大小 |
0x10~0x13 count of strings:字符串個數, |
0x14~0x17 count of styles: 類型個數 |
0x18~0x1b reserve field: 保留的,為0 |
0x1c~0x1f string的起始偏移值:注意,這個偏移值是相對於stringChunk而言的! |
0x20~0x23 styles的起始偏移值:同上 |
下面存儲的就是n個連續的string的偏移值,每個偏移值占4字節,需要注意的是,這個偏移值加上string的起始偏移值和0x8才是真正的偏移值!n的大小就是0x10~0x13的大小 |
然后就是n個連續的style的偏移值,同上~ |
String數據塊 ........ |
Style數據塊 ........ 注意:到這里,stringchunk就算是結束了 |
下面就是ResourceChunk了,里面保存的就是資源ID號 |
ResourceTag: 0x80010800 |
ResourceChunkSize: 資源ID塊的大小 |
連續 ResourceChunkSize/4 -2個res id值。-2主要是除去上面的8字節resourceChunkHeader。 每個res id占4字節 |
ResourceChunk結束 |
下面就是一些連續的chunk塊了: |
CHUNK_STARTNS: doc開始標志,0x00011000 |
CHUNK SIZE: |
line number |
unknown, 0xffffffff |
下面就是一個namespace record結構體,簡稱NsRecord |
NsRecord->prefix |
NsRecord->uri, |
然后就是遞歸地進行chunk操作,因為一個命名空間里面往往含有很多子chunk |
CHUNK_TYPE:0x02011000->0x00100102為CHUNK_STARTTAG |
CHUNK_SIZE |
line number |
unknown, 0xffffffff |
current tag's namespace's uri |
當前tag的名字 所一個string索引值 |
flags, unknown usage |
當前標簽含有的attr個數,注意最后結果要&0x0000ffff |
classAttribute, unknown usage |
下面就是連續的n個attribution chunk,attribution的結構體如下: /* attribute structure within tag */ typedef struct{ uint32_t uri; /* uri of its namespace index of strings*/ uint32_t name; /*屬性名,索引值 index of strings */ uint32_t string; /* attribute value if type == ATTR_STRING ,索引值*/ uint32_t type; /* attribute type, == ATTR_* * / 注意該值需要右移24位 uint32_t data; /* attribute value, encoded on type */ } Attribute_t; |
依次類推 ........ |
2.2修改AXML的注意事項
了解了AXML的文件格式,我們就可以想法進行屬性插入了。不過在屬性插入之前,我們必須規划好具體地實施方案,因為它涉及到的東西並不算少。
1)首先,需要對屬性結構體做進一步分析。它的格式如下:
/* attribute structure within tag */ typedef struct{ uint32_t uri; /* uri of its namespace index of strings*/ uint32_t name; /*屬性名,索引值 index of strings */ uint32_t string; /* attribute value if type == ATTR_STRING ,索引值*/ uint32_t type; /* attribute type, == ATTR_* * / 注意該值需要右移24位 uint32_t data; /* attribute value, encoded on type */ } Attribute_t; |
重點是name, string, data。我提取出了一個AXML中屬性片段,如下所示:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0C 00 00 00 05 00 00 00 FF FF FF FF 08 00 00 01 01 00 06 7F
0C 00 00 00 06 00 00 00 FF FF FF FF 08 00 00 01 00 00 05 7F
0C 00 00 00[w1] 04 00 00 00[w2] 17 00 00 00[w3] 08 00 00 03 [w4] 17 00 00 00[w5]
[w1]uri:命名空間的URI,是string的索引值
[w2]name:屬性名,也是一個string的索引值
[w3]string:如果屬性type為ATTR_STRING的話,此值就是屬性android:name="xxx",xxx在string的索引值。其余情況均為0xffffffff
[w4]type:屬性的類型,對於android:name,類型值為0x03000008
[w5]data:屬性的數據值,對於ATTR_STRING而言,它的值就是string的值。
可以發現,結構體里面並沒有一個叫做res ID的成員,那么系統又是如何獲取某個屬性的ID號的呢?原來這里的name成員是身兼兩職,即作為屬性名的一個string索引,又作為res ID的索引。比如這里name = 4,它對應StringChunk中的字符串為"name",對應ResourceChunk中的res ID 0x01010003。所以要插入一個屬性名為name,ID號又為0的屬性,我們就必須新建一個string,該string的值為name,再新建一個res ID,值為0,且兩者在各自Chunk區域的索引值是相等的(這是重點)。
2)其次,就是在StringChunk中string的對齊問題(最初被弄得腦洞大開~)。
AXML中幾乎所有的成員都是uint32型的,除了使用UTF-16編碼的string數據塊之外。所以在加入string后必須對string數據塊進行4字節對齊。而如果原AXML的string數據塊已經進行過4字節對齊(即人為地填充了幾個0x00)的話,我們就需要注意UTF-16編碼的最后一個string的第一個字節的大小並不包含這幾個填充的0x00(這個字節表示該string所占用的字節數,詳情可查閱UTF-16編碼相關資料)。為了繞過煩人的對齊問題,我們使用取巧的方式獲取字符串的長度:
stringLen = stringChunkSize - stringOffset; //此時的stringLen肯定是4字節對齊的 |
當然,這是在沒有style的情況下,如果有的話,還得采取額外的操作(實現代碼中有~)。為了簡便,我是直接將添加的string加在這個對齊后的字符串之后的,這樣就只需要考慮添加的字符串是否需要對齊了~
3)然后,就是ResourceChunk的擴充。
在1)中已經提到插入的屬性的name的值同時充當res ID索引值。而通常ResourceChunk中的res ID個數是遠少於string 的個數的,那么這就需要我們將ResourceChunk進行擴充。擴充很簡單,全部賦值為0即可。
4)最后,除了需要添加數據外,還需要修改原文件的某些“計量值”,這些計量值都是與數據塊大小或偏移值有關的,總結如下:
①fileSize
②StringChunkSize
③count of string
④styles的起始偏移值(如果有style的話就需要修改)
⑤ResourceChunkSize
⑥application所屬chunk的chunksize
⑦applicationh含有的屬性個數
2.3 修改AXML步驟
1)修改StringChunk,添加UTF-16表示的字符串chouchou.class和name,並為這兩個字符串添加偏移值條目。同時對StringChunkSize、count of string、styles的起始偏移值進行修復;
2)修改ResourceChunk,主要是進行res ID擴充和對ResourceChunkSize的修復
3)修改application所在的chunk,插入屬性,同時對chunksize和applicationh含有的屬性個數進行修復;
4)將不需要修改的部分copy到合適的位置;
5)修復fileSize
當然,具體地實現肯定比上訴步驟復雜一些,不過實現源碼中有較為詳細的注釋,大家可參照源碼閱讀~
3 代碼說明
AxmlParser.h/.c是Claud大大解析axml的源碼,出於對作者的感謝以及讓大家更詳細地了解AXML的解析過程(其實,是我實在是不想自己寫解析代碼o(╯□╰)o),我將實現代碼跟它合並到一塊了。AxmlModify.c就是我寫的實現AXML修改功能。
4 使用方法
當前代碼還不完善,只是初步實現了插入application.attr("name", "chouchou.class",0x0)的功能。所以並非最終版。
代碼只能在linux下運行,下載代碼后make即可生成可執行文件manifestAmbiguity。然后直接運行./manifestAmbiguity可以得到完整的使用說明。
修改前:
修改后:
將修改后的xml覆蓋原APK中的xml,然后刪掉原來的簽名文件夾再進行簽名即可。這時候如果對按照此方案修改后的APK進行重打包,就會發現重打包的APK已經無法啟動了。
5 下一步工作
由於目前的apk軟件保護主要是基於dex代碼加密和so庫文件加密,對AndroidManifest.xml並沒有進行任何操作,而AndroidManifest.xml作為apk的入口文件,其重要性是不言而喻的。所以我想能不能在此文件中做些“手腳”,然后結合相應的處理代碼實現另一角度的軟件保護。
比如,我們完全可以實現那個陷阱類trap.class,且這個類繼承自application等等,以便被重打包的apk也可運行。只是,從一開始,該apk就運行在一個錯誤的環境中,至於之后的操作,那就可以盡情發揮了。
或者,我們可以在其他tag中插入一些不會影響apk運行的屬性(即新添加的屬性不可被系統識別,重打包后該屬性能被系統識別但又不會影響apk的運行),然后在代碼中檢查AndroidManifest.xml是否含有該屬性,如果有就說明軟件被重打包了。
等等~
如果大家有好的建議或方法,請一定不吝賜教~謝謝!
代碼地址:
https://github.com/wanchouchou/ManifestAmbiguity