<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址: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了。這里只是提供了一種簡單的實現動態加載模塊的方法,主要是方便模塊的編寫及測試。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线