drozer浅析三:命令实现与交互
阅读原文时间:2023年07月08日阅读:1

前面走马观花的看了几个模块的源码,看到是用python(会加载自定义的java类)写的。产生2个问题:在命令行中输入command,drozer是如何去执行的;python是如何与java交互的。

drozer console connect:

当我们在命令行输入以上字符串时,在drozer中是如此来执行的:

1  Console().run(sys.argv[2::])

——>2  def run(self, argv=None)

——>3  self.__invokeCommand(arguments)

——>4 getattr(self, "do_" + command)(arguments)

——>5 def do_connect(self, arguments)

——> 6.1 response = server.startSession(device, password)

——>6.1.1 self.sendAndReceive(SystemRequestFactory.startSession(device_id))

——>6.1.1.1 startSession(device_id)

6.1.1.2self.sendAndReceive(builder)

——>6.1.1.2.1 message_id = self.send(message)

6.1.1.2.2 elif response.id == message_id:  return
response

6.2 if response.type == Message.SYSTEM_RESPONSE and response.system_response.status==Message.SystemResponse.SUCCESS: session_id
=response.system_response.session_id

——>6.2.1 session = Session(server, session_id, arguments)

6.2.2  session.cmdloop()

当输入drozer console connect时,首先会把提取argv[2::]即connect。在步骤2里的run函数里经过转换去执行do_connect方法即步骤5。上面这几步都是次要的,接下来才是主题。步骤6.1建立会话过程;若会话建立成功即步骤6.2,进入6.2.2等待命令的输入,至此drozer console connect 命令全部执行完毕。需要理解什么是会话?

看上面的执行步骤我们可以看到,会话是在步骤6.1.1过程中建立的。在步骤6.1.1.1中封装开始会话请求信息及加入设备ID,budiler。

builder = SystemRequestFactory(Message.SystemRequest.START_SESSION) #注意这里请求的参数是start_session

    builder.addDeviceId(device\_id)

    return builder

至此,步骤6.1.1变成变成步骤6.1.1.2。sendAndReceive中的操作:发送msg和接收到对应的msg才返回。详细分析发送和接收过程:首先给msg自动分配唯一标识符message_id,并通过socket将msg发送给agent;接收msg,通过message_id来识别是否为之前发送出去msg的返回信息。ok,会话建立过程就是给agent发送Message.SystemRequest.START_SESSION。

继续步骤6.2.1,初始化session,在此期间会输出drozer的由字母组成的头像(在最后)。接着进入步骤6.2.2cmdloop(),在preloop()中输出drozer console的版本号,接着循环等待用户输入命令。

run app.service.send:

上面的cmdloop在等待用户的输入,输入以上的字符串。读取字符串到argv中argv[0] = run,argv[1] = app.service.send。根据list[0]会先进入Session类中的do_run方法(Session类中有很多do_xx方法)。然后判断argv[1]是不是模块命令(module.run(argv[1:]))进入drozer.module.base.py中Module类(每个命令都是继承自Module类:class
Send(Module, common.ServiceBinding))中的run方法:

parser = self.__prepare_parser()

    parser.description = self.usage.formatted\_description()  
    parser.usage = self.usage.formatted\_usage(parser)

    if "-h" in args or "--help" in args:  
        return parser.print\_help()  
    else:  
        arguments = parser.parse\_args(args)

        if hasattr(self, 'execute'):  
            <span style="color:#ff0000;">result = self.execute</span></span>

至此,self.execute(arguments)就去执行类app.service.send下的execute方法了。之前分析过app.service.send的执行过程中会加载自定义的java类来发送和接收service的msg,注意send类的定义class Send(Module, common.ServiceBinding):

def obtain_binder(self):
if self.binder == None:
ServiceBinder = self.context.loadClass("common/ServiceBinder.apk", "ServiceBinder")

            self.binder = self.context.new(ServiceBinder)

        return self.binder</span>

首先会调用loader.py文件中的ClassLoader里的loadclass方法(class ServiceBinding(loader.ClassLoader))。

