一、前言
前兩篇文章鏈接:
1、DEX文件頭解析
2、DEX文件校驗和解析
PS:前幾天檢查文件夾的時候發現DEX文件解析還只寫了開頭,正好找點事情來做,就去接着解析DEX文件其余部分了。。。。。(還得多虧了一波疫情,不然都忘了還有這回事了。。。)
二、DEX文件中的字符串
1、DEX文件大致上可以粗略的分為3個部分:文件頭、索引區以及數據區。而文件頭一般來說占了整個DEX文件0x70個字節(還不了解DEX文件頭的可以看一下我前面兩篇文章),在文件頭中,關於字符串的相關信息一共有8個字節,分別位於0x38(4 Bytes)和0x3c(4 Bytes)處,前者說明了該DEX文件包含了多少個字符串,后者則是字符串索引區的起始地址,但是需要注意的是,DEX存儲是以小端序存儲的(通俗一點的說就是從后往前讀),如下所示:
2、前面我們通過文件頭知道了字符串數量和字符串索引區起始地址等信息,接下來我們就來具體看一下字符串索引區。字符串索引區存儲的是字符串真正存儲在數據區的偏移地址,以4個字節為一組,表示一個字符串在數據區的偏移地址,所以索引區一個占字符串數量 X 4
個字節那么多,同樣的,索引區也采用的是小端序存儲,所以我們在讀取地址時,需要與小端序的方式來讀取真正的地址,如下所示:
3、從上面我們已經知道了如何找到字符串在數據區的偏移地址,接下來我們需要做的就是解析這些數據區的字節。通過偏移地址我們可以在數據區找到代表字符串的這些字節,在DEX文件中,字符串是通過MUTF-8
編碼而成的(至於mutf-8是什么編碼,我會將一些相關博客鏈接貼在文末),在MUTF-8
編碼中,第一個字節代表了這個字符串所需要用到的字節數目(不包括最后一個代表終結的字節),最后一個字節為0x00
,表示這個字符串到此結束,跟c語言有點類似,中間部分才是一個字符串的具體內容,如下所示:(PS:mutf-8
第一個字節還經過uleb128
編碼,所以簡單的進行進制換算得到的字節數很多人奇怪對不上,由於比較復雜,就不過多解釋了,想進一步了解更深的可以去看一下安卓源碼中對DEX文件解析出字符串這一部分)
三、解析代碼:
PS:我電腦運行環境--python3.6
代碼如下:
import binascii
import os
import sys
def getStringsCount(f):
f.seek(0x38)
stringsId = f.read(4)
a = bytearray(stringsId)
a.reverse()
stringsId = bytes(a)
stringsId = str(binascii.b2a_hex(stringsId),encoding='UTF-8')
count = int(stringsId,16)
print('[+] stringSize ==> ' + str(count))
return count
def getStringByteArr(f,addr):
byteArr = bytearray()
f.seek(addr + 1)
b = f.read(1)
b = str(binascii.b2a_hex(b),encoding='UTF-8')
b = int(b,16)
index = 2
while b != 0:
byteArr.append(b)
f.seek(addr + index)
b = f.read(1)
b = str(binascii.b2a_hex(b),encoding='UTF-8')
b = int(b,16)
index = index + 1
return byteArr
def BytesToString(byteArr):
try:
bs = bytes(byteArr)
stringItem = str(bs,encoding='UTF-8')
print('[*] str = ' + stringItem)
return stringItem
except:
pass
def getAddress(addr):
address = bytearray(addr)
address.reverse()
address = bytes(address)
address = str(binascii.b2a_hex(address),encoding='UTF-8')
address = int(address,16)
return address
def getStrings(f,stringAmount):
stringsList = []
f.seek(0x3c)
stringOff = f.read(4)
Off = getAddress(stringOff)
f.seek(Off)
for i in range(stringAmount):
addr = f.read(4)
address = getAddress(addr)
byteArr = getStringByteArr(f,address)
stringItem = BytesToString(byteArr)
stringsList.append(stringItem)
Off = Off + 4
f.seek(Off)
if __name__ == '__main__':
filename = str(os.path.join(sys.path[0])) + '\\1.dex'
f = open(filename,'rb',True)
stringsCount = getStringsCount(f)
getStrings(f,stringsCount)
f.close()
運行截圖:
四、一些總結
其實也沒有什么好總結的,因為本身這沒有什么難點之處,就記錄一些遇見的問題吧!!!最開始解析字符串的時候發現MUTF-8
編碼的時候好不容易弄懂了的時候,發現還經過uleb128
,所以最開始一直沒辦法通過第一個字節計算出需要編碼的字節個數,最后取了個巧,從第二個字節開始讀取知道讀取到0x00
為止;然后是怎么編碼這些字節顯示字符串,后來看了一下姜維大佬寫的解析代碼,發現直接用的是utf-8
進行編碼,轉念一想,mutf-8
也是utf-8
的變種,所以大部分解析出來基本沒有問題。寫完代碼后本來打算去看一下安卓源碼是怎么解析這一塊的再來模仿一下,但是這疫情讓我已經耍了這么久了,實在沒精神去看了,我還是接着去微博上蹲在@四川教育吧,看源碼什么的還是開學了再說吧!!!
五、一下鏈接和附件
1、相關知識鏈接:
MUTF-8編碼:https://blog.csdn.net/Roland_Sun/article/details/46716965
uleb128:https://blog.csdn.net/Roland_Sun/article/details/46708061
2、樣本及代碼下載鏈接:
百度網盤鏈接:https://pan.baidu.com/s/1_CQP7Zrj9LHcLOIjdGD95A;提取碼:yc9y