How to hack the Android kernel in 3 easy steps to gain unrestricted read/write access, deliver malware and install arbitrary code.
This is part 3 on the Futex Vulnerability. This time we’ll discuss how to use the basic building block of the limited form of kernel-write we found last time in order to get unrestricted write to the kernel, and ultimately root privileges.
Link to part 1 in this series: The Futex Vulnerability
Link to part 2 in this series: Escalating Futex Vulnerability & How to Fix it
A Quick Recap – Hacking the Android Kernel
I’ll just quickly review what we have so far. Using the futex bug, we created a scenario where the waiter list associated with a certain lock contains dangling references to the thread which called
By calling a
sendmmsg with specific parameters, we were able to make the dangling waiter object point to a fake waiter object in shared memory. The fake waiter points to another fake waiter in shared memory as its previous node.
When we add a new waiter, which is basically spawning a thread and calling
futex_requeue_pi ,whose priority is greater than the dangling
rt_waiter, but smaller than the fake waiter’s, it gets added between the two fake waiters, exposing the kernel address of the new waiter via the next/prev pointers of the fake waiters.
The process can be encapsulated in the following expression:
*(fake_waiter.prev) = kernel_address_of_added_waiter;
Now, while the first time the fake waiter pointed to shared memory, the result was a disclosure of a kernel address, we can use this method to write anywhere in the kernel provided we have a valid address, which we now do.
Extending the Reach
The form of write we have now is pretty limited for two reasons, first, we can only write, and second, we can only write values which are addresses of
Not all is lost though. First, let’s remember that the address of the waiter is an address in the kernel stack of the thread whose waiter was added to the list. Let’s call it the compromised thread for reasons which will be obvious soon enough.
In the kernel, each thread is allocated its own stack, at the bottom of which there’s a
thread_info structure which describes several parameters of the thread. Among which is the
Every time the kernel handles a user supplied address, it verifies that this address is below
addr_limit, so that a user can’t just read/write to kernel memory via:
fd = open(
write(fd, kernel_address, 0x1000);
In the emulated kernel, the limit is set to
Now, if we could infer the address of
addr_limit from the disclosed address we would be able to write to it.
Luckily we can. The kernel allocates the stack aligned to a 8KiB boundary, which means that the base of the stack, and consequently the address of the compromised thread’s
thread_info is at
disclosed_address & (~(8 * 1024 - 1)) = disclosed_address & 0xffffe000. The
addr_limit field is just 8 bytes above that address.
Good, then we have an address, which means we can prepare the fake waiter’s prev to point to it, and when we trigger adding a new waiter to the list, we will override
addr_limit with an address in the kernel. We still have several problems though:
- The compromised thread is sleeping. Well that’s not a big problem, we can wake it up with a signal.
- The values with which we override the
addr_limit might be below the stack frame of the compromised thread, so it doesn’t really help us very much.
To solve the second issue, we can perform the following staggered process: on the main thread we’ll be spawning new waiter threads, after every spawn, we try to read
addr_limit in the compromised thread using the write trick. We do that until we manage to sneak in a successful write. Once this happens, i.e. if we can read
addr_limit, then we can also write to
addr_limit using the same trick. In order to gain unrestricted read/write, we’d want to write
uint32_t max_limit = 0xffffffff;
We can now read and write anywhere in the kernel, but only from the compromised thread.
Unrestricted Root Access to Android
If you’ll notice, the
thread_info structure contains a pointer to the task structure. We can now read the address of the structure and the structure itself. There’s a particular field which interests us in the task structure:
cred structure specifies the process’ credentials. If we were to change all the ids to 0, we’ll essentially make the process a root process, following which we can do whatever we want in the context of that process.
In Summary – Hacking the Android Kernel
This is where this article series comes to its end. We’ve covered the inner workings of the TowelRoot exploit from its core and the bugs on which it was based, up to the gaining kernel r/w and root on the device.
There’s just one open issue left, which should be somewhat challenging. You see, all the things we did wreaked havoc on the waiter list and the waiter objects, which as you remember from the first article, leaves the kernel in a fragile state, where as soon as all the threads are released will cause the kernel to panic, which in a real-life scenario gets translated to a device (soft) reset. It would be interesting to try to locate all the orphaned waiters and mend the waiter list. I’ll leave this as an open challenge to you readers.
That’s it. I hope you found the articles informative, insightful and ultimately an enjoyable read.