Finish the article after a few stitches.

preface

Get a sense of what this article is about.

Android implements ADB to connect to another Android without ROOT, in this case ow2.

Android installs an app on another Android without ROOT

ADB introduction

ADB is an Android Debug Bridge that separates ADB Server from the ADBD process on Android devices in order to achieve distribution (which is really the official word).

What is the use of this separate ADB Server layer?

For example, WHEN PC A is connected to 10 Andros, ADB Server runs on PC A and other PCS on the LAN only need to debug 10 Andros through port 5037 of PC A. This is the benefit brought by Server. The actual argument is adB-l

-L SOCKET  listen on given socket for adb server [default=tcp:localhost:5037]
Copy the code

And the server does not exit with the ADB command itself, reducing the handshaking process with individual devices.

ADB also has its own set of protocols that communicate with devices. In the ADB binary, ADB forks the server the first time it uses ADB devices or actively executes ADB start-server, binding port 5037. Adb then acts as a client to communicate with the server on port 5037. For example, after executing ADB Devices, ADB does not directly communicate with the device through the serial port. Instead, ADB tells the server to execute adb Devices commands. The server communicates with the device serial port or network and then passes the results to the ADB client.

There are other communication protocols between ADB Client and ADB Server that we don’t care about. We care about the communication between ADB Server and ADBD (a process on Android devices that responds to ADB messages).

We need to implement ADB functionality without using any compiled ADB binaries

An architecture diagram seems to be needed here, but 🤫 is not yet drawn

Why does Android need ADB?

Adb has shell user group, this user group is much higher than the ordinary app, can directly take a screenshot/record, freeze/kill app, uninstall the ordinary/system app.

Therefore, there are a series of software such as refrigerator, Xiaohei House and Shizuku. They have a shell server, but after connecting to PC, users take the initiative to paste a script and execute it to start their own service process. Since it is started by ADB, this service process also has the ability of shell. This way, as long as the phone does not restart, these apps will be able to get shell permissions through the server that is started.

Even like shizuku, you can directly give shell permission to your server to any app through AIDL communication (other apps need to introduce the shizuku API), some users who can’t root device, or some mobile phones that can’t root (Huawei), They can also get Shell privileges to better manage their Android devices and control their processes. But not all users have computers! And when you want to use this feature, you’re not near your computer, so the need arises

Currently android implements ADB to connect to another Android

In Magisk github.com/Magisk-Modu… Patch is not difficult to cross-compile adb to Android, and there are existing solutions in Android-Tools.

This library is maintained after forking the Termux package yourself.

At this time, ADB can connect to network devices, but when another Android device is connected through OTG, ADB Devices always cannot find this device. The reason is very simple: There is a difference between the kernel of Android and Linux. When connecting serial devices, Linux tries to recognize and drive this device, eventually producing a character device under /dev that can be easily read and written to by ordinary users.

Here’s an example:

crw-rw-rw-   1 root       wheel            9,   0 11 12 10:05 tty.Bluetooth-Incoming-Port
Copy the code

You can see that the leading character is C, not D or -, which represents the character device.

When a serial device to connect to the android, character device file, the average user is unreadable, the reason is very simple, cameras, microphones are such devices as a serial port to connect to the android, if these ordinary character device can read and write, that it is no meaning to the existence of the android permission mechanism.

What about using root? The answer is yes, with root permissions, adb on Android is basically the same as ON PC, but users may not have root on their phones!! Demand arises again

As for which devices the kernel can drive by default, it is optional when compiling the kernel. In addition to the regular keyboard and mouse, we can also make the kernel drive ch340x/ CP210X drivers, but this cost is high, so the PC can support the installation of drivers.

User: I want SHELL privileges, I understand why even better than you as a developer, but I don’t have a computer, and I don’t have root on my device.

Developer: Woo woo woo

Breakthrough analysis

