Skip to content

MuntashirAkon/libadb-android

Repository files navigation

LibADB Android

ADB library for Android. It enables an app to connect to the ADB daemon (adbd process) belonging to the same or a different device and execute arbitrary services or commands (via shell: service).

Disclaimer: This library has never gone through a security audit. Please, proceed with caution if security is crucial for your app. Avoid using the APIs for reasons other than connecting or using ADB. For the safety of your app and its users, open a remote service instead of using ADB and ask the user to disconnect Wireless debugging.

Getting Started

Adding Dependencies

LibADB Android is available via JitPack.

// Top level build file
repositories {
    mavenCentral()
    maven { url "https://jitpack.io" }
}

// Add to dependencies section
dependencies {
    // Add this library
    implementation 'com.github.MuntashirAkon:libadb-android:1.0.1'
    
    // Library to generate X509Certificate. You can also use BouncyCastle for
    // this. See example for use-case.
    // implementation 'com.github.MuntashirAkon:sun-security-android:1.1'

    // Bypass hidden API if you want to use the Android default Conscrypt in
    // Android 9 (Pie) or later. It also requires additional steps. See
    // https://github.com/LSPosed/AndroidHiddenApiBypass to find out more about
    // this.
    // implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:2.0'

    // Use custom Conscrypt library. If you want to connect to a remote ADB
    // daemon instead of the device the app is currently running or do not want
    // to bypass hidden API, this is the recommended choice.
    implementation 'org.conscrypt:conscrypt-android:2.5.2'
}

If you're using the custom Conscrypt library in order to connect to a remote ADB daemon and the app targets Android version below 4.4, you have to extend android.app.Application to apply fixes for the random number generation:

public class MyAwesomeApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // Fix random number generation in Android versions below 4.4.
        PRNGFixes.apply();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // Uncomment the following line if you want to bypass hidden API as
        // described above.
        // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        //     HiddenApiBypass.addHiddenApiExemptions("L");
        // }
    }
}

Notice: Conscrypt supports only API 9 (Gingerbread) or later, meaning you cannot use ADB pairing or any TLSv1.3 features in API less than 9. The corresponding methods are already annotated properly. So, you don't have to worry about compatibility issues that may arise when your app's minimum SDK is set to one of the unsupported versions.

Configuring ADB

Instead of doing everything manually, you can create a concrete implementation of the AbsAdbConnectionManager class. Example:

public class AdbConnectionManager extends AbsAdbConnectionManager {
    private static AbsAdbConnectionManager INSTANCE;

    public static AbsAdbConnectionManager getInstance() throws Exception {
        if (INSTANCE == null) {
            INSTANCE = new AdbConnectionManager();
        }
        return INSTANCE;
    }

    private PrivateKey mPrivateKey;
    private Certificate mCertificate;

    private AdbConnectionManager() throws Exception {
        // Set the API version whose `adbd` is running
        setApi(Build.VERSION.SDK_INT);
        // TODO: Load private key and certificate (along with public key) from
        //  some place such as KeyStore or file system.
        mPrivateKey = ...;
        mCertificate = ...;
        if (mPrivateKey == null) {
            // Generate a new key pair
            int keySize = 2048;
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(keySize, SecureRandom.getInstance("SHA1PRNG"));
            KeyPair generateKeyPair = keyPairGenerator.generateKeyPair();
            PublicKey publicKey = generateKeyPair.getPublic();
            mPrivateKey = generateKeyPair.getPrivate();
            // Generate a certificate
            // On Android, it requires sun.security-android library as mentioned
            // above.
            String subject = "CN=My Awesome App";
            String algorithmName = "SHA512withRSA";
            long expiryDate = System.currentTimeMillis() + 86400000;
            CertificateExtensions certificateExtensions = new CertificateExtensions();
            certificateExtensions.set("SubjectKeyIdentifier", new SubjectKeyIdentifierExtension(
                    new KeyIdentifier(publicKey).getIdentifier()));
            X500Name x500Name = new X500Name(subject);
            Date notBefore = new Date();
            Date notAfter = new Date(expiryDate);
            certificateExtensions.set("PrivateKeyUsage", new PrivateKeyUsageExtension(notBefore, notAfter));
            CertificateValidity certificateValidity = new CertificateValidity(notBefore, notAfter);
            X509CertInfo x509CertInfo = new X509CertInfo();
            x509CertInfo.set("version", new CertificateVersion(2));
            x509CertInfo.set("serialNumber", new CertificateSerialNumber(new Random().nextInt() & Integer.MAX_VALUE));
            x509CertInfo.set("algorithmID", new CertificateAlgorithmId(AlgorithmId.get(algorithmName)));
            x509CertInfo.set("subject", new CertificateSubjectName(x500Name));
            x509CertInfo.set("key", new CertificateX509Key(publicKey));
            x509CertInfo.set("validity", certificateValidity);
            x509CertInfo.set("issuer", new CertificateIssuerName(x500Name));
            x509CertInfo.set("extensions", certificateExtensions);
            X509CertImpl x509CertImpl = new X509CertImpl(x509CertInfo);
            x509CertImpl.sign(mPrivateKey, algorithmName);
            mCertificate = x509CertImpl;
            // TODO: Store the key pair to some place else.
        }
    }

    @NonNull
    @Override
    protected PrivateKey getPrivateKey() {
        return mPrivateKey;
    }

    @NonNull
    @Override
    protected Certificate getCertificate() {
        return mCertificate;
    }

    @NonNull
    @Override
    protected String getDeviceName() {
        return "MyAwesomeApp";
    }
}

Connecting to ADB

You can connect to ADB in several ways from the AbsAdbConnectionManager:

Method Description
connect(host, port) Connect using a host address and a port number
connect(port) Connect using a host address set by setHostAddress() and a port number
connectTcp(Context, timeout) (SDK 16+) Discover host address and port number automatically for ADB over TCP and connect to it
connectTls(Context, timeout) (SDK 16+) Discover host address and port number automatically for TLS (from Android 9) and connect to it
autoConnect(Context, timeout) (SDK 16+) Discover host address and port number automatically for both ADB over TCP and TLS and connect to it

Wireless Debugging

Internally, ADB over TCP and Wireless Debugging are very similar except Wireless Debugging requires an extra step of pairing the device. In order to pair a new device, you can simply invoke AdbConnectionManager.getInstance().pair(host, port, pairingCode). After the pairing, you can connect to ADB via the usual connect() methods without any additional steps.

Opening ADB Shell for Executing Arbitrary Commands

Simply use AdbConnectionManager.getInstance().openStream("shell:"). This will return an AdbStream which can be used to read/write to the ADB shell via AdbStream#openInputStream() and AdbStream#openOutputStream() methods respectively like a normal Java Process. While it is possible to read/write in the same thread (first write and then read), this is not recommended because the shell might be stuck indefinitely for commands such as top.

NOTE: If you want to create a full-featured terminal emulator, this approach isn't recommended. Instead, you should create a remote service via app_process or start an SSH server and connect to it.

Other services

You can also use other services via the AdbConnectionManager#openStream() methods. See SERVICES.md for more information.

For Java (non-Android) Projects

It is possible to modify this library to work on non-Android project. But it isn't supported because Spake2-Java only provides stable releases for Android. However, you can incorporate this library in your project by manually compiling Spake2 library for your platforms.

Contributing

By contributing to this project, you permit your work to be released under the terms of GNU General Public License, Version 3 or later or Apache License, Version 2.0.

License

Copyright 2021 © Muntashir Al-Islam

Dual licensed under the terms of GPL-3.0-or-later or Apache-2.0 license. Use whatever license you need for your project.

Note regarding the Apache-2.0 license, this library has an LGPL dependency which may go against the policy of some organizations such as ASF.