def loadClass(self, source, klass, relative_to=None):
"""
Load a Class from a local apk file (source) on the running Dalvik VM.
"""

    if relative\_to == None:  
        relative\_to = os.path.join(os.path.dirname(\_\_file\_\_), "..")  
    elif relative\_to.find(".py") >= 0 or relative\_to.find(".pyc") >= 0:  
        relative\_to = os.path.dirname(relative\_to)

    if not Module.cached\_klass(".".join(\[source, klass\])):  
        loader = utils.ClassLoader(source, self.\_\_get\_cache\_path(), self.\_\_get\_constructor(), self.klass('java.lang.ClassLoader').getSystemClassLoader(), relative\_to=relative\_to)  
        loader.android\_path = lambda: Configuration.library("android.jar")  
        loader.dx\_path = lambda: Configuration.executable("dx.bat") if platform.system() == "Windows" else Configuration.executable("dx")  
        loader.javac\_path = lambda: Configuration.executable("javac")

        Module.cache\_klass(".".join(\[source, klass\]), loader.loadClass(klass))

    return Module.get\_cached\_klass(".".join(\[source, klass\]))

loadClass()先利用反射得到ClassLoader来初始化loader,然后利用loader将klass(自定义的java类)装载进dvm。先看反射步骤:.klass('java.lang.ClassLoader')

Module类中的klass方法:

def klass(self, class_name):
"""
Resolves a class name, and returns an object reference for the class.
"""

    if not Module.cached\_klass(class\_name):  
        Module.cache\_klass(class\_name, self.reflector.resolve(class\_name))

    return Module.get\_cached\_klass(class\_name)

通过Module.cached_klass判断当前类名是否已在Module__klass中,若不存在则调用self.reflector.resolve来加载并在cache_klass中记录(一个cached,一个是cache)。reflector.resolve: <—— reflector.py

def resolve(self, class\_name):  
    """  
    Resolves a Java class, given its fully qualified name, and returns a  
    ReflectedObject that can be used to instantiate it with #construct.  
    """

    response = self.sendAndReceive(ReflectionRequestFactory.resolve(class\_name))

    if response is None:  
        raise ReflectionException("expected a response to RESOLVE")  
    elif response.reflection\_response.status == Message.ReflectionResponse.SUCCESS:  
        return ReflectedType.fromArgument(response.reflection\_response.result, reflector=self)  
    else:  
        raise ReflectionException(response.reflection\_response.errormessage)

分两步:resolve(class_name)组合msg;sendAndReceive发送msg要求Reflection calss_name。到此为止得到java.lang.ClassLoader,然后再loader.loadClass(klass)

def loadClass(self, klass):  
    return self.getClassLoader().loadClass(klass);

def getClassLoader(self):  
    """  
    Gets a DexClassLoader on the agent, given compiled source or an apk  
    file from the local system.  
    """

    self.source = self.\_\_get\_source(self.source\_or\_relative\_path, relative\_to=self.relative\_to)

    if self.source != None:  
        file\_path = "/".join(\[self.cache\_path, self.\_\_get\_cached\_apk\_name()\])

        file\_io = self.construct('java.io.File', file\_path)

        if not self.\_\_verify\_file(file\_io, self.source):  
            source\_data = \[ReflectedPrimitive("byte", (ord(i) if ord(i) < 128 else ord(i) - 0x100), reflector=None) for i in self.source\]

            file\_stream = self.construct("java.io.FileOutputStream", file\_path)  
            file\_stream.write(source\_data, 0, len(source\_data))  
            file\_stream.close()  
        return self.construct('dalvik.system.DexClassLoader', file\_path, self.cache\_path, None, self.system\_class\_loader)  
    else:  
        raise RuntimeError("drozer could not find or compile a required extension library.\\n")

ok,仔细分析发现上面对于涉及到java的操作都是利用sendAndReceive来实现的(关于message如何定义查看https://github.com/mwrlabs/mercury-common/blob/master/protobuf.proto)。

startSession:sendAndReceive()——>Message.SystemRequest.START_SESSION

reflector.resolve:sendAndReceive()——>Message.ReflectionRequest.RESOLVE

construct :        sendAndReceive()——> Message.REFLECTION_REQUEST

…..

sendAndReceive是通过socket把msg发送给agent的,实际还是由agent来执行。

参考资料:Android开源审计框架drozer--源码浅析

版权声明:本文为博主原创文章,未经博主允许不得转载。