adb forward的细节(4):使用adb forward打造一个PC端与手机端交互的工具

adb forward的细节(4):使用adb forward打造一个PC端与手机端交互的工具

本文根据《adb forward的细节(1):概述》一文中的原理,做了一个PC端应用与手机端应用交互的工具。

本文目的是,学习和理解 adb forward 的原理,重点没有放在所做的工具上。所以这个PC端应用与手机端应用交互的工具比较简陋,通过命令行实现的。当然你可以根据自己的想法作出更好的工具。

★ 1. 先做一些准备工作

♦ 1.1 下载代码
  • 下载PC端ClientApp的代码,此代码在Eclipse中编译,在PC端运行:
git clone https://github.com/galian123/ClientApp
  • 下载Samples工程的代码,此代码在Android Studio中编译,运行在手机中
git clone https://github.com/galian123/Samples
♦ 1.2 安装Eclipse (装过的请忽略)

Eclipse下载地址
Eclipse zip包格式下载地址
选『Eclipse IDE for Java Developers 』

♦ 1.3 安装fatjar (装过的请忽略)

FatJar的下载地址:https://github.com/galian123/ClientApp/raw/master/Fatjar_0.0.32.zip

将此 Fatjar_0.0.32.zip 解压缩,将 .jar 拷贝到 Eclipse 的 plugins 目录中。
然后,重启Eclipse。

注:fatjar目前最新只有0.0.31版本。其实,Fatjar_0.0.32.zip 的内容与 fatjar 0.0.31版本 是一样的,只是目录结构调整了,目的是让fatjar可以加载到eclipse中。

安装后,如图:
这里写图片描述

Build Fat Jar时, 别忘了写Main Class, 如图中的 com.galian.ClientApp

这里写图片描述

♦ 1.4 安装Android Studio

Android Studio下载地址:https://developer.android.com/studio/
目前最新3.1.2版本。

★ 2. PC端 ClientApp 代码

在 Eclipse 中 Import 这个工程,然后 Build project,并通过『+Build Fat Jar』生成 ClientApp_fat.jar

注意:为了节省篇幅,省略了try-catch中的catch块的代码,在实际编码中catch异常并作出适当处理是必须的。

♦ 2.1 创建 client socket,连接127.0.0.1:8888的server
    private static void startClient() {
        try {
            // 连接到 地址127.0.0.1,端口8888的server
            socket = new Socket("127.0.0.1", 8888);
            mRunning = true;// 为了exit设置的变量

            // InThread 接收手机端传来的数据
            new Thread(new InThread(socket)).start();

            // OutThread 用于从PC端输入并传给手机端
            new Thread(new OutThread(socket)).start();
        } catch ...
    }
♦ 2.2 InThread 接收手机端传来的数据
static class InThread implements Runnable {
    Socket socket = null;

    public InThread(Socket s) {
        socket = s;
    }

    @Override
    public void run() {
        while (mRunning) {
            if (socket.isClosed()) {
                mRunning = false;
                break;
            }
            InputStream in = null;
            try {
                in = socket.getInputStream();
            } catch ...

            InputStreamReader reader = new InputStreamReader(in);
            try {
                char[] buf = new char[1024000];
                int cnt = reader.read(buf);
                if (cnt > 0) {
                    String msg = new String(buf, 0, cnt);
                    System.out.println(msg);
                }
            } catch ...
        }
    }
}
♦ 2.3 OutThread 从PC端输入并传给手机端
static class OutThread implements Runnable {
    Socket socket = null;

    public OutThread(Socket s) {
        socket = s;
    }