We can’t use the camera or microphone through serial communication, but we can apply for permission to use those devices through android API. Although the common Android app cannot directly access the character device under /dev/we can also use android API. Read and write usb devices.

This part of the design of a lot of Android USB knowledge, this article will not be discussed in detail, there is all the code in the warehouse

I did a couple of waves of USB-related articles, then went to Github and found a barely working open source tinker.

Read and write OTG

Access permissions

Device_filter.xml that recognizes connections to Android devices


      
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <usb-device
        class="255"
        protocol="1"
        subclass="66" />
    <usb-device
        class="255"
        protocol="3"
        subclass="66" />
</resources>
Copy the code

This section is written in the manifest

            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
Copy the code

To apply for permission

 UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
        if(device ! =null) {
            Static broadcast is enabled
            // The user receives a pop-up "Do you want to open ADB tools?" and clicks yes
            System.out.println("From Intent!");
            asyncRefreshAdbConnection(device);
        } else {
            System.out.println("From onCreate!");
            for (String k : mManager.getDeviceList().keySet()) {
                UsbDevice usbDevice = mManager.getDeviceList().get(k);
                if (mManager.hasPermission(usbDevice)) {
                    asyncRefreshAdbConnection(usbDevice);
                } else {
                    mManager.requestPermission(usbDevice, PendingIntent.getBroadcast(getApplicationContext(), 0.new Intent(Message.USB_PERMISSION), 0)); }}}Copy the code

Great minds think alike

Initialize the OTG


        UsbEndpoint epOut = null;
        UsbEndpoint epIn = null;
        // look for our bulk endpoints
        for (int i = 0; i < intf.getEndpointCount(); i++) {
            UsbEndpoint ep = intf.getEndpoint(i);
            if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                    epOut = ep;
                } else{ epIn = ep; }}}if (epOut == null || epIn == null) {
            throw new IllegalArgumentException("not all endpoints found");
        }
        mEndpointOut = epOut;
        mEndpointIn = epIn;
Copy the code

This code can be copied anywhere.

read

    public void readx(byte[] buffer, int length) throws IOException {

        UsbRequest usbRequest = getInRequest();

        ByteBuffer expected = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN);
        usbRequest.setClientData(expected);

        if(! usbRequest.queue(expected, length)) {throw new IOException("fail to queue read UsbRequest");
        }

        while (true) {
            UsbRequest wait = mDeviceConnection.requestWait();

            if (wait == null) {
                throw new IOException("Connection.requestWait return null");
            }

            ByteBuffer clientData = (ByteBuffer) wait.getClientData();
            wait.setClientData(null);

            if (wait.getEndpoint() == mEndpointOut) {
                // a write UsbRequest complete, just ignore
            } else if (expected == clientData) {
                releaseInRequest(wait);
                break;

            } else {
                throw new IOException("unexpected behavior");
            }
        }
        expected.flip();
        expected.get(buffer);
    }
Copy the code

write

public void writex(byte[] buffer) throws IOException {
        Log.i("Nightmare"."> > > > > > > >" + new String(buffer));
        int offset = 0;
        int transferred = 0;

        while ((transferred = mDeviceConnection.bulkTransfer(mEndpointOut, buffer, offset, buffer.length - offset, defaultTimeout)) >= 0) {
            offset += transferred;
            if (offset >= buffer.length) {
                break; }}if (transferred < 0) {
            throw new IOException("bulk transfer fail"); }}Copy the code

ADB Protocol Analysis

ADB protocol is the application layer protocol, this serial communication protocol, will have some handshake mechanism, similar to TCP handshake, in short, is to some password, because usb will write something to the phone not only ADB, probably MTP copy files, it can not be mistaken.

To realize the adb connect

In the PC connection android for the first time, there will be a popup window on android, ask whether to allow debugging, is actually the PC request connection android devices, click on the confirmation in order to be connected over, after connection, there is no this pop-up window again, but this request is in process So the first step is how to implement android otg successfully connect another android, And invokes the debug popover.

