Global Platform (GP) interface
As mentioned in the previous post, TAs implement the GP interface. The most interesting entry point is TA_InvokeCommandEntryPoint, which is executed every time a Client Application (CA) sends a command to the TA.
The GP specifications document the prototype for the function:
Figure 1. TA_InvokeCommandEntryPoint description
The API allows the CA to specify up to 4 parameters. Each parameter has a parameter type associated. The same GP document lists the possible parameter types:
Figure 2. GP parameter types
Assuming that a parameter is used (and therefore its type is not TEE_PARAM_TYPE_NONE), there are two main types: value and memref. The value of each parameter type is encoded as a nibble and merged together they form the paramTypes argument.
TEE_Param is a union, defined as follows:
Figure 3.TEE_Param union definition
Its contents will be filled in differently depending on whether a parameter is of type value or memref:
- value: members a and b are set to the exact same value that was passed by the CA.
- memref: The CA passed a reference to a buffer in its own private memory. The TEE OS will map the same memory in the address space of the TA and fill the buffer and size members of the union accordingly. Specifically, buffer will point to a virtual address in the TA memory space that is guaranteed to be valid. Both the TA and the CA will then have a view of the same physical memory.
From the description, it is clear that checking the parameter types is crucial. If a TA expects a memref , but a CA instead passes a value, there is no guarantee that buffer or a will point to a valid shared memory location. This can cause a type confusion in which the buffer pointer and its size, that a TA assumes to be valid, can be fully controlled by a CA.
GP checks in TAs
Considering the importance of this check, it is worth verifying if all TAs implement it properly. To our surprise, it seemed that not all TAs do so.
The following is taken from the TIMA TA:
Figure 4. TIMA TA parameter types validation
The TA correctly checks the paramTypes argument first, and returns an error if they are not the expected value. This is the expected and non-exploitable behavior.
Let’s now switch to another TA, the HDCP TA (UUID 00000000-0000-0000-0000-000048444350):
Figure 5. HDCP TA parameter types validation
This TA contains no checks at all on the parameter types. The parameters are taken from the input and directly passed to the main function. This means that it is possible to perform a type confusion attack on this TA.
Assuming that we can now set the input parameters to any value we want, let’s try to find a way to exploit this. Ideally, we would like to exploit it to achieve arbitrary read and write and later leverage this to get runtime control of the TA.
After analyzing all the commands implemented in the TA, we found that by chaining three commands, we could obtain both read and write primitives. Note that we do not know the exact purpose of commands implemented in TAs since the GP API does not define any standard commands. They are TA specific, and the same command values in different TAs will likely have a completely different functionality. The commands we targeted are FB, FC and CB:
- Command FB:
This command takes an input buffer from an Android app, wraps it into a secure object (meaning that the buffer is encrypted and signed with a TA specific key) and returns the secure object back to the app. Because of the type confusion vulnerability, both input and output pointers can be fully controlled by an attacker. However, the command by itself does not provide a very powerful primitive since the output contents are unpredictable.
- Command FC:
Command FC does the inverse of command FB: it takes a secure object and unwraps it back to its original contents. However, the unwrapped output is not returned to the REE, it is instead stored at a fixed memory location inside the TA.
- Command CB:
Finally, command CB takes the buffer that contains the unwrapped data and returns a part of it back to the REE. Note that the destination address can also be set to point to TA internal memory because of the type confusion vulnerability.
Normally, the chaining of the three commands works as follows:
Figure 6. Normal chaining for commands FB/FC/CB
Command FB takes input coming from the REE, encrypts it and returns it back to the REE. The REE passes it to the TA with command FC, which decrypts it and stores it in a TA internal memory location. Finally, with command CB a part of the buffer is returned back to the REE. The final outcome is that the output of command CB is a partial copy of the input of command FB.
Arbitrary read can be obtained by specifying the input of command FB to point to TA internal memory, and the output of CB to point to REE memory:
Figure 7. Type confusion attack on the FB command input to extract TA memory
Similarly, arbitrary write can be obtained by setting the input of command FB to REE memory and the output of command CB to TA memory:
Figure 8. Type confusion attack on the CB command output to overwrite TA memory
We now have arbitrary read and write within the HDCP TA. This is already a significant achievement since TAs are supposed to be secure and completely isolated from the untrusted Android OS. However, because of exploit mitigations implemented by Samsung, there are still two complications:
- Due to ASLR, we do not know the exact memory mapping of the TA.
- As we want to leverage the TA to escalate privileges and obtain full access to the whole TEE memory, we probably want to get code execution as well.
As mentioned in the previous blog post, ASLR is implemented as a random slide applied to the code and data sections. The slide is a random between 0 and 32767 multiplied by 4096 (the page size).
We did not find a way to somehow leak information (e.g. a pointer value) that would allow us to recover the random offset. What happens if we just chain the three commands showed above in an attempt to read from a random location? The TA will crash, and on the Android side, we will get an error log in dmesg:
[ 119.608950] SW> [TEEgris:SCrypto] SCrypto 2.4 is in FIPS approved mode
[ 119.608979] SW> HDCP : TA_CreateEntryPoint
[ 119.608992] SW> HDCP : TA_OpenSessionEntryPoint
[ 119.612625] SW> SECUREOS VERSION: Samsung Secure OS Release Version 220.127.116.11 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[ 119.612651] SW> ERR: UNKNOWN TEE_MemMove() pid=114: Panic Reason: check of [inbuf] parameter is failed in ID_TEE_MemMove
[ 119.612665] SW> SECUREOS VERSION: Samsung Secure OS Release Version 18.104.22.168 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[ 119.612678] SW> ERR: UNKNOWN _TEE_Panic() pid=114: Function No: 0x607, Specification No: 0xa
[ 119.612688] SW> SECUREOS VERSION: Samsung Secure OS Release Version 22.214.171.124 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[ 119.612699] SW> ERR: UNKNOWN _TEE_Panic() pid=114: Panic thrown out from file:
[ 119.612706] SW> src/teesl/tee_string.c,
[ 119.612712] SW> Line - 20,
[ 119.612719] SW> Code - 0x607
[ 119.612727] SW> Samsung Secure OS Release Version 126.96.36.199 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[ 119.612734] SW> Th#222 of Pid=114 panicked with code: 0x00000607
However, nothing prevents us from trying again to talk to the same TA, access the same random address, and see if we have been lucky this time so that we actually hit mapped memory. This can be repeated a number of times until the address we tried to access is mapped. Due to the fact that the possible random numbers are only 32k, finding a lucky random is not so difficult and can be usually achieved within less than one minute. While not ideal during exploit development, it can be acceptable. Also, note that the actual chance of hitting a valid random is higher than 1 in 32k, since we have access to an arbitrary read primitive and we only need to find and read one page from the binary that is mapped; after extracting the memory contents, we can match them with the known binary contents to retrieve the actual random offset.
Achieving code execution
At this stage, we have arbitrary read/write and an ASLR bypass. With such a powerful primitive, it is not too difficult to leverage it to obtain code execution. Several options are possible, but in the end, we decided to combine it with another vulnerability we identified in the TA: a stack based buffer overflow.
Command TZ_RepeaterAuth_Send_ReceiverId_List_T() receives certain information within the request buffer, performs some verification over the passed parameters, and then calls function TZ_RepeaterAuth_Send_ReceiverId_List20_T() or TZ_RepeaterAuth_Send_ReceiverId_List21_T() depending on the set HDCP version. However, as it can be seen in the following figure, the parameter verification performs the size validation by comparing two attacker provided values: request and req_size. This allows an attacker to craft a request such that the check always succeeds.
Figure 9. Decompiled TZ_RepeaterAuth_Send_ReceiverId_List_T function
Going into function TZ_RepeaterAuth_Send_ReceiverId_List20_T() in the following figure we can see that the contents of our request buffer are copied into an array of 160 bytes which resides on the stack with a controlled size of 5 * request. This leads to a stack-based buffer overflow of the in buffer.
Figure 10. Decompiled TZ_RepeaterAuth_Send_ReceiverId_List20_T function
This a textbook stack-based buffer overflow, in which we control the size of the copy and the buffer contents. In total, up to 1275 bytes can be copied, which are plenty of space for storing shellcode. However, the TA uses stack canaries as shown in the following figure, therefore exploitation of this vulnerability is not trivial:
Figure 11. Disassembled epilogue of TZ_RepeaterAuth_Send_ReceiverId_List20_T
Due to the fact that we have arbitrary read and ASLR bypass, we can simply read the value of __stack_chk_guard and fill it in our shellcode so that the canary verification will succeed.
We tried to combine all of the steps to prove that we finally can control the program counter (PC) in the HDCP TA. When the stack canary is set to the wrong value, we get the following debug information in dmesg:
[38142.232347] SW> [TEEgris:SCrypto] SCrypto 2.4 is in FIPS approved mode
[38142.232374] SW> HDCP : TA_CreateEntryPoint
[38142.232381] SW> HDCP : TA_OpenSessionEntryPoint
[38142.240917] SW> TZ_SET_HDCP_VERSION_T : HDCP 2.0 version is setuped
[38142.243372] SW> *** tzsl detected *** Stack smashing
[38142.243387] SW> rettadr: 0x286f9c
When the canary is read and set correctly, and we replace the return address on the stack with 0xAAAAAAAA, the following is printed instead:
[37276.997902] SW> [TEEgris:SCrypto] SCrypto 2.4 is in FIPS approved mode
[37276.997921] SW> HDCP : TA_CreateEntryPoint
[37276.997928] SW> HDCP : TA_OpenSessionEntryPoint
[37277.005590] SW> TZ_SET_HDCP_VERSION_T : HDCP 2.0 version is setuped
[37277.007939] SW> Samsung Secure OS Release Version 188.8.131.52 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[37277.007952] SW> Th#2528 of Pid=5344 panicked with signal: 5 (SIGTRAP)
[37277.007960] SW> Fault addr 0xaaaaaaaa in module N/A
This concludes the first part of the TA exploitation. We obtained arbitrary read, write, and code execution within the HDCP TA; however, we are only allowed to reuse existing code because of XN. More on this in part 3 of our blog series.
Just when we reached this point in our investigation, we saw the following on Samsung’s newest (at the time) security patch notes:
Figure 12. Samsung release notes, including HDCP TA parameter types check
It seemed that the missing parameters check was reported to Samsung by other researchers and fixed. Was it done correctly? Let’s take a look at the new TA_InvokeCommandEntryPoint:
Figure 13. TA_InvokeCommandEntryPoint in the new HDCP TA
It seems that the function implements proper checks on the parameter types. The stack based buffer overflow is still present, however it is not exploitable without a way to leak the canary. Does this mean that all the work was done for nothing?
Well, not exactly. In the end, TAs are just a signed blob of code that is passed by the REE to the TEE to be executed. What happens if the REE asks the TEE to execute the old TA? In the previous post, we saw that depending on the TA version, anti-rollback can be enabled. Version SEC2 does not support it, while SEC3 and SEC4 do. What’s the version specified in the header for the new HDCP TA? Let’s open it with a hex editor.
Figure 14. New TA header
It seems that the new TA version is still SEC2! This means that there should be no anti-rollback enforced. We then proceeded to verify this: we upgraded the firmware of our phone to the newer release and created a modified copy of “libteecl.so” that looks for TAs in “/data/local/tmp” instead of “/vendor/tee”. This way, we can put the old TA in /data/local/tmp and have the TEE execute that one instead.
Indeed, the TEEGRIS OS allowed loading the old TA, which still contained the exploitable vulnerabilities. This means that even though the new TA version correctly fixed the parameters check, an attacker can always force the TEE to load the old TA to go back to the vulnerable state.
In this blog post, we demonstrated how it was possible to identify and exploit two vulnerabilities in the HDCP TA, and we showed the importance of anti-rollback (or lack thereof) in TEEs.
Stay tuned for the next post, in which we will show how we managed to escalate privilege and get complete access to the whole TEE memory.