Hands-on iMX6 HAB

Hands-on iMX6 High Assurance Boot a.k.a. Secure boot

Introduction

These notes are based on Boundary Device’s blog on implementing HAB on an imx6 SoC.

The guide above is out of date, and missing information. I hope this helps to fill in some of those gaps.

I won’t cover the purpose of secure boot or the chain of trust. You can read up on more info here.

The steps described here alone will not prevent you system from running untrusted code. A number of other steps will need to be included, such as 

  • blowing fuses to prevent debug interfaces
  • prevent the system from booting from arbitrary RAM location
  • protecting OS filesystem
  • Locked down uboot
    • Disable any external scripts from loading. e.g. 6x_bootscript
    • protect any dtbs
    • Strip the uboot binary down to the basic feature required
  • Basically protect any artifacts that are involved in the boot process and running of the OS

Disclaimer: These are just some of my notes and should not be used in any production systems without due caution and validation for accuracy.
This guide also doesn’t cover encrypting your firmware.

Board spec

Boundary devices Nitrogen6X dev board

https://boundarydevices.com/product/nitrogen6x/

SoC MCiMX6Q6AVT10AC Arm Cortex-A9 Quad core 1GB DDR3 Ram

Comes with Linux pre-installed on sdcard.

Terminal access

HDMI

Get a working terminal on boundary devices’ shipped sdcard with Linux.

We need to disable qt gui startup from systemd.

Mount sdcard from another system.

Delete /media/mkocbayi/c1a350a8-ddfb-4d54-8f9f-56b0cbfab55c/etc/systemd/system/multi-user.target.wants/qtlauncher.service

This will stop qt gui on startup.

Boot the board with the modified sdcard.

Might have to press ctl-alt-f5 to get login prompt

Login root, no password.

RS232

The board comes with an rs232 cable for tty access and is enabled in uboot and linux.

I used the Trendnet TU-S9 usb to rs232 cable to connect with no issues.

CPU serial number

Serial number can be read from imx6 On-Chip OTP Controller.

From imx6 console run

cat /sys/fsl_otp/HW_OCOTP_CFG0
0xee669fb8
cat /sys/fsl_otp/HW_OCOTP_CFG1
0x150e11d4

From build system, set certificate serial using cpu uid, set CA passphrase to protect code signing private key, and generate certs

$ cd keys
$ echo “ee669fb8ee669fb8” > serial$ echo “SUPER_SECURE_SECRET” > key_pass.txt$ echo “SUPER_SECURE_SECRET” >> key_pass.txt$ ./hab4_pki_tree.sh

Rest of steps can be followed using HAB tutorial.

Create fuse table and binary to be flashed to imx fuses.

~/projects/imx-sb/release/crts$ ../linux64/bin/srktool -h 4 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -d sha256 -c ./SRK1_sha256_4096_65537_v3_ca_crt.pem,./SRK2_sha256_4096_65537_v3_ca_crt.pem,./SRK3_sha256_4096_65537_v3_ca_crt.pem,./SRK4_sha256_4096_65537_v3_ca_crt.pem -f 1

Display binary contents

$ hexdump -C SRK_1_2_3_4_fuse.bin
00000000  0f 4f 1f dd 9e 34 91 25  84 63 1d 3f 7d ea 65 eb |.O…4.%.c.?}.e.|
00000010  2e e4 f6 fc b9 7c 12 02  40 c5 ac 92 16 cf 42 ed  |…..|..@…..B.|
00000020

Corrected endianness

$ hexdump -e ‘/4 “0x”‘ -e ‘/4 “%X””\n”‘ < SRK_1_2_3_4_fuse.bin
0xDD1F4F0F
0x2591349E
0x3F1D6384
0xEB65EA7D
0xFCF6E42E
0x2127CB9
0x92ACC540
0xED42CF16

Backup known working sdcard

partitions

$ sudo partclone.fat16 -c -d -s /dev/sdb1 -o sdb1.img           
…                                                           
Partclone successfully cloned the device (/dev/sdb1) to the image (sdb1.img)  
Cloned successfully.

and 

$ sudo partclone.ext3 -c -d -s /dev/sdb2 -o sdb2.img

Syncing… OK!
Partclone successfully cloned the device (/dev/sdb2) to the image (sdb2.img)
Cloned successfully.

partition table

$ sudo dd if=/dev/sdb of=backup-sdb.mbr count=1 bs=512
1+0 records in
1+0 records out
512 bytes copied, 0.00168312 s, 304 kB/s

extended partition table

$ sudo sfdisk -d /dev/sdb > backup-sdb.sf

Restore sdcard backup

