一、前言
前置技能連結:
DEX檔案解析---1、dex檔案頭解析
DEX檔案解析---2、Dex檔案checksum(校驗和)解析
DEX檔案解析--3、dex檔案字串解析
DEX檔案解析--4、dex類的型別解析
DEX檔案解析--5、dex方法原型解析
DEX檔案解析--6、dex檔案欄位和方法定義解析
PS:Dex檔案解析到現在,終於到了最重要也是結構最複雜的部分了,不瞭解前面的一些必要知識的,可以看我前面幾篇文章;這篇文章分析的dex樣本來自一個複雜apk的dex檔案,但是程式碼執行時使用的樣本是一個在網上找的很簡單的dex樣本,原因很簡單,分析使用的dex涉及的smali指令太多了,大概有200多個,挨個解析起來工作量太大了,有時間我會寫一個通用的python解析模組,完成了我會上傳到github倉庫,有興趣的完成後可以看一下,用簡單的dex只涉及到5個指令,程式碼寫起來就沒那麼麻煩了!!!(tips:Dex類資料這裡解析起來有種俄羅斯套娃的感覺,多看幾篇就很容易理解了。)
PS:這篇文章及其之前同系列的整合版(只是所有文章彙總在一起的整合版)都發在某公眾號上面了,名字就不說了,怕被認為打廣告,所以這不是抄襲哦!!!
二、uleb128編碼
PS:本來關於uleb128編碼網上一大堆,沒必要寫這個,但是網上的你抄我的我抄你的,能找的的相關資料基本都一樣。。。。或者乾脆貼個官方程式碼,官方程式碼的位運算寫的很巧妙,但是直接去看的化,反正我是沒看懂到底是怎麼解碼出來的。
uleb128編碼,是一種可變長度的編碼,長度大小為1-5位元組
,uleb128通過位元組的最高位來決定是否用到下一個位元組,如果最高位為1,則用到下一個位元組,直到某個位元組最高位為0或已經讀取了5個位元組為止,接下來通過一個例項來理解uleb128編碼。
假設有以下經過uleb128編碼的資料(都為16進位制)--81 80 04
,首先來看第一個位元組81
,他的二進位制為10000001
,他的最高位為1
,則說明還要用到下一個位元組,它存放的資料則為0000001
;再來看第二個位元組80
,它的二進位制為10000000
,它的最高位為1
,則說明還需要用到第三個位元組,存放的資料為0000000
;再來看第三個位元組04
,它的二進位制為00000100
,最高位為0
,說明一共使用了三個位元組,它存放的資料為0000100
;通過上面的資料我們已經獲取了存放的資料,接下來就是把這些bit組合起來獲取解碼後的資料,dex檔案裡面的資料都是採用的小端序的方式,uleb128也不例外,在這三個位元組,也不例外,第三個位元組04
存放的資料0000100
作為解碼後的資料的高7位
,第二個位元組80
存放的資料0000000
作為解碼後的資料的中7位
,第一個位元組81
存放的資料0000001
作為解碼後的資料的低7位
;那麼解碼後的資料二進位制則為0000100 0000000 0000001
,轉換為16進位制則為0x10001
。其他使用5個位元組、4個位元組照此類推即可,下面是python讀取uleb128的程式碼(ps:該程式碼是最終類資料解析程式碼的一共函式,無法單獨執行,僅供參考,採用的是官方提供的位運算演算法):
def readuleb128(f,addr):
result = [-1,-1]
n = 0
f.seek(addr)
data = oneByte2Int(f.read(1))
if data > 0x7f:
f.seek(addr + 1)
n = 1
tmp = oneByte2Int(f.read(1))
data = (data & 0x7f) | ((tmp & 0x7f) << 7)
if tmp > 0x7f:
f.seek(addr + 2)
n = 2
tmp = oneByte2Int(f.read(1))
data |= (tmp & 0x7f) << 14
if tmp > 0x7f:
f.seek(addr + 3)
n = 3
tmp = oneByte2Int(f.read(1))
data |= (tmp & 0x7f) << 21
if tmp > 0x7f:
f.seek(addr + 4)
n = 4
tmp = oneByte2Int(f.read(1))
data |= tmp << 28
result[0] = data
result[1] = addr + n + 1
return result
三、類解析第一層結構:class_def_item
1、在dex檔案頭0x60-0x63
這四個位元組,指明瞭class
的數量,在0x64-0x67
這四個位元組,指明的class_def_item
的偏移地址。如下所示:
2、通過上面的偏移地址,我們可以找到class_def_item的起始地址,class_def_item包含了一個類的類名、介面、父類、所屬java檔名等資訊。一個class_def_item結構大小為32位元組,分別包含8個資訊,每個資訊大小為4位元組(小端序儲存):
第1-4位元組--class_idx
(該值為前面解析出來的類的型別列表的索引,也就是這個類的類名);第5-8位元組--access_flags
(類的訪問標誌,也就是這個類是public還是private等,這個通過官方的文件查表得知,具體演算法在最後面說明);第9-12位元組--superclass_idx
(該值也為前面解析出來的類的型別列表的索引,指明瞭父類的類名)第13-16位元組--interfaces_off
(該值指明瞭介面資訊的偏移地址,所指向的地址結構為typelist,前面的文章有說過,這裡不再多說,如果該類沒有介面,該值則為0)第17-20位元組--source_file_idx
(該值為dex字串列表的的索引,指明瞭該類所在的java檔名)第21-24位元組--annotations_off
(該值為註釋資訊的偏移地址,由於註釋資訊不是我要解析的重點,要檢視註釋資訊具體結構的可以參考官方文件,官方文件地址貼上在文末)第25-28位元組--class_data_off
(該值是這個類資料第二層結構的偏移地址,在該結構中指明瞭該類的欄位和方法)第29-32位元組--static_value_off
(該值也是一個偏移地址,指向了一個結構,不是重點,感興趣的參考官方文件,如果沒相關資訊,則該值為0)
具體分析過程,如下圖所示:
四、類解析第二層結構:class_data_item
1、通過上面class_def_item的分析,我們知道了類的基本資訊,例如類名、父類等啊,接下來就是要找到類裡面的欄位和方法這些資訊,而這些資訊,在class_def_item裡面的class_data_off欄位給我們指明class_data_item
就包含這些資訊並給出了偏移地址,即現在需要解析class_data_iem
結構獲取欄位和方法資訊。(ps:以下的資料結構不做特別說明都為uleb128編碼格式)
2、class_data_item
結構包含以下資訊:
第一個uleb128編碼--static_field_size
,指明瞭該類的靜態欄位的數量第二個uleb128編碼--instance_field_size
,指明瞭該類的例項欄位的數量(例項欄位不知道是啥的建議百度)第三個uleb128編碼--direct_method_size
,指明瞭該類的直接方法的個數第四個uleb128編碼--virtual_method_size
,指明瞭該類的虛方法的個數(虛方法理解不清楚的建議百度一下)encoded_field--static_fields
,該結構指明瞭具體的靜態欄位資訊,該結構的存在前提是static_field_size >0
,該結構包含兩個uleb128編碼,第一個uleb128編碼為前面解析出來的欄位列表的索引,第二個uleb128編碼指明瞭該欄位的訪問標誌encoded_field--instance_fields
,跟上面類似,不再多說,值得注意的是,該結構存在的前提是instance_field_size > 0
encoded_method--direct_methods
,該結構指明瞭直接方法具體資訊,該結構存在的前提同樣是direct_method_size > 0
,該結構包含3個uleb128編碼,第一個uleb128為前面文章解析出來的方法原型列表的索引值,第二個uleb128編碼為該方法的訪問標誌,第三個uleb128為code_off,也就是該方法具體程式碼的位元組碼的偏移地址,對應的結構為code_item,code_item結構裡面包含了該方法內部的程式碼,這裡是位元組碼,也就是smali(ps:如果該方法為抽象方法,例如native方法,這時code_off對應的值為0,即該方法不存在具體程式碼)encoded_method--virtual_methods
,該結構指明瞭該類的虛方法的具體資訊,存在前提為virtual_method_size > 0
,具體結構和上面一樣,不再多說
具體分析過程,如下圖所示:
五、類解析的第三層結構:code_item
1、在上面的class_data_item結構中的encoded_method
結構的第三個uleb128編碼中,指出了一個類中的方法具體程式碼的偏移地址,也就是dv虛擬機器在執行該方法的具體指令的偏移地址,該值指向的地址結構為code_item
,裡面包含了暫存器數量、具體指令等資訊,下面來分析一下該結構。
2、code_item
結構包含以下資訊:
第1-2位元組--registers_size
,該值指明瞭該方法使用的暫存器數量,對應的smali語法中的.register
的值第3-4位元組--ins_size
,該值指明瞭傳入引數的個數第5-6位元組--outs_size
,該值指明瞭該方法內部呼叫其他函式用到的暫存器個數第7-8位元組--tries_size
,該值指明瞭該方法用到的try-catch
語句的個數第9-12位元組--debug_info_off
,該值指明瞭除錯資訊結構的偏移地址,如果不存在除錯資訊,則該值為0第13-16位元組--insns_size
,該值指明瞭指令列表的大小,可以這麼理解:規定了指令所用的位元組數大小--2 x insns_size
ushort[insns_size]--insns
,這個是指令列表,包含了該方法所用到的指令的位元組,每個指令佔用的位元組數可以參考官方文件,這個沒什麼演算法,就是一個查表的過程,例如invoke-direct
指令佔用6個位元組,return-void
指令佔用2個位元組2個位元組--padding
,該值存在的前提是tries-size > 0
,作用用來對齊程式碼try_item--tries
,該值存在的前提是tries-size > 0
,作用是指明異常具體位置和處理方式,該結構不是解析重點,重點是解析指令,感興趣的檢視官方文件encoded_catch_handler_list--handlers
,該結構存在前提為tries-size > 0
,同樣不是解析重點,感興趣的檢視官方文件
具體分析過程,如下圖所示:
六、access_flags演算法
access_flags訪問標誌具體值可以去檢視官方文件,下圖只截了一部分。如果access_flags的演算法為access_flags = flag1 | flag2 | ...
,如果訪問標誌只有一共,直接查表即可,如果是兩個,按照演算法對比值即可,下面舉給=個例子來理解該演算法。
例如我有一個類的訪問標誌為public static
,經過查表得知public
對應的值為0x01
,static
對應的值為0x8
,那麼public static
對應的訪問標誌為0x01 | 0x08 = 0x9
,如果讀取出來的access_flags為0x09,那麼對應的訪問標誌則為public static
,其餘的照此演算法計算即可!!!
七、解析程式碼
PS:程式碼執行環境推薦3.6及其以上,需要模組binascii
,執行樣本為Hello.dex
,樣本附在文末網盤連結中!!!
執行截圖
通過指令碼解析出來的和通過apktools反編譯出來的smali檔案對比圖
(ps:左側為apktools反編譯出來的,右側為指令碼解析出來的,可以發現基本差不多)
解析程式碼(ps:程式碼量有點多):
'''
__----~~~~~~~~~~~------___
. . ~~//====...... __--~ ~~
-. \_|// |||\\ ~~~~~~::::... /~
___-==_ _-~o~ \/ ||| \\ _/~~-
__---~~~.==~||\=_ -_--~/_-~|- |\\ \\ _/~
_-~~ .=~ | \\-_ '-~7 /- / || \ /
.~ .~ | \\ -_ / /- / || \ /
/ ____ / | \\ ~-_/ /|- _/ .|| \ /
|~~ ~~|--~~~~--_ \ ~==-/ | \~--===~~ .\
' ~-| /| |-~\~~ __--~~
|-~~-_/ | | ~\_ _-~ /\
/ \ \__ \/~ \__
_--~ _/ | .-~~____--~-/ ~~==.
((->/~ '.|||' -_| ~~-/ , . _||
-_ ~\ ~~---l__i__i__i--~~_/
_-~-__ ~) \--______________--~~
//.-~~~-~_--~- |-------~~~~~~~~
//.-~~~--\
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
神獸保佑 永無BUG
@Author: windy_ll
@Date: 2020-07-08 16:21:27
@LastEditors: windy_ll
@LastEditTime: 2020-07-14 23:45:28
@Description: file content
'''
import binascii
import re
import os
import sys
def byte2int(bs):
tmp = bytearray(bs)
tmp.reverse()
rl = bytes(tmp)
rl = str(binascii.b2a_hex(rl),encoding='UTF-8')
rl = int(rl,16)
return rl
def oneByte2Int(bs):
num = str(binascii.b2a_hex(bs),encoding='UTF-8')
num = int(num,16)
return num
def getSmaliName(oldname):
newname = ''
tmpname = oldname.split('.')
newname = str(os.path.join(sys.path[0])) + '\\smali\\' + str(tmpname[0]) + '.smali'
return newname
def readuleb128(f,addr):
result = [-1,-1]
n = 0
f.seek(addr)
data = oneByte2Int(f.read(1))
if data > 0x7f:
f.seek(addr + 1)
n = 1
tmp = oneByte2Int(f.read(1))
data = (data & 0x7f) | ((tmp & 0x7f) << 7)
if tmp > 0x7f:
f.seek(addr + 2)
n = 2
tmp = oneByte2Int(f.read(1))
data |= (tmp & 0x7f) << 14
if tmp > 0x7f:
f.seek(addr + 3)
n = 3
tmp = oneByte2Int(f.read(1))
data |= (tmp & 0x7f) << 21
if tmp > 0x7f:
f.seek(addr + 4)
n = 4
tmp = oneByte2Int(f.read(1))
data |= tmp << 28
result[0] = data
result[1] = addr + n + 1
return result
def getAccessFlags(flag):
accessFlag = ''
flagList = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x100,0x200,0x400,0x800,0x2000,0x4000,0x10000]
flagdict = {0x01:'public',0x02:'private',0x04:'protected',0x08:'static',0x10:'final',0x20:'synchronized',0x40:'volatile',0x80:'transient',0x100:'native',\
0x200:'interface',0x400:'abstract',0x800:'strictfp',0x2000:'annotayion',0x4000:'enum',0x10000:'constructor'}
if flag == 0x1:
accessFlag = 'public'
elif flag == 0x2:
accessFlag = 'private'
elif flag == 0x4:
accessFlag = 'protected'
elif flag == 0x8:
accessFlag = 'static'
elif flag == 0x10:
accessFlag = 'final'
elif flag == 0x20:
accessFlag = 'synchronized'
elif flag == 0x40:
accessFlag = 'volatile'
elif flag == 0x80:
accessFlag = 'transient'
elif flag == 0x100:
accessFlag = 'native'
elif flag == 0x200:
accessFlag = 'interface'
elif flag == 0x400:
accessFlag = 'abstract'
elif flag == 0x800:
accessFlag = 'strictfp'
elif flag == 0x2000:
accessFlag = flagdict[0x2000]
elif flag == 0x4000:
accessFlag = flagdict[0x4000]
elif flag == 0x10000:
accessFlag = flagdict[0x10000]
else:
mark = 0
for k in range(14):
if mark == 1:
break
for item in flagList[(k + 1):]:
if flag == (flagList[k] | item):
idx1 = flagList[k]
idx2 = item
accessFlag = flagdict[idx1] + ' ' + flagdict[idx2]
mark = 1
break
return accessFlag
def parseTypeList(f,addr,tList):
paramList = []
f.seek(addr)
size = byte2int(f.read(4))
if size == 0:
return paramList
else:
addr = addr + 4
for k in range(size):
f.seek(addr + (k * 2))
paramString = typeList[byte2int(f.read(2))]
paramList.append(paramString)
return paramList
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')
return stringItem
except:
pass
def getTypeAmount(f):
f.seek(0x40)
stringsId = f.read(4)
count = byte2int(stringsId)
return count
def getclassCount(f):
f.seek(0x60)
class_num = f.read(4)
class_size = byte2int(class_num)
return class_size
def getStringsCount(f):
f.seek(0x38)
stringsId = f.read(4)
count = byte2int(stringsId)
return count
def getStrings(f,stringAmount):
stringsList = []
f.seek(0x3c)
stringOff = f.read(4)
Off = byte2int(stringOff)
f.seek(Off)
for i in range(stringAmount):
addr = f.read(4)
address = byte2int(addr)
byteArr = getStringByteArr(f,address)
stringItem = BytesToString(byteArr)
stringsList.append(stringItem)
Off = Off + 4
f.seek(Off)
return stringsList
def getTypeItem(f,count,strLists):
typeList = []
f.seek(0x44)
type_ids_off = f.read(4)
type_off = byte2int(type_ids_off)
f.seek(type_off)
for i in range(count):
typeIndex = f.read(4)
typeIndex = byte2int(typeIndex)
typeList.append(strLists[typeIndex])
type_off = type_off + 0x04
f.seek(type_off)
return typeList
def parserField(f,stringList,typelist):
fieldList = []
f.seek(0x50)
fieldSize = byte2int(f.read(4))
fieldAddr = byte2int(f.read(4))
for i in range(fieldSize):
fieldStr = ''
f.seek(fieldAddr)
classIdx = typelist[byte2int(f.read(2))]
f.seek(fieldAddr + 2)
typeIdx = typelist[byte2int(f.read(2))]
f.seek(fieldAddr + 4)
nameIdx = stringList[byte2int(f.read(4))]
fieldAddr += 8
fieldStr = nameIdx + ':' + typeIdx
fieldList.append(fieldStr)
return fieldList
def parseProtold(f,typeList,stringList):
pList = []
f.seek(0x48)
protoldSizeTmp = f.read(4)
protoldSize = byte2int(protoldSizeTmp)
f.seek(0x4c)
protoldAddr = byte2int(f.read(4))
for i in range(protoldSize):
f.seek(protoldAddr)
AllString = stringList[byte2int(f.read(4))]
protoldAddr += 4
f.seek(protoldAddr)
returnString = typeList[byte2int(f.read(4))]
protoldAddr += 4
f.seek(protoldAddr)
paramAddr = byte2int(f.read(4))
if paramAddr == 0:
protoldAddr += 4
pList.append(returnString + '()')
continue
f.seek(paramAddr)
paramSize = byte2int(f.read(4))
paramList = []
if paramSize == 0:
pass
else:
paramAddr = paramAddr + 4
for k in range(paramSize):
f.seek(paramAddr + (k * 2))
paramString = typeList[byte2int(f.read(2))]
paramList.append(paramString)
protoldAddr += 4
paramTmp = []
for paramItem in paramList:
paramTmp.append(paramItem)
param = returnString + '(' + ','.join(paramTmp) + ')'
pList.append(param)
return pList
def parserMethod(f,stringlist,typelist,protoldlist):
methodlist = []
f.seek(0x58)
methodSize = byte2int(f.read(4))
f.seek(0x5c)
methodAddr = byte2int(f.read(4))
for i in range(methodSize):
f.seek(methodAddr)
classIdx = typelist[byte2int(f.read(2))]
f.seek(methodAddr + 2)
protoldIdx = protoldlist[byte2int(f.read(2))]
f.seek(methodAddr + 4)
nameIdx = stringlist[byte2int(f.read(4))]
tmp = protoldIdx.split('(',1)
methodItem = nameIdx + '(' + str(tmp[1]) + str(tmp[0])
methodlist.append(methodItem)
methodAddr += 8
return methodlist
def parseBytecode(f,addr,bytecount,stringsList,fieldsList,methodsList):
codestr = ''
n = 0
while True:
f.seek(addr)
op = byte2int(f.read(1))
if op == 0x0e:
codestr += '\treturn-void\r\n'
addr = addr + 2
n += 2
elif op == 0x1a:
f.seek(addr + 1)
register = oneByte2Int(f.read(1))
f.seek(addr + 2)
idx = byte2int(f.read(2))
stringIdx = stringsList[idx]
re.sub("[\n]","",stringIdx)
re.sub("[\r]","",stringIdx)
codestr += '\tconst-string v' + str(register) + ', "' + stringIdx + '"\r\n'
addr = addr + 4
n += 4
elif op == 0x62:
f.seek(addr + 1)
register = oneByte2Int(f.read(1))
f.seek(addr + 2)
idx = byte2int(f.read(2))
codestr += '\tset-object v' + str(register) + ', ' + fieldsList[idx] + '\r\n'
addr = addr + 4
n += 4
elif op == 0x70 or op == 0x6e:
f.seek(addr + 1)
data = oneByte2Int(f.read(1))
f.seek(addr + 4)
data1 = oneByte2Int(f.read(1))
f.seek(addr + 5)
data2 = oneByte2Int(f.read(1))
f.seek(addr + 2)
idx = byte2int(f.read(2))
registerNum = (data & 0xf0) >> 4
register = ''
if registerNum == 1:
register_1 = data & 0xf
register = '{v' + str(register_1) + '}, '
elif registerNum == 2:
register_1 = data & 0xf
register_2 = (data1 & 0xf0) >> 4
register = '{v' + str(register_1) + ', v' + str(register_2) + '}, '
elif registerNum == 3:
register_1 = data & 0xf
register_2 = (data1 & 0xf0) >> 4
register_3 = data1 & 0xf
register = '{v' + str(register_1) + ', v' + str(register_2) + ', v' + str(register_3) + '}, '
elif registerNum == 4:
register_1 = data & 0xf
register_2 = (data1 & 0xf0) >> 4
register_3 = data1 & 0xf
register_4 = (data2 & 0xf0) >> 4
register = '{v' + str(register_1) + ', v' + str(register_2) + ', v' + str(register_3) + ', v' + str(register_4) + '}, '
else:
register_1 = data & 0xf
register_2 = (data1 & 0xf0) >> 4
register_3 = data1 & 0xf
register_4 = (data2 & 0xf0) >> 4
register_5 = data2 & 0xf
register = '{v' + str(register_1) + ', v' + str(register_2) + ', v' + str(register_3) + ', v' + str(register_4) + ', v' + str(register_5) + '}, '
if op == 0x70:
codestr += '\tinvoke-direct ' + register + methodsList[idx] + '\r\n'
else:
codestr += '\tinvoke-virtual ' + register + methodsList[idx] + '\r\n'
addr = addr + 6
n += 6
else:
pass
if n == bytecount:
break
return codestr
def parseCode(f,addr,fn,slist,flist,mlist):
f.seek(addr)
register_size = byte2int(f.read(2))
f.seek(addr + 2)
ins_size = byte2int(f.read(2))
f.seek(addr + 4)
out_size = byte2int(f.read(2))
f.seek(addr + 6)
try_size = byte2int(f.read(2))
f.seek(addr + 8)
debug_off = byte2int(f.read(4))
f.seek(addr + 12)
insns_size = byte2int(f.read(4))
address = addr + 16
bytecount = insns_size * 2
registerString = '\t.register ' + str(register_size) + '\r\n'
fn.write(registerString)
codestr = parseBytecode(f,address,bytecount,slist,flist,mlist)
fn.write(codestr)
endstr = '.end method\r\n'
fn.write(endstr)
def parseClassData(f,addr,fn,fList,mList,strsList):
re = readuleb128(f,addr)
static_fields_size = re[0]
address = re[1]
re = readuleb128(f,address)
instance_fields_size = re[0]
address = re[1]
re = readuleb128(f,address)
direct_method_size = re[0]
address = re[1]
re = readuleb128(f,address)
virtual_method_size = re[0]
address = re[1]
fieldStr = ''
if static_fields_size != 0:
fieldStr += '# static fields\r\n'
for i in range(static_fields_size):
re = readuleb128(f,address)
fieldidx = re[0]
address = re[1]
re = readuleb128(f,address)
accflag = re[0]
address = re[1]
fieldStr += '.field ' + getAccessFlags(accflag) + ' ' + fList[fieldidx] + '\r\n'
fieldStr += '\r\n\r\n'
fn.write(fieldStr)
fieldStr = ''
if instance_fields_size != 0:
fieldStr += '# instance fields\r\n'
for i in range(instance_fields_size):
re = readuleb128(f,address)
fieldidx = re[0]
address = re[1]
re = readuleb128(f,address)
accflag = re[0]
address = re[1]
fieldStr += '.field ' + getAccessFlags(accflag) + ' ' + fList[fieldidx] + '\r\n'
fieldStr += '\r\n\r\n'
fn.write(fieldStr)
methodStr = ''
if direct_method_size != 0:
methodStr += '# direct methods\r\n'
fn.write(methodStr)
for i in range(direct_method_size):
re = readuleb128(f,address)
methodidx = re[0]
address = re[1]
re = readuleb128(f,address)
accflag = re[0]
address = re[1]
re = readuleb128(f,address)
code_off = re[0]
address = re[1]
methodStr = '.method ' + getAccessFlags(accflag) + ' ' + mList[methodidx] + '\r\n'
fn.write(methodStr)
parseCode(f,code_off,fn,strsList,fList,mList)
methodStr = '\r\n\r\n'
fn.write(methodStr)
methodStr = ''
if virtual_method_size != 0:
methodStr = '# virtual methods\r\n'
fn.write(methodStr)
for i in range(virtual_method_size):
re = readuleb128(f,address)
methodidx = re[0]
address = re[1]
re = readuleb128(f,address)
accflag = re[0]
address = re[1]
re = readuleb128(f,address)
code_off = re[0]
address = re[1]
methodStr = '.method ' + getAccessFlags(accflag) + ' ' + mList[methodidx] + '\r\n'
fn.write(methodStr)
parseCode(f,code_off,fn,strsList,fList,mList)
methodStr = '\r\n\r\n'
fn.write(methodStr)
def parseClassDefItem(f,class_num,tList,sList,fieldlist,methodlist):
f.seek(0x64)
addr = byte2int(f.read(4))
for i in range(class_num):
f.seek(addr)
classIdx = tList[byte2int(f.read(4))]
f.seek(addr + 4)
accessFlags = getAccessFlags(byte2int(f.read(4)))
if accessFlags != 'error':
pass
f.seek(addr + 8)
superclass_idx = tList[byte2int(f.read(4))]
f.seek(addr + 12)
interfaces_off = byte2int(f.read(4))
if interfaces_off == 0:
pass
else:
parseTypeList(f,interfaces_off,tList)
f.seek(addr + 16)
sourceFileIdx = sList[byte2int(f.read(4))]
f.seek(addr + 20)
annotions_off = byte2int(f.read(4))
address = 0
f.seek(addr + 24)
class_data_off = byte2int(f.read(4))
f.seek(addr + 28)
static_value_off = byte2int(f.read(4))
fname = getSmaliName(sourceFileIdx)
fn = open(fname,'a+',True)
headstr = '.class ' + str(accessFlags) + ' ' + str(classIdx) + '\r\n'
headstr += '.super ' + str(superclass_idx) + '\r\n'
headstr += '.source ' + '"' + str(sourceFileIdx) + '"\r\n\r\n'
fn.write(headstr)
if class_data_off != 0:
parseClassData(f,class_data_off,fn,fieldlist,methodlist,sList)
fn.close()
print('[*] %s檔案的類%s寫入完畢!'%(fname,classIdx))
addr += 32
if __name__ == '__main__':
filename = str(os.path.join(sys.path[0])) + '\\Hello.dex'
dir = str(os.path.join(sys.path[0])) + '\\smali'
if not os.path.exists(dir):
os.makedirs(dir)
f = open(filename,'rb',True)
stringsCount = getStringsCount(f)
strList = getStrings(f,stringsCount)
typeCount = getTypeAmount(f)
typeList = getTypeItem(f,typeCount,strList)
fieldList = parserField(f,strList,typeList)
protoldList = parseProtold(f,typeList,strList)
methodList = parserMethod(f,strList,typeList,protoldList)
classNum = getclassCount(f)
parseClassDefItem(f,classNum,typeList,strList,fieldList,methodList)
f.close()
八、參考資料以及樣本下載
參考資料:
1、Android逆向之旅—解析編譯之後的Dex檔案格式:http://www.520monkey.com/archives/579
2、一篇文章帶你搞懂DEX檔案的結構:https://blog.csdn.net/sinat_18268881/article/details/55832757
3、官方文件:https://source.android.google.cn/devices/tech/dalvik/dex-format#embedded-in-class_def_item,-encoded_field,-encoded_method,-and-innerclass
樣本及程式碼下載:
藍奏雲連結:https://wws.lanzous.com/iG8Cuemlw4d;密碼:chb6
github連結:https://github.com/windy-purple/parserDex