Tamir Zahavi-Brunner, Security Researcher at Zimperium zLabs posted the technical details of the vulnerability affecting multiple high-privileged Android devices and its exploit, earlier this week. Brunner had disclosed this vulnerability to Google who then designated it as CVE-2018-9411.
As per Brunner, Google claims Project Treble ( introduced as part of Android 8.0 Oreo and that makes updates faster and easier for OEMs to roll out to devices) benefits Android security. However, as per the vulnerability disclosed by Brunner, elements of Project Treble could hamper Android security.
“This vulnerability is in a library introduced specifically as part of Project Treble and does not exist in a previous library which does pretty much the same thing. This time, the vulnerability is in a commonly used library, so it affects many high-privileged services”, says Brunner.
One of the massive changes that come with Project Treble is the split of many system services. Previously, these system services contained both AOSP (Android Open Source Project) and vendor code. After Project Treble, all of these services were split into one AOSP service and one or more vendor services called HAL services. This means that data which used to be previously passed in the same process between AOSP and vendor now will have to pass through IPC (enables communication between different Android components) between AOSP and HAL services.
Now, most of the IPC in Android goes through Binder (enables a remote procedure calls mechanism between the client and server processes), so Google decided that the new IPC should do so as well. But Google also decided to perform some modifications.
They introduced HIDL which is a whole new format for the data passed through Binder IPC (makes use of shared memory to maintain simplicity and good performance). HIDL is supported by a new set of libraries and is dedicated to the new Binder domain for IPC between AOSP and HAL services. HIDL comes with its own new implementation for many types of objects. An important object for sharing memory in HIDL is hidl_memory.
Technical details of the Vulnerability
The hidl_memory comprises members namely, mHandle (HIDL object which holds file descriptors, mSize (size of the memory to be shared), mName (represents the type of memory).
These structures are transferred through Binder in HIDL, where complex objects (like hidl_handle or hidl_string) have their own custom code for writing and reading the data.
Transferring structures via 64-bit processes cause no issues, however, this size gets truncated to 32 bit in 32-bit processes, so only the lower 32 bits are used. So if a 32-bit process receives a hidl_memory whose size is bigger than UINT32_MAX (0xFFFFFFFF), the actually mapped memory region will be much smaller.
“For instance, for a hidl_memory with a size of 0x100001000, the size of the memory region will only be 0x1000. In this scenario, if the 32-bit process performs bounds checks based on the hidl_memory size, they will hopelessly fail, as they will falsely indicate that the memory region spans over more than the entire memory space. This is the vulnerability!” writes Brunner.
After the vulnerability has been tracked, it is time to find a target for the vulnerability. To find the target, an eligible HAL service is needed such as android.hardware.cas, or MediaCasService. MediaCasService allows the apps to decrypt the encrypted data.
Exploiting the Vulnerability
To exploit the vulnerability, there are two other issues that need to be solved such as finding the address of the shared memory and of other interesting data and making sure that the shared memory gets mapped in the same location each time.
The second issue gets solved by looking at the memory maps of the linker in the service memory space. To solve the first issue, the data in the linker_alloc straight after the gap is analyzed, and a shared memory is mapped before a blocked thread stack, which makes it easy to reach the memory relatively through the vulnerability. Hence, instead of only getting one thread to that blocked state, multiple (5) threads are generated, which in turn, causes more threads to be created, and more thread stacks to get allocated.
Once this shared memory gets mapped before the blocked thread stack, the vulnerability is used to read two things from the thread stack, the thread stack address, and the address where libc is mapped at to build a ROP chain. The last step is executing this ROP chain. However, Brunner states that the SELinux limitations on this process prevent turning this ROP chain into full arbitrary code execution.
“There is no execmem permission, so anonymous memory cannot be mapped as executable, and we have no control over file types which can be mapped as executable”.
Now, as the main objective is to obtain the QSEOS version, a code using ROP chain does that. This makes sure that the thread does not crash immediately after running the ROP chain. Then this process is left in a bit of an unstable state. To leave everything in a clean state, service using the vulnerability is crashed (by writing to an unmapped address) in order to let it restart.
For complete information, read the official Zimperium blog post.