來自i春秋作者:penguin_wwy

一、概述

上篇分析了Androguard如何讀取dex,而且還提到Androguard很適合進行擴展或者移植成為自己項目的某一模塊。 這篇文章就來研究一下如何在Androguard基礎上進行擴展。 App對抗靜態分析的方法之一就是利用反射,如果對反射的字符串進行加密會得到更好的效果,而且不但反射可以通過加密字符串,凡是動態注冊、加載都可以通過加密字符串提高隱蔽性,對抗靜態分析。 比如這樣

localIntentFilter.addAction(Fegli.a("OBMpJw07KEgVPC1TLjoMPGIlNBcXOA4BKwQFMiIGGjUMGyUX:Y}MUbRLf{Y"));

或者這樣

Object localObject2 = this.h.getString(Fegli.a("PS5CRyQ=:YK.&]$zqZl"), Fegli.a(":qO7sY!p@wN"));

下面我們就給Androguard補充一個功能,提取dex文件中經過加密的字符串

要提取dex文件中的加密字符串,主要兩個步驟

  1. 提取dex文件中的字符串池,獲取全部字符串
  2. 判斷是否為加密字符串

二、提取

首先如何提取?上篇說道Androguard讀取dex文件對各個item進行處理,處理邏輯在MapItem.next函數中看一下源碼,找到跟字符串相關的Item,有兩處

一個是StringIdItem

if TYPE_MAP_ITEM[ self.type ] == "TYPE_STRING_ID_ITEM":
????self.item = [ StringIdItem( buff, cm ) for i in xrange(0, self.size) ]

主要記錄String的偏移

class StringIdItem(object):
????"""
????????This class can parse a string_id_item of a dex file
?
????????:param buff: a string which represents a Buff object of the string_id_item
????????:type buff: Buff object
????????:param cm: a ClassManager object
????????:type cm: :class:`ClassManager`
????"""
????def __init__(self, buff, cm):
????????self.__CM = cm
????????self.offset = buff.get_idx()
?
????????self.string_data_off = unpack("=I", buff.read(4))[0]

一個是StringDataItem

elif TYPE_MAP_ITEM[ self.type ] == "TYPE_STRING_DATA_ITEM":
????self.item = [ StringDataItem( buff, cm ) for i in xrange(0, self.size) ]

看看代碼

class StringDataItem(object):
????"""
????????This class can parse a string_data_item of a dex file
?
????????:param buff: a string which represents a Buff object of the string_data_item
????????:type buff: Buff object
????????:param cm: a ClassManager object
????????:type cm: :class:`ClassManager`
????"""
????def __init__(self, buff, cm):
????????self.__CM = cm
?
????????self.offset = buff.get_idx()
?
????????self.utf16_size = readuleb128( buff )
?
????????self.data = utf8_to_string(buff, self.utf16_size)?? #重點,保存String data
????????expected = buff.read(1)
????????if expected != '\x00':
????????????warning('\x00 expected at offset: %x, found: %x' % (buff.get_idx(), expected))

重點在self.data,通過utf8_to_string函數將字節碼轉換為字符串。

我們知道了每個字符串保存在每個StringDataItem.data中,那我們如何獲得它們呢。回到next函數,MapItem.Item保存所有StringDataItem組成的列表

for i in xrange(0, self.size):
????idx = buff.get_idx()
?
????mi = MapItem( buff, self.CM )
????self.map_item.append( mi )
?
????buff.set_idx( idx + mi.get_length() )
?
????c_item = mi.get_item()
????if c_item == None:
??????mi.set_item( self )
??????c_item = mi.get_item()
?
????self.CM.add_type_item( TYPE_MAP_ITEM[ mi.get_type() ], mi, c_item )

而這個MapItem會被加入到MapList.map_item這個隊列以及self.CM中,當然加入到ClassManager中的過程更復雜。如果從map_item中獲取到字符串,需要首先找到處理StringDataItem的mi,然后遍歷map_item中的所有MapItem對象,依次拿到MapItem.data,這無疑很復雜。那就讓我們把目光放到ClassManager上,看看add_type_item

def add_type_item(self, type_item, c_item, item):
????self.__manage_item[ type_item ] = item
?
????self.__obj_offset[ c_item.get_off() ] = c_item
????self.__item_offset[ c_item.get_offset() ] = item
?
????sdi = False
????if type_item == "TYPE_STRING_DATA_ITEM":
????????sdi = True
????#當處理StringDataItem時
????if item != None:
????????if isinstance(item, list):? #條件為真
????????????for i in item:????????? #i為StringDataItem對象
????????????????goff = i.offset???? #每個String再dex文件中的偏移
????????????????self.__manage_item_off.append( goff )
?
????????????????self.__obj_offset[ i.get_off() ] = i
?
????????????????if sdi == True:
??????????????????self.__strings_off[ goff ] = i??? #字典中保存StringDataItem
????????else:
????????????self.__manage_item_off.append( c_item.get_offset() )

