<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/13239

    drozer是MWR Labs開發的一款Android安全測試框架。是目前最好的Android安全測試工具之一。drozer提供了命令行交互式界面,使用drozer進行安全測試,用戶在自己的console端輸入命令,drozer會將命令發送到Android設備上的drozer agent代理程序執行。drozer采用了模塊化的設計,用戶可以定制開發需要的測試模塊。編寫drozer模塊主要涉及python模塊及dex模塊。python模塊在drozer console端運行,類似于metasploit中的插件,可以擴展drozer console的測試功能。dex模塊是java編寫的android代碼,類似于android的dex插件,在android手機上運行,用于擴展drozer agent的功能。

    0x00 簡單的drozer模塊demo代碼


    首先看看drozer wiki給出的demo,該模塊的功能就是在android設備上反射調用java.util.Random類生成一個隨機數并返回:

    #!python
    from drozer.modules import Module
    
    class GetInteger(Module):
    
        name = ""
        description = ""
        examples = ""
        author = "Joe Bloggs (@jbloggs)"
        date = "2012-12-21"
        license = "BSD (3-clause)"
        path = ["exp", "test"]
    
        def execute(self, arguments):
            random = self.new("java.util.Random")
            integer = random.nextInt()
    
            self.stdout.write("int: %d\n" % integer)
    

    GetInteger類就是一個簡單的drozer模塊,它繼承自drozer提供的模塊基類Module。每個繼承了Module的類都對應著一個drozer模塊,模塊具體實現的功能則是在類中重寫excute函數,實現新的功能。在drozer console中執行以下命令就可以運行該模塊了:

    #!bash
    dz> run exp.test.getinteger
    

    運行效果如下:

    1

    drozer 通過Module類的metadata來配置和管理每個模塊,因此模塊編寫時需要包含以下 metadata信息:

    name          模塊的名稱
    description   模塊的功能描述 
    examples      模塊的使用示例
    author        作者
    date          日期
    license       許可
    path          描述模塊命令空間
    

    這些信息中比較重要的就是path變量,它描述了模塊在drozer namespace中的路徑,結合對應的classname可以唯一確定drozer中的模塊。例如demo中的path = ["exp", "test"],類名為GetInteger,那么在drozer console中該模塊就以exp.test.getinteger唯一確定。需要注意的是,盡管類的名字有大小寫之分,但運行該模塊的時候,drozer console中的名字都為小寫。

    0x01 drozer模塊倉庫的創建及模塊安裝


    drozer模塊安裝有兩種方法,一種是直接在repository中按照python包管理的方法新建目錄結構,將python文件放入相應目錄中,另一種是在drozer console中通過module install命令直接安裝模塊。

    這兩種方法都必須先在本地創建一個drozer 的repository目錄,可以直接在drozer console中通過命令創建:

    #!bash
     dz> module repository create [/path/to/repository]
    

    也可以在~/.drozer_config文件中指定本地repository目錄

    #!bash
    [repositories]  
    /path/to/repository  =  /path/to/repository
    

    創建好本地repository后就可以安裝自己的模塊了。兩種安裝方法:

    1) 按照python包管理的方式,在本地repository目錄下創建目錄exp,新建__int__.py空白文件,然后將Python模塊源碼放入exp目錄即可。例如將test.py放入exp目錄下,test.py的內容如下:

    #!python
    from drozer.modules import Module
    
    class GetInteger(Module):
    
        name = ""
        description = ""
        examples = ""
        author = "Joe Bloggs (@jbloggs)"
        date = "2012-12-21"
        license = "BSD (3-clause)"
        path = ["exp", "test"]
    
        def execute(self, arguments):
            random = self.new("java.util.Random")
            integer = random.nextInt()
    
            self.stdout.write("int: %d\n" % integer)
    

    安裝好模塊之后即可在drozer console端通過命令run exp.test.getinteger運行該模塊了。

    2) 通過drozer console中的命令module install 安裝。首先將編輯好的python模塊源文件命名為 exp.test2,文件的內容同上。在drozer console中執行

    #!bash
    dz> module install  [/path/to/exp.test2]      
    

    執行成功后則可以在本地repository目錄下exp目錄中看到生成了test2.py文件,內容和原來的exp.test2文件一致。安裝成功后及可執行該模塊了。module install除了可以安裝本地倉庫的模塊外,還可以遠程安裝gitbub上的模塊,地址為

    https://raw.github.com/mwrlabs/drozer-modules/repository/

    例如運行

    #!bash
    dz>module install jubax.javascript     
    

    將遠程下載并安裝scanner.misc.checkjavascriptbridge模塊,安裝完成后執行

    #!bash
    dz> run scanner.misc.checkjavascriptbridge
    

    就可以運行該模塊,該模塊的功能是檢查webview中addJavascriptInterface的使用是否存在安全隱患。

    0x02 利用drozer提供的API擴展功能


    drozer封裝了android中大部分API功能,使得能夠在python中方便的使用這些API擴展功能,發揮drozer及python的強大威力。

    1)利用反射直接與Dalvik虛擬機交互,其實就是Python直接在寫android代碼,非常簡單方便。drozer主要是利用了drozer agent代理實現相關功能,實例化某個類的代碼如下:

    #!python
    my_object = self.new("some.package.MyClass")
    

    例如drozer.android模塊中封裝了Intent類,用戶可以通過如下方式構造需要的Intent:

    #!python
    someintent = android.Intent(action=act, category=cat, data_uri=data, component=comp, extras=extr, flags=flgs)
    

    然后通過intent打開某個activity:

    #!python 
    self.getContext().startActivity(someintent)
    

    2) drozer針對比較常用的功能還二次封裝了很多python的mixins工具類,提供了更簡單易用的API,這些mixins都在drozer.modules.common包中:

    例如FileSystem類提供了訪問android手機文件系統的接口,可以方便地讀寫、創建及刪除andoid手機上的目錄和文件。ZipFile類提供了解壓zip文件的功能。 為了使用這些mixin類提供的功能,在模塊中可以直接繼承這些類就可以了:

    #!python
    from drozer.modules import common, Module
    
    class MyModule(Module, common.FileSystem, common.ZipFile):
               ……
               ……
           self.deleteFile(“somepath”)
               ……
               ……
           dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
    

    其中,self.deleteFile來自FileSystem類,self.extractFromZip來自ZipFile類。

    0x03 實現find port及find IP模塊


    1) app開放端口查找模塊

    Android app通常會監聽某些端口進行本地IPC或者遠程網絡通信,但是這些暴露的端口卻代表了潛在的本地或遠程攻擊面,具體可以參考大牛的文章:

    淺談Android開放網絡端口的安全風險

    文章中提供了查找開放端口及對應app的python腳本,我們將其重寫為drozer模塊,方便測試時使用:

    #!python
    from drozer.modules import Module,common
    import re
    
    class findport(Module,common.Shell):
    
        name = ""
        description = "find open port in android"
        examples = "run exp.work.findport"
        author = "[email protected]"
        date = "2015-12-02"
        license = "BSD (3-clause)"
        path = ["exp","work"]
    
        def toHexPort(self,port):
            hexport = str(hex(int(port)))
            return hexport.strip('0x').upper()     
    
        def finduid(self,protocol, entry):
            if (protocol=='tcp' or protocol=='tcp6'):
                uid = entry.split()[-10]
    
            else: # udp or udp6
                uid = entry.split()[-6]
    
            try: 
                uid = int(uid)
            except:
                return -1   
            if (uid > 10000): # just for non-system app
                return 'u0_a'+str(uid-10000) 
            else:
                return -1
    
        def execute(self, arguments):        
    
            proc_net = "/proc/net/"      
            ret = self.shellExec("netstat -anp | grep -Ei 'listen|udp*'")
            list_line = ret.split('\n')
            apps = []
            strip_listline = []
            #pattern = re.compile("^Proto") # omit the first line
    
            for line in list_line:              
                    if (line != ''):               
                       socket_entry = line.split()
                       protocol = socket_entry[0]  
                       port = socket_entry[3].split(':')[-1]
                       grep_appid = 'grep  '+ self.toHexPort(port) + ' ' + proc_net + protocol                    
    
                       net_entry = self.shellExec(grep_appid)                         
                       uid = self.finduid(protocol, net_entry)
    
                       if (uid == -1): 
                           continue
    
                       applist = self.shellExec('ps | grep ' + uid).split()    
                       app = applist[8]
                       apps.append(app)
                       strip_listline.append(line)
    
            itapp= iter(apps)
            itline=iter(strip_listline)
    
            self.stdout.write("Proto  Recv-Q Send-Q  Local Address        Foreign Address        State            APP\r\n")
            try:
                while True:
                    self.stdout.write( itline.next() + ' '*10 + itapp.next() + '\n')
    
            except StopIteration:
                pass
    
            self.stdout.write('\n')
    

    該模塊的主要功能都是在findport類中的execute函數中實現,查找開放端口及app的方法和原來文章中的一樣,這里主要用到了drozer提供的common.Shell類,用于在android設備上執行shell命令:

    #!python 
    ret = self.shellExec("netstat -anp | grep -Ei 'listen|udp*'") 
    

    在drozer console中直接運行如下命令即可:

    #!bash 
    dz> run exp.work.findport
    

    運行效果如下:

    image

    2)app中IP地址掃描模塊

    drozer的scanner.misc.weburls提供了掃描app中http及https URL地址的功能,仿照該模塊的功能,我們實現了app中IP地址的掃描模塊,這些收集到的IP地址可以在web滲透測試中使用:

    #!python
    import re
    from pydiesel.reflection import ReflectionException
    from drozer.modules import common, Module 
    
    class findips(Module, common.FileSystem, common.PackageManager, common.Provider, common.Strings, common.ZipFile):
    
        name = "Find IPs specified in packages."
        description = """
        Find IPs in apk files
        """
        examples = ""
        author = "[email protected]"
        date = "2015-12-9"
        license = ""
        path = ["exp", "server"]
        permissions = ["com.mwr.dz.permissions.GET_CONTEXT"]
    
        def add_arguments(self, parser):
            parser.add_argument("-a", "--package", help="specify a package to search")
    
        def execute(self, arguments):
            self.ip_matcher = re.compile(r"((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))")
            if arguments.package != None:
                self.check_package(arguments.package, arguments)
            else:
                for package in self.packageManager().getPackages(common.PackageManager.GET_PERMISSIONS):
                    try:
                        self.check_package(package.packageName, arguments)
                    except Exception, e:
                        print str(e)
    
        def check_package(self, package, arguments):
            self.deleteFile("/".join([self.cacheDir(), "classes.dex"]))
            ips = []
    
            for path in self.packageManager().getSourcePaths(package):
                strings = []
                if ".apk" in path:
                    dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
                    if dex_file != None:
                        strings = self.getStrings(dex_file.getAbsolutePath())
    
                        dex_file.delete()
                        strings += self.getStrings(path.replace(".apk", ".odex")) 
                elif (".odex" in path):
                    strings = self.getStrings(path)
                else:
                    continue
    
                for s in strings:
                    m = self.ip_matcher.search(s)
                    if m is not None:
                        ips.append(s)
    
                if len(ips) > 0:
                    self.stdout.write("%s\n" % str(package))
    
                for ip in ips:
                        self.stdout.write("  %s\n" % ip)
    
                if len(ips) > 0 :
                    self.stdout.write("\n")
    

    add_arguments函數是drozer提供的接口,用于添加命令行參數,這里我們添加了--package參數,用于指定app名稱,如果沒有指定--package參數,那么默認會查找所有app中的IP地址,比較耗時。check_package函數主要實現指定app掃描IP地址的功能,該函數首先從app相關目錄中查找apk文件、odex文件,如果是apk文件則從apk文件中解壓出classes.dex文件:

    #!python
    for path in self.packageManager().getSourcePaths(package):
        strings = []
        if ".apk" in path:
            dex_file = self.extractFromZip("classes.dex", path, self.cacheDir())
    

    然后從得到的dex、odex文件中獲取到所有的strings:

    #!python
    strings = self.getStrings(path)
    

    這里的getStrings是drozer提供的API,實現了類似linux下strings命令的功能。

    找到app中的所有strings后再用re匹配得到相應的IP地址:

    #!python
    for s in strings:
        m = self.ip_matcher.search(s)
        if m is not None:
            ips.append(s)
    

    ip_matcher的正則表達式為:

    #!python
    self.ip_matcher = re.compile(r"((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))")
    

    最后,在drozer console中通過如下命令運行該模塊:

    #!bash
    dz> run exp.server.findips -a com.dianping.v1
    

    運行效果如下所示:

    image

    0x04 編寫dex插件


    除了利用drozer以python代碼形式提供的API,用戶還可以用java代碼編寫dex插件。 例如下面的java代碼就可以編譯為drozer的dex插件:

    #!python
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    import android.util.Base64;
    import android.util.Log;
    import android.widget.Toast;
    import android.net.Uri;
    import android.content.ContentResolver;
    import android.database.Cursor;
    import android.provider.ContactsContract;
    import android.content.Context;
    
    
    public class dextest {
    
      private static final int BUFFER_SIZE = 4096;
    
    
        public static String test(Context c, String number) {
    
                String name = null;
                Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/" + number);
                ContentResolver resolver = c.getContentResolver();
                Cursor cursor = resolver.query(uri, new String[]{android.provider.ContactsContract.Data.DISPLAY_NAME}, null, null, null);
                if (cursor.moveToFirst()) {
                    name = cursor.getString(0);
                    Log.d("drozer", name);
                }
                cursor.close();
    
    
             Log.d("drozer","this is a drozer dex module!");
             return "hello world! this is a test! " + number + ": " + name;
      }
    
    }
    

    首先我們將該java文件編譯為class文件:

    #!bash
    javac -cp lib/android.jar dextest.java
    

    然后用android sdk提供的dx工具將class文件轉換為dex文件:

    #!bash     
    dx  --dex --output=dextest.apk dextest*.class
    

    最后將生成的dextest.apk文件放到drozer的modules/common目錄下,在編寫drozer模塊時可以通過以下方式調用該dex插件:

    #!python
    dextest = self.loadClass("common/dextest.apk", "dextest")
    self.stdout.write("[color red]get string from dex plugin: %s  [/color]\n" % dextest.test(self.getContext(),"181" ) )   
    

    該測試插件根據提供的部分電話號碼去匹配手機通訊錄中的聯系人,并返回匹配到的聯系人姓名,執行效果如下:

    2

    dex插件是由drozer上傳到android手機上加載執行,它的作用還是為drozer模塊提供更方便易用的接口,擴展更多的功能。由于dex插件是Java編寫的原生android代碼,在執行效率上比通過反射調用更高。drozer的modules/common目錄下包含了多個dex插件的源碼,有興趣的同學可以自己查看。

    0x05 drozer模塊的reload及動態加載問題


    編寫drozer module難免會涉及到調試的問題,drozer console提供了debug選項,會在console中打印異常信息,但是比較麻煩的是,修改module源碼后必須要重啟drozer console才能生效。

    查看drozer源碼,發現drozer在debug模式下提供了reload命令,但是測試了下,在mac下并沒有用,還是要重啟console才能生效。仔細研究drozer loader.py的相關源碼:

    #!python
    def all(self, base):
         """
         Loads all modules from the specified module repositories, and returns a  collection of module identifiers.
         """
    
        if(len(self.__modules) == 0):
            self.__load(base)
    
        return sorted(self.__modules.keys())
    
    def get(self, base, key):
        """
        Gets a module implementation, given its identifier.
        """
    
        if(len(self.__modules) == 0):
            self.__load(base)
    
        return self.__modules[key]
    
    def reload(self):
        self.__modules = {}
    

    reload命令將self.__modules置為空,在get中按理說就會重新加載所有的drozer模塊。但是在mac下始終無法實現該功能,其他平臺未做測試。這里就涉及到python模塊的import及reload機制問題,在網上查找到python的reload機制一些解釋:

    reload會重新加載已加載的模塊,但原來已經使用的實例還是會使用舊的模塊, 而新生產的實例會使用新的模塊, reload后還是用原來的內存地址;不能支持from。。import。。格式的模塊進行重新加載。
    http://blog.csdn.net/five3/article/details/7762870

    猜測可能就是這個問題,雖然用python的reload機制可以重新加載模塊,但是以前使用的模塊可能還是在使用中,導致修改的源碼沒有生效。

    為什么不在執行時動態加載模塊呢?這樣可以保證加載的模塊源碼是最新的。

    分析了drozer相關的所有源碼,終于在session.py中找到實例化模塊類的代碼:

    #!python
    def __module(self, key):
        """
        Gets a module instance, by identifier, and initialises it with the
        required session parameters.
        """
    
        module = None
    
        try:
            module = self.modules.get(self.__module_name(key))
        except KeyError:
            pass
    
        if module == None:
            try:
                module = self.modules.get(key)
            except KeyError:
                pass
    
        if module == None:
            raise KeyError(key)
        else:
            return module(self)
    

    該函數的功能就是根據模塊類的key實例化該模塊,從而運行該模塊。因此,我們可以在這里實現動態加載要運行的模塊類,放棄已經加載的模塊:

    #!python
     def __module(self, key):
    
        """
        Gets a module instance, by identifier, and initialises it with the
        required session parameters.
        """
    
        module = None
    
        try:
            module = self.modules.get(self.__module_name(key))
        except KeyError:
            pass
    
        if module == None:
            try:
                module = self.modules.get(key)
            except KeyError:
                pass
    
        if module == None:
            raise KeyError(key)
        else:
    
            #reload module
            mod = reload(sys.modules[module.__module__])
    
            module_class_name = module.__name__
            module_class = getattr(mod,module_class_name)  #get module class object
            return module_class(self)
    

    關鍵的代碼如下:

    #!python
    #reload module   
    mod = reload(sys.modules[module.__module__])
    
    module_class_name = module.__name__
    module_class = getattr(mod,module_class_name)  #get module class object
    return module_class(self)
    

    首先使用python的reload函數重新加載指定的模塊,然后再在重新加載的模塊中查找到drozer模塊關聯的類,最后實例化并返回。只需添加幾行代碼便可實現動態加載模塊類,這樣調試的時候就不用每次重啟drozer console了。這里只是提供了一種簡單的實現動態加載模塊的方法,主要是方便模塊的編寫及測試。

    97av