Search code examples
javaandroidandroid-ndkandroid-shell

Android: Execute binary from application


As the title suggests I want to execute a binary from my android application. I created a binary with the NDK and can run it with the adb shell. I then tried to run it with Runtime exec, but the permission is denied. I tried different directories and changing the file permissions.

I looked into the problem and found a lot of stack overflow questions/answers that are mostly outdated. It seems like Android security changed a lot over the different versions.

What I found out: The OS prevents an application to execute code that is was written by itself. This is called Write xor execute and makes sense. Exceptions to this are .apk and .dex files. This should mean what I want is not possible.

I also found some applications that are able to execute such binaries like Android offline C compiler, which compiles and executes C code. Another well-known application that can execute binaries is the terminal emulator Termux. Also, interesting is that both of them have the same performance as the adb shell, when executing a binary file. Therefore, I assumed that they run the binaries over the shell.

What I ultimately want to find out is how these Apps can execute these binaries and if I can achieve the same.

I further looked into the code of these two applications and they use native code to create a process that executes shell commands. I instead always used the Runtime object, which can be accessed from Java. Also, they used native code to change file permissions. This makes me wonder if using native code gives a developer more freedom regarding these restrictions.

My question: Does it make a difference if Java or native code is used when trying to execute binaries? If no, what is the reason they can execute binaries and can this be achieved with Java?

Here is the native code from Termux that creates a subprocess, which executes the shell commands:


static int create_subprocess(JNIEnv *env, const char *cmd, char *const argv[], char *const envp[], int masterFd)
{
    // same size as Android 1.6 libc/unistd/ptsname_r.c
    char devname[64];
    pid_t pid;

    fcntl(masterFd, F_SETFD, FD_CLOEXEC);

    // grantpt is unnecessary, because we already assume devpts by using /dev/ptmx
    if(unlockpt(masterFd)){
        throwIOException(env, errno, "trouble with /dev/ptmx");
        return -1;
    }
    memset(devname, 0, sizeof(devname));
    // Early (Android 1.6) bionic versions of ptsname_r had a bug where they returned the buffer
    // instead of 0 on success.  A compatible way of telling whether ptsname_r
    // succeeded is to zero out errno and check it after the call
    errno = 0;
    int ptsResult = ptsname_r(masterFd, devname, sizeof(devname));
    if (ptsResult && errno) {
        throwIOException(env, errno, "ptsname_r returned error");
        return -1;
    }

    pid = fork();
    if(pid < 0) {
        throwIOException(env, errno, "fork failed");
        return -1;
    }

    if(pid == 0){
        int pts;

        setsid();

        pts = open(devname, O_RDWR);
        if(pts < 0) exit(-1);

        ioctl(pts, TIOCSCTTY, 0);

        dup2(pts, 0);
        dup2(pts, 1);
        dup2(pts, 2);

        closeNonstandardFileDescriptors();

        if (envp) {
            for (; *envp; ++envp) {
                putenv(*envp);
            }
        }

        execv(cmd, argv);
        exit(-1);
    } else {
        return (int) pid;
    }
}

If I am missing something and there is some other trick to execute binaries I would also like to hear it.

Any help and/or insight is very much appreciated!

P.S.: Probably also important, I do not want to include the binary in the .apk, but download or write it during runtime of my application. Also, the binary is nothing complicated. I currently just try to execute a "Hello world"-binary.


Solution

  • I found the difference and it has nothing to do with native code...

    These applications use targetSdkVersion 28 or lower, where the execution of binaries was still allowed. This means that binaries can be executed with the Runtime object as well as with native code in this SDK version.

    The applications can still be installed on newer Android versions.

    So I solved my issue by setting the targetSdkVersion to 28.