TSEC Firmware: Difference between revisions
No edit summary |
→Main: correct secureboot falcon os address memcpy source |
||
(12 intermediate revisions by 4 users not shown) | |||
Line 6: | Line 6: | ||
== Initialization == | == Initialization == | ||
During this stage several clocks are programmed. | During this stage several clocks are programmed. | ||
<pre> | |||
// Program the HOST1X clock and resets | |||
// Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X | |||
enable_host1x_clkrst(); | |||
// Program the TSEC clock and resets | |||
// Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC | |||
enable_tsec_clkrst(); | |||
// Program the SOR_SAFE clock and resets | |||
// Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE | |||
enable_sor_safe_clkrst(); | |||
// Program the SOR0 clock and resets | |||
// Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0 | |||
enable_sor0_clkrst(); | |||
// Program the SOR1 clock and resets | |||
// Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1 | |||
enable_sor1_clkrst(); | |||
// Program the KFUSE clock resets | |||
// Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE | |||
enable_kfuse_clkrst(); | |||
</pre> | |||
== Configuration == | == Configuration == | ||
In this stage the Falcon IRQs, interfaces and DMA engine are configured. | In this stage the Falcon IRQs, interfaces and DMA engine are configured. | ||
<pre> | |||
// Clear the Falcon DMA control register | |||
*(u32 *)FALCON_DMACTL = 0; | |||
// Enable Falcon IRQs | |||
*(u32 *)FALCON_IRQMSET = 0xFFF2; | |||
// Enable Falcon IRQs | |||
*(u32 *)FALCON_IRQDEST = 0xFFF0; | |||
// Enable Falcon interfaces | |||
*(u32 *)FALCON_ITFEN = 0x03; | |||
// Wait for Falcon's DMA engine to be idle | |||
wait_flcn_dma_idle(); | |||
</pre> | |||
== Firmware loading == | == Firmware loading == | ||
The Falcon firmware code is stored in the first bootloader's data segment in IMEM. | The Falcon firmware code is stored in the first bootloader's data segment in IMEM. | ||
<pre> | |||
// Set DMA transfer base address to 0x40011900 >> 0x08 | |||
*(u32 *)FALCON_DMATRFBASE = 0x400119; | |||
u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM | |||
u32 dst_offset = 0; | |||
u32 src_offset = 0; | |||
// Load code into Falcon (0x100 bytes at a time) | |||
while (src_offset < 0xF00) | |||
{ | |||
flcn_load_firm(trf_mode, src_offset, dst_offset); | |||
src_offset += 0x100; | |||
dst_offset += 0x100; | |||
} | |||
</pre> | |||
[6.2.0+] The transfer base address and size of the Falcon firmware code changed. | [6.2.0+] The transfer base address and size of the Falcon firmware code changed. | ||
<pre> | |||
// Set DMA transfer base address to 0x40010E00 >> 0x08 | |||
*(u32 *)FALCON_DMATRFBASE = 0x40010E; | |||
u32 trf_mode = 0; // A value of 0 sets FALCON_DMATRFCMD_IMEM | |||
u32 dst_offset = 0; | |||
u32 src_offset = 0; | |||
// Load code into Falcon (0x100 bytes at a time) | |||
while (src_offset < 0x2900) | |||
{ | |||
flcn_load_firm(trf_mode, src_offset, dst_offset); | |||
src_offset += 0x100; | |||
dst_offset += 0x100; | |||
} | |||
</pre> | |||
== Firmware booting == | == Firmware booting == | ||
Falcon is booted up and the first bootloader waits for it to finish. | Falcon is booted up and the first bootloader waits for it to finish. | ||
// Set magic value in host1x scratch space | |||
*(u32 *)0x50003300 = 0x34C2E1DA; | |||
// Clear Falcon scratch1 MMIO | |||
*(u32 *)FALCON_SCRATCH1 = 0; | |||
// Set Falcon boot key version in scratch0 MMIO | |||
*(u32 *)FALCON_SCRATCH0 = 0x01; | |||
// Set Falcon's boot vector address | |||
*(u32 *)FALCON_BOOTVEC = 0; | |||
// Signal Falcon's CPU | |||
*(u32 *)FALCON_CPUCTL = 0x02; | |||
// Wait for Falcon's DMA engine to be idle | |||
wait_flcn_dma_idle(); | |||
u32 boot_res = 0; | |||
// The bootloader allows the TSEC two seconds from this point to do its job | |||
u32 maximum_time = read_timer() + 2000000; | |||
while (!boot_res) | |||
{ | |||
// Read boot result from scratch1 MMIO | |||
boot_res = *(u32 *)FALCON_SCRATCH1; | |||
// Read from TIMERUS_CNTR_1US (microseconds from boot) | |||
u32 current_time = read_timer(); | |||
// Booting is taking too long | |||
if (current_time > maximum_time) | |||
panic(); | |||
} | |||
// Invalid boot result was returned | |||
if (boot_res != 0xB0B0B0B0) | |||
panic(); | |||
</pre> | |||
[6.2.0+] Falcon is booted up, but the first bootloader is left in an infinite loop. | [6.2.0+] Falcon is booted up, but the first bootloader is left in an infinite loop. | ||
<pre> | |||
// Set magic value in host1x scratch space | |||
*(u32 *)0x50003300 = 0x34C2E1DA; | |||
// Clear Falcon scratch1 MMIO | |||
*(u32 *)FALCON_SCRATCH1 = 0; | |||
// Set Falcon boot key version in scratch0 MMIO | |||
*(u32 *)FALCON_SCRATCH0 = 0x01; | |||
// Set Falcon's boot vector address | |||
*(u32 *)FALCON_BOOTVEC = 0; | |||
// Signal Falcon's CPU | |||
*(u32 *)FALCON_CPUCTL = 0x02; | |||
// Infinite loop | |||
while (1); | |||
</pre> | |||
== TSEC key generation == | == TSEC key generation == | ||
The TSEC key is generated by reading SOR1 registers modified by the Falcon CPU. | The TSEC key is generated by reading SOR1 registers modified by the Falcon CPU. | ||
<pre> | |||
// Clear magic value in host1x scratch space | |||
*(u32 *)0x50003300 = 0; | |||
// Read TSEC key | |||
u32 tsec_key[4]; | |||
tsec_key[0] = *(u32 *)NV_SOR_DP_HDCP_BKSV_LSB; | |||
tsec_key[1] = *(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB; | |||
tsec_key[2] = *(u32 *)NV_SOR_TMDS_HDCP_CN_MSB; | |||
tsec_key[3] = *(u32 *)NV_SOR_TMDS_HDCP_CN_LSB; | |||
// Clear SOR1 registers | |||
*(u32 *)NV_SOR_DP_HDCP_BKSV_LSB = 0; | |||
*(u32 *)NV_SOR_TMDS_HDCP_BKSV_LSB = 0; | |||
*(u32 *)NV_SOR_TMDS_HDCP_CN_MSB = 0; | |||
*(u32 *)NV_SOR_TMDS_HDCP_CN_LSB = 0; | |||
if (out_size < 0x10) | |||
out_size = 0x10; | |||
// Copy back the TSEC key | |||
memcpy(out_buf, tsec_key, out_size); | |||
</pre> | |||
[6.2.0+] This is now done inside an encrypted TSEC payload. | [6.2.0+] This is now done inside an encrypted TSEC payload. | ||
Line 169: | Line 182: | ||
== Cleanup == | == Cleanup == | ||
Clocks and resets are disabled before returning. | Clocks and resets are disabled before returning. | ||
<pre> | |||
// Deprogram KFUSE clock and resets | |||
// Uses RST_DEVICES_H, CLK_OUT_ENB_H and CLK_H_KFUSE | |||
disable_kfuse_clkrst(); | |||
// Deprogram SOR1 clock and resets | |||
// Uses RST_DEVICES_X, CLK_OUT_ENB_X, CLK_SOURCE_SOR1 and CLK_X_SOR1 | |||
disable_sor1_clkrst(); | |||
// Deprogram SOR0 clock and resets | |||
// Uses RST_DEVICES_X, CLK_OUT_ENB_X and CLK_X_SOR0 | |||
disable_sor0_clkrst(); | |||
// Deprogram SOR_SAFE clock and resets | |||
// Uses RST_DEVICES_Y, CLK_OUT_ENB_Y and CLK_Y_SOR_SAFE | |||
disable_sor_safe_clkrst(); | |||
// Deprogram TSEC clock and resets | |||
// Uses RST_DEVICES_U, CLK_OUT_ENB_U, CLK_SOURCE_TSEC and CLK_U_TSEC | |||
disable_tsec_clkrst(); | |||
// Deprogram HOST1X clock and resets | |||
// Uses RST_DEVICES_L, CLK_OUT_ENB_L, CLK_SOURCE_HOST1X and CLK_L_HOST1X | |||
disable_host1x_clkrst(); | |||
</pre> | |||
= TSEC Firmware = | = TSEC Firmware = | ||
Line 199: | Line 212: | ||
Stored inside the first bootloader, this firmware binary is split into 4 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code) and [[#Key data|key data]]. | Stored inside the first bootloader, this firmware binary is split into 4 blobs (names are unofficial): [[#Boot|Boot]] (unencrypted and unauthenticated code), [[#KeygenLdr|KeygenLdr]] (unencrypted and authenticated code), [[#Keygen|Keygen]] (encrypted and authenticated code) and [[#Key data|key data]]. | ||
[6.2.0+] There are now 2 new blobs (names are unofficial): [[#SecureBootLdr|SecureBootLdr]] (unencrypted and unauthenticated code), [[#SecureBoot|SecureBoot]] (part unencrypted and unauthenticated code, part encrypted and authenticated code) | [6.2.0+] There are now 2 new blobs (names are unofficial): [[#SecureBootLdr|SecureBootLdr]] (unencrypted and unauthenticated code), [[#SecureBoot|SecureBoot]] (part unencrypted and unauthenticated code, part encrypted and authenticated code). | ||
Firmware can be disassembled with [http://envytools.readthedocs.io/en/latest/ envytools'] [https://github.com/envytools/envytools/tree/master/envydis envydis]: | Firmware can be disassembled with [http://envytools.readthedocs.io/en/latest/ envytools'] [https://github.com/envytools/envytools/tree/master/envydis envydis]: | ||
Line 214: | Line 227: | ||
=== Initialization === | === Initialization === | ||
The firmware initially sets up the stack pointer to the end of the available data segment. | |||
<pre> | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x09; | |||
data_seg_size &= 0x1FF; | |||
data_seg_size <<= 0x08; | |||
// Set the stack pointer | |||
*(u32 *)sp = data_seg_size; | |||
</pre> | |||
=== Main === | === Main === | ||
The firmware reads the [[#Key data|key data]] blob and then loads, authenticates and executes [[#KeygenLdr|KeygenLdr]] which sets the TSEC key. | |||
<pre> | |||
u32 dmem_start = 0; | |||
u8 key_data_buf[0x7C]; | |||
// Read the key data blob from code segment | |||
u32 key_data_addr = 0x300; | |||
u32 key_data_size = 0x7C; | |||
memcpy_i2d(key_data_buf, key_data_addr, key_data_size); | |||
// Read the next stage from code segment into data space | |||
u32 blob1_addr = 0x400; | |||
u32 blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
memcpy_i2d(dmem_start, blob1_addr, blob1_size); | |||
// Upload the next stage into Falcon's code segment | |||
u32 blob1_virt_addr = 0x300; | |||
bool use_secret = true; | |||
memcpy_d2i(blob1_virt_addr, dmem_start, blob1_size, blob1_virt_addr, use_secret); | |||
u32 boot_res = 0; | |||
u32 time = 0; | |||
bool is_blob_dec = false; | |||
while (true) | |||
{ | { | ||
if (time == 4000001) | |||
{ | |||
// Write boot failed (timeout) magic to FALCON_SCRATCH1 | |||
boot_res = 0xC0C0C0C0; | |||
*(u32 *)FALCON_SCRATCH1 = boot_res; | |||
break; | |||
} | |||
// Load key version from FALCON_SCRATCH0 (bootloader sends 0x01) | |||
u32 key_version = *(u32 *)FALCON_SCRATCH0; | |||
if (key_version == 0x64) | |||
{ | |||
// Skip all next stages | |||
boot_res = 0xB0B0B0B0; | |||
*(u32 *)FALCON_SCRATCH1 = boot_res; | |||
break; | |||
} | |||
else | |||
{ | |||
if (key_version > 0x03) | |||
boot_res = 0xD0D0D0D0; // Invalid key version | |||
else if (key_version == 0) | |||
boot_res = 0xB0B0B0B0; // No keys used | |||
else | |||
{ | |||
u32 key_buf[0x7C]; | |||
// Copy key data | |||
memcpy(key_buf, key_data_buf, 0x7C); | |||
u32 crypto_reg_flag = 0x00060000; | |||
u32 blob1_hash_addr = key_buf + 0x20; | |||
// Set auth_addr to 0x300 and auth_size to blob1_size | |||
$cauth = ((blob1_size << 0x10) | 0x03); | |||
// The next 2 xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
// Transfer data to crypto register c6 | |||
xdst(0, (blob1_hash_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
// Jump to KeygenLdr | |||
u32 keygenldr_res = exec_keygenldr(key_buf, key_version, is_blob_dec); | |||
is_blob_dec = true; // Set this to prevent decrypting again | |||
// Set boot finish magic on success | |||
if (keygenldr_res == 0) | |||
boot_res = 0xB0B0B0B0 | |||
} | |||
// Write result to FALCON_SCRATCH1 | |||
*(u32 *)FALCON_SCRATCH1 = boot_res; | |||
if (boot_res == 0xB0B0B0B0) | |||
break; | |||
} | |||
time++; | |||
} | } | ||
// Overwrite the TSEC key in SOR1 registers | |||
// This has no effect because the KeygenLdr locks out the TSEC DMA engine | |||
tsec_set_key(key_data_buf); | |||
return boot_res; | |||
</pre> | |||
[6.2.0+] | [6.2.0+] The firmware calculates the start address of [[#SecureBootLdr|SecureBootLdr]] through [[#Key data|key data]] and jumps to it. | ||
<pre> | |||
u8 key_data_buf[0x84]; | |||
// Read the key data blob | |||
u32 key_data_addr = 0x300; | |||
u32 key_data_size = 0x84; | |||
memcpy_i2d(key_data_buf, key_data_addr, key_data_size); | |||
// Calculate the next blob's address in Falcon code segment | |||
u32 blob4_size = *(u32 *)(key_data_buf + 0x80); | |||
u32 blob0_size = *(u32 *)(key_data_buf + 0x70); | |||
u32 blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
u32 blob2_size = *(u32 *)(key_data_buf + 0x78); | |||
u32 blob3_addr = blob0_size + blob1_size + 0x100 + blob2_size + blob4_size; | |||
// Jump to next blob | |||
(void *)blob3_addr(); | |||
return 0; | |||
</pre> | |||
==== tsec_set_key ==== | ==== tsec_set_key ==== | ||
This method takes '''key_data_buf''' (a 16 bytes buffer) as argument and writes its contents to SOR1 registers. | This method takes '''key_data_buf''' (a 16 bytes buffer) as argument and writes its contents to SOR1 registers. | ||
<pre> | |||
// This is TSEC_MMIO + 0x1000 + (0x1C300 / 0x40) | |||
*(u32 *)TSEC_DMA_TIMEOUT = 0xFFF; | |||
// Read the key's words | |||
u32 key0 = *(u32 *)(key_data_buf + 0x00); | |||
u32 key1 = *(u32 *)(key_data_buf + 0x04); | |||
u32 key2 = *(u32 *)(key_data_buf + 0x08); | |||
u32 key3 = *(u32 *)(key_data_buf + 0x0C); | |||
u32 result = 0; | |||
// Write key0 to SOR1 and check for errors | |||
result = tsec_dma_write(NV_SOR_DP_HDCP_BKSV_LSB, key0); | |||
if (result) | |||
return result; | |||
// Write key1 to SOR1 and check for errors | |||
result = tsec_dma_write(NV_SOR_TMDS_HDCP_BKSV_LSB, key1); | |||
if (result) | |||
return result; | |||
// Write key2 to SOR1 and check for errors | |||
result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_MSB, key2); | |||
if (result) | |||
return result; | |||
// Write key3 to SOR1 and check for errors | |||
result = tsec_dma_write(NV_SOR_TMDS_HDCP_CN_LSB, key3); | |||
if (result) | |||
return result; | |||
return result; | return result; | ||
</pre> | |||
===== tsec_dma_write ===== | ===== tsec_dma_write ===== | ||
This method takes '''addr''' and '''value''' as arguments and performs a DMA write using TSEC MMIO. | This method takes '''addr''' and '''value''' as arguments and performs a DMA write using TSEC MMIO. | ||
<pre> | |||
u32 result = 0; | |||
// Wait for TSEC DMA engine | |||
// This waits for bit 0x0C in TSEC_DMA_CMD to be 0 | |||
result = wait_tsec_dma(); | |||
// Wait failed | |||
if (result) | |||
return 1; | |||
// Set the destination address | |||
// This is TSEC_MMIO + 0x1000 + (0x1C100 / 0x40) | |||
*(u32 *)TSEC_DMA_ADDR = addr; | |||
// Set the value | |||
// This is TSEC_MMIO + 0x1000 + (0x1C200 / 0x40) | |||
*(u32 *)TSEC_DMA_VAL = value; | |||
// Start transfer | |||
// This is TSEC_MMIO + 0x1000 + (0x1C000 / 0x40) | |||
*(u32 *)TSEC_DMA_CMD = 0x800000F2; | |||
// Wait for TSEC DMA engine | |||
// This waits for bit 0x0C in TSEC_DMA_CMD to be 0 | |||
result = wait_tsec_dma(); | |||
// Wait failed | |||
if (result) | |||
return 1; | |||
return 0; | |||
</pre> | |||
== KeygenLdr == | == KeygenLdr == | ||
Line 428: | Line 440: | ||
=== Main === | === Main === | ||
<pre> | |||
// Clear interrupt flags | |||
$flags.ie0 = 0; | |||
$flags.ie1 = 0; | |||
$flags.ie2 = 0; | |||
// Clear overrides | |||
cxset(0x80); | |||
// Clear bit 0x13 in cauth | |||
$cauth = ($cauth & ~(1 << 0x13)); | |||
// Set the target port for memory transfers | |||
$xtargets = 0; | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
// Wait for all code loads to finish | |||
xcwait(); | |||
// The next 2 xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
// Transfer data to crypto register c0 | |||
// This should clear any leftover data | |||
xdst(0, 0); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
// Clear all crypto registers, except c6 which is used for auth | |||
cxor($c0, $c0); | |||
cmov($c1, $c0); | |||
cmov($c2, $c0); | |||
cmov($c3, $c0); | |||
cmov($c4, $c0); | |||
cmov($c5, $c0); | |||
cmov($c7, $c0); | |||
// Clear TSEC_TEGRA_CTL_TKFI_KFUSE | |||
// This is TSEC_MMIO + 0x1000 + (0x20E00 / 0x40) | |||
*(u32 *)TSEC_TEGRA_CTL &= 0xFFFEFFFF; | |||
// Set TSEC_SCP_CTL_PKEY_REQUEST_RELOAD | |||
// This is TSEC_MMIO + 0x1000 + (0x10600 / 0x40) | |||
*(u32 *)TSEC_SCP_CTL_PKEY |= 0x01; | |||
u32 is_pkey_loaded = 0; | |||
// Wait for TSEC_SCP_CTL_PKEY_LOADED | |||
while (!is_pkey_loaded) | |||
is_pkey_loaded = (*(u32 *)TSEC_SCP_CTL_PKEY & 0x02); | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x09; | |||
data_seg_size &= 0x1FF; | |||
data_seg_size <<= 0x08; | |||
// Check stack bounds | |||
if (($sp >= data_seg_size) || ($sp < 0x800)) | |||
exit(); | |||
// Load and execute the Keygen stage | |||
load_keygen(key_buf, key_version, is_blob_dec); | |||
// Clear the cauth signature | |||
csigclr(); | |||
// Clear all crypto registers | |||
cxor($c0, $c0); | |||
cxor($c1, $c1); | |||
cxor($c2, $c2); | |||
cxor($c3, $c3); | |||
cxor($c4, $c4); | |||
cxor($c5, $c5); | |||
cxor($c6, $c6); | |||
cxor($c7, $c7); | |||
// Take SCP out of lockdown | |||
// This is TSEC_MMIO + 0x1000 + (0x10300 / 0x40) | |||
*(u32 *)TSEC_SCP_CTL_LOCK = 0; | |||
return; | |||
</pre> | |||
==== load_keygen ==== | ==== load_keygen ==== | ||
This method takes '''key_buf''', '''key_version''' and '''is_blob_dec''' as arguments and is responsible for loading, decrypting, authenticating and executing [[#Keygen|Keygen]]. | |||
Notably, it also does AES-CMAC over the unauthorized [[#Boot|Boot]] blob to make sure it hasn't been tampered with. | |||
<pre> | |||
u32 res = 0; | |||
u32 dmem_start = 0; | |||
u32 blob0_addr = 0; | |||
u32 blob0_size = *(u32 *)(key_buf + 0x70); | |||
// Load blob0 code to the start of the data segment | |||
memcpy_i2d(dmem_start, blob0_addr, blob0_size); | |||
// Generate "CODE_SIG_01" key into c4 crypto register | |||
gen_usr_key(0, 0); | |||
// Encrypt buffer with c4 | |||
u8 sig_key[0x10]; | |||
enc_buf(sig_key, blob0_size); | |||
u32 src_addr = dmem_start; | |||
u32 src_size = blob0_size; | |||
u32 iv_addr = sig_key; | |||
u32 dst_addr = sig_key; | |||
u32 mode = 0x02; // AES-CMAC | |||
u32 use_imem = 0; | |||
// Do AES-CMAC over blob0 code | |||
do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, use_imem); | |||
// Compare the resulting hash with the one from the key buffer | |||
if (memcmp(dst_addr, key_buf + 0x10, 0x10)) | |||
{ | |||
res = 0xDEADBEEF; | |||
return res; | |||
} | |||
u32 blob1_size = *(u32 *)(key_buf + 0x74); | |||
// Decrypt Keygen blob if needed | |||
if (!is_blob_dec) | |||
{ | |||
// Read Stage2's size from key buffer | |||
u32 blob2_size = *(u32 *)(key_buf + 0x78); | |||
// Check stack bounds | |||
if ($sp > blob2_size) | |||
{ | |||
u32 blob2_virt_addr = blob0_size + blob1_size; | |||
u32 blob2_phys_addr = blob2_virt_addr + 0x100; | |||
// Read the encrypted Keygen blob | |||
memcpy_i2d(dmem_start, blob2_phys_addr, blob2_size); | |||
// Generate "CODE_ENC_01" key into c4 crypto register | |||
gen_usr_key(0x01, 0x01); | |||
u32 src_addr = dmem_start; | |||
u32 src_size = blob2_size; | |||
u32 iv_addr = key_buf + 0x40; | |||
u32 dst_addr = dmem_start; | |||
u32 mode = 0; // AES-128-CBC | |||
u32 use_imem = 0; | |||
// Decrypt Keygen blob with AES-128-CBC | |||
do_crypto(src_addr, src_size, iv_addr, dst_addr, mode, use_imem); | |||
// Upload decrypted Keygen into Falcon's code segment | |||
bool use_secret = true; | |||
memcpy_d2i(blob2_virt_addr, dmem_start, blob2_size, blob2_virt_addr, use_secret); | |||
// Clear out the decrypted blob | |||
memset(dmem_start, 0, blob2_size); | |||
} | |||
} | } | ||
// The next 2 xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
u32 crypto_reg_flag = 0x00060000; | |||
u32 blob2_hash_addr = key_buf + 0x30; | |||
// Transfer the Keygen auth hash to crypto register c6 | |||
xdst(0, (blob2_hash_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
// Save previous cauth value | |||
u32 cauth_old = $cauth; | |||
// Set auth_addr to blob2_virt_addr and auth_size to blob2_size | |||
$cauth = ((blob2_virt_addr >> 0x08) | (blob2_size << 0x10)); | |||
u32 hovi_key_addr = 0; | |||
// Select next stage key | |||
if (key_version == 0x01) // Use HOVI_EKS_01 | |||
hovi_key_addr = key_buf + 0x50; | |||
else if (key_version == 0x02) // Use HOVI_COMMON_01 | |||
hovi_key_addr = key_buf + 0x60; | |||
else if (key_version == 0x03) // Use debug key (empty) | |||
hovi_key_addr = key_buf + 0x00; | |||
else | |||
res = 0xD0D0D0D0 | |||
// Jump to Keygen | |||
if (hovi_key_addr) | |||
res = exec_keygen(hovi_key_addr, key_version); | |||
// Clear out key data | |||
memset(key_buf, 0, 0x7C); | |||
// Restore previous cauth value | |||
$cauth = cauth_old; | |||
return res; | |||
</pre> | |||
===== gen_usr_key ===== | ===== gen_usr_key ===== | ||
This method takes '''type''' and '''mode''' as arguments and generates a key. | This method takes '''type''' and '''mode''' as arguments and generates a key. | ||
<pre> | |||
u8 seed_buf[0x10]; | |||
// Read a 16 bytes seed based on supplied type | |||
/* | |||
type == 0: "CODE_SIG_01" + null padding | |||
type == 1: "CODE_ENC_01" + null padding | |||
*/ | |||
get_seed(seed_buf, type); | |||
// This will write the seed into crypto register c0 | |||
crypto_store(0, seed_buf); | |||
// Load selected secret into crypto register c1 | |||
csecret($c1, 0x26); | |||
// Bind c1 register as the key for enc/dec operations | |||
ckeyreg($c1); | |||
// Encrypt seed_buf in c0 using keyreg value as key into c1 | |||
cenc($c1, $c0); | |||
// Encrypt the auth signature (stored in c6) with c1 and store in c1 | |||
csigenc($c1, $c1); | |||
// Copy the result to c4 (will be used as key) | |||
cmov($c4, $c1); | |||
// Do key expansion for decryption if necessary | |||
if (mode != 0) | |||
ckexp($c4, $c4); | |||
return; | |||
</pre> | |||
===== | ===== enc_buf ===== | ||
This method takes '''buf''' (a 16 bytes buffer) and '''size''' as arguments and encrypts the supplied buffer. | This method takes '''buf''' (a 16 bytes buffer) and '''size''' as arguments and encrypts the supplied buffer. | ||
<pre> | |||
// Set first 3 words to null | |||
*(u32 *)(buf + 0x00) = 0; | |||
*(u32 *)(buf + 0x04) = 0; | |||
*(u32 *)(buf + 0x08) = 0; | |||
// Swap halves (b16, b32 and b16 again) and store it as the last word | |||
*(u32 *)(buf + 0x0C) = ( | |||
((size & 0x000000FF) << 0x08 | |||
| (size & 0x0000FF00) >> 0x08) << 0x10 | |||
| ((size & 0x00FF0000) >> 0x10) << 0x08 | |||
| (size & 0xFF000000) >> 0x18 | |||
); | |||
// This will write buf into crypto register c3 | |||
crypto_store(0x03, buf); | |||
// Bind c4 register as the key for enc/dec operations | |||
ckeyreg($c4); | |||
// Encrypt buf in c3 using keyreg value as key and store in c5 | |||
cenc($c5, $c3); | |||
// This will read into buf from crypto register c5 | |||
crypto_load(0x05, buf); | |||
return; | |||
</pre> | |||
===== crypto_store ===== | |||
This method takes '''reg''' (a crypto register) and '''buf''' (a 16 bytes buffer) as arguments and loads the supplied buffer into the crypto register. | |||
<pre> | |||
// The next two xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
// Encode the source buffer and the destination register for the xfer | |||
u32 crypto_xfer_flag = (u32)buf | reg << 0x10; | |||
// Transfer the supplied buffer to the supplied crypto register | |||
xdst(crypto_xfer_flag, crypto_xfer_flag); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
return; | |||
</pre> | |||
===== crypto_load ===== | |||
This method takes '''reg''' (a crypto register) and '''buf''' (a 16 bytes buffer) as arguments and loads the contents of the supplied register into the supplied buffer. | |||
<pre> | |||
// The next two xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
// Encode the destination buffer and the source register for the xfer | |||
u32 crypto_xfer_flag = (u32)buf | reg << 0x10; | |||
// Transfer the contents of the supplied crypto register into the supplied buffer | |||
xdld(crypto_xfer_flag, crypto_xfer_flag); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
return; | |||
</pre> | |||
===== do_crypto ===== | ===== do_crypto ===== | ||
This is the method responsible for all crypto operations performed during [[#KeygenLdr|KeygenLdr]]. It takes '''src_addr''', '''src_size''', '''iv_addr''', '''dst_addr''', '''mode''' and '''use_imem''' as arguments. | This is the method responsible for all crypto operations performed during [[#KeygenLdr|KeygenLdr]]. It takes '''src_addr''', '''src_size''', '''iv_addr''', '''dst_addr''', '''mode''' and '''use_imem''' as arguments. | ||
<pre> | |||
// Check for invalid source data size | |||
if (!src_size || (src_size & 0x0F)) | |||
exit(); | |||
// Check for invalid source data address | |||
if (src_addr & 0x0F) | |||
exit(); | |||
// Check for invalid destination data address | |||
if (dst_addr & 0x0F) | |||
exit(); | |||
// Use IV if available | |||
if (iv_addr) | |||
{ | |||
// This will write the iv_addr into crypto register c5 | |||
crypto_store(0x05, iv_addr); | |||
} | |||
else | |||
{ | |||
// Clear c5 register (use null IV) | |||
cxor($c5, $c5); | |||
} | |||
// Bind c4 register as the key for enc/dec operations | |||
ckeyreg(c4); | |||
if (mode == 0x00) // AES-128-CBC decrypt | |||
{ | |||
// Create crypto script with 5 instructions | |||
cs0begin(0x05); | |||
cxsin($c3); // Read 0x10 bytes from crypto stream into c3 | |||
cdec($c2, $c3); // Decrypt from c3 into c2 | |||
cxor($c5, $c2); // XOR c2 with c5 and store in c5 | |||
cxsout($c5); // Write 0x10 bytes into crypto stream from c5 | |||
cmov($c5, $c3); // Move c3 into c5 | |||
} | |||
else if (mode == 0x01) // AES-128-CBC encrypt | |||
{ | |||
// Create crypto script with 4 instructions | |||
cs0begin(0x04); | |||
cxsin($c3); // Read 0x10 bytes from crypto stream into c3 | |||
cxor($c3, $c5); // XOR c5 with c3 and store in c3 | |||
cenc($c5, $c3); // Encrypt from c3 into c5 | |||
cxsout($c5); // Write 0x10 bytes into crypto stream from c5 | |||
} | |||
else if (mode == 0x02) // AES-CMAC | |||
{ | |||
// Create crypto script with 3 instructions | |||
cs0begin(0x03); | |||
cxsin($c3); // Read 0x10 bytes from crypto stream into c3 | |||
cxor($c5, $c3); // XOR c5 with c3 and store in c5 | |||
cenc($c5, $c5); // Encrypt from c5 into c5 | |||
} | |||
else if (mode == 0x03) // AES-128-ECB decrypt | |||
{ | |||
// Create crypto script with 3 instructions | |||
cs0begin(0x03); | |||
cxsin($c3); // Read 0x10 bytes from crypto stream into c3 | |||
cdec($c5, $c3); // Decrypt from c3 into c5 | |||
cxsout($c5); // Write 0x10 bytes into crypto stream from c5 | |||
} | |||
else if (mode == 0x04) // AES-128-ECB encrypt | |||
{ | |||
// Create crypto script with 3 instructions | |||
cs0begin(0x03); | |||
cxsin($c3); // Read 0x10 bytes from crypto stream into c3 | |||
cenc($c5, $c3); // Encrypt from c3 into c5 | |||
cxsout($c5); // Write 0x10 bytes into crypto stream from c5 | |||
} | |||
else | |||
return; | |||
// Main loop | |||
while (src_size > 0) | |||
{ | |||
u32 blk_count = (src_size >> 0x04); | |||
if (blk_count > 0x10) | |||
blk_count = 0x10; | |||
// Check size align | |||
if (blk_count & (blk_count - 0x01)) | |||
blk_count = 0x01; | |||
u32 blk_size = (blk_count << 0x04); | |||
u32 crypto_xfer_src = 0; | |||
u32 crypto_xfer_dst = 0; | |||
if (block_size == 0x20) | |||
{ | |||
crypto_xfer_src = (0x00030000 | src_addr); | |||
crypto_xfer_dst = (0x00030000 | dst_addr); | |||
// Execute crypto script 2 times (1 for each block) | |||
cs0exec(0x02); | |||
} | |||
else if (block_size == 0x40) | |||
{ | |||
crypto_xfer_src = (0x00040000 | src_addr); | |||
crypto_xfer_dst = (0x00040000 | dst_addr); | |||
// Execute crypto script 4 times (1 for each block) | |||
cs0exec(0x04); | |||
} | |||
else if (block_size == 0x80) | |||
{ | |||
crypto_xfer_src = (0x00050000 | src_addr); | |||
crypto_xfer_dst = (0x00050000 | dst_addr); | |||
// Execute crypto script 8 times (1 for each block) | |||
cs0exec(0x08); | |||
} | |||
else if (block_size == 0x100) | |||
{ | |||
crypto_xfer_src = (0x00060000 | src_addr); | |||
crypto_xfer_dst = (0x00060000 | dst_addr); | |||
// Execute crypto script 16 times (1 for each block) | |||
cs0exec(0x10); | |||
} | |||
else | |||
{ | |||
crypto_xfer_src = (0x00020000 | src_addr); | |||
crypto_xfer_dst = (0x00020000 | dst_addr); | |||
// Execute crypto script 1 time (1 for each block) | |||
cs0exec(0x01); | |||
// Ensure proper block size | |||
block_size = 0x10; | |||
} | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto input/output stream | |||
if (use_imem) | |||
cxset(0xA1); // Flag 0xA0 is falcon imem <-> crypto input/output stream | |||
else | |||
cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream | |||
// Transfer data into the crypto input/output stream | |||
xdst(crypto_xfer_src, crypto_xfer_src); | |||
// AES-CMAC only needs one more xfer instruction | |||
if (mode == 0x02) | |||
{ | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto input/output stream | |||
if (use_imem) | |||
cxset(0xA1); // Flag 0xA0 is falcon imem <-> crypto input/output stream | |||
else | |||
cxset(0x21); // Flag 0x20 is external mem <-> crypto input/output stream | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
} | |||
else // AES enc/dec needs 2 more xfer instructions | |||
{ | |||
// The next 2 xfer instructions will be overridden | |||
// and target changes from DMA to crypto input/output stream | |||
if (use_imem) | |||
cxset(0xA2); // Flag 0xA0 is falcon imem <-> crypto input/output stream | |||
else | |||
cxset(0x22); // Flag 0x20 is external mem <-> crypto input/output stream | |||
// Transfer data from the crypto input/output stream | |||
xdld(crypto_xfer_dst, crypto_xfer_dst); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
// Increase the destination address by block size | |||
dst_addr += block_size; | |||
} | |||
// Increase the source address by block size | |||
src_addr += block_size; | |||
// Decrease the source size by block size | |||
src_size -= block_size; | |||
} | |||
// AES-CMAC result is in c5 | |||
if (mode == 0x02) | |||
{ | |||
// This will read into dst_addr from crypto register c5 | |||
crypto_load(0x05, dst_addr); | |||
} | |||
return; | |||
</pre> | |||
== Keygen == | == Keygen == | ||
This stage is decrypted by [[#KeygenLdr|KeygenLdr]] using a key generated by encrypting a seed with | This stage is decrypted by [[#KeygenLdr|KeygenLdr]] using a key generated by encrypting the KeygenLdr auth signature with a seed encrypted with a csecret. It will generate the final TSEC key. | ||
=== Main === | |||
The main function takes '''key_addr''' and '''key_type''' as arguments from [[#KeygenLdr|KeygenLdr]]. | |||
<pre> | |||
u32 falcon_rev = *(u32 *)FALCON_HWCFG2 & 0x0F; | |||
// Falcon hardware revision must be 5 | |||
if (falcon_rev != 0x05) | |||
exit(); | |||
// Clear interrupt flags | |||
$flags.ie0 = 0; | |||
$flags.ie1 = 0; | |||
$flags.ie2 = 0; | |||
// Set the target port for memory transfers | |||
$xtargets = 0; | |||
// Generate the TSEC key | |||
gen_tsec_key(key_addr, key_type); | |||
// Clear the cauth signature | |||
csigclr(); | |||
// Clear all crypto registers | |||
cxor($c0, $c0); | |||
cxor($c1, $c1); | |||
cxor($c2, $c2); | |||
cxor($c3, $c3); | |||
cxor($c4, $c4); | |||
cxor($c5, $c5); | |||
cxor($c6, $c6); | |||
cxor($c7, $c7); | |||
return; | |||
</pre> | |||
==== gen_tsec_key ==== | |||
This method is responsible for generating the final TSEC key. It takes '''key_addr''' and '''key_type''' as arguments. | |||
<pre> | |||
// This will use TSEC DMA to look for 0x34C2E1DA in host1x scratch space | |||
u32 host1x_res = check_host1x_magic(); | |||
// Failed to find magic word | |||
if (host1x_res != 0) | |||
return; | |||
u32 crypto_reg_flag = 0x00000000; | |||
// The next 0x02 xfer instructions will be overridden | |||
// and target changes from DMA to crypto register | |||
cxset(0x02); | |||
// Transfer the seed in key_addr to crypto register c0 | |||
xdst(0, (key_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
crypto_reg_flag = 0x00020000; | |||
if (key_type == 0x01) // HOVI_EKS_01 | |||
{ | |||
// Load selected secret into crypto register c1 | |||
csecret($c1, 0x3F); | |||
// Encrypt the auth signature with c1 and store in c1 | |||
csigenc($c1, $c1); | |||
// Load selected secret into crypto register c2 | |||
csecret($c2, 0x00); | |||
// Bind c2 register as the key for enc/dec operations | |||
ckeyreg($c2); | |||
// Encrypt the seed from key_addr and store in c2 | |||
cenc($c2, $c0); | |||
// Bind c2 register as the key for enc/dec operations | |||
ckeyreg($c2); | |||
// Encrypt the auth signature with c2 and store in c2 | |||
csigenc($c2, $c2); | |||
// Bind c2 register as the key for enc/dec operations | |||
ckeyreg($c2); | |||
// Encrypt c1 and store in c2 | |||
cenc($c2, $c1); | |||
// The next 0x02 xfer instructions will be overridden | |||
// and target changes from DMA to crypto register | |||
cxset(0x02); | |||
// Transfer the resulting key from crypto register c2 to key_addr | |||
xdld(0, (key_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
} | |||
else if (key == 0x02) // HOVI_COMMON_01 | |||
{ | |||
// Load selected secret into crypto register c2 | |||
csecret($c2, 0x00); | |||
// Bind c2 register as the key for enc/dec operations | |||
ckeyreg($c2); | |||
// Encrypt the seed from key_addr and store in c2 | |||
cenc($c2, $c0); | |||
// Bind c2 register as the key for enc/dec operations | |||
ckeyreg($c2); | |||
// Encrypt the auth signature with c2 and store in c2 | |||
csigenc($c2, $c2); | |||
// The next 0x02 xfer instructions will be overridden | |||
// and target changes from DMA to crypto register | |||
cxset(0x02); | |||
// Transfer the resulting key from crypto register c2 to key_addr | |||
xdld(0, (key_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
} | |||
// Use TSEC DMA to write the key in SOR1 registers | |||
sor1_set_key(key_addr); | |||
return; | |||
</pre> | |||
==== sor1_set_key ==== | |||
This method takes '''key_addr''' (start address of a 16 bytes buffer) as argument and transfers its contents to SOR1 registers. | |||
The implementation is equivalent to [[#tsec_set_key|tsec_set_key]]. | |||
== SecureBootLdr == | == SecureBootLdr == | ||
Line 919: | Line 1,103: | ||
=== Main === | === Main === | ||
<pre> | |||
u8 key_data_buf[0x84]; | |||
u8 tmp_key_data_buf[0x84]; | |||
// Read the key data from memory | |||
u32 key_data_addr = 0x300; | |||
u32 key_data_size = 0x84; | |||
memcpy_i2d(key_data_buf, key_data_addr, key_data_size); | |||
// Read the KeygenLdr blob from memory | |||
u32 boot_base_addr = 0; | |||
u32 blob1_addr = 0x400; | |||
u32 blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
memcpy_i2d(boot_base_addr, blob1_addr, blob1_size); | |||
// Upload the next code segment into Falcon's CODE region | |||
u32 blob1_virt_addr = 0x300; | |||
bool use_secret = true; | |||
memcpy_d2i(blob1_virt_addr, boot_base_addr, blob1_size, blob1_virt_addr, use_secret); | |||
// Backup the key data | |||
memcpy(tmp_key_data_buf, key_data_buf, 0x84); | |||
// Save previous cauth value | |||
u32 cauth_old = $cauth; | |||
// Set auth_addr to 0x300 and auth_size to blob1_size | |||
$cauth = ((blob1_size << 0x10) | (0x300 >> 0x08)); | |||
// The next 2 xfer instructions will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x02); | |||
u32 crypto_reg_flag = 0x00060000; | |||
u32 blob1_hash_addr = tmp_key_data_buf + 0x20; | |||
// Transfer data to crypto register c6 | |||
xdst(0, (blob1_hash_addr | crypto_reg_flag)); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
u32 key_version = 0x01; | |||
bool is_blob_dec = false; | |||
// Jump to KeygenLdr | |||
u32 keygenldr_res = exec_keygenldr(tmp_key_data_buf, key_version, is_blob_dec); | |||
// Set boot finish magic on success | |||
if (keygenldr_res == 0) | |||
keygenldr_res = 0xB0B0B0B0 | |||
// Write result to FALCON_SCRATCH1 | |||
*(u32 *)FALCON_SCRATCH1 = keygenldr_res; | |||
if (keygenldr_res != 0xB0B0B0B0) | |||
return keygenldr_res; | |||
// Restore previous cauth value | |||
$cauth = cauth_old; | |||
u8 flcn_hdr_buf[0x18]; | |||
u8 flcn_os_hdr_buf[0x10]; | |||
blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
u32 blob2_size = *(u32 *)(key_data_buf + 0x78); | |||
u32 blob0_size = *(u32 *)(key_data_buf + 0x70); | |||
// Read the SecureBoot blob's Falcon header from memory | |||
u32 blob4_flcn_hdr_addr = (((blob0_size + blob1_size) + 0x100) + blob2_size); | |||
memcpy_i2d(flcn_hdr_buf, blob4_flcn_hdr_addr, 0x18); | |||
blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
blob2_size = *(u32 *)(key_data_buf + 0x78); | |||
blob0_size = *(u32 *)(key_data_buf + 0x70); | |||
u32 flcn_hdr_size = *(u32 *)(flcn_hdr_buf + 0x0C); | |||
// Read the SecureBoot blob's Falcon OS header from memory | |||
u32 blob4_flcn_os_hdr_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_hdr_size); | |||
memcpy_i2d(flcn_os_hdr_buf, blob4_flcn_os_hdr_addr, 0x10); | |||
blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
blob2_size = *(u32 *)(key_data_buf + 0x78); | |||
blob0_size = *(u32 *)(key_data_buf + 0x70); | |||
u32 flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10); | |||
u32 flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04); | |||
// Read the SecureBoot blob's Falcon OS image from memory | |||
u32 blob4_flcn_os_addr = ((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size); | |||
memcpy_i2d(boot_base_addr, blob4_flcn_os_addr, flcn_os_size); | |||
// Upload the SecureBoot's Falcon OS image boot stub code segment into Falcon's CODE region | |||
u32 blob4_flcn_os_boot_virt_addr = 0; | |||
u32 blob4_flcn_os_boot_size = 0x100; | |||
use_secret = false; | |||
memcpy_d2i(blob4_flcn_os_boot_virt_addr, boot_base_addr, blob4_flcn_os_boot_size, blob4_flcn_os_boot_virt_addr, use_secret); | |||
flcn_os_size = *(u32 *)(flcn_os_hdr_buf + 0x04); | |||
// Upload the SecureBoot blob's Falcon OS encrypted image code segment into Falcon's CODE region | |||
u32 blob4_flcn_os_img_virt_addr = 0x100; | |||
u32 blob4_flcn_os_img_size = (flcn_os_size - 0x100); | |||
use_secret = true; | |||
memcpy_d2i(blob4_flcn_os_img_virt_addr, boot_base_addr + 0x100, blob4_flcn_os_img_size, blob4_flcn_os_img_virt_addr, use_secret); | |||
// Wait for all code loads to finish | |||
xcwait(); | |||
blob1_size = *(u32 *)(key_data_buf + 0x74); | |||
blob2_size = *(u32 *)(key_data_buf + 0x78); | |||
blob0_size = *(u32 *)(key_data_buf + 0x70); | |||
flcn_code_hdr_size = *(u32 *)(flcn_hdr_buf + 0x10); | |||
u32 flcn_os_code_size = *(u32 *)(flcn_os_hdr_buf + 0x08); | |||
// Read the SecureBoot blob's falcon OS image's hash from memory | |||
u32 blob4_flcn_os_img_hash_addr = (((((blob0_size + blob1_size) + 0x100) + blob2_size) + flcn_code_hdr_size) + flcn_os_code_size); | |||
memcpy_i2d(0, blob4_flcn_os_img_hash_addr, 0x10); | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x03; | |||
data_seg_size &= 0x3FC0; | |||
u32 data_addr = 0x10; | |||
// Clear all data except the first 0x10 bytes (SecureBoot blob's Falcon OS image's hash) | |||
for (int data_word_count = 0x04; data_word_count < data_seg_size; data_word_count++) | |||
{ | |||
*(u32 *)(data_addr) = 0; | |||
data_addr += 0x04; | |||
} | |||
// Clear all crypto registers | |||
cxor($c0, $c0); | |||
cxor($c1, $c1); | |||
cxor($c2, $c2); | |||
cxor($c3, $c3); | |||
cxor($c4, $c4); | |||
cxor($c5, $c5); | |||
cxor($c6, $c6); | |||
cxor($c7, $c7); | |||
// Clear the cauth signature | |||
csigclr(); | |||
// Jump to SecureBoot | |||
load_secboot(); | |||
return 0xB0B0B0B0; | |||
</pre> | |||
== SecureBoot == | == SecureBoot == | ||
Line 1,078: | Line 1,260: | ||
=== Main === | === Main === | ||
<pre> | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x01; | |||
data_seg_size &= 0xFF00; | |||
// Set the stack pointer | |||
$sp = data_seg_size; | |||
// Jump to the SecureBoot blob's Falcon OS image boot stub | |||
init_secboot(); | |||
// Halt execution | |||
exit(); | |||
return; | |||
</pre> | |||
==== | ==== init_secboot ==== | ||
This method takes no arguments and is responsible for loading, authenticating and executing [[#SecureBoot|SecureBoot]]. | |||
u32 | <pre> | ||
// Read the transfer base address from IO space | |||
u32 xfer_ext_base_addr = *(u32 *)FALCON_DMATRFBASE; | |||
// Copy transfer base address to data memory | |||
u32 scratch_data_addr = 0x300; | |||
*(u32 *)scratch_data_addr = xfer_ext_base_addr; | |||
// Set the transfer base address | |||
xcbase(xfer_ext_base_addr); | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x01); | |||
u32 crypto_reg_flag = 0x00060000; | |||
u32 blob4_flcn_os_img_hash_addr = 0; | |||
// Transfer data to crypto register c6 | |||
xdst(0, (blob4_flcn_os_img_hash_addr | crypto_reg_flag)); | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x01); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
cmov($c7, $c6); | |||
cxor($c7, $c7); | |||
// Set auth_addr to 0x100, auth_size to 0x1300, | |||
// bit 16 (use_secret) and bit 17 (is_encrypted) | |||
$cauth = ((0x02 << 0x10) | (0x01 << 0x10) | (0x1300 << 0x10) | (0x100 >> 0x08)); | |||
// Clear interrupt flags | |||
$flags.ie0 = 0; | |||
$flags.ie1 = 0; | |||
$flags.ie2 = 0; | |||
// Jump to the SecureBoot blob's Falcon OS image | |||
exec_secboot(); | |||
return 0x0F0F0F0F; | |||
</pre> | |||
[8.1.0+] Removed transfer base address setting and added IMEM protection. | |||
<pre> | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x01); | |||
// Clear interrupt flags | u32 crypto_reg_flag = 0x00060000; | ||
*( | u32 blob4_flcn_os_img_hash_addr = 0; | ||
// Transfer data to crypto register c6 | |||
xdst(0, (blob4_flcn_os_img_hash_addr | crypto_reg_flag)); | |||
// The next xfer instruction will be overridden | |||
// and target changes from DMA to crypto | |||
cxset(0x01); | |||
// Wait for all data loads/stores to finish | |||
xdwait(); | |||
cmov($c7, $c6); | |||
cxor($c7, $c7); | |||
// Set auth_addr to 0x100, auth_size to 0x1D00, | |||
// bit 16 (use_secret) and bit 17 (is_encrypted) | |||
$cauth = ((0x02 << 0x10) | (0x01 << 0x10) | (0x1D00 << 0x10) | (0x100 >> 0x08)); | |||
// Clear interrupt flags | |||
$flags.ie0 = 0; | |||
$flags.ie1 = 0; | |||
$flags.ie2 = 0; | |||
// Fill remaining IMEM with secret pages | |||
bool use_secret = true; | |||
memcpy_d2i(0x1E00, 0, 0x2200, 0x1E00, use_secret); | |||
memcpy_d2i(0x4000, 0, 0x4000, 0x4000, use_secret); | |||
// Wait for all code loads to finish | |||
xcwait(); | |||
// Jump to the SecureBoot blob's Falcon OS image | |||
exec_secboot(); | |||
return 0x0F0F0F0F; | |||
</pre> | |||
==== exec_secboot ==== | |||
This is the signed and encrypted portion of the [[#SecureBoot|SecureBoot]] payload. | |||
<pre> | |||
// Recover the transfer base address from the stack | |||
u32 xfer_ext_base_addr = *(u32 *)scratch_data_addr; | |||
// Return the TLB entry that covers the virtual address | |||
u32 tlb_entry = vtlb(xfer_ext_base_addr); | |||
// Clear Falcon CPU control | |||
*(u32 *)FALCON_CPUCTL = 0; | |||
// Halt if the external page is marked as secret | |||
if ((tlb_entry & 0x4000000) != 0) | |||
exit(); | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x01; | |||
data_seg_size &= 0xFF00; | |||
// Set the stack pointer | |||
$sp = data_seg_size; | |||
// Fill all DMEM with a pointer to a trap function (just exits 3 times) | |||
for (int i = 0; i < data_seg_size; i += 0x04) { | |||
*(u32 *)i = (u32)trap_func(); | |||
} | |||
// Initialize the TRNG and generate random data in DMEM | |||
init_rnd(); | |||
// Issue a randomized delay and return a random value | |||
u32 rnd_val = rnd_delay(0xFF); | |||
// Load the TSEC key from SOR1 registers into DMEM | |||
sor1_get_key(); | |||
// Initialize CAR registers | |||
car_init(); | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
// Ensure CLK_RST_CONTROLLER_CLK_SOURCE_TSEC_0 is 0x02 | |||
test_clk_source_tsec(); | |||
// Set FLOW_MODE_WAITEVENT in FLOW_CTLR_HALT_COP_EVENTS_0 | |||
halt_bpmp(); | |||
// Initialize the CCPLEX | |||
ccplex_init(); | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
bool is_se_ready = false; | |||
// Wait for SE to be ready | |||
while (!is_se_ready) | |||
is_se_ready = check_se_status(); | |||
// Test MC_IRAM_BOM and MC_IRAM_TOM | |||
u32 mc_iram_aperture_res = test_mc_iram_aperture(); | |||
if (mc_iram_aperture_res != 0xAAAAAAAA) | |||
{ | |||
// Clear the entire DMEM region | |||
clear_dmem(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Ensure FUSE_SKU_INFO is 0x83 | |||
test_fuse_sku_info(); | |||
// Write TSEC key to SE keyslot 0x0C | |||
se_set_keyslot_12(); | |||
// Write TSEC root key to SE keyslot 0x0D | |||
se_set_keyslot_13(); | |||
// Decrypt Package1 | |||
decrypt_pk11(); | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
// Parse Package1 header and return entry address | |||
u32 entry_addr = parse_pk11(); | |||
// Set the exception vectors | |||
set_excp_vec(entry_addr); | |||
// Fill the top 0x500 bytes in DMEM with a pointer to trap function (just exits 3 times) | |||
for (int i = 0; i < 0x500; i += 0x04) { | |||
*(u32 *)i = (u32)trap_func(); | |||
} | |||
// Clear all crypto registers | |||
cxor($c0, $c0); | |||
cxor($c1, $c1); | |||
cxor($c2, $c2); | |||
cxor($c3, $c3); | |||
cxor($c4, $c4); | |||
cxor($c5, $c5); | |||
cxor($c6, $c6); | |||
cxor($c7, $c7); | |||
// Take SCP out of lockdown | |||
unlock_scp(); | |||
// Clear FLOW_CTLR_HALT_COP_EVENTS_0 | |||
resume_bpmp(); | |||
// Clear the entire DMEM region | |||
clear_dmem(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
return; | |||
</pre> | |||
[7.0.0+] Many changes were introduced to mitigate and prevent attacks. | |||
<pre> | |||
// Recover the transfer base address from the stack | |||
u32 xfer_ext_base_addr = *(u32 *)scratch_data_addr; | |||
// Return the TLB entry that covers the virtual address | |||
u32 tlb_entry = vtlb(xfer_ext_base_addr); | |||
// Clear Falcon CPU control | |||
*(u32 *)FALCON_CPUCTL = 0; | |||
// Halt if the external page is marked as secret | |||
if ((tlb_entry & 0x4000000) != 0) | |||
exit(); | |||
// Read data segment size from IO space | |||
u32 data_seg_size = *(u32 *)FALCON_HWCFG; | |||
data_seg_size >>= 0x01; | |||
data_seg_size &= 0xFF00; | |||
// Set the stack pointer | |||
$sp = data_seg_size; | |||
// Fill all DMEM with a pointer to a trap function (just exits 3 times) | |||
for (int i = 0; i < data_seg_size; i += 0x04) { | |||
*(u32 *)i = (u32)trap_func(); | |||
} | |||
// Initialize the TRNG and generate random data in DMEM | |||
init_rnd(); | |||
// Issue a randomized delay and return a random value | |||
u32 rnd_val = rnd_delay(0xFF); | |||
// Enable and test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x01); | |||
// Issue a randomized delay and return a random value | |||
rnd_val = rnd_delay(0xFF); | |||
// Test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x00); | |||
// Issue a randomized delay and return a random value | |||
rnd_val = rnd_delay(0xFF); | |||
// Test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x00); | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Test randomized offsets for read/write integrity in MC, FUSE, IRAM and TZRAM | |||
u32 test_res = test_mc_fuse_iram_tzram(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Try to detect virtualization by enabling and disabling random CAR devices | |||
test_res = test_car(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Test memory transfer integrity | |||
test_res = test_mem_xfer(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Set FLOW_MODE_WAITEVENT in FLOW_CTLR_HALT_COP_EVENTS_0 | |||
halt_bpmp(); | |||
// Initialize the CCPLEX | |||
ccplex_init(); | |||
// Check if SE is ready | |||
u32 se_status = check_se_status(); | |||
if (se_status != 0) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Load the TSEC key from SOR1 registers into DMEM | |||
sor1_get_key(); | |||
// Initialize CAR registers | |||
car_init(); | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
// Try to detect virtualization by enabling and disabling random CAR devices | |||
test_res = test_car(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Ensure FUSE_SKU_INFO is 0x83 | |||
test_fuse_sku_info(); | |||
// Try to detect virtualization using MC_SMMU_AVPC_ASID and FUSE_ECO_RESERVE_0 | |||
test_smmu_fuse(); | |||
// Test MC_IRAM_BOM and MC_IRAM_TOM | |||
test_res = test_mc_iram_aperture(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
// Test memory transfer integrity | |||
test_res = test_mem_xfer(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Try to detect virtualization using MC_SMMU_AVPC_ASID and FUSE_ECO_RESERVE_0 | |||
test_smmu_fuse(); | |||
// Test MC_IRAM_BOM and MC_IRAM_TOM | |||
test_res = test_mc_iram_aperture(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x00); | |||
// Decrypt Package1 | |||
decrypt_pk11(); | |||
// Write TSEC root key to SE keyslot 0x0D | |||
se_set_keyslot_13(); | |||
// Write TSEC key to SE keyslot 0x0C | |||
se_set_keyslot_12(); | |||
// Clear the cauth signature | |||
csigclr(); | |||
// Check certain CAR, PMC and FUSE registers | |||
test_car_pmc_fuse(); | |||
// Test memory transfer integrity | |||
test_res = test_mem_xfer(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Try to detect virtualization using MC_SMMU_AVPC_ASID and FUSE_ECO_RESERVE_0 | |||
test_smmu_fuse(); | |||
// Test randomized offsets for read/write integrity in MC, FUSE, IRAM and TZRAM | |||
test_res = test_mc_fuse_iram_tzram(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Test MC_IRAM_BOM and MC_IRAM_TOM | |||
test_res = test_mc_iram_aperture(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x00); | |||
// Parse Package1 header and return entry address | |||
u32 entry_addr = parse_pk11(); | |||
// Set the exception vectors | |||
set_excp_vec(entry_addr); | |||
// Fill the top 0x500 bytes in DMEM with a pointer to trap function (just exits) | |||
for (int i = 0; i < 0x500; i += 0x04) { | |||
*(u32 *)i = (u32)trap_func(); | |||
} | |||
// Clear all crypto registers | |||
cxor($c0, $c0); | |||
cxor($c1, $c1); | |||
cxor($c2, $c2); | |||
cxor($c3, $c3); | |||
cxor($c4, $c4); | |||
cxor($c5, $c5); | |||
cxor($c6, $c6); | |||
cxor($c7, $c7); | |||
// Take SCP out of lockdown | |||
unlock_scp(); | |||
// Test memory transfer integrity | |||
test_res = test_mem_xfer(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Try to detect virtualization using MC_SMMU_AVPC_ASID and FUSE_ECO_RESERVE_0 | |||
test_smmu_fuse(); | |||
// Test MC_IRAM_BOM and MC_IRAM_TOM | |||
test_res = test_mc_iram_aperture(); | |||
if (test_res != 0xAAAAAAAA) | |||
{ | |||
// Fill SE keyslots 12 and 13 with random data | |||
se_set_keyslot_rnd(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
} | |||
// Test SMMU bypassing in the TFBIF | |||
tfbif_smmu_cfg(0x00); | |||
// Clear FLOW_CTLR_HALT_COP_EVENTS_0 | |||
resume_bpmp(); | |||
// Clear the entire DMEM region and every crypto register | |||
clear_dmem_and_crypto(); | |||
// Halt 5 times for no good reason | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
exit(); | |||
return; | |||
</pre> | |||
[8.1.0+] Key derivation algorithm was changed. Very minor changes were introduced to mitigate and prevent attacks. | |||
== Key data == | == Key data == |