Drozer是MWR Labs开发的一款具有pc端(主要python编写)和移动端(agent代理)的开源Android渗透测试框架。可以自定义python脚本,与通过代理agent与Android虚拟机进行交互。本文主要解析移动端agent代码,为各位同学后续写自己的安全工具起到抛砖引玉的个作用。
Drozer的通信框架如下图所示,它有两张通信方式,具体请看 Android AppInjection&&Drozer Use
首先从个github https://github.com/mwrlabs 下载 drozer-agent 、 jdiesel 、 mwr-tls 、 mwr-android 。
1、drozer-agent:移动端界面工程
2、jdiesel:反射和通信协议的核心jar工程
3、mwr-tls:安全通信jar工程
4、mwr-android: 移动端界面jar工程
打开jdiesel->common中protobuf.proto文件,原来是ProtocolBuffers协议,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
优点:体积小,解析速度快。
缺点:二进制的方式存储,抓包后几乎不可读,调试起来可能不方便。
1 required 不可以增加或删除的字段,必须初始化
2 optional 可选字段,可删除,可以不初始化
3 repeated 可重复字段, 对应到java文件里,生成的是List
代码中常常用到Build,生成具体的java类时,例如protobuf.java,同时会存在build方法。意思是对于转化后的数据,具有唯一性,build提供了便利的方法来初始化这些数据。 protobuf 详情
先看看protobuf.proto文件,其中主要的架构是这样。
消息类型除了id和消息类型(MessageType),就是消息体,是可有可无的分为四种:
SystemRequest:包含设备信息,会话信息,连接控制
SystemResponse::主要包括响应的类型(绑定服务,设备列表,会话列表等)状态信息。
ReflectionRequest:java反射请求,主要有resolve(所反射的classname),Construct(对象引用ObjectReference(int32类型),方法对象method,和方法参数Argument),Invoke调用方法(同样包括对象引用ObjectReference(int32类型),方法对象method,和方法参数Argument),以及对Property的get与set,还有Delete(对象引用ObjectReference(int32类型))
ReflectionResponse:主要是反射响应后的状态和参数,还有一些错误信息。
在jdiesel代码中com.mwr.jdiesel.api.transport包下四个文件
SecureTransport是加了ssl加密传输的,从Transport文件中可以看出,receive和send都是有Frame封装的数据格式,如下
public abstract class Transport { public abstract void close(); protected abstract InputStream getInputStream() throws IOException; protected abstract OutputStream getOutputStream() throws IOException; public abstract boolean isLive(); public Frame receive() throws APIVersionException, IOException,TransportDisconnectedException { if(this.getInputStream() != null) return Frame.readFrom(this.getInputStream()); else throw newTransportDisconnectedException(); } public void send(Frame frame) throws IOException { this.getOutputStream().write(frame.toByteArray()); } }
外部调用时只需new一个SocketTransport(socket)即可,再看看socket在哪儿传送过来的
在ServerSocketFactory类中有创建serversocket的方法
public class ServerSocketFactory { public ServerSocket createSocket(Server server) throwsCertificateException, IOException, KeyManagementException, KeyStoreException,UnrecoverableKeyException { if(server.isSSL()) return this.createSSLSocket(server); else return new ServerSocket(server.getPort()); } . . .
在Link包中,server.java代码中重载run方法中有accept()接受socket请求。
public class Server extends Link { @Override public void run() { this.running = true; this.log(LogMessage.INFO, "Starting Server..."); while(this.running) { try { if(this.connection == null) { this.parameters.setStatus(com.mwr.jdiesel.api.connectors.Server.Status.CONNECTING); this.log(LogMessage.INFO, "Attempting tobind to port " + ((com.mwr.jdiesel.api.connectors.Server)this.parameters).getPort() + "..."); this.server_socket = new ServerSocketFactory().createSocket((com.mwr.jdiesel.api.connectors.Server)this.parameters); this.log(LogMessage.INFO, "Waiting forconnections..."); Socket socket = this.server_socket.accept();
其中关于连接link的类关系图如下图所示。
在jdiesel代码中server.java的run方法中有循环接受客户端的Socketsocket=this . server_socket accept(); .
在agent代码中找到一个Service(此服务为android四大组件的服务),在消息循环中有startServer()方法被调用,就有server线程的开启。该server正是import com.mwr.jdiesel.api.links.Server的Server
public class ServerService extends ConnectorService { public void startServer() { if(this.server == null) { (new ServerSettings()).load(this.server_parameters); this.server_parameters.enabled = true; this.server = new Server(this.server_parameters, Agent.getInstance().getDeviceInfo()); this.server.setLogger(this.server_parameters.getLogger()); this.server_parameters.getLogger().addOnLogMessageListener(this); this.server.start(); @Override public void handleMessage(Message msg) { switch(msg.what) { caseMSG_START_SERVER: try { this.startServer(); Message message = Message.obtain(null,MSG_GET_SERVER_STATUS); message.setData(this.getStatus()); msg.replyTo.send(message);
上面所讲的是console连接手机server的通信方式,是比较常用的通信方式。
还有一种通信方式是PC端开启一个Server,手机端Client连接PC服务,以上两张通信方式都离不开adb forword tcp:[localport] tcp:[remoteport]
在jdiesel代码中Client.java的run方法中有连接PC服务端的代码
Socket socket=new EndpointSocketFactory().createSocket(endpoint);
在jdiesel代码中最关键的反射代码为Reflector.java
这其中有Construct、Field、Method、NativeArgument、invoke、Property等
public static Object construct(Class<?> klass, ReflectedType[] arguments) throws IllegalAccessException, InstantiationException,InvocationTargetException, NoSuchMethodException { Constructor<?> constructor = null; if(arguments.length == 0) constructor = klass.getConstructor(); else constructor =getConstructor(klass, arguments); if(constructor != null) return constructor.newInstance(getNativeArguments(arguments)); else throw newNoSuchMethodException(); } private static Constructor<?> getConstructor(Class<?> object, ReflectedType[] arguments) { for(Constructor<?> constructor : object.getConstructors()) { if(hasCompatibleSignatures(constructor.getParameterTypes(),getNativeArguments(arguments))) return constructor; } return null; } 对引用实例化constructor.newInstance(getNativeArguments(arguments)); private static Field getField(Object object, String property) throws NoSuchFieldException { if(object instanceof Class) return ((Class<?>)object).getField(property); else return object.getClass().getField(property); }
getField用于返回一个指定名称的属性,但是这个属性必须是公有的,这个属性可以在父类中定义。如果是私有属性或者是保护属性,那么都会抛出异常提示找不到这个属性。getFields则是返回类型中的所有公有属性,所有的私有属性和保护属性都找不到。
private static Method lookupMethod(Class<?> klass, String method_name, ReflectedType[] arguments) throws NoSuchMethodException { for(Method method: klass.getMethods()) { if(method_name.equals(method.getName())) { if(hasCompatibleSignatures(method.getParameterTypes(),getNativeArguments(arguments))) return method; } } throw new NoSuchMethodException(method_name + " for " + klass.toString()); } private static Method getMethod(Object object, String method_name, ReflectedType[] arguments) throws NoSuchMethodException { if(object instanceof Class) { Method m = lookupMethod((Class<?>)object, method_name, arguments); if(!Modifier.isStatic(m.getModifiers())) return null; else return m; } else return lookupMethod(object.getClass(), method_name, arguments); }
获取方法getMethod在类方法中获取特定方法名的方法。
getDeclaredField获得在这个类型的声明中定义的指定名称的属性,这个属性必须是在这个类型的声明中定义,但可以使私有和保护的。
private static boolean isWrapperTypeOf(Class<?> klass, Class<?> primitive) { try { return !klass.isPrimitive() && klass.getDeclaredField("TYPE").get(null).equals(primitive); } catch(IllegalAccessException e) { return false; } catch(NoSuchFieldException e) { return false; }
public static Object invoke(Object object, String method_name, ReflectedType[] arguments) throws IllegalAccessException, IllegalArgumentException,InvocationTargetException, NoSuchMethodException { if(arguments.length == 0) return getMethod(object, method_name, arguments).invoke(object, (Object[])null); else return getMethod(object, method_name, arguments).invoke(object,getNativeArguments(arguments)); }
public static Class<?> resolve(String className) { try { return Class.forName(className); } catch(ClassNotFoundException e) { return null; } }