First restore partition tables

$ sudo dd if=parti-backup/backup-sdb.mbr of=/dev/sdb
1+0 records in
1+0 records out
512 bytes copied, 0.00145495 s, 352 kB/s

Now extended partitions

$ sudo sfdisk /dev/sdb < parti-backup/backup-sdb.sf                                                    
Checking that no-one is using this disk right now … OK                                                                                                  
                                                                                                                                                         
Disk /dev/sdb: 29.8 GiB, 32010928128 bytes, 62521344 sectors                                                                                              
…                                                      
Old situation:

Device     Boot Start     End Sectors Size Id Type
/dev/sdb1  * 8192   40959 32768   16M c W95 FAT32 (LBA)
/dev/sdb2       40960 2711551 2670592  1.3G 83 Linux

Update OS with drive’s new partitions

$ sudo partprobe /dev/sdb

Restore partitions

First partition

$ sudo partclone.fat16 -r -d -s parti-backup/sdb1.img -o /dev/sdb1
Partclone v0.3.11 http://partclone.org

Syncing… OK!
Partclone successfully restored the image (parti-backup/sdb1.img) to the device (/dev/sdb1)
Cloned successfully.

second partition

$ sudo partclone.extfs -r -d -s parti-backup/sdb2.img -o /dev/sdb2
Partclone v0.3.11 http://partclone.org

Syncing… OK!
Partclone successfully restored the image (parti-backup/sdb2.img) to the device (/dev/sdb2)
Cloned successfully.

Programming imx fuses

Sanity test

imx6 fuse prog command <bank> <word> <value>

As an example sanity check, we will read the mac address of our imx

imx6 eFuse base address = 0x400

On the i.MX6Q The MAC address is located at 0x620[31:0] (Lower MAC address) and 0x630[15:0] (Upper MAC address), the following example in U-Boot documentation can be used for calculating the bank and word for the Lower MAC address.

(0x620 – 0x400)/0x10 = 0x22 Hexadecimal = 34 Decimal

As the fuses are arranged in banks of 8 words:

34 / 8 = 4 and the remainder is 2, so in this case:

Bank = 4

Word = 2

Python code to calculate MAC_ADDR bank and word

def get_bank_word(fuse_address):
    if fuse_address <= 0x0: # TODO:Change to actual lowest possible value
        print('Must supply fuse address from imx reference manual')
        return

    fuse_base = 0x400

    result = int((fuse_address - fuse_base)/0x10) # result should be in decimal

    bank = int(result / 8)
    word = int(result % 8)

    print('Bank {} Word {}'.format(bank,word))
In [34]: get_bank_word(0x620)
Bank 4 Word 2
In [35]: get_bank_word(0x630)
 Bank 4 Word 3

This fuse word is shadowed in the register OCOTP_MAC0

And reading the fuses from uboot we get

=> fuse read 4 2
 Reading bank 4:
Word 0x00000002: b806928d
 => fuse read 4 3
 Reading bank 4:
Word 0x00000003: 00000019

Booting into the Linux kernel confirms the ethernet mac address

root@b2qt-nitrogen6x:~# ifconfig eth0
 eth0      Link encap:Ethernet  HWaddr 00:19:B8:06:92:8D  
           BROADCAST MULTICAST  MTU:1500  Metric:1
           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 txqueuelen:1000 
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

One Time Program of a SHA256 of Four RSA public keys

Using the knowledge from above, let’s plug-in the fuse address for burning the SRK (super root key) hash.

Page 351 of the imx6q reference manual says the fuse address starts at 0x580

In [35]: get_bank_word(0x580)
Bank 3 Word 0

From board’s u-boot prompt

=> fuse prog -y 3 0 0xDD1F4F0F
=> fuse prog -y 3 1 0x2591349E
=> fuse prog -y 3 2 0x3F1D6384
=> fuse prog -y 3 3 0xEB65EA7D
=> fuse prog -y 3 4 0xFCF6E42E
=> fuse prog -y 3 5 0x2127CB9
=> fuse prog -y 3 6 0x92ACC540
=> fuse prog -y 3 7 0xED42CF16

Each value is 32bits x 8 = 256bit hash

Build u-boot boundary-v2018.07

Note: I originally followed boundary device’s HAB blog which instructed to compile u-boot 2016, and then decided to compile a more recent version instead.

Download and compile

~$ git clone https://github.com/boundarydevices/u-boot-imx6 -b boundary-v2018.07
~$ cd u-boot-imx6
~/u-boot-imx6$ export ARCH=arm
~/u-boot-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf-
~/u-boot-imx6$ make nitrogen6q_defconfig
~/u-boot-imx6$ make menuconfig

Select ‘ARM architecture’ —> ‘Support i.MX HAB features’

And

Select ‘Device Drivers’  —> Hardware crypto devices  —>

[*] Freescale Crypto Driver Support

[*] Big-endian access to Freescale Secure Boot

(4) Freescale Secure Boot compatibility

-*- Little-endian access to Freescale Secure Boot

And

Select ‘Library routines’  —> ‘Security support’ —>

[*] Support the AES algorithm

Compile

u-boot-imx6$ make -j4 V=1 u-boot.imx

Should have a file called u-boot.imx

Now, if the output of your make command displayed your HAB blocks values then great. However, at the time of writing these notes, my version of mkimage did not display the hab values.

Luckily a kind author wrote a patch.

u-boot-imx6$ wget -O 940826.diff https://patchwork.ozlabs.org/patch/940826/raw/
u-boot-imx6$ git apply 940826.diff

Compile again

u-boot-imx6$ make -j4 V=1 u-boot.imx
u-boot-imx6$ ./tools/mkimage -l u-boot.imx
Image Type:   Freescale IMX Boot Image
Image Ver:    2 (i.MX53/6/7 compatible)
Mode:         DCD
Data Size:    585728 Bytes = 572.00 KiB = 0.56 MiB
Load Address: 177ff420
Entry Point:  17800000
HAB Blocks:   0x177ff400 0x00000000 0x0008cc00
DCD Blocks:   0x00910000 0x0000002c 0x00000318

We will also need to create the uboot update script.

u-boot-imx6$ ./tools/mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n “update script” -d board/boundary/bootscripts/upgrade.txt upgrade.scr
Image Name:   update script
Created:      Sun Aug 25 17:26:27 2019
Image Type:   ARM Linux Script (uncompressed)
Data Size:    5351 Bytes = 5.23 KiB = 0.01 MiB
Load Address: 00000000
Entry Point:  00000000
Contents:
  Image 0: 5343 Bytes = 5.22 KiB = 0.01 MiB

Copy your u-boot.imx file to where your code signing tools are.

u-boot-imx6$ cp u-boot.imx ../release/linux64/

Sign boot loader

Download and edit u-boot_sign.csf file.

Amended HAB block values to 

Blocks = 0x177ff400 0x0 0x8cc00 “u-boot.imx”

Now create csf file

u-boot-imx6$ cd ../release/linux64/release/linux64$ ./bin/cst –o u-boot_csf.bin –i u-boot_sign.csf                                                     
CSF Processed successfully and signed data available in u-boot_csf.bin 

concat csf binary with uboot

release/linux64$ cat u-boot.imx u-boot_csf.bin > u-boot_signed.imx

Flash signed uboot to eeprom

Copy the signed boot loader binary to the root of an SD card along with the upgrade script.

u-boot-imx6$ cp upgrade.scr /media/user/Boot\ nitrog/release/linux64$ cp u-boot_signed.imx /media/user/Boot\ nitrog/u-boot.nitrogen6q

Note: The board boots uboot from spi flash.

From the uboot prompt run

=> env default -a
=> savee

=> run upgradeu

Reset the board

U-Boot 2018.07-35628-gc54f6b851b (Aug 25 2019 – 17:01:05 -0700)

CPU:   Freescale i.MX6Q rev1.2 at 792 MHz
Reset cause: WDOG
Board: nitrogen6x
I2C:   ready
DRAM:  1 GiB
MMC:   FSL_SDHC: 0, FSL_SDHC: 1
Loading Environment from SPI Flash…
SF: Detected sst25vf016b with page size 256 Bytes, erase size 4 KiB, total 2 MiB
OK
auto-detected panel 1280x720M@60
Display: hdmi:1280x720M@60 (1280×720)
In:    serial
Out:   serial
Err:   serial
Net:   Micrel ksz9021 at 6
FEC [PRIME], usb_ether
Hit any key to stop autoboot:  0

And the uboot message shows the correct version.

Verify SRK hash is burnt into fuses

After upgrading uboot and restarting the board into uboot, read the fuses.

=> fuse read 3 0 1
Reading bank 3:
Word 0x00000000: dd1f4f0f
=> fuse read 3 1 1
Reading bank 3:
Word 0x00000001: 2591349e
=> fuse read 3 2 1
Reading bank 3:
Word 0x00000002: 3f1d6384
=> fuse read 3 3 1
Reading bank 3:
Word 0x00000003: eb65ea7d
=> fuse read 3 4 1
Reading bank 3:
Word 0x00000004: fcf6e42e
=> fuse read 3 5 1
Reading bank 3:
Word 0x00000005: 02127cb9
=> fuse read 3 6 1
Reading bank 3:
Word 0x00000006: 92acc540
=> fuse read 3 7 1
Reading bank 3:
Word 0x00000007: ed42cf16

Check HAB for any events which might indicate failures.

=> hab_status

Secure boot disabled

HAB Configuration: 0xf0, HAB State: 0x66
No HAB Events Found!

Enable secure boot

From uboot prompt run

=> fuse prog 0 6 0x2

These values are obtained from the imx6 reference manual page 348, 0x460[1]

SEC_CONFIG[1].

Restart system and enter uboot again to verify secure boot mode

=> hab_status

Secure boot enabled

HAB Configuration: 0xcc, HAB State: 0x99
No HAB Events Found!

Sign kernel

Unsigned kernel

You can copy the zImage file from the original sdcard

release/linux64$ cp /media/user/Boot\ nitrog/zImage .

Download the script that will help generate the signed image’s ivt

https://gist.github.com/muttiopenbts/1106bc6dc99c5ecafc3320a2a86d5568

Calculate kernel image size in hex

release/linux64$ hexdump -C zImage | tail -n 1
006188e8

Pad kernel to nearest 4KB

def get_next_boundary(size, boundary):
    ”’Helper to calculate next memory value which is bounded by
    increments of sizes in bytes.
   
    Args:
        size:     hex. Base starting value. e.g. 0x468628
        boundary:    integer. Interval steps in binary byte size . e.g. 4KB = 4096 bits
       
    Returns:
        bytes: hex string. Next highest interval to boundary size.
       
    e.g. get_next_boundary(0x468628,4096)
        ‘0x469000’
    ”’
    for x in range(size,size+boundary):
        if x % boundary == 0:
            return hex(x)
In [44]: get_next_boundary(0x006188e8,4096)Out[44]: ‘0x619000’

Pad our kernel image

release/linux64$ objcopy -I binary -O binary –pad-to=0x619000   –gap-fill=0x00 zImage zImage-pad.bin

Obtain uboot load address

10800000

Generate an ivt for the kernel image.

In [89]: gen_ivt(0x10800000,’/home/user/projects/imx-sb/release/linux64/zImage-pad.bin’)
Creating ivt.
/home/user/projects/imx-sb/release/linux64/zImage-pad.bin size:0x619000
New ivt.bin size is 32

Merge ivt binary with padded kernel

release/linux64$ cat zImage-pad.bin ivt.bin > zImage-pad-ivt.bin

Download example csf file for zImage

$ wget \
https://storage.googleapis.com/boundarydevices.com/zImage.csf

and plug in specific values for blocks.

[Header]
Version = 4.0
Hash Algorithm = sha256
Engine Configuration = 0
Certificate Format = X509
Signature Format = CMS

[Install SRK]
File = “../crts/SRK_1_2_3_4_table.bin”
Source index = 0

[Install CSFK]
File = “../crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem”

[Authenticate CSF]

[Install Key]
Verification index = 0
Target index = 2
File = “../crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem”

[Authenticate Data]
Verification index = 2
# Description for blocks.
# 0x10800000 = uboot loadaddr
# 0x619000 = zImage size
# 0x20 = ivt bin size
# 0x619020 = zImage_size + ivt_size
Blocks = 0x10800000 0x0 0x619020 “zImage-pad-ivt.bin”

Generate signature

release/linux64$ ./bin/cst –o zImage_csf.bin –i zImage.csf
CSF Processed successfully and signed data available in zImage_csf.bin

Create signed binary by appending signature to kernel and ivt

cat zImage-pad-ivt.bin zImage_csf.bin > zImage_signed_10800000

Load signed image into ram via uboot and test

=> tftp 10800000 zImage_signed_10800000
Using FEC device
TFTP from server 192.168.146.1; our IP address is 192.168.146.10
Filename ‘zImage_signed_10800000’.
Load address: 0x10800000
Loading: #################################################################

        ################
        4.2 MiB/s
done
Bytes transferred = 6400368 (61a970 hex)
=> hab_auth_img 10800000 61a970 619000
Authenticate image from DDR location 0x10800000…

Secure boot enabled
HAB Configuration: 0xcc, HAB State: 0x99
No HAB Events Found!

Values for hab_auth_img

=> help hab_auth_img
hab_auth_img – authenticate image via HAB

Usage:
hab_auth_img addr length ivt_offset
addr – image hex address
length – image hex length
ivt_offset – hex offset of IVT in the image

$ hexdump -C /tftpboot/zImage_signed_10800000 |tail

0061a970

Boot

Need to modify u-boot script to load signed image when hab test passes.

I took Boundary device’s u-boot 6x_bootscript from my sdcard and stripped it down to what I needed to test the hab testing of the signed kernel image.

This is the result of my testing.

Once I was satisfied with the results, I then incorporated the hab test and signed kernel load commands to the main 6x_bootscript, and copied my signed kernel to the sdcard.

The final version of the 6x_bootscript is here.

As stated in the intro, the writeup isn’t a secure configuration and was created for testing imx6 hab features.

Test unsigned bootloader

Flash unsigned bootloader to verify that HAB feature works.

I wanted to test what the board would do if I flashed an unsigned bootloader.

I proceeded to reflash the EEPROM using the old unsigned uboot image and upgradeu. When the upgrade process completed and I power cycled the board, the system wouldn’t boot and no messages were displayed on the serial terminal.

Recover bricked board (imx_usb_loader)

Follow these instructions 

imx_usb_loader install

https://boundarydevices.com/unbricking-nitrogen6x-sabre-lite-i-mx6-board/

imx_usb_loader using signed image

Follow the steps described here https://boundarydevices.com/high-assurance-boot-hab-dummies/#flashing under “What about imx_usb_loader?”

These were the steps I performed

Set my board’s jumpers to boot into otg mode 1 off, and 2 on. Attached to board’s usb otg port and power cycled.

Obtain compiled unsigned u-boot binary

release/linux64$ cp ../../u-boot-imx6/u-boot.imx .

Get dcd length from u-boot binary

release/linux64$ dd if=u-boot.imx bs=1 skip=45 count=2 | od -t x2 -A none –endian=big
2+0 records in
2+0 records out
2 bytes copied, 8.1967e-05 s, 24.4 kB/s 0318

Update csf file. 

e.g. u-boot_sign.csf

with

[Authenticate Data]
Verification index = 2
Blocks = 0x00910000 0x2C 0x318 “u-boot.imx”

Downloaded mod_4_mfgtool.sh tool

release/linux64$ wget \
https://storage.googleapis.com/boundarydevices.com/mod_4_mfgtool.sh

Clear dcd address from u-boot binary

release/linux64$ bash ./mod_4_mfgtool.sh clear_dcd_addr u-boot.imx

Create csf bin

release/linux64$ ./bin/cst –o u-boot_csf.bin –i u-boot_sign.csf
CSF Processed successfully and signed data available in u-boot_csf.bin

Set dcd in u-boot

release/linux64$ bash ./mod_4_mfgtool.sh set_dcd_addr u-boot.imx
4+0 records in
4+0 records out
4 bytes copied, 0.000401627 s, 10.0 kB/s

Append both binaries

release/linux64$ cat u-boot.imx u-boot_csf.bin > u-boot_signed.imx

Place board into spl mode and flash

imx_usb_loader$ sudo ./imx_usb ../release/linux64/u-boot_signed.imx

You should now see u-boot prompt on the serial console.

I inserted my sdcard which contained my signed u-boot image (without the imx_usb_loader modification).

Ran 

=> run upgradeu

from uboot and waited for uboot to say completed and reset.

Turned off board power and set boot switches to 1, and 2 off.

Board started up as normal.

On my first attempt I failed. I went back and erased any old files and it worked on my second attempt.

References

I.MX6 High Assurance Boot (HAB) tutorialhttps://boundarydevices.com/high-assurance-boot-hab-dummies/
Obtain serial numberhttps://freescale.jiveon.com/docs/DOC-94459
Unbrickinghttps://boundarydevices.com/unbricking-nitrogen6x-sabre-lite-i-mx6-board/
Reference manualPage 351 fuse map for srk hashPage 347 boot fuse mapPage 351 SRK_HASH fuse map descriptionhttps://www.nxp.com/doc/IMX6DQRM
Secure boot HABv4Page 8 SRK fuse map programmingPage 381 Boot eFUSE descriptionshttps://www.nxp.com/docs/en/application-note/AN4581.pdf
Calculate efuse bank and wordhttps://imxdev.gitlab.io/tutorial/Burning_eFuses_on_i.MX/
uBoot tipshttps://www.linuxjournal.com/content/handy-u-boot-trick
Locking down bootloaderhttps://elinux.org/images/e/e0/Josserand-schulz-secure-boot.pdf
Excellent writeup on HAB implementation and bootrom securityhttps://casualhacking.io/blog/2018/2/10/exploring-secured-boot-on-the-sabre-lite-imx6s-v13-sbc-and-nxp-habv4