Add a Remote Shell to your Android App
Android
Add a Remote Shell to your Android App
2016-11-05
By
David "DeMO" Martínez Oliveira

This may sound weird but, some time ago, I actually was in the need of this functionality. At that time I was developing an application that used some external USB device. In other words, I couldn't connect the Android Device to my development machine to access the debug messages and I couldn't figure out any other way. So...
Sure there are quite some other options to get those messages. You can install an ssh server and connect to the device or just use NetKitty and do the same. By the way, you know you can download an Android-ready version from the tools page, don't you?

All those options are perfect. The only cons is that you need to install and configure some SW. That is not really a big deal but... let's face it. It is not cool. OK, using NetKitty is pretty cool, but other than that, any other solution you may come up will not be that awesome as the one we are going to present in this paper.

So, let's go.

Create an Android App

We will start creating an Android app. We do not need anything special so, the handy "Hello World" created by the Android SDK tools will just work.

Please refer to Android Development for Web Programmers for details on how to setup your environment. You can skip the WebView modification part, but that will also work fine.

So, supposing you have your dev environment configured, the following commands will just create our test apk

$ android create project --target 2 --name RemoteShell --path ./RemoteShell --activity RemoteShell --package com.papermintdesigns.remoteshell
$ cd RemoteShell
$ ant debug
$ adb install -r  bin/RemoteShell-debug.apk

After that you should see a nice application in your device/emulator showing a friendly message.

Going Native

Now, we are going to extend our cute application with some native code. You will find out soon that this is easier than you have imagined.

Note: You need to install the Android NDK to be able to go native!. You may want to check the first part of the AAXH series

So, first move to your source directory (should be RemoteShell) and create a folder named jni

$ cd RemoteShell
~RemoteShell $ mkdir jni
~RemoteShell $ cd jni
~RemoteShell/jni $ 

Now we have to create some files to compile our native code and include it into the application. The first one is a kind-of Makefile. We have to name it Android.mk.

~RemoteShell/jni $ cat < LOCAL_PATH := $(call my-dir)
>
> include $(CLEAR_VARS)
> 
> LOCAL_SRC_FILES := rs-jni.c
> LOCAL_MODULE    := rs-jni
>
> LOCAL_LDLIBS := -llog
>
> include $(BUILD_SHARED_LIBRARY)
>
> EOM
~RemoteShell/jni $

Sure you can use nano if you want LoL!

We need to create another file to be able to compile our native code. Something like this:

~RemoteShell/jni $ echo "APP_ABI := all" > Application.mk

Yes, yes, you can use nano.

Time to code

Now, we need to write some code. Let's start with some minimal code to get everything up and running and later we will add our remote shell code in here.

Let's create a C source file named rs-jni.c. This is the name we typed in the Android.mk file we created a few seconds ago. This minimal code I mentioned looks like this:

#include <jni.h>
#include <android/log.h>
#define logv(...) __android_log_print (ANDROID_LOG_VERBOSE, "RShell", __VA_ARGS__)

void
Java_com_papermintdesigns_remoteshell_RemoteShell_runDaemon (JNIEnv *env, jobject thiz)
{
	logv ("Remote Shell Seasonal Greetings!\n");
}

The function is just logging a simple message. The _android_log_print function is a kindof equivalent to syslog. The message passed as parameters gets logged in the Android system log and can be seen with logcat. We'll see in a bit how to use that.

The function name... well... that is painful. Basically it is build as:

JAVA_yourpackageName_activityName_methodName_signature

For the package name we have to substitute the dots (.) by underscores (_). In this case, our function does not have a signature so there is nothing after the method name (runDaemon in this case).

This is it for the native part. Now we need to do some changes on the Java side in order to load and run our code.

Calling the C code

Now, in order to invoke our C code, we have to do two small changes in our Android Application. The first one is to declare our method native, and the second one is to load the native component we have just created.

So, open the Java source file and change it like this:

~RemoteShell/jni $ cd ..
~RemoteShell $ cat src/com/papermint_designs/remoteshell/RemoteShell.java
package com.papermint_designs.remoteshell;

import android.app.Activity;
import android.os.Bundle;

public class RemoteShell extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
	// This line you need to add to start the native code
	runDaemon ();
    }
    // This code you have to add too
    public native void  runDaemon();
    static {
        System.loadLibrary("rs-jni");
    }
    // It ends here!
}

So we have declared a native method named runDaemon whose name matches the name of the method we have defined in our C code. Then we use the LoadLibrary method to load our native library. This last command is included in a static class block, so it gets executed when the class is first loaded.

Finally, we have to call our native method. We are doing this from the onCreate method. This method gets executed whenever the activity is created, so it is a good place to call our native code.

Now that we have all the bits and pieces, let's see what we have so far.

