转载

drozer模块的编写及模块动态加载问题研究

drozer是MWR Labs开发的一款Android安全测试框架。是目前最好的Android安全测试工具之一。drozer是一种交互式的安全测试工具,使用drozer进行安全测试,用户在自己的console端输入命令,drozer会将命令发送到Android设备上的drozer agent代理程序执行。drozer采用了模块化的设计,用户可以定制开发需要的测试模块。编写drozer模块主要涉及python模块及dex模块。python模块就是drozer console端使用的,类似于metasploit中的插件,可以扩展drozer的测试功能。dex模块就是java编写的android代码,类似于android的dex插件,用于扩展 drozer agent的功能。

0x00 简单的demo代码

首先看看wiki给出的demo:

#!java 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 通过Module类的metadata来配置和管理每个模块,因此模块编写时需要包含以下 metadata信息:

name          模块的名称 description   模块的功能描述  examples      模块的使用示例 author        作者 date          日期 license       许可 path          描述模块命令空间 

这些信息中比较重要的就是path变量,它描述了模块在drozer namespace中的路径,结合对应的classname可以唯一确定drozer中的模块。例如demo中module 的 path = ["exp", "test"] ,类名为GetInteger,那么在drozer console 执行

#!bash dz> run exp.test.getinteger 

就可以运行该模块了。特别注意的是,尽管类的名字有大写,但执行的时候,run 中的模块名字都为小写!该demo模块的功能是反射调用Java中的Random类生成一个随机数,运行效果如下:

drozer模块的编写及模块动态加载问题研究

0x01 module仓库的创建及module安装

drozer module 安装有两种方法,一种是直接在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在app安全测试中的强大威力。

1)利用反射直接与Dalvik虚拟机交互,其实就是Python直接在写android代码,非常简单方便。drozer主要是利用了drozer agent代理实现相关功能,例如实例化某个类:

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

drozer.android模块中还封装了Intent类,用户可以通过如下方式构造需要的Intent:

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

通过intent打开某个activity:

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

2) drozer针对比较常用的功能还二次封装了很多python的mixins工具类,提供了更简单易用的API,这些mixins都在drozer.modules.common包中:

  • Assets
  • BusyBox
  • ClassLoader
  • FileSystem
  • Filters
  • PackageManager
  • Provider
  • ServiceBinding
  • Shell
  • Strings
  • SuperUser
  • TableFormatter
  • ZipFile

例如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 编写dex插件

除了利用drozer以python代码形式提供的API,用户还可以用java代码编写dex插件。

例如下面的java代码:

#!java 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;   }  } 

首先我们将该文件编译为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目录下,在python写的module模块中可以通过以下方式调用该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" ) )    

该测试插件根据提供的部分电话号码去匹配手机通讯录中的联系人,并返回匹配到的联系人姓名。执行效果:

drozer模块的编写及模块动态加载问题研究

0x04 drozer module的reload及动态加载问题

编写drozer module难免会涉及到调试的问题,drozer console提供了debug选项,会在console中打印异常信息,但是坑爹的是,修改module源码后必须要重启drozer console才能生效,这不科学。

查看drozer源码,发现drozer在debug模式下提供了reload命令,so easy。但是测试了下,并没有卵用,还是要重启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         reload(sys.modules[module.__module__])         mod =importlib.import_module(module.__module__)           module_class_name = module.__name__         module_class = getattr(mod,module_class_name)  #get module class object         return module_class(self) 

只需添加几行代码便可实现动态加载模块类,这样就不用每次重启drozer console了。这里只是提供了一种简单的实现动态加载模块的方法,主要是方便模块的编写及测试。

原文  http://drops.wooyun.org/tips/13239
正文到此结束
Loading...