咦,似乎有了意外的發現,當處理到StringDataItem時,會設置一個標志位。當標志位為真時,self.__strings_off這個字典才會保存數據,也就是StringDataItem相關的數據。 我們來仔細研究一下這段代碼,先理解參數。type_item表示Item的類型,c_item則是mi = MapItem( buff, self.CM )的mi,也就是一個完整的MapItem對象。參數中的item則是mi.get_item( ),也就是MapItem.item。所以當type為StringDataItem時item就是保存StringDataItem對象的列表。

整理一下思路,現在的情況是我們可以從ClassManager中的strings_off字典根據偏移得到每個StringDataItem。但是悲催的是ClassManager當中并沒有獲得strings_off的方法,我們只能自己先加一個

def get__strings_off(self):
????return self.__strings_off

只要遍歷__strings_off,拿到每個Item,獲取data就可以得到字符串了。 類似如下處理

soff = vm.get_class_manager().get__strings_off()
str_list = []
for i in soff:
????str_list.append(soff.get_data())

str_list就會保存dex文件中的所有字符串了。

三、判斷加密字符串

得到所有字符串之后,我們就依次判斷它是否是加密字符串。如何判斷呢?公司倒是有一個判斷隨機字符串的工具(也就是人類無法識別的字符串),但畢竟是公司的東西,為也沒有源碼。搞一個字典太費勁,而且字典越大也會影響運行時間。我暫時想了一個辦法來判斷隨機字符串。 首先,先弄個小字典,大概十幾二十個有關單詞(果然是小字典。。。。),先用小字典過濾一下。對于剩下的字符串,將字符分為大寫英文,小寫英文,和其他字符(除 \ / . ; 以及空格)。對于一般有意義的字符串,ASCII 相對集中,以大寫或小寫為主。比如ACCESS_CHECKIN_PROPERTIES這是權限,Landroid/os/Debug這是類名。而加密后的字符串ASCII分布就會相對隨機比如KS9FRUc6HgxFByUhQ1A=:MN1$gNqcet,這三種字符或者其中兩種的字符數量相差就不會太大。我們可以統計一個字符串中三種字符的數量,如果其中兩種或三種數量相對接近,就認為是隨機字符

def flag(s, long):
????if s[0] > (0.25 * long) and s[2] < (0.35 * long):
????????return True
?
????if s[0] < (0.25 * long) and (s[2] - s[1]) < (0.1 * long):
????????return True
?
class randStr:
????def __init__(self, data):
????????self.data = data
????????self.count = len(data)
????????self.lowCount = 0
????????self.upCount = 0
????????self.othCount = 0
????????self.isRand = False
?
????def analyze(self):
????????for i in self.data:
????????????if i in r'\/ .;':
????????????????continue
????????????elif i >= 'a' and i <= 'z':
????????????????self.lowCount += 1
????????????elif i >= 'A' and i <= 'Z':
????????????????self.upCount += 1
????????????else:
????????????????self.othCount += 1
?
????????for i in d:
????????????if i in self.data:
????????????????return False
?
????????if self.count < 5:
????????????return False
????????elif self.othCount == 0 or self.lowCount == 0 or self.upCount == 0:
????????????return False
????????else:
????????????s = [self.lowCount, self.upCount, self.othCount]
????????????s.sort()
????????????self.isRand = flag(s, self.count)
?
????????????return self.isRand

這是我寫的代碼,以供參考。

四、測試

將模塊代碼補充完整,我們來測試一波 準備兩個dex文件,一個有加密字符串,一個沒有,將加密字符串輸出到屏幕 先測試未加密的dex文件

沒有加密,所以沒有輸出。 再測試一下有加密的dex文件

統計一下,共輸出45個,有30個經辨認為隨機字符串。效果還湊活 如果增加一下字典,再改進算法應該可以更高。

五、總結

思路:閱讀源碼查找字符串池保存位置 ——> 設計輸出字符串池 ——> 判斷是否加密

順便問一句,i春秋有像看雪那樣的招聘版嗎,給找工作的人一點幫助咯。

大四狗秋招找工作好憂桑啊啊啊。。。

本文由i春秋學院提供:http://bbs.ichunqiu.com/thread-11580-1-1.html?from=paper


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/40/