    @Override
    public void run() {
        System.out.println("Please type in something...");
        System.out.println("Type in 'exit' to exit.");
        System.out.println("Type in 'getattacksurface package_name' to get attack surface.");
        System.out.println();
        while (mRunning) {
            if (socket.isClosed()) {
                mRunning = false;
                break;
            }

            OutputStream out = null;
            try {
                out = socket.getOutputStream();
            } catch ...

            InputStreamReader reader = new InputStreamReader(System.in);
            String msg = "";
            try {
                char[] buf = new char[10240];
                int cnt = reader.read(buf);
                if (cnt > 0) {
                    msg = new String(buf, 0, cnt);
                }
            } catch ...

            msg = msg.trim();// delete trailing linefeed
            if (msg.equalsIgnoreCase("exit")) {
                mRunning = false;
                try {
                    socket.close();
                } catch ...
                break;
            }
            // input "getattacksurface package_name", get the attack surface of that package
            try {
                out.write(msg.getBytes());
                out.flush();
            } catch ...
        }
    }
}

★ 3. 手机端 ServerActivity 代码

注意:为了节省篇幅,省略了try-catch中的catch块的代码,在实际编码中catch异常并作出适当处理是必须的。

♦ 3.1 声明网络权限

一定要声明android.permission.INTERNET权限,否则会报异常java.net.SocketException: Permission denied

在AndroidManifest.xml中声明权限:

<uses-permission android:name="android.permission.INTERNET" />
♦ 3.2 启动Server

启动Server之后,会显示『Server is started.』的Toast。

    void startServer() {
        new ServerThread().start();
    }
♦ 3.3 ServerThread
    private class ServerThread extends Thread {
        @Override
        public void run() {
            if (!mRunning) {
                try {
                    mServerSocket = new ServerSocket(PORT);
                    mRunning = true;
                } catch ...

                while (mRunning) {
                    Socket socket;
                    try {
                        socket = mServerSocket.accept();
                        new ProcessClientRequestThread(socket).start();
                    } catch ...
                }
            }
        }
    }
♦ 3.4 ProcessClientRequestThread

为每一个连接的client,启动一个线程来处理请求。

    private class ProcessClientRequestThread extends Thread {
        private Socket mSocket = null;

        ProcessClientRequestThread(Socket socket) {
            mSocket = socket;
        }

        @Override
        public void run() {
            while (mRunning) {
                if (!mSocket.isConnected()) {
                    break;
                }
                InputStream in;
                OutputStream out;
                try {
                    in = mSocket.getInputStream();
                    out = mSocket.getOutputStream();
                } catch ...

                // 处理client传来的数据
                InputStreamReader reader = new InputStreamReader(in);
                try {
                    char[] buf = new char[10240];
                    int cnt = reader.read(buf);
                    if (cnt > 0) {
                        String msg = new String(buf, 0, cnt);
                        Log.d(TAG, "Receive: " + msg);

                        String GET_ATTACK_SURFACE = "getattacksurface";
                        if (msg.startsWith(GET_ATTACK_SURFACE)) {
                            String pkgName = msg.substring(GET_ATTACK_SURFACE.length()).trim();
                            Log.d(TAG, "Get Attack Surface for " + pkgName);
                            ArrayList<String> results = getAttackSurface(pkgName);
                            for (String res : results) {
                                out.write(res.getBytes());
                                out.flush();
                            }
                        } else {
                            String reply = "Server Said: I received '" + msg + "'";
                            out.write(reply.getBytes());
                            out.flush();
                        }
                    }
                } catch ...
            }
        }
    }