I’ve expressed the whole protocol in terms of chat, with the right side representing write content, representing PC, and the left side representing return content, representing Android. The $sign joins the string to represent the variable, AUTH and CNXN and so on to represent the character itself, + symbol represents the connection, convenient representation, does not represent the protocol has this character. The contents between () are explanatory notes

For the first time to connect

                    CNXN + '$protcol_version' + '$maxdata'
                    '$payload'($payloadHost: : \ 0) AUTH'$token1'
                    AUTH
                    '$signature'(Sent the Android back$token1After signing) AUTH'$token2'
                    AUTH
                    '$RSA_PUBLIC_KEY'CNXN device::ro.product.name=elish; ro.product.model=M2105K81AC; ro.product.device=elish; features=sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir, fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2Copy the code

Finally, the device will return to device::.* after clicking “allow” in the popup, representing the end of the whole handshake process.

Non-first connection

                    CNXN + '$protcol_version' + '$maxdata'
                    '$payload'
AUTH
'$token1'
                    AUTH
                    '$signature'(Sent the Android back$token1CNXN device::ro.product. Name =elish; ro.product.model=M2105K81AC; ro.product.device=elish; features=sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v 2,cmd,ls_v2,apex,stat_v2Copy the code

There is one less procedure requiring RSA to be sent.

All the above has been verified by code.

After the connection is successful, you can quickly find out what function you want to implement in services. TXT and sync. TXT.

To realize the adb shell

The agreement document

shell:command arg1 arg2 ...
    Run 'command arg1 arg2 ... ' in a shell on the device, and return
    its output and error streams. Note that arguments must be separated
    by spaces. If an argument contains a space, it must be quoted with
    double-quotes. Arguments cannot contain double quotes or things
    will go very wrong.

    Note that this is the non-interactive version of "adb shell"
复制代码

完整协议过程。

                OPEN
                shell:cmd
OKAY
CLSE
复制代码

实际协议内容

箭头表示消息方向,不是具体协议内容,>>>>>>>>代表发送过去的消息,<<<<<<<<代表发送回来的消息

>>>>>>>>OPEN��������������-������+��������
>>>>>>>>shell:settings put system pointer_location 1��
<<<<<<<<OKAY%��������������������������������
<<<<<<<<CLSE%��������������������������������
复制代码

‘�’字符是编码成 String 后出现的,其实就是这个字节没有内容。

实现 adb shell interactive

与上一个不同,这个是可交互式终端。

协议文档

shell:
    Start an interactive shell session on the device. Redirect
    stdin/stdout/stderr as appropriate. Note that the ADB server uses
    this to implement "adb shell", but will also cook the input before
    sending it to the device (see interactive_shell() in commandline.c)
复制代码

完整协议过程

                OPEN
                shell:
OKAY
WRTE
elish:/ $(根据设备类型打印不同的字符,就是pc终端执行 adb shell 看到的前缀)
                OKAY
复制代码

实际协议内容

>>>>>>>>OPEN��������������������R��������
>>>>>>>>shell:��
<<<<<<<<OKAY+��������������������������������
<<<<<<<<WRTE+������������
���������������
<<<<<<<<elish:/ $
>>>>>>>>OKAY������+��������������������������

复制代码

这个时候我敲下 pwd 再按回车。

>>>>>>>>WRTE������'������������p����������
>>>>>>>>p
<<<<<<<<OKAY'��������������������������������
<<<<<<<<WRTE'������������������p����������
<<<<<<<<p
>>>>>>>>OKAY������'��������������������������
>>>>>>>>WRTE������'������������w����������
>>>>>>>>w
<<<<<<<<OKAY'��������������������������������
<<<<<<<<WRTE'������������������w����������
<<<<<<<<w
>>>>>>>>OKAY������'��������������������������
>>>>>>>>WRTE������'������������d����������
>>>>>>>>d
<<<<<<<<OKAY'��������������������������������
<<<<<<<<WRTE'������������������d����������
<<<<<<<<d
>>>>>>>>OKAY������'��������������������������
>>>>>>>>
<<<<<<<<OKAY'��������������������������������
<<<<<<<<WRTE'������������������$����������
<<<<<<<<
>>>>>>>>OKAY������'��������������������������
<<<<<<<<WRTE'������������
<<<<<<<</
elish:/ $
>>>>>>>>OKAY������'��������������������������

复制代码

实现 adb push

这个比上俩都要复杂一些,毕竟涉及到文件的上传。

协议文档

sync:
    This starts the file synchronization service, used to implement "adb push"
    and "adb pull". Since this service is pretty complex, it will be detailed
    in a companion document named SYNC.TXT
复制代码
SYNC SERVICES:
Requesting the sync service ("sync:") using the protocol as described in
SERVICES.TXT sets the connection in sync mode. This mode is a binary mode that
differ from the regular adb protocol. The connection stays in sync mode until
explicitly terminated (see below).

After the initial "sync:" command is sent the server must respond with either
"OKAY" or "FAIL" as per usual. 

In sync mode both the server and the client will frequently use eight-byte
packets to communicate in this document called sync request and sync
responses. The first four bytes is an id and specifies sync request is
represented by four utf-8 characters. The last four bytes is a Little-Endian
integer, with various uses. This number will be called "length" below. In fact
all binary integers are Little-Endian in the sync mode. Sync mode is
implicitly exited after each sync request, and normal adb communication
follows as described in SERVICES.TXT
复制代码
SEND:
The remote file name is split into two parts separated by the last
comma (","). The first part is the actual path, while the second is a decimal
encoded file mode containing the permissions of the file on device.

Note that some file types will be deleted before the copying starts, and if
the transfer fails. Some file types will not be deleted, which allows
  adb push disk_image /some_block_device
to work.

After this the actual file is sent in chunks. Each chunk has the following
format.
A sync request with id "DATA" and length equal to the chunk size. After
follows chunk size number of bytes. This is repeated until the file is
transferred. Each chunk must not be larger than 64k.

When the file is transferred a sync request "DONE" is sent, where length is set
to the last modified time for the file. The server responds to this last
request (but not to chuck requests) with an "OKAY" sync response (length can
be ignored).
复制代码

完整协议过程

                OPEN
                sync:
OKAY
                WRTE
                SEND+'$length'($length$remote_file_path与,33206字符的总长度)
OKAY
                WRTE
                '$remote_file_path'
OKAY
                WRTE
                ,+'$mode'(mode是文件权限,33206代表权限是0666)
OKAY

> start
                WRTE
                DATA+'$len'($len是即将发送的字节长度)
OKAY
                WRTE
                '$byte'(此次的字节, 不能超过16k) OKAY < end start 到 end 循环直到一个文件被完整的发送 WRTE DONE(通知安卓 adbd 这个文件发送完了) OKAY WRTE OKAY WRTE QUIT(退出sync模式) OKAY CLSE复制代码

实际协议内容

>>>>>>>>OPEN�����������������������������
>>>>>>>>sync:��
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE������������������F��������
>>>>>>>>SEND������
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE���������������������������
>>>>>>>>/sdcard/switch_work.sh
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE������������������*��������
>>>>>>>>,33206
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE������������������y��������
>>>>>>>>DATA_������
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE������������_������3"��������
>>>>>>>>this is file content byte
<<<<<<<<OKAY��������������������������������
>>>>>>>>WRTE���������������������������
>>>>>>>>DONE���
<<<<<<<<OKAY��������������������������������
<<<<<<<<WRTE������������������4��������
<<<<<<<<OKAY��������
>>>>>>>>OKAY��������������������������������
>>>>>>>>WRTE������������������C��������
>>>>>>>>QUIT��������
<<<<<<<<OKAY��������������������������������
<<<<<<<<CLSE��������������������������������

复制代码

实现 adb install

这个其实还是使用的 adb shell。 首先 push apk 到 /data/local/tmp/ 文件夹,再使用 shell:pm install -r /data/local/tmp/xxx.apk,最后再删掉这个 apk 就完成了。

源代码可以参考otgadb_channel

实现 adb tcpip、adb usb

完整协议过程

                            OPEN
                            tcpip:+'$port'
OKAY
WRTE
restarting in TCP mode port: $port
                            OKAY
                            CLSE
复制代码

实际协议内容

>>>>>>>>OPEN��������������������.��������
>>>>>>>>tcpip:5555��
<<<<<<<<OKAY/��������������������������������
<<<<<<<<WRTE/������������"������#��������
<<<<<<<<restarting in TCP mode port: 5555
>>>>>>>>OKAY������/��������������������������
<<<<<<<<CLSE/��������������������������������
复制代码

adb usb 只需要把 tcpip 替换成 usb 即可

开箱即用,拿来吧你

想要更完善的拥有 adb 的功能,依然还是交叉编译 adb binary,客户端就需要对 OTG 的 ADB 与 网络 ADB 做一下区分,简单解释一下实现。

面相抽象

abstract class ADBChannel {
  Future<String> execCmmand(String cmd);

  Future<void> push(String localPath, String remotePath);
  Future<void> install(String file);
  Future<void> changeNetDebugStatus(int port);
}
复制代码

实现

class BinADBChannel extends ADBChannel {
  BinADBChannel(this.serial);

  final String serial;
  @override
  Future<String> execCmmand(String cmd) async {
    String out = '';
    final List<String> cmds = cmd.split('\n');
    for (final String cmd in cmds) {
      out += await execCmd(cmd);
    }
    return out;
  }

  @override
  Future<void> install(String file) async {
    await execCmmand('adb -s $serial install -t $file');
  }

  @override
  Future<void> push(String localPath, String remotePath) async {
    final String fileName = basename(localPath);
    await execCmmand('adb -s $serial push $localPath $remotePath$fileName');
  }

  @override
  Future<void> changeNetDebugStatus(int port) async {
    if (port == 5555) {
      await execCmmand(
        'adb -s $serial tcpip 5555',); }else {
      await execCmmand(
        'adb -s $serial usb',); }}}Copy the code

ExecCmmand is a wrapper around process. run.

class OTGADBChannel extends ADBChannel {
  @override
  Future<String> execCmmand(String cmd) async {
    // otG will remove adb -s XXX shell
    final String shell = cmd.replaceAll(RegExp('.*shell'), ' ');
    final String data = await PluginUtil.execCmd(shell);
    Log.e('OTGADBChannel execCmmand -> $data');
    return data;
  }

  @override
  Future<void> install(String file) async {
    final String fileName = basename(file);
    await PluginUtil.pushToOTG(file, '/data/local/tmp/');
    await PluginUtil.execCmd('pm install -r /data/local/tmp/$fileName');
  }

  @override
  Future<void> push(String localPath, String remotePath) async {
    await PluginUtil.pushToOTG(localPath, remotePath);
  }

  @override
  Future<void> changeNetDebugStatus(int port) async {
    final String data = await PluginUtil.changeNetDbugStatus(port);
    Log.w('changeNetDebugStatus ->$data'); }}Copy the code

PluginUtil is a Plugin management class for hand rubbing.

Experience the

Welcome to experience ADB TOOL Android version, have the ability to compile their own experience.

ADB TOOL coolan download address

ADB TOOL personal server download address

Personal software quick download address

Open source address

The adb_tool tool is still in a lot of development, polishing details. A separate article will be introduced later.

The warehouse has been open source since the establishment of the first line of code, and it has always been a toy in development, with the initial intention that they need such an APP.

The resources

  • [Android] EXPLORE the principle of ADB tools
  • Android realizes communication with USB_HID device through control transmission

enjoy!