{
	"id": "1d6efcf6-c6ea-4122-abd4-c1803cdf0112",
	"created_at": "2026-04-06T00:13:22.770696Z",
	"updated_at": "2026-04-10T13:12:35.564875Z",
	"deleted_at": null,
	"sha1_hash": "663e6637a0b89fc502f6c7a919f9c42b5480b5c0",
	"title": "War of the Worlds - Hijacking the Linux Kernel from QSEE",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 486874,
	"plain_text": "War of the Worlds - Hijacking the Linux Kernel from QSEE\r\nArchived: 2026-04-05 17:50:19 UTC\r\nAfter seeing a full QSEE vulnerability and exploit in the previous blog post, I thought it might be nice to see some\r\nQSEE shellcode in action.\r\nAs we've previously discussed, QSEE is extremely privileged - not only can it interact directly with the TrustZone\r\nkernel and access the hardware-secured TrustZone file-system (SFS), but it also has some direct form of access to\r\nthe system's memory.\r\nIn this blog post we'll see how we can make use of this direct memory access in the \"Secure World\" in order to\r\nhijack the Linux Kernel running in the \"Normal World\", without even requiring a kernel vulnerability.\r\nInteracting with QSEE\r\nAs we've seen in the previous blog post, when a user-space Android application would like to interact with a\r\ntrustlet running in QSEE, it must do so by using a special Linux Kernel device, \"qseecom\". This device issues\r\nSMC calls which are handled by QSEOS, and are passed on to the requested trustlet in order to be handled.\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 1 of 8\n\nEach command issued to a trustlet has a pair of associated input and output buffers, which are usually used to\r\nconvey all the information to and from the \"Normal World\" and the trustlet.\r\nHowever, there are some special use-cases in which a faster mode of communication is required  - for example,\r\nwhen decrypting (or encrypting) large DRM-protected media files, the communication cost must be as small as\r\npossible in order to enable \"smooth\" playback.\r\nMoreover, some devices include trustlets which are meant to assure the device's integrity (mostly in corporate\r\nsettings).  For example, Samsung provides \"TrustZone-based Integrity Measurement Architecture\" (TIMA) - a\r\nframework used to assure device integrity. According to Samsung, TIMA performs (among other things) periodic\r\nmeasurements of the \"Normal World\" kernel, and verifies that they match the original factory kernel.\r\nSo... Trustlets need fast communication with the \"Normal World\" and also need some ability to inspect the\r\nsystem's memory - sounds dangerous! Let's take a closer look.\r\nSharing (Memory) is Caring\r\nContinuing our research on the \"widevine\" trustlet, let's take a look at the command used to DRM-encrypt a chunk\r\nof memory:\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 2 of 8\n\nAs we can see above, the function receives two pointers denoting the \"input\" and \"output\" buffers, respectively.\r\nThese can be any arbitrary buffers provided by the user, so it stands to reason that some preparation would be\r\nneeded in order to access them.  Indeed, we can see that the preparation is done by calling \"cacheflush_register\",\r\nand, once the encryption process is done, the buffers are released by calling \"cacheflush_deregister\".\r\nUpon closer inspection, \"cacheflush_register\" and \"cacheflush_deregister\" are simple wrappers around a couple of\r\nQSEE syscalls (each):\r\ncacheflush_register           cacheflush_deregister         \r\nqsee_register_shared_buffer qsee_prepare_shared_buf_for_nosecure_read\r\nqsee_prepare_shared_buf_for_secure_read qsee_deregister_shared_buffer\r\nSo what do these syscalls do?\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 3 of 8\n\nLooking at the relevant handling code in QSEOS reveals that these names are a little misleading - in fact,\r\n\"qsee_prepare_shared_buf_for_secure_read\" merely invalidates the given range in data cache (so that QSEE will\r\nobserve the updated data), and similarly \"qsee_prepare_shared_buf_for_nosecure_read\" clears the given range\r\nfrom the data cache (so that the \"Normal World\" will see the changes made by QSEE).\r\nAs for \"qsee_register_shared_buffer\" - this syscall is used to actually map the given ranges into QSEE. Let's see\r\nwhat it does:\r\nAfter some sanity checks, the function checks whether the given memory region is within the \"Secure World\". If\r\nthat's the case, it could be that the trustlet is trying to attack the TrustZone kernel by mapping-in and modifying\r\nmemory regions used by TZBSP or QSEOS. Since that would be extremely dangerous, only a select few (six)\r\nspecific regions within the \"Secure World\" can be mapped into QSEE. If the given address range is not within any\r\nof these special \"tagged\" regions, the operation is denied.\r\nHowever - for any address in the \"Normal World\", there are no extra checks made! This means that QSEOS will\r\nhappily allow us to use \"qsee_register_shared_buffer\" in order to map in any physical address in the \"Normal\r\nWorld\".\r\n...Are you pondering what I'm pondering?\r\nSince QSEE has read-write access to all of the \"Normal World\"'s memory (all it needs to do is ask), we should\r\ntheoretically be able to locate the running Linux Kernel in the \"Normal World\" directly in physical memory and\r\ninject code into it.\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 4 of 8\n\nAs a fun exercise, let's create a QSEE shellcode that doesn't require any kernel symbols - this way it can be used in\r\nany QSEE context in order to locate and hijack the running kernel.\r\nRecall that after booting the device, the bootloader uses the data specified in the Android boot image in order to\r\nextract the Linux Kernel into a given physical address and execute it:\r\nThe physical load address of the Linux Kernel is then available to any process via the world-readable file\r\n/proc/iomem:\r\nHowever, simply knowing where the kernel is loaded does not absolve us from the need to find kernel symbols -\r\nthere is a large amount of kernel images and an equally large amount of symbols per kernel. As such, we need\r\nsome way to find the all of the kernel's symbols dynamically using the running kernel's memory. However, all is\r\nnot lost - remember that the Linux Kernel keeps a list of all kernel symbols internally (!), and allows kernel\r\nmodules to lookup these symbols using a special lookup function - \"kallsyms_lookup_name\". So how does this\r\nwork?\r\nAs we've previously seen - the names in the kernel's symbol table are compressed using a 256-entry huffman\r\ncoding generated at build time. The huffman table is stored within the kernel's image, alongside the descriptors for\r\neach symbol denoting the indices in the huffman table used to decompress it's name. And, of course, the actual\r\naddresses for all of the symbols are similarly stored in the kernel's image.\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 5 of 8\n\nIn order to access all the information in the symbol table, we must first find it within the kernel's image.\r\nAs luck would have it, the first region of the symbol table - the \"Symbol Address Table\", always begins with two\r\npointers to the kernel's virtual load address (which can be easily calculated from the kernel's physical load address\r\nsince there's no KASLR). Moreover, the symbol addresses in the table are monotonically nondecreasing addresses\r\nwithin the kernel's virtual address range - a fact which we can use to confirm our suspicion whenever we find two\r\nsuch consecutive pointers to the kernel's virtual load address.\r\nSymbol Address Table\r\nNow that we can find the symbol table within the kernel's image, all we need to do is implement the\r\ndecompression scheme in order to be able to iterate over it and lookup any symbol. Great!\r\nUsing the method above to find the kernel's symbol table, we can now locate and hijack any kernel function from\r\nQSEE. Following the tradition from the previous kernel exploits, let's hijack an easily accessible function pointer\r\nfrom a very rarely-used network protocol - PPPOLAC.\r\nThe function pointers relating to this protocol are stored in the following kernel structure:\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 6 of 8\n\nOverwriting the \"release\" pointer in this structure would cause the kernel to execute our crafted function pointer\r\nwhenever a PPPOLAC socket is closed.\r\nPutting it all together\r\nNow that we have all the pieces, all we need to do to gain code execution within the Linux Kernel is to:\r\nAchieve QSEE code execution\r\nMap-in all the kernel's memory in QSEE using \"qsee_register_shared_buffer\"\r\nFind the kernel's symbol table\r\nLookup the \"pppolac_proto_ops\" symbol in the symbol table\r\nOverwrite any function pointer to our user-supplied function address \r\nFlush the changes made in QSEE using \"qsee_prepare_shared_buf_for_nosecure_read\"\r\nCause the kernel to call our user-supplied function by using a PPPOLAC socket\r\nI've written some QSEE code which performs all of these steps and exports an easy-to-use interface to allow\r\nkernel code execution, like so:\r\nAs always, you can find the full code here:\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 7 of 8\n\nhttps://github.com/laginimaineb/WarOfTheWorlds\r\nI should note that the code currently only reads memory one DWORD at a time, making it quite slow. I didn't\r\nbother to speed it up, but any and all improvements are more than welcome (for example, reading large chunks of\r\nmemory at a time would be much faster).\r\nIn the next blog post, we'll continue our journey from zero-to-TrustZone, and attempt to gain code execution\r\nwithin the TrustZone kernel.\r\nSource: http://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nhttp://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"http://bits-please.blogspot.co.il/2016/05/war-of-worlds-hijacking-linux-kernel.html"
	],
	"report_names": [
		"war-of-worlds-hijacking-linux-kernel.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434402,
	"ts_updated_at": 1775826755,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/663e6637a0b89fc502f6c7a919f9c42b5480b5c0.pdf",
		"text": "https://archive.orkl.eu/663e6637a0b89fc502f6c7a919f9c42b5480b5c0.txt",
		"img": "https://archive.orkl.eu/663e6637a0b89fc502f6c7a919f9c42b5480b5c0.jpg"
	}
}