♦ 3.5 获取攻击面的代码
    private ArrayList<String> getAttackSurface(String pkgName) {
        PackageManager pm = getPackageManager();
        int[] exportedCnt = new int[4]; // 0 for activity, 1 for receiver, 2 for service, 3 for provider

        String sharedUid = null;
        // for not exceed binder buffer, get activities solely
        int flags = PackageManager.GET_ACTIVITIES;
        ArrayList<BaseInfo> activities = new ArrayList<>();
        try {
            PackageInfo pkgInfo = pm.getPackageInfo(pkgName, flags);
            sharedUid = pkgInfo.sharedUserId;
            for (ActivityInfo ai : pkgInfo.activities) {
                if (ai.exported) {// not check 'enabled', because 'disabled' activity can be enabled later
                    exportedCnt[0]++;
                    activities.add(new BaseInfo(ai.name, ai.permission));
                }
            }
        } catch ...

        ArrayList<BaseInfo> services = new ArrayList<>();
        ArrayList<BaseInfo> receivers = new ArrayList<>();
        ArrayList<MyProviderInfo> providers = new ArrayList<>();
        flags = PackageManager.GET_PROVIDERS |
                PackageManager.GET_SERVICES | PackageManager.GET_RECEIVERS;
        try {
            PackageInfo pkgInfo = pm.getPackageInfo(pkgName, flags);
            if (pkgInfo.receivers != null && pkgInfo.receivers.length > 0) {
                for (ActivityInfo ri : pkgInfo.receivers) {
                    if (ri.exported) {
                        exportedCnt[1]++;
                        receivers.add(new BaseInfo(ri.name, ri.permission));
                    }
                }
            }

            if (pkgInfo.services != null && pkgInfo.services.length > 0) {
                for (ServiceInfo si : pkgInfo.services) {
                    if (si.exported) {
                        exportedCnt[2]++;
                        services.add(new BaseInfo(si.name, si.permission));
                    }
                }
            }

            if (pkgInfo.providers != null && pkgInfo.providers.length > 0) {
                for (ProviderInfo pi : pkgInfo.providers) {
                    if (pi.exported) {
                        exportedCnt[3]++;
                        MyProviderInfo mpi = new MyProviderInfo(pi.name, pi.authority, pi.readPermission,
                                pi.writePermission, pi.grantUriPermissions, pi.multiprocess);
                        if (pi.uriPermissionPatterns != null && pi.uriPermissionPatterns.length > 0) {
                            mpi.uriPermissionPatterns = new ArrayList<>();
                            for (PatternMatcher patternMatcher : pi.uriPermissionPatterns) {
                                mpi.uriPermissionPatterns.add(new MyPattern(patternMatcher.getType(), patternMatcher.getPath()));
                            }
                        }
                        if (pi.pathPermissions != null && pi.pathPermissions.length > 0) {
                            mpi.pathPermissions = new ArrayList<>();
                            for (PathPermission pathPermission : pi.pathPermissions) {
                                mpi.pathPermissions.add(new MyPathPermission(pathPermission.getType(), pathPermission.getPath(),
                                        pathPermission.getReadPermission(), pathPermission.getWritePermission()));
                            }
                        }
                        providers.add(mpi);
                    }
                }
            }
        } catch ...

        ArrayList<String> results = new ArrayList<>();
        StringBuffer buf = new StringBuffer();
        buf.append("Package: ").append(pkgName).append("\n");
        buf.append("Attack Surface:").append("\n");
        buf.append("  ").append(exportedCnt[0]).append(" activities exported").append("\n");
        buf.append("  ").append(exportedCnt[1]).append(" broadcast receivers exported").append("\n");
        buf.append("  ").append(exportedCnt[2]).append(" services exported").append("\n");
        buf.append("  ").append(exportedCnt[3]).append(" content providers exported").append("\n");
        buf.append("    Shared UID ( ").append(sharedUid).append(" )").append("\n");
        buf.append("\n");
        results.add(buf.toString());

        buf = new StringBuffer();
        buf.append("Exported activities: ").append(exportedCnt[0]).append("\n");
        for (int i = 0; i < activities.size(); i++) {
            BaseInfo bi = activities.get(i);
            buf.append("  ").append(i + 1).append("  ").append(bi.name).append("\n");
            buf.append("    Permission: ").append(bi.permission).append("\n");
        }
        buf.append("\n");
        results.add(buf.toString());

        buf = new StringBuffer();
        buf.append("Exported receivers: ").append(exportedCnt[1]).append("\n");
        for (int i = 0; i < receivers.size(); i++) {
            BaseInfo bi = receivers.get(i);
            buf.append("  ").append(i + 1).append("  ").append(bi.name).append("\n");
            buf.append("    Permission: ").append(bi.permission).append("\n");
        }
        buf.append("\n");
        results.add(buf.toString());

        buf = new StringBuffer();
        buf.append("Exported services: ").append(exportedCnt[2]).append("\n");
        for (int i = 0; i < services.size(); i++) {
            BaseInfo bi = services.get(i);
            buf.append("  ").append(i + 1).append("  ").append(bi.name).append("\n");
            buf.append("    Permission: ").append(bi.permission).append("\n");
        }
        buf.append("\n");
        results.add(buf.toString());

        buf = new StringBuffer();
        buf.append("Exported providers: ").append(exportedCnt[3]).append("\n");
        for (int i = 0; i < providers.size(); i++) {
            MyProviderInfo mpi = providers.get(i);
            buf.append("  ").append(i + 1).append("  ").append(mpi.name).append("\n");
            buf.append("    Authority: ").append(mpi.authority).append("\n");
            buf.append("    Read Permission: ").append(mpi.readPermission).append("\n");
            buf.append("    Write Permission: ").append(mpi.writePermission).append("\n");
            buf.append("    Multiprocess Allowed: ").append(mpi.multiprocess ? "True" : "False").append("\n");
            buf.append("    Grant Uri Permissions: ").append(mpi.grantUriPermissions ? "True" : "False").append("\n");
            if (mpi.uriPermissionPatterns != null && mpi.uriPermissionPatterns.size() > 0) {
                buf.append("    UriPermissionPatterns: ").append("\n");
                for (int j = 0; j < mpi.uriPermissionPatterns.size(); j++) {
                    MyPattern myPattern = mpi.uriPermissionPatterns.get(i);
                    buf.append("      ").append(j + 1).append(" Type: ").append(myPattern.getTypeStr());
                    buf.append(" ").append(myPattern.pattern).append("\n");
                }
            }
            if (mpi.pathPermissions != null && mpi.pathPermissions.size() > 0) {
                buf.append("    PathPermissions: ").append("\n");
                for (int j = 0; j < mpi.pathPermissions.size(); j++) {
                    MyPathPermission myPathPermission = mpi.pathPermissions.get(i);
                    buf.append("      ").append(j + 1).append(" Type: ").append(myPathPermission.getTypeStr());
                    buf.append(" ").append(myPathPermission.pattern).append("\n");
                    buf.append("      Read permission: ").append(myPathPermission.readPermission).append("\n");
                    buf.append("      Write permission: ").append(myPathPermission.writePermission).append("\n");
                }
            }
        }
        buf.append("\n");
        results.add(buf.toString());
        return results;
    }