Build and Run Take 1

To build the application we need to first build the native code, that will be compiled into a shared library, and then our Android application as we did before:

~RemoteShell $ ndk-build
~RemoteShell $ ant debug

Now we can install our application:

~RemoteShell $ adb install -r bin/RemoteShell-debug.apk

And run it. First we will monitor the system log and then we will start the application from the device. The log monitoring is something like this:

~RemoteShell $ adb logcat RShell:V *:S
--------- beginning of system
--------- beginning of main
--------- beginning of crash

After running our application we should see:

~RemoteShell $ adb logcat RShell:V *:S
--------- beginning of system
--------- beginning of main
--------- beginning of crash
11-02 21:23:04.575  6502  6502 V RShell  : Remote Shell Seasonal Greetings!

Of course, everything before RShell will be different in your system.

Now that we have checked that everything is in place, let's code our remote shell.

A Basic Remote Shell

I will not comment the code for this. I think I have done that a million times. If anybody has any doubt, just drop a comment at the end of the paper.

#include <stdlib.h>

#include <jni.h>
#include <android/log.h>

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/prctl.h>
#include <errno.h>

#define logv(...) __android_log_print (ANDROID_LOG_VERBOSE, "RShell", __VA_ARGS__)

void
Java_com_papermintdesigns_remoteshell_RemoteShell_runDaemon (JNIEnv *env, jobject thiz)
{
  struct sockaddr_in   server, client;
  socklen_t            len = sizeof (struct sockaddr_in);
  int                  s,s1, sid;
  pid_t                pid;

  logv ("Remote Shell Seasonal Greetings!\n");  
  /* try to become a daemon */
  pid = fork ();
  if (pid < 0) exit (1);
  if (pid != 0) return;

  if ((sid = setsid()) < 0)
    logv ("Setsid error (%s)\n", strerror (errno));
   
  while (1)
    {     
      server.sin_addr.s_addr = INADDR_ANY;
      server.sin_family = AF_INET;
      server.sin_port = htons(8080);
      
      s = socket (PF_INET, SOCK_STREAM, 0);
      if (s < 0)
	logv ("Cannot create socket (%s)\n", strerror (errno));
      
      if ((bind (s, (struct sockaddr *) &server, sizeof (server)))< 0)
	logv ("Cannot Bind (%s)\n", strerror (errno));
      
      listen (s, 10);
      while (1)
	{  
	  s1 = accept (s, (struct sockaddr *)&client, &len);
	  if (s1 < 0)
	    {
	      logv("Cannot accept connection (%s)\n", strerror (errno));
	    }
	  
	  pid_t pid ;
	  char *name[3] ;
	  
	  // Create a process to serve this shell session
	  if ((pid = fork ()) < 0)
	    write (2, "Cannot create process\n", 22);
	  else
	    {
	      if (pid) close (s1);
	      else
		{
		  // Dup fds
		  dup2 (s1, 0);
		  dup2 (s1, 1);
		  dup2 (s1, 2);
		  name[0] = "/system/bin/sh";
		  name[1] = "-i";
		  name[2] = NULL;
		  execv (name[0], name );
		  exit (1);
		}
	    }
	}
    } 
}

That's it for the remote shell code. However, there is one more thing we need to do to complete our application. Right now, our test App does not ask for any permission and, in order to access the network we need to ask for permission.

For doing that we just need to add this line to our project's AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />

Add it just before the "<application android..." line.

Now we can rebuild (ant debug), re-install (adb install) and re-launch (tap the icon). Then, just connect to your Android Shell using, for instance, NetKitty:

nk -c T,android_ip:8080

Some Security Related Notes

If you have follow this paper you may be right now connected to a shell in your phone. In general, you will just get errors for most of the commands. The reason is that this is an un-privileged shell and you cannot do much with it.

If your device is rooted you can try to su and then you can do much more. You better run su like this:

su -c /system/bin/sh -i

Otherwise, these are some things you can do with this restricted shell:

  • Print the system log with the logcat command
  • svc and change some parameters
  • input to simulate some user interactions

As a final note, I was experimenting with a slightly modified version of this program on some Android 4.x devices. For that Android version, it is possible to completely detach the process we fork in the native code from the Android process. In other words, even if you kill the Android application, the server will remain in the back ground. This behaviour was fixed on Android 5 and onwards.

RELATED POSTS
Awesome Android eXtreme Hacking. Part I
Awesome Android eXtreme Hacking. Part II (Sensors)
Awesome Android eXtreme Hacking. Part II. More sensors
Awesome Android eXtreme Hacking. Part III. What a Shell!
Awesome Android eXtreme Hacking. Part IV. GNU/Linux on your Pocket
Android Development for Web Programmers