查看原文
其他

drozer源码浅析(下)

Mason vivo千镜 2022-11-05
喜欢就关注我吧,订阅更多最新消息

上次聊了聊drozer的整体架构和PC端通信模块分析,本次就来聊聊上次留下的N个坑。

Agent的通信模块是一个常用的通信架构,使用ServerSocket和Socket构建与PC间的通信,socket服务由一个server组件服务维护。

本次重点聊聊java反射机制模块和插件化模块


1、 java反射机制模块

在分析源码之前,先简单了解一下java的反射机制,通俗点说,就是在应用程序运行时,以字符串的方式指定任意类名,都能够知道这个类的所有属性和方法,且能调用其属性,其中还可以以字符串的形式指定任意方法名来调用该方法。听起还挺绕口的。


先通过流程图了解下drozer整个反射机制的执行流程



步骤说明如下

1、 在PC端写下一句java代码

2、 根据约定好的数据格式组装成一个message对象

3、 message对象经过protobuf序列化之后通过建立的socket通道传递给手机端Agent

4、 Agent接收后先反序列化还原message对象

5、 Agent从message对象中解析出class_name、method_name、arguments、property等信息

6、 根据操作类型传递对应的方法执行

7、 最后反方向将结果传递回PC端



需要说明一点,这个交互是实时的,PC每一个java代码操作都会进行一次交互,但如果所有都实时操作,是很耗费性能的,所以drozer还设计了一个缓存机制,在反射类之前先查询是否已经操作过了,若有则使用历史对象,无则走一圈反射流程。


那么代码上是如何实现的呢?先来看看两端的代码目录结构,下图分别为PC端和Agent端反射机制的关键代码。


             


其中Reflector.java和reflector.py是核心的反射操作代码,包括resolve、Construct、getField、getMethod、getNativeArgument、invoke、getProperty等。types目录下则是具体的反射类型,反射类型的作用,后面会有详情介绍。

简单分析Reflector.java一些关键操作是如何实现的

01通过类名获取一个类

 

02通过方法名和其参数来调用一个类方法

 

从上面的代码可以看到,Agent端主要是将接收到的class_name,method_name等执行,然后将执行结果返回。我们再来看看PC端的reflector.py,列举两个方法,对所有方法感兴趣可阅读源码。


1)将一个class_name传递给Agent并解析返回的结果,获取class_name对象。


 

2)将类对象、方法名、参数列表传递至Agent,并解析返回的结果,从中提取代码执行的结果


 

讲了那么多,是不是很好奇,PC端的java代码是以什么样的形式来通过python传递给Agent的呢?这个问题解决思路,drozer设计得很巧妙,很好的利用了一些python的特性。我们先看一个例子,然后通过这个例子一步一步分析是如何进行的。


Python代码:

 

Java代码:

上面这两段代码的在drozer里面,执行的结果是一样。Python代码做如下事情:

1、使用Self.new(self, class_or_class_name, *args)方法获取了一个

    android.content.ContentValues对象。

2、调用android.content.ContentValues对象的put方法设置一组key-value。


但实际上,values 是一个ReflectedObject对象,其代码文件见上文截图的reflected_object.py。阅读ReflectedObject类上并没有找到put方法,那么put(key,value)方法哪里来?这里运用到了的python的两个魔法方法:__getattr__、__setattr__。在python里面,当对未定义的属性名称和实例进行点号运算时,就会用属性名作为字符串调用__getattr__方法。


从ReflectedObject.__getattr__()和ReflectedObject._invoker()的代码实现不难看出,这个未定义的方法最后通过partial(self._invoker, attr) 走了一遍反射流程,是从Agent获取回来的。如果是获取一个属性值,流程也是一样的。这里的代码也解释上文说的实时交互的问题,每一次java类操作都会出现N次PC与Agent交互。


 


顺便也瞅瞅属性赋值部分的代码,实现思路是和上面一样,动态的执行反射流程,从Agent获取对应的数据回来。



其余的反射类型ReflectedArray、ReflectedBinary、ReflectedNull、ReflectedPrimitive、ReflectedString 实现方式的思路是和ReflectedObject一样的。所以在python上实际操作的对象是ReflectedArray、ReflectedBinary、ReflectedNull、ReflectedPrimitive、ReflectedString 、ReflectedObject,然后通过操作这些个对象来和Agent交互,最终执行java代码并获取执行结果。

综上我们在drozer框架上写python代码的时候,就可以跟写java代码一样,去操作这些反射回来java类的方法和属性。


关于java的调用,主要是通过drozer封装好的两个关键API进行的。

1)通过名称或使用类引用实例化Java类,粗浅的理解其作用是调用原生类

self.new(self, class_or_class_name, *args)

代码路径:drozer\modules\base.py  

 

2)从本地系统将Java源代码加载到代理上的Dalvik VM的程序方法,补充

self.new()的不足,完善自定义的java类的调用:

ClassLoader.loadClass(self, source, klass, relative_to=None)

代码路径:drozer\modules\common\loader.py


举个例子:

聊完了反射机制,那么就该聊聊这么个使用它去实现各种功能了。


2、 插件化模块


插件化框架,我们应该都多多少少听过一些。一般为了更好的扩展程序的功能,我们会采用插件式结构来设计,在运行时可以根据用户的输入,找到指定插件然后加载执行。如使用Drozer的时候一般是在终端输入一个字符串,最后就能执行到对应的插件并获取到结果。

Python是天然的支持插件式编程,其标准库中提供了importlib模块,其目的是提供import语句(还有import())的底层实现,另外importlib可以让程序在导入过程中创建自己的对象。Drozer就是基于importlib 模块来实现插件化的设计。

Drozer插件化模块的代码主要在drozer\modules\目录下。直接定位到drozer\modules\loader.py 文件,该文件是实现动态加载的核心逻辑。 __import_modules() 是给程序import一个模块的实现方式。 


 

插件化框架除了核心的动态加载部分,还会设计一个基类和一个扩展的编码规则,这个基类一般会实现一些通用的方法和定义通用的变量属性。Drozer的基类是drozer\modules\base.py中的Module类,在modules下我们可以看到drozer已有插件都是继承这个基类实现的。

Drozer还为模块提供了一系列Android程序常见类,例如文件系统访问,与包管理器和一些模块模板,在drozer\modules\common目录下:



其编码规则为继承Module基类,并实现def execute(self, arguments)方法。如:


有了插件的编写规则和反射机制,那我们就可以编写任意的插件去实现我们的各种渗透思路了。


思考

Drozer 现在还是一个终端工具,并没有开放供二次开发的API,但是我们梳理完它的运作流程之后,是不是可以做一些调整,封装一些API使其能集成进我们已有服务里面呢?

END

1、drozer源码浅析(上)

2、AI安全之对抗样本的攻与防(上)

3、软件源码安全攻防之道(下)

4、家贼难防系列之——加密算法安全


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存