★ 4. PC端与手机端通信

♦ 4.1 建立通信

执行3步就可以让PC端工具(ClientApp)和手机端工具(Samples app)通信了。

  • 第一步:PC端执行 adb forward tcp:8888 tcp:3652
    此步执行后,没有输出。
    可以通过adb forward --list看到结果。
$ adb forward --list
4391b53a tcp:8888 tcp:3652
  • 第二步:手机端打开Samples app,打开Server(端口号为3652)
    运行Samples app -> 点击『Test Adb Forward(Server)』 button -> 点击『Start Server』,然后会看到『Server is started』的toast。

  • 第三步:PC端运行:java -jar ClientApp_fat.jar
    执行后,界面上显示如下,此时可以输入一些文本了。

    $ java -jar ClientApp_fat.jar
    Please type in something...
    Type in 'exit' to exit.
    Type in 'getattacksurface package_name' to get attack surface.
    

    输入『exit』,退出client端。
    输入『getattacksurface xxx』,获取某个package的攻击面。
    输入任意其他内容『xxx』,手机端会返回『Server Said: I received ‘xxx’』。

♦ 4.2 基本的操作

输入『hi』、『hello』、『How are you?』等随意的文本,手机端返回『Server Said: I received ‘hi’』这样的文本。

hi
Server Said: I received 'hi'
hello
Server Said: I received 'hello'
How are you?
Server Said: I received 'How are you?'
♦ 4.3 获取手机中的包名

如果要获取手机中某个应用的攻击面,则需要先知道应用的包名。

我的adb版本 1.0.39 :

$ adb version
Android Debug Bridge version 1.0.39
Revision 3db08f2c6889-android
Installed as D:\androidsdk\platform-tools\adb.exe

查看手机中有哪些package,执行adb shell cmd package list packages。我用的是坚果Pro,找到包名含有『smartisanos』的,只是举个例子,你可以找你的手机中的任意的包名。

