您的位置:首页 > 运维架构 > Linux

War of the Worlds - Hijacking the Linux Kernel from QSEE

2017-09-28 04:57 1011 查看
After seeing a full QSEE vulnerability and exploit in the previous
blog post, I thought it might be nice to see some QSEE shellcode in action.

As we've previously discussed, QSEE is extremely privileged - not only can it interact directly with the TrustZone kernel and access the hardware-secured
TrustZone file-system (SFS), but it also has some direct form of access to the system's memory.

In this blog post we'll see how we can make use of this direct memory access in the "Secure World" in order to hijack the Linux Kernel running in the
"Normal World", without even requiring a kernel vulnerability.






INTERACTING WITH QSEE


 

As we've seen in the previous blog post, when a user-space Android application would like to interact with a trustlet running in QSEE, it must do so
by using a special Linux Kernel device, "qseecom". This device issues SMC calls which are handled by QSEOS, and are passed on to the requested trustlet in order to be handled.




Each command issued to a trustlet has a pair of associated input and output buffers, which are usually used to convey all the information to and from
the "Normal World" and the trustlet.

However, there are some special use-cases in which a faster mode of communication is required  - for example, when decrypting (or encrypting) large
DRM-protected media files, the communication cost must be as small as possible in order to enable "smooth" playback.

Moreover, some devices include trustlets which are meant to assure the device's integrity (mostly in corporate settings).  For example, Samsung provides
"TrustZone-based Integrity Measurement Architecture"
(TIMA) - a framework used
to assure device integrity. According to Samsung, TIMA performs (among other things) periodic measurements of the "Normal World" kernel, and verifies that they match the original factory kernel. 

So... Trustlets need fast communication with the "Normal World" and also need some ability to inspect the system's memory - sounds dangerous! Let's
take a closer look.


SHARING (MEMORY) IS CARING

Continuing our research on the "widevine" trustlet, let's take a look at the command used to DRM-encrypt a chunk of memory:








As we can see above, the function receives two pointers denoting the "input" and "output" buffers, respectively. These can be any arbitrary buffers
provided by the user, so it stands to reason that some preparation would be needed in order to access them.  Indeed, we can see that the preparation is done by calling "cacheflush_register", and, once the encryption process is done, the buffers are released
by calling "cacheflush_deregister".

Upon closer inspection, "cacheflush_register" and "cacheflush_deregister" are simple wrappers around a couple of QSEE syscalls (each):

cacheflush_register          cacheflush_deregister          
qsee_register_shared_bufferqsee_prepare_shared_buf_for_nosecure_read 
qsee_prepare_shared_buf_for_secure_readqsee_deregister_shared_buffer 
So what do these syscalls do?

Looking at the relevant handling code in QSEOS reveals that these names are a little misleading - in fact, "qsee_prepare_shared_buf_for_secure_read"
merely invalidates the given range in data cache (so that QSEE will observe the updated data), and similarly "qsee_prepare_shared_buf_for_nosecure_read" clears the given range from the data cache (so that the "Normal World" will see the changes made by QSEE). 

As for "qsee_register_shared_buffer" - this syscall is used to actually map the given ranges into QSEE. Let's see what it does:





After some sanity checks, the function checks whether the given memory region is within the "Secure World". If that's the case, it could be that the
trustlet is trying to attack the TrustZone kernel by mapping-in and modifying memory regions used by TZBSP or QSEOS. Since that would be extremely dangerous, only a select few (six) specific regions within the "Secure World" can be mapped into QSEE. If the
given address range is not within any of these special "tagged" regions, the operation is denied.

However - for any address in the "Normal World", there are no extra checks made! This means that QSEOS will happily allow us to use "qsee_register_shared_buffer"
in order to map in any physical address in the "Normal World".

...Are you pondering what I'm pondering?


HIJACKING THE LINUX KERNEL

Since QSEE has read-write access to all of the "Normal World"'s memory (all it needs to do is ask), we should theoretically be able to locate the running
Linux Kernel in the "Normal World" directly in physical memory and inject code into it.

As a fun exercise, let's create a QSEE shellcode that doesn't require any kernel symbols - this way it can be used in any QSEE context in order to locate
and hijack the running kernel.

Recall that after booting the device, the bootloader uses the data specified in the Android boot image in order to extract the Linux Kernel into a given
physical address and execute it: 





The physical load address of the Linux Kernel is then available to any process via the world-readable file /proc/iomem:





However, simply knowing where the kernel is loaded does not absolve us from the need to find kernel symbols - there is a large amount of kernel images
and an equally large amount of symbols per kernel. As such, we need some way to find the all of the kernel's symbols dynamically using the running kernel's memory. However, all is not lost - remember that the Linux Kernel keeps a list of all kernel symbols
internally (!), and allows kernel modules to lookup these symbols using a special lookup function - "kallsyms_lookup_name". So how does this work?

As we've previously seen -
the names in the kernel's symbol table are compressed using a 256-entry huffman coding generated at build time. The huffman table is stored within the kernel's image, alongside the descriptors for each symbol denoting the indices in the huffman table used
to decompress it's name. And, of course, the actual addresses for all of the symbols are similarly stored in the kernel's image.





In order to access all the information in the symbol table, we must first find it within the kernel's image.

As luck would have it, the first region of the symbol table - the "Symbol Address Table", always begins with two pointers to the kernel's virtual load
address (which can be easily calculated from the kernel's physical load address since there's no KASLR). Moreover, the symbol addresses in the table are monotonically nondecreasing
addresses within the kernel's virtual address range - a fact which we can use to confirm our suspicion whenever we find two such consecutive pointers to the kernel's virtual load address.



Symbol Address Table
Now that we
can find the symbol table within the kernel's image, all we need to do is implement the decompression scheme in order to be able to iterate over it and lookup any symbol. Great!

Using the method above to find the kernel's symbol table, we can now locate and hijack any kernel function from QSEE. Following the tradition from the previous
kernel exploits, let's hijack an easily accessible function pointer from a very rarely-used network protocol - PPPOLAC.

The function pointers relating to this protocol are stored in the following kernel structure:




Overwriting the "release" pointer in this structure would cause the kernel to execute our crafted function pointer whenever a PPPOLAC socket is closed.


PUTTING IT ALL TOGETHER

Now that we have all the pieces, all we need to do to gain code execution within the Linux Kernel is to:
Achieve QSEE code execution
Map-in all the kernel's memory in QSEE using "qsee_register_shared_buffer"
Find the kernel's symbol table
Lookup the "pppolac_proto_ops" symbol in the symbol table
Overwrite any function pointer to our user-supplied function address 
Flush the changes made in QSEE using "qsee_prepare_shared_buf_for_nosecure_read"
Cause the kernel to call our user-supplied function by using a PPPOLAC socket
I've written some QSEE code which performs all of these steps and exports an easy-to-use interface to allow kernel code execution, like so:





As always, you can find the full code here:

https://github.com/laginimaineb/WarOfTheWorlds

I should note that the code currently only reads memory one DWORD at a time, making it quite slow. I didn't bother to speed it up, but any and all improvements
are more than welcome (for example, reading large chunks of memory at a time would be much faster).

In the next blog post, we'll continue our journey from zero-to-TrustZone, and attempt to gain code execution within the TrustZone kernel.

Source: http://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