Communicating With An Android App Over USB

26 December 2019

About the motivation you may read in the previous parts of this series. Now it is the time to get finally my hands dirty! :)

An approach with IPC sockets

After doing a quick research it turned out for me that one of the main SDK tools for Android Development, ADB does support forwarding data over UNIX IPC Sockets in a similar way SSH is capable to.

A little refresh on sockets

A socket is a logical abstraction of an endpoint for receiving or sending data. Put it another way, it is “something that we are able to receive or send data through”. (there is usually a “something” involved in an abstraction, because it does not limit what that “something” can be)

The concept of sockets is used most of the time to encapsulate the details of a network connection. For example, if I wanted to connect to IP 1.2.3.4 on port 8099 to send data, then, the result of a successful connection would be a socket. You may think about it as a “portal to the other side”. After the connection is made, we no longer care about what IP and what port (or wherever in general) it redirects the data to. All we care about is that this “something” connects us with some other side, and that we can read from it and write to it.

Being an abstract concept, nobody forbids us to encapsulate other entities into sockets in various programming languages. Roughly speaking, a UNIX IPC socket is another kind of implementation. One can send and receive data through it quite the same way like through a network socket, however, the data gets exchanged differently.

In a POSIX-compatible operating system a UNIX socket usually appears as a file. Processes can connect to files of this kind and can “communicate” with them. These files are, however, only sort of pointers. What actually connects the two parties - the two processes at the two sides - is the system kernel. It keeps track of which files are marked as sockets and which processes are using them. (recently, Windows 10 began supporting these kind of sockets as well)

As UNIX sockets are represented with files, they have the advantage, that they can be guarded the way regular files are: by setting permissions on them. By contrast, if we used a TCP or UDP port, then it could be seen and could be abused by any of our local programs on our machine - or on our phone, if we open a port on the phone - which is a security concern right from the beginning.

ADB socket forwarding

If I wanted to communicate with an app on my phone, I would let it create a socket in a place where adb can access it, and thus act as a server. Then, I would ask adb to create another socket on my computer. Now if I tried to read from the socket on the computer, adb would be the other party to answer my request. adb would then read from the socket of the app and forward the response. Writing to the sockets would work similarly.

adb diagram

Notice that, as mentioned recently, a UNIX socket is only eligible for inter-process communication (IPC) - with other words, it does not use a network connection. The communication happens instead “in house”, between processes. By forwarding the traffic, adb managed to create another socket on the other side, that behaves in a way like if the original one was copied from the phone to the computer with the connection still alive.

Otherwise, if simply copying the socket from the phone to the computer could retain the connection on its own (which is “in house” inter-process only!) , that would be probably an alerting sign that our reality just stopped making sense. Sorry, there is an XKCD for absolutely everything :)

Let’s see how it works on the machine first…

I created a small server and client example application. Upon an incoming connection, the server sends a string with the current time to the client, and then shuts down the socket. The client reads one message and then closes the connection.

Note that I’m using libsocket for creating sockets and sds library for handling strings, both for easier readability.

server.c:

// ...

int socketd = create_UNIX_server_socket(argv[1], LIBSOCKET_STREAM, 0);
// (error check omitted)
    
sds sendbuf = sdsempty();

for(;;) {
    printf("Waiting for new connection...\n");
    
    int connection = accept_UNIX_stream_socket(socketd, 0);
    // (error check omitted)

    sendbuf = construct_message(sendbuf); // puts time into the buffer

    printf("Answering call with message: %s (length: %d)\n", sendbuf, sdslen(sendbuf));
    write(connection, sendbuf, sdslen(sendbuf) * sizeof(char));

    printf("Hang up\n");
    destroy_UNIX_socket(connection);
}

sdsfree(sendbuf);

// ...

client.c:

// ...

int socketd = create_UNIX_stream_socket(argv[1], 0);
// (error check omitted)

size_t buf_len = 150;
char buf[buf_len];    
read(socketfd, buf, buf_len);
printf("Got message: %s", buf);

shutdown_UNIX_stream_socket(socketd, 0);

// ...

Test run

$ ./server test_socket
Waiting for new connection...

$ ./client test_socket 
Got message: Local time is: 2019-12-26 10:09:33
$

After running the client, the server gave us some additional output:

$ ./server test_socket
Waiting for new connection...
Answering call with message: Local time is: 2019-12-26 10:09:33
 (length: 35)
Hang up
Waiting for new connection...

So far it works!

In addition, at this point, we already have a program that we can use to test adb socket forwarding with.

Let’s compile this program for Android. For this I need to have the Android NDK downloaded which is the toolkit for creating native executables for Android.

These can be downloaded in Android Studio:

Installing NDK in Android Studio

I’m also using CMake for my project, which means I can easily write a new toolkit and compile against it:

armv7a-linux-androideabi24.toolchain:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER /opt/kuklinistvan/android-sdk/ndk/20.1.5948944/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang)
set(CMAKE_C_COMPILER_TARGET armv7a-linux-androideabi24)
$ rm CMakeCache.txt
$ cmake -DCMAKE_TOOLCHAIN_FILE=armv7a-linux-androideabi24.toolchain .
(...)
-- Configuring done
-- Generating done
-- Build files have been written to: (...)
$ make
(...)
[ 55%] Built target server
(...)
[100%] Built target client

Great! Note that I cannot run this executable as it is compiled for ARM processor.

$ ./client
zsh: Érvénytelen végrehajtható fájlformátum: ./client
$

(it says “invalid executable file format” in Hungarian)

Let me hook up my phone. Will it blend?

$ adb push server /data/local/tmp
server: 1 file pushed. 2.2 MB/s (70268 bytes in 0.031s)
$ adb shell
mido:/ $ cd /data/local/tmp
mido:/data/local/tmp $ ./server                                                
Usage: ./server <socket_path>
1|mido:/data/local/tmp $ 

Yay! I can run it!

mido:/data/local/tmp $ ./server test_socket                                    
Failed to bind to socket at test_socket.
1|mido:/data/local/tmp $ ./server /sdcard/test_socket                          
Failed to bind to socket at /sdcard/test_socket.
1|mido:/data/local/tmp $ ./server /storage/FC30-3DA9/test_socket               
Failed to bind to socket at /storage/FC30-3DA9/test_socket.
1|mido:/data/local/tmp $ ./server /storage/self/primary/test_socket
Failed to bind to socket at /storage/self/primary/test_socket.
1|mido:/data/local/tmp $ ./server /storage/emulated/test_socket                
Failed to bind to socket at /storage/emulated/test_socket.
1|mido:/data/local/tmp $ 

No good! Android does not let me create a UNIX Socket. Well, then, uhh…

take 2!

An approach with UNIX sockets in the abstract namespace

There is an alternative solution to create a UNIX socket. Even if we cannot map it to an entity on the file system, we are still able to create it in the abstract namespace.

In fact, Android does provide an API for the latter, called LocalServerSocket.

Creating a socket in the abstract namespace pretty much means we can name it however we want. Not using a file for representing our socket, however, comes with a serious disadvantage: it is just as unguarded as a TCP port number.

But hey! At least it works! Here is the relevant part of the Android application:

// ...

// The name is specified in the constructor
serverSocket = new LocalServerSocket("com.codekuklin.UNIX_socket_time_applet.time_applet");

// It is not only sensible but also strictly required by the API
// that the network function does not block the main thread
new Thread(() -> { 
    for(;;) {
        try {
            LocalSocket connection = serverSocket.accept();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));

            String currentTime = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
            String out = "Current time: " + currentTime + "\n";

            writer.write(out);
            writer.flush();

            connection.close();
        } catch (IOException e) {
            // ... handle error somehow
        }
    }
}).start();

// ...
$ adb forward localfilesystem:./test_socket localabstract:com.codekuklin.UNIX_socket_time_applet.time_applet
* daemon not running; starting now at tcp:5037
* daemon started successfully
$ ls
client  server  test_socket
$ ./client test_socket 
Got message: Current time: 2019-12-26 16:19:48
$

We’re not finished yet…

Spending a lot of time googling I did not really found a reliable solution for using file system level UNIX sockets on Android.

The greatest problem with the solution above is that the abstract namespaced socket is at the risk of being abused by any of the apps installed on the phone.

This socket, however, is in the wild public, and it is even very easy to find!

generic_x86_64:/$ cat /proc/net/UNIX | grep com                             
0000000000000000: 00000002 00000000 00010000 0001 01 46156 @com.codekuklin.UNIX_socket_time_applet.time_applet

Well, at least, the PC is okay?

At the other side of the line - on the computer - a file system guarded UNIX socket is used. I was about to write that it is somewhat more secure on the machine end, but unfortunately, there is also difference.

On the computer we are able to gain administrative rights, whereas we cannot do that if we’re on a phone, unless it is rooted. Therefore, on Android, if an app cannot read the data of another app, and neither is adb capable of that, than that data is locked inside, at least in theory. Only the app can decide how it uses that information. This is also the property that may allow a phone to be converted into a secure encryption key storage.

Now, on Android, in theory, not even a virus could read the locked and guarded data as it does not have permission to. In the case of the computer, we are responsible to limit the permissions of software installed, and usually most installations begin by asking you to unlock the administrative privileges. Once we said okay to an UAC warning, we are at the mercy of the executable.

UAC Warning

(I sometimes find gaining superuser rights is unnecessary anyway for a software installation. We only need create and write permissions to the C:\Program Files\xyz folder and create some registry entries.)

That means, that having the socket endpoint guarded on a computer may help reduce the risk of security exploits of other unprivileged program, but still cannot protect us from rootkits.

There is hope!

The problem of insecure computer networks is something we face daily. The option is still there to secure line by applying some kind of encryption, preferably matching some fingerprints before starting the operation.

It would be interesting to figure out for next time how an encryption layer could be applied.

Thanks for reading!

Download

android_UNIX_socket_poc.zip

Source code available

Icon