$ adb shell cmd package list packages | grep smartisanos
package:com.smartisanos.payment
package:com.smartisanos.setupwizard
package:com.smartisanos.security
...
...
♦ 4.4 获取某个应用的攻击面

例如,获取com.smartisanos.security的攻击面(注:根据drozer工具的run app.package.attacksurface设计的),仅供参考。
其实检测的并不准确,因为没有考虑权限的类型。
例如,
smartisan.permission.READ_SECURITYCENTER_PACKAGEsmartisan.permission.WRITE_SECURITYCENTER_PACKAGE 权限 是signature & system的。
所以使用了这两个权限的Provider基本上是没问题的。

smartisanos.expandservice.data_synccom.smartisanos.permission.CALL_SETTINGS 权限也是signature & system的。使用这两个权限保护的组件大概率也没问题。

获取com.smartisanos.security的攻击面的返回结果:

$ java -jar ClientApp_fat.jar
Please type in something...
Type in 'exit' to exit.
Type in 'getattacksurface package_name' to get attack surface.

getattacksurface com.smartisanos.security  (输入的,然后按回车)
Package: com.smartisanos.security           (从这行开始,是手机端返回的)
Attack Surface:
  9 activities exported
  4 broadcast receivers exported
  2 services exported
  5 content providers exported
    Shared UID ( null )

Exported activities: 9
  1  com.smartisanos.securitycenter.MainActivity
    Permission: null
  2  com.smartisanos.security.PermissionsActivity
    Permission: null
  3  com.smartisanos.security.RadioPermissions
    Permission: null
  4  com.smartisanos.security.PackagesOverview
    Permission: null
  5  com.smartisanos.security.PackageDetail
    Permission: null
  6  com.smartisanos.security.AllPermissionDetail
    Permission: null
  7  com.smartisanos.security.invokeHistory.InvokeHistoryActivity
    Permission: null
  8  com.smartisanos.preventdisturb.DisturbActivity
    Permission: null
  9  com.smartisanos.securitycenter.NetworkOptimizationSetting
    Permission: null

Exported receivers: 4
  1  com.smartisanos.securitycenter.PackageReceiver
    Permission: null
  2  com.smartisanos.securitycenter.SyncReceiver
    Permission: smartisanos.expandservice.data_sync
  3  com.smartisanos.datausage.BootCompletedReceiver
    Permission: null
  4  com.smartisanos.security.PermissionTestingBroadcastReceiver
    Permission: com.smartisanos.permission.CALL_SETTINGS

Exported services: 2
  1  com.smartisanos.datausage.TencentService
    Permission: null
  2  com.smartisanos.cleaner.FakeService
    Permission: null

Exported providers: 5
  1  com.smartisanos.securitycenter.dynamicResolution.DynamicResolutionProvider
    Authority: com.smartisanos.security.dynamicResolution
    Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
    Multiprocess Allowed: False
    Grant Uri Permissions: False
  2  com.smartisanos.securitycenter.appBatteryUseOptimization.AppUseBatteryOptimizationProvider
    Authority: com.smartisanos.security.appUseBatteryOptimization
    Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
    Multiprocess Allowed: False
    Grant Uri Permissions: False
  3  com.smartisanos.security.invokeHistory.InvokeHistoryProvider
    Authority: com.smartisanos.security.invokeHistory
    Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
    Multiprocess Allowed: False
    Grant Uri Permissions: False
  4  com.smartisanos.security.PermissionAPIProvider
    Authority: com.smartisanos.security.apiprovider
    Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Write Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Multiprocess Allowed: False
    Grant Uri Permissions: False
  5  com.smartisanos.datausage.DataUsageProvider
    Authority: com.smartisanos.tencent
    Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Write Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
    Multiprocess Allowed: False
    Grant Uri Permissions: False

★ 5. 参考

(1)Fatjar:http://fjep.sourceforge.net/
(2)Drozer: https://labs.mwrinfosecurity.com/tools/drozer/
(3)《adb forward的细节(1):概述》

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值