Our extended paper describes our research on the cryptographic design and implementation of Android's Hardware-Backed Keystore in Samsung's Galaxy devices. This documents extends the paper by providing technical details that help understand our research.
As Kinibi and QSEE have been more thoroughly studied by the security community (e.g. by Quarkslab and by Beniamini), when we discuss details we refer to TEEGRIS unless otherwise noted.
This document borrows some text from the appendix of the extended paper.
In this work, we expose the cryptographic design and implementation of Android's Hardware-Backed Keystore in Samsung's Galaxy S8, S9, S10, S20, and S21 flagship devices. We reversed-engineered and provide a detailed description of the cryptographic design and code structure, and we unveil severe design flaws.
We present an IV reuse attack on AES-GCM that allows an attacker to extract hardware-protected key material, and a downgrade attack that makes even the latest Samsung devices vulnerable to the IV reuse attack.
For the full details and proof-of-concept attacks, see the README of each attack. In the remainder of this document, we describe technical details that supplement our paper.
For the full introduction and background please see the paper.
Android's Hardware-Backed Keystore uses secure software that runs in the TEE to handle cryptographic operations. Per the documentation:
Keymaster TA (trusted application) is the software running in a secure context, most often in TrustZone on an ARM SoC, that provides all of the secure Keystore operations, has access to the raw key material, validates all of the access control conditions on keys, etc.
ARM provides a reference implementation of secure world software called ARM Trusted Firmware (ATF), and the Secure World is usually implemented by a specific vendor (e.g., Qualcomm, Trustonic, Samsung) based on ATF. ATF is responsible for performing Secure Boot, loading the different bootloaders and launching the REE and TEE. It also contains a reference implementation for a Secure Monitor.
To achieve the isolation of the TEE and the REE, TrustZone uses the NS (Non-Secure) bit which is set to 0 if the processor is in Secure state and set to 1 if the processor is in Non-Secure state. The secure state can be switched by executing the SMC opcode (in exception level higher than EL0, e.g., EL1).
The Secure state applies to hardware peripherals and memory, by using the TZASC register (allows to restrict memory to Secure World only) and the TrustZone Protection Controller (TZPC). Menarini shows an example of how the Trusted User Interface (TUI) uses the TZPC to modify the display and touch controllers as secure and the TZASC configures secure memory for the display. Thus, a user can enter a pin for a payment which will be safe from any Normal World attacker (even if the attacker executes code in the Android OS kernel) and will not be leaked.
The Normal World can only access Non-secure memory, but the Secure World can access Non-Secure memory. The ARM documentation states that Secure and Non-secure cache entries can coexist, and that the Normal World can only get a cache hit on Non-secure cache lines.
The ARMv8-A processor supports 4 exception levels:
- EL0 - usermode (application in Android, TA in TZOS)
- EL1 - kernelmode (Android kernel, TZOS kernel)
- EL2 - hypervisor (used by Samsung to implement RKP, which protects the integrity of the Android kernel)
- EL3 - Secure Monitor
The following figure1 shows the components in each exception level in the TrustZone architecture:
When the processor is in Secure mode, we can denote S-ELx, e.g., S-EL0 is the secure EL0. Most of our research focuses on S-EL0 (where the Keymaster TA executes), S-EL1 (where the TZOS kernel handles ioctls that the Keymaster TA calls) and EL3 (where the Secure Monitor executes a function handler for a given SMC).
The Secure Monitor provides the interface between the two worlds and performs switching when the SMC (Secure Monitor Call) opcode is executed. Per the ARM SMC Calling Convention:
The SMC instruction is used to generate a synchronous exception that is handled by Secure Monitor code running in EL3. The arguments are passed in registers and then used to select which Secure function to execute. These calls may then be passed on to a Trusted OS in S-EL1.
Note that the Secure World also uses SMC for some operations, such as power management or privileged operations that can only be done in the Secure Monitor (EL3).
A Trusted Application (TA) is a program that runs in the TEE and exposes security services to Android client applications.
The application can open a session with the TA and invoke commands within the session. After receiving a command, a TA parses the commands input, performs required processing and sends a response back to the client. Control is transferred to the TA via the dedicated SMC (Secure Monitor Call) instruction, and the TA and Normal World application usually exchange arguments and output using a shared memory buffer called World Shared Memory.
As performing SMCs requires EL1 privileges, a device driver in the Android kernel handles the communication with the TA and exposes an API for Normal World applications.
Client Applications in the Normal World can communicate with Trusted Applications (TAs) using World Shared Memory buffers and SMCs.
In TEEGRIS, Samsung followed the GlobalPlatform TEE specification that defines a set of C APIs for the development of TA running inside a TEE.
In most cases, a Client Application calls a client API function such as TEEC_OpenSession
in the Normal World (EL0), which triggers an SMC opcode in the Normal World kernel (EL1) and execution switches to the Secure Monitor, who will switch execution to the Secure World TZOS kernel (S-EL1) that will schedule the TA and call the appropriate TA API function such as TA_CreateEntryPoint
, and will return the response to the client. The common API functions are:
TA_CreateEntryPoint
: ConstructorTA_OpenSessionEntryPoint
: Called when a client opens a session withTEEC_OpenSession
TA_InvokeCommandEntryPoint
: Called when a client callsTEEC_InvokeCommand
.TA_CloseSessionEntryPoint
: Called when a client closes a session withTEEC_CloseSession
TA_DestroyEntryPoint
: Destructor
The firmware of the device contains a binary called sboot.bin
that is Samsung's implementation of Secure Boot for Exynos models based on ATF.
Based on our reverse engineering using Ghidra and on previous work by Quarkslab on SBOOT in Galaxy S6 devices with Kinibi as the TZOS, as well as useful information by Tarasikov about reverse engineering SBOOT in Galaxy S10 devices, we extracted BL2 (the second stage bootloader) and the TEEGRIS OS binary from SBOOT. Both SBOOT and TEEGRIS are 64 bit binaries in a proprietary format.
Images of TAs were found in vendor/tee
and system/tee
, while root_task
(a task in TEEGRIS that is similar to init
in Linux and is responsible to spawn TAs) and important libraries were found in startup.tzar
(and were extracted by the following script). We mostly reversed 32 bit TAs (mainly the Keymaster TA) and libraries which are ELF files with a special header and footer, therefore by stripping the headers we were able to reverse them easily - especially as most functions have debug strings (usually with the name of the function and other useful information). Important files include:
00000000-0000-0000-0000-4b45594d5354
: the Keymaster TA (invendor/tee
)libteesl.so
: TEE API for TAslibscrypto.so
: Samsung SCrypto Cryptographic Module, which seems to be a modification of BoringSSLlibtzsl.so
: Includes wrappers to TEEGRIS syscalls
The main object of interest in our research was not TEEGRIS itself but the crypto driver (dev://crypto
) that wraps and unwraps hardware-protected keys.
To begin our exploration we've used the following steps:
- Download (e.g., from SamMobile) the firmware of the specific model (e.g., Samsung Galaxy S10 G973F)
- Extract
system.img
,vendor.img
andsboot.bin
(includes TEEGRIS)- Use
simg2img
to convert the system/vendor images to ext4 images and extract usingtestdisk
ormount
- Use
lz4
to extract archives
- Use
- Download the open source archive for the specific model and extract
startup.tzar
- Use a script by Tarasikov to unpack files in
startup.tzar
- Use a script by Tarasikov to unpack files in
- Read public documentation and security certifications
- Reverse engineer using Ghidra
- Keymaster TA and libraries that it uses
- Keymaster HAL libraries
sboot.bin
for TEEGRIS- The open-source kernel drivers can be used for reference
The Keymaster HAL API is a set of shared-objects that implement the required API for sending and receiving requests to the Keymaster TA using kernel drivers, as well as creating and using world-shared memory buffers.
- Normal World usermode applications can request (using a binder API) cryptographic operations from system applications.
- Normal world usermode system applications, such as
keystored
andvold
, can use the Keymaster HAL API. - Normal World kernel drivers, such as
tzdaemon
, handle requests from Normal World and can perform a SMC to communicate with TEEGRIS and a particular TA. - The Secure Monitor handles SMCs from Normal World and forwards the request to the Secure World OS (TEEGRIS).
- The Secure World kernel handles requests from the Secure Monitor and prepares the Keymaster TA.
- The Keymaster TA in the Secure World usermode handles input from Normal World and returns output to World Shared buffers.
The following figure1 illustrates the flow:
The Keymaster HAL is made up of several components in the Android usermode:
SKeymaster4Device
: Samsung's implementation ofAndroidKeymaster4Device
(a C++ class that implements Keymaster functions by calling API from C libraries). Implemented inlibkeymaster4device.so
.- Wrapper libraries that expose Keymaster API, such as
libkeymaster4.so
[email protected]
: exposes API to construct requests in the vendor-specific format for the Keymaster TA.
Most of our focus was on libkeymaster_helper.so
which the HAL uses.
The Keymaster HAL is registered as a service by the Keymaster HIDL:
IKeymasterDevice* keymaster =
skeymaster::CreateSKeymasterDevice(
SecurityLevel::TRUSTED_ENVIRONMENT);
// ...
status_t status = keymaster->registerAsService();
// ...
LOG(INFO) << "Keymaster HAL service is Ready.";
The CreateKeymasterDevice
function creates an object of SKeymaster4Device
and calls the waitKeymaster_
method which opens a session to the Keymaster TA and runs the configure
command. Later, Android services such as keystored
will call specific Keymaster functions (e.g., generateKey
) that the device object implements - most will use library functions to construct the appropriate requests in the vendor-specific format and use the GlobalPlatform Client API that libteecl.so
implements to send it to the Keymaster TA.
The Keymaster HAL uses libkeymaster_helper.so
to communicate with the Keymaster TA using a TZOS-specific API. By calling it directly we can provide arbitrary parameters to the Keymaster TA and expand the attack surface. By patching it or recreating it we can bypass all usermode input validation (e.g., hash check of key blobs).
The libkeymaster_helper.so
exports the following functions:
KM_Result nwd_open_connection(void);
KM_Result nwd_close_connection(void);
KM_Result nwd_configure(keymaster_key_param_set_t *param_set);
KM_Result nwd_generate_key(
keymaster_key_param_set_t *param_set,
vector_t *ekey,
keymaster_key_characteristics_t *characteristics);
KM_Result nwd_get_key_characteristics(
vector_t *ekey,
vector_t *application_id,
vector_t *application_data,
keymaster_key_characteristics_t * characteristics);
KM_Result nwd_import_key(
keymaster_key_param_set_t *param_set,
long key_format,
vector_t *key_data,
vector_t *ekey,
keymaster_key_characteristics_t *characteristics);
KM_Result nwd_export_key(
long key_format,
vector_t *ekey,
vector_t *application_id,
vector_t *application_data,
vector_t *exported);
KM_Result nwd_upgrade_key(
vector_t *ekey,
keymaster_key_param_set_t *param_set,
vector_t *new_ekey);
KM_Result nwd_begin(
keymaster_key_param_set_t *param_set,
long purpose,
vector_t *ekey,
int64_t *operation_handle,
keymaster_key_param_set_t *out_params);
KM_Result nwd_finish(
keymaster_key_param_set_t *param_set,
vector_t *data,
vector_t *signature,
int64_t *operation_handle,
vector_t *output,
keymaster_key_param_set_t *output_params);
Internaly, the following functions are used:
nwd_tz_open
: Read the TA file and initialize a Keymaster TA context using a TZOS-specific API (e.g., in TEEGRIS it callsTEEC_InitializeContext
,TEECS_OpenSession
, andTEEC_RegisterSharedMemory
).nwd_km_KM_INDATA_pack
: Prepares the vendor-specific formatkm_indata_t
ASN.1, writes it to World Shared memory buffers, and callsnwd_tz_run_cmd
.nwd_tz_run_cmd
: Invokes a command in the Keymaster TA using a TZOS-specific API (e.g., in TEEGRIS it callsTEEC_InvokeCommand
per the GlobalPlatform Client API, and in Kinibi it usesmcNotify
andmcWaitNotification
).nwd_KM_OUTDATA_unpack
: Parse the vendor-specific formatkm_outdata_t
ASN.1 from the World Shared memory buffers.
For our research it's enough to call nwd_import_key
directly (and pass custom parameters), but it can be useful to patch or re-implement the internal functions (e.g., pass the input checks in nwd_km_KM_INDATA_pack
).
The Keymaster TA and Keymaster HAL Normal World libraries communicate using the following formats:
// input from Keymaster HAL to Keymaster TA
typedef struct km_indata_t {
ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00
ASN1_INTEGER *km_ver; // offset: 0x04, flags: 0x00, tag: 0x00
ASN1_INTEGER *cmd; // offset: 0x08, flags: 0x00, tag: 0x00
ASN1_INTEGER *pid; // offset: 0x0c, flags: 0x00, tag: 0x00
ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00
ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01
ASN1_INTEGER *long1; // offset: 0x18, flags: 0x91, tag: 0x02
ASN1_OCTET_STRING *bin0; // offset: 0x1c, flags: 0x91, tag: 0x03
ASN1_OCTET_STRING *bin1; // offset: 0x20, flags: 0x91, tag: 0x04
ASN1_OCTET_STRING *bin2; // offset: 0x24, flags: 0x91, tag: 0x05
ASN1_OCTET_STRING *key; // offset: 0x28, flags: 0x91, tag: 0x06
km_param_t *par; // offset: 0x2c, flags: 0x93, tag: 0x08
int flags;
} km_indata_t;
// Output from Keymaster TA to Keymaster HAL
typedef struct km_outdata_t {
ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00
ASN1_INTEGER *cmd; // offset: 0x04, flags: 0x00, tag: 0x00
ASN1_INTEGER *pid; // offset: 0x08, flags: 0x00, tag: 0x00
ASN1_INTEGER *err; // offset: 0x0c, flags: 0x00, tag: 0x00
ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00
ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01
ASN1_OCTET_STRING *bin0; // offset: 0x18, flags: 0x91, tag: 0x02
ASN1_OCTET_STRING *bin1; // offset: 0x1c, flags: 0x91, tag: 0x03
ASN1_OCTET_STRING *bin2; // offset: 0x20, flags: 0x91, tag: 0x04
ASN1_OCTET_STRING *log; // offset: 0x24, flags: 0x91, tag: 0x05
int flags;
} km_outdata_t;
The Keymaster TA uses ASN.1 to serialize keys as follows:
- The key material is serialized into an ASN.1 structure called
km_key_blob_t
that contains a version number, key material and key parameters (km_param_t
). - The ASN.1 structure is then encrypted using AES-256-GCM with an Hardware Derived Key-encryption-key (HDK). This encryption is called "wrapping" and is the topic of much of our work.
- The "wrapped" key blob is serialized again into another ASN.1 structure called
km_ekey_blob_t
that contains information that is required for decryption, such as the IV and AAD that was used to encrypt.
The following ASN.1 structures are used:
// key parameters
typedef struct km_param_t {
ASN1_INTEGER *tag; // offset: 0x00, flags: 0x00, tag: 0x00
ASN1_INTEGER *i; // offset: 0x04, flags: 0x91, tag: 0x00
ASN1_OCTET_STRING *b; // offset: 0x08, flags: 0x91, tag: 0x01
int flags; // offset: 0x10
} km_param_t;
// key material and key parameters
typedef struct km_key_blob_t {
ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00
ASN1_OCTET_STRING *key; // offset: 0x04, flags: 0x00, tag: 0x00
km_param_t *par; // offset: 0x08, flags: 0x93, tag: 0x00
} km_key_blob_t;
// encrypted key blob and encryption parameters
typedef struct km_ekey_blob_t {
km_key_blob_t *key_blob;
ASN1_INTEGER *enc_ver; // offset: 0x04, flags: 0x00, tag: 0x00
ASN1_OCTET_STRING *ekey; // offset: 0x08, flags: 0x00, tag: 0x00
km_param_t *enc_par; // offset: 0x0c, flags: 0x02, tag: 0x00
} km_ekey_blob_t;
See skeymaster_asn1.h
for more details.
SEQUENCE {
INTEGER 0x0F (15 decimal)
OCTETSTRING FBDA5965263CB66E9B378CD32A3E498BB26AD9A6F70105A16C7EE3AE09ACCDDC2CB9D4C70A35EEE3225872DE0BA4165A644B45279A6F8BD97E505002049951AA9A0E4A29A215452295AE5E1CD5330A9A8E2837019DB09EBABEDA12AF92F648185CB8F1C9AD76AF609471815DA1FA280845ECB6CD10CFB18AABC58087045B4B7CE5D863099799BCA26EA27D54C4A48E675977F7A3DC9DC94D41D17EAFDC17B7800821444FB17549741737DF7C09D5473581736FA4DD45600DFBFF9B94242C00A738788E5EDDFD62F7909DD1B5DFE50755BDDDFA993FE5DE2728C4AF8A3506B592DE19FF4CA8F583B8C941EDE6DE2537C604401E497411055BF3AAB4B01EBF941FFC1DE5B5C4DCD23B75BBC62F5214B783B0FCE8EABD4217773ED81589C251BDB8670E5F658017472F4C5B1774C559149C9D760AA5A80D2954C29BA4A853FB02A5DFADA107A1884352898AD854D6A29EFE6EC4B640B5740DDDF4C79620528C91BBDFF63469EFC436062EE0F41B00C8AE2DDD576CF50666
SET {
SEQUENCE {
INTEGER 0x90001388
[1] {
OCTETSTRING 784D473DD1C619981FFFDCF8
}
}
SEQUENCE {
INTEGER 0x90001389
[1] {
OCTETSTRING 0619443C086011B2263D3D35FA823FD8
}
}
SEQUENCE {
INTEGER 0x90001392
[1] {
OCTETSTRING 476DA2F2613A18C2F034EE4672AA2D58
}
}
}
}
SEQUENCE {
INTEGER 0x02 (2 decimal)
OCTETSTRING 6CB9E19A1A1DD01113FDD2160E2485509DACC4A799A61C7EE70323A3E35F29C6
[0] {
SET {
SEQUENCE {
INTEGER 0x10000002 (268435458 decimal)
[0] {
INTEGER 0x20 (32 decimal)
}
}
SEQUENCE {
INTEGER 0x20000004 (536870916 decimal)
[0] {
INTEGER 0x20 (32 decimal)
}
}
SEQUENCE {
INTEGER 0x30001390 (805311376 decimal)
[0] {
INTEGER 0x0F (15 decimal)
}
}
SEQUENCE {
INTEGER 0x700001F7 (1879048695 decimal)
[0] {
INTEGER 0x01 (1 decimal)
}
}
SEQUENCE {
INTEGER 0x7000025A (1879048794 decimal)
[0] {
INTEGER 0x01 (1 decimal)
}
}
SEQUENCE {
INTEGER 0x30000003 (805306371 decimal)
[0] {
INTEGER 0x0100 (256 decimal)
}
}
SEQUENCE {
INTEGER 0x30000008 (805306376 decimal)
[0] {
INTEGER 0x0080 (128 decimal)
}
}
SEQUENCE {
INTEGER 0x00900002BC (2415919804 decimal)
[1] {
OCTETSTRING 61
}
}
SEQUENCE {
INTEGER 0x0090000259 (2415919705 decimal)
[1] {
OCTETSTRING 6D65
}
}
SEQUENCE {
INTEGER 0x0090001388 (2415924104 decimal)
[1] {
OCTETSTRING 784D473DD1C619981FFFDCF8
}
}
}
}
}
Upon receiving control from an API call (from our client or from the Keymaster HAL), the Keymaster TA has the following flow in TA_InvokeCommandEntryPoint
:
- Validates the parameter types for the input and output buffers and makes sure that the memory references that are sent from the Normal World belong to the REE.
- Parses the input buffer as an ASN.1 structure
indata
and validates it. - Calls the appropriate command handler based on
indata->cmd
. - Fills the output buffer with ASN.1 structure
outdata
.
There are more than 21 command handlers in the Keymaster TA, including:
- swd_add_rng_entropy
- swd_export_key
- swd_import_key
- swd_get_key_characteristics
- swd_begin
- swd_update
- swd_finish
- swd_abort
- swd_generate_key
- swd_encrypt_key
- swd_key_attest
- swd_configure
- swd_key_upgrade
- swd_generate_sak
- swd_install_gak
- swd_import_wrapped_key
- swd_compute_shared_hmac
- swd_get_hmac_sharing_parameter
- swd_verify_authorization
- swd_generate_csr
- swd_install_sgak
Some of the entry points are not documented in the Keymaster API, for instance swd_encrypt_key
.
We saw that the Keymaster TA is similar across different models (both Exynos/Snapdragon variants) of S9, S10, S20, and S21. The only difference was in TZOS-specific (Kinibi/TEEGRIS/QSEE) functions that should be logically equivalent (e.g., syscalls and calls to cryptographic engine).
The main functionality is the same, including the vulnerable flows that we describe. The S9 models have a minor variation that makes it them more vulnerable, as we discuss in the paper.
The following table describes the locations of the Keymaster TA and Keymaster HAL in the different TZOS:
TZOS | Keymaster TA | Keymaster HAL |
---|---|---|
TEEGRIS | /vendor/tee/00000000-0000-0000-0000-4b45594d5354 |
/vendor/lib64/libkeymaster_helper_vendor.so |
QSEE | /vendor/firmware_mnt/image/skeymast |
/vendor/lib64/libkeymaster_helper.so |
Kinibi | /vendor/app/mcRegistry/ffffffff00000000000000000000003e.tlbin |
/vendor/lib64/libkeymaster_helper_vendor.so |
The Keymaster TA encrypts/decrypts key blobs using a function called tz_wrap
/tz_unwrap
, which calls a TZOS-specific function to use the cryptographic engine (e.g., in TEEGRIS it calls TEES_WrappedWithREK
/TEES_DeriveKeyKDF
from libteesl.so
).
The following figure shows some the flow that call tz_unwrap
:
The following figure shows some the flow that call tz_wrap
:
Blob-creating commands (such as swd_generate_key
and swd_import_key
) accept key parameters that are delivered in the indata
structure. The parameters control how the key is generated and are also placed inside the blob. They are subsequently used during the cryptographic operations that take the blob as input. Key parameters include:
- Cipher information including:
- Algorithm (RSA/EC/AES/DES/HMAC)
- Key size (e.g., 768/1024/2048/3072/4096 for RSA or 128/192/256 for AES)
- Mode of operation (e.g., ECB/CBC/CTR/GCM)
- Padding (e.g., none/RSA-OAEP/RSA-PSS)
- Digest (e.g., none/md5/sha1/sha256)
- The parameters can also include optional access control restrictions on the created blob, including:
- Purpose (e.g., limit to encryption/signing only, or only encryption and decryption).
- Maximum number of uses per boot / minimum seconds between operations / expiration date.
- Require authentication (e.g., by password or biometric prompt) or confirmation by the user.
Key parameters are also known as "tags". We mention the following tags (that are not documented in the official API):
KM_TAG_EKEY_BLOB_IV
(value0x90001388
, type bytes)KM_TAG_HEK_RANDOMNESS
(value0x90001392
, type bytes)KM_TAG_EKEY_BLOB_ENC_VER
(value0x30001390
, type integer)KM_TAG_EXPORTABLE
(value0x7000025a
, type boolean)
To ensure that key blobs are hardware-protected, the device uses the following keys:
- Root Encryption Key (REK): a 256-bit AES key that is available only in secure hardware and is device-unique.
- Hardware Derived Key (HDK): a 256-bit AES key that is derived from the REK per blob encryption using the Key Derivation Function (KDF) which we discussed in the paper.
The AES encryption key in the Keymaster TA is derived from a "salt" that the function swd_get_salt
computes - which is the SHA-256 digest of a concatenation of values.
The salt depends on the enc_ver
("encryption version") tag in the encrypted key blob, as well as on the application id and application data tags, and is used to derive an encryption key (HDK) from the hardware REK (Root Encryption Key) in a unique way (different keys per application).
We refer to values of enc_ver
symbolically as either v15
, v20_s9
or v20_s10
based on the constant strings that are used by the KDF and the device model we observed them on (technically enc_ver
is a byte value).
The following figure illustrates how the salt is computed:
On the S8 the v15
KDF is used. On the S9 by default new blobs are created using v20-s9
, and on the S10 and later models, by default, the v20-s10
KDF version is used.
However on the S9, S10, and later models the KDF version can be overridden by the Normal World caller by specifying the tag KM_TAG_EKEY_BLOB_ENC_VER
when generating a new key: this can be used to force the creation of a v15
blob. The fact that the latent code to generate and use v15
blobs exists on newer devices such as S10, S20, and S21 is at the heart of our downgrade attack.
At a high level, the AES operation uses the following fields:
- The IV, that is either generated or is located in the parameters that are required for decryption (
KM_TAG_EKEY_BLOB_IV
) - The AAD that is computed in
swd_get_aad
- The data to encrypt/decrypt
- The authentication tag for decryption (
KM_TAG_EKEY_BLOB_AUTH_TAG
) - A salt value that is computed in
swd_get_salt
and is used by KDF to derive the HDK from the REK.
The function swd_encrypt_ekey
is responsible for encrypting key blobs. On Galaxy S9, it does the following:
- Extracts
enc_ver
from the encrypted key blob: useskm_get_tag
withKM_TAG_EKEY_BLOB_ENC_VER
, and if it fails usesekey_blob->enc_ver
(the ASN1 integer in the ekey). - Computes the salt using
swd_get_salt
(withenc_ver
and key parameters) swd_get_iv
tries to get 12 bytes of IV from the ekey blob (KM_TAG_EKEY_BLOB_IV
), otherwise generates a random IV (RAND_bytes
).- Computes the AAD (Additional Authenticated Data) using
swd_get_aad
- Calls
tz_wrap
with the plaintext key material (serialized as ASN.1), the computed salt, IV and AAD.
On Galaxy S10, S20, and S21, swd_encrypt_ekey
does the following:
- Extracts
enc_ver
from the encrypted key blob: useskm_get_tag
withKM_TAG_EKEY_BLOB_ENC_VER
, and if it fails usesekey_blob->enc_ver
(the ASN1 integer in the ekey). - Generates 16 random bytes for
hek_randomness
- Computes the salt using
swd_get_salt
(withenc_ver
andhek_randomness
) - Computes the AAD (Additional Authenticated Data) using
swd_get_aad
_swd_get_iv
tries to get 12 bytes of IV from the ekey blob (KM_TAG_EKEY_BLOB_IV
), otherwise generates a random IV (RAND_bytes
).- Calls
tz_wrap
with the plaintext key material (serialized as ASN.1), the computed salt, IV and AAD.
Internally, tz_wrap
calls TZOS-specific functions (such asTEES_WrappedWithREK
or TEES_DeriveKeyKDF
on TEEGRIS) to derive a key from the hardware REK and use it for the AES-GCM operation.
The decryption/encryption of ASN.1-serialized key material occurs in the tz_unwrap
/tz_wrap
functions (resp.), which call TEES_WrappedWithREK
/TEES_DeriveKeyKDF
from libteesl.so
, which in turn does a ioctl to the crypto driver (dev://crypto
).
The following figure1 illustrates the two flows that use the salt, IV, AAD, and authentication tag to perform the cryptographic wrapping/unwrapping in TEEGRIS:
If the length of the ASN.1-serialized key is at most 4096 bytes, the Keymaster TA calls the TEES_WrappedWithREK
library function to derive the HDK from the salt and then perform AES-GCM in the crypto engine. Conversely, if the length is greater than 4096 bytes, the Keymaster TA uses the TEES_DeriveKeyKDF
library function to derive the HDK by calling the crypto driver, and then uses a software implementation of AES-GCM-256 (using the SCrypto library that is based on BoringSSL) to perform the encryption.
In order to understand how the key blobs are encrypted, we reversed engineered TEEGRIS, found the dev://crypto
driver and analysed its ioctl method. We focus on two specific ioctl commands: CRYPT_FUNC_WRAPPED_WITH_REK
(that encrypts or decrypts key blobs) and CRYPT_FUNC_KDF
(that derives a HDK from the REK), that are called from TEES_WrappedWithREK
/TEES_DeriveKeyKDF
in the Keymaster TA (resp.).
CRYPT_FUNC_WRAPPED_WITH_REK
checks that the calling task in TEEGRIS is the Keymaster TA by comparing the current UID to the UID of the Keymaster (10 bytes of null, then "KEYMST") and rejects any other task. It then copies the struct that the Keymaster TA sent to DMA memory, edits the salt by appending the Keymaster TA's own UID (16 bytes) and executes an SMC instruction (passing the physical address of the memory where the struct resides as the third argument). If the SMC returns 0, the modified struct is copied back to the Keymaster TA.
CRYPT_FUNC_KDF
also calls the same SMC function but with a different arguments (0 as the first argument instead of 1). It computes the SHA-256 digest of the KDF key, the task UID and group and the salt, then passes the address of the struct that contains both the hash and the HDK (with its length). The SMC fills the bytes of the HDK.
See IV reuse for more details and proof-of-concept attacks.
TL;DR: A privileged attacker can specify an IV during key generation/import, and this IV will be used in the AES-GCM encryption of the key material - as mentioned above, swd_encrypt_ekey
uses the KM_TAG_EKEY_BLOB_IV
tag (that an attacker fully controls) if it is given (otherwise, a random IV is used).
This means that if an attacker can force the AES encryption key to be the same as the key that encrypted a different blob, they will be able to generate a collision - two ciphertexts that were encrypted using the same key and IV.
On Galaxy S9, the salt is deterministic so given an application ID and application data the encryption key will be constant, therefore an attacker can set the IV of a new key blob to be equal to a different key blob and generate a collision - thus compromising any key blob.
Given key blob blob_A
with unknown key key_A
, an attacker can import another blob blob_B
with a known key key_B
that was encrypted using the same IV and the same salt - which means the same hardware-derived key HDK
is used in the AES operation - then xor blob_A
with blob_B
and key_B
to fully recover key_A
, since:
blob_A xor blob_B xor key_B = (E(HDK, IV) xor key_A) xor (E(HDK, IV) xor key_B) xor key_B =
(key_B xor key_B) xor key_A = key_A
To get the same IV, we pass the same value of KM_TAG_EKEY_BLOB_IV
from blob_A
when creating blob_B
.
v15
key blobs are immediately vulnerable to the IV reuse attack: since they are only determined by the application ID and application data (and a constant string), an attacker can generate/import a key using the same IV, application ID and application data (given in ekey blob parameters) and create a collision.
v20-s9
key blobs on Galaxy S9 are also immediately vulnerable - as the key is deterministic (as a function of the application ID and application data and other constant strings), an attacker can force an IV and create a collision. Thus, all key blobs on the S9 are vulnerable.
An ASN.1 string includes a type (e.g. integer/octet string), length and value. Possible values for type include:
0x2
forASN1_INTEGER
0x4
forASN1_OCTET_STRING
0x30
forASN1_SEQUENCE
0x31
forASN1_SET
ekey_blob->ekey
is an ASN.1 string that is the AES-256-GCM encryption of an ASN.1 string of a key blob (i.e., km_key_blob_t
). A key blob includes an ASN1_INTEGER
for version, ASN1_OCTET_STRING
for key material and a SET
of key parameters.
Let num_bytes
be a function that returns the number of bytes that is required to represent a given integer. Let ekey_blob
be a blob that represents a key blob for a key whose material is of length key_len
.
We are interested in the offset of the key material in the encrypted key blob ekey_blob->ekey
, which contains the following:
- 1 byte of type for the key blob:
0x30
num_bytes(ekey->length)
bytes of length, depending on total string lengthekey->length
- 1 byte of type for ASN1_INTEGER (
key_blob->ver
):0x2
- 1 byte of length for ASN1_INTEGER (
key_blob->ver
):0x1
- 1 byte of value for ASN1_INTEGER (
key_blob->ver
): E.g.0x2
(in current version) - 1 byte of type for ASN1_OCTET_STRING (
key_blob->key
):0x4
num_bytes(key_len)
bytes of length, depending on key length (key_len
)key_len
bytes of value for ASN1_OCTET_STRING (key_blob->key
)
That is, 6 + num_bytes(ekey->length) + num_bytes(key_len)
bytes until we reach key material.
See attack.c for the implementation of the IV reuse attack (that finds the offset of encrypted key materials and performs the xor operation).
See downgrade attack for more details and proof-of-concept attacks.
TL;DR: A privileged attacker in the Normal World can set the KM_EKEY_BLOB_ENC_VER
tag in the ekey blob that the Keymaster TA checks in order to decide whether to encrypt with v15
or v20
encryption version. The latent code for the v15
encryption version uses a deterministic KDF, therefore an attacker can perform the IV reuse attack on AES-GCM and recover the full key material.
According to the Keymaster API, key blobs become "old" when the Keymaster device is updated or when the Normal World OS is upgraded to a newer version. When a key becomes "old", the API functions (such as getCharacteristics
/begin
)
return a special error code that indicates that the key must be "upgraded".
The Keymaster API exposes the upgradeKey
method which unwraps (decrypts) the key, examines the OS versions inside the key parameters and compares them to the current OS version. If the current OS version is higher it "upgrades" the key by wrapping (encrypting) it again (and adding the current OS version to the key parameters list).
However, we found that the key parameters that are used in the new blob's wrapping are the same as those in the old key. As any key blob created by our downgrade attack includes the encryption version parameter (KM_TAG_EKEY_BLOB_ENC_VER
), "upgrading" such a downgraded v15
key blob will result in a new but still vulnerable v15
blob.
According to Samsung, this is the intended behavior, and since the S10 and newer devices have v20-s10
as the default version, no v15
key should exist "in the wild". From their response:
The key blob version is information about what data is bound to the KEK (Key Encryption Key) that encrypts the key blob. And, the upgradeKey API is used when a device is upgraded to an image with more recent Security Patch Level information, and it does not upgrade the key blob version, but updates the SPL (Security Patch Level) value in the key blob to the latest. In other words, it is our intended behavior that the key blob version is not upgraded.
However, this behavior of the upgradeKey
function allows our downgrade attack (that forces blobs to be created as v15
) to persist through firmware updates.
There are two reasons for the appearance of KM_TAG_EKEY_BLOB_ENC_VER
inside the key blob:
km_pack_key_data
(responsible for encrypting the key) creates a key blob and sets the blob parameters (key_blob->par
) to the input parameters (client controlled).- When serializing to ASN.1, the function
km_mark_hidden_tags
marks tags that should be removed (e.g. application ID/data, otherKM_EKEY_TAG_*
tags) so that they won't appear in thegetKeyCharacteristics
API and reveal information.
In both cases, the KM_TAG_EKEY_BLOB_ENC_VER
is not checked and remains in the key blob - this can be observed by calling getKeyCharacteristics
on a v15 blob.
According to the Keymaster API (or Keymaster Functions), exportKey
should only export the public key of an asymmetric key pair (RSA/EC) and support only KeyFormat::X509
for the key format.
However, we discovered that in Samsung devices swd_export_key
(responsible for the export command of the Keymaster TA) also allows to export raw symmetric key material (AES/DES) if a particular tag KM_TAG_EXPORTABLE
(value 0x7000025a
, type boolean) exists in the key parameters (that is, swd_export_key
supports KeyFormat::RAW
). When we create a symmetric key with this tag and run the export command we get the plaintext key material.
This seems to be a remainder of the Keymaster2 API, and overall should probably not exist, as an exportable private key can be easily compromised.
According to Samsung, this is not considered as an issue as "the tag (km_tag_exportable) related operation has been removed from the Android P OS and remains only as a legacy code, so it does not operate in the application". Samsung said they will deprecate the KM_TAG_EXPORTABLE
tag as part of their plan to remove unnecessary code in response to our report.