1 of 36

Montréhack: nsec-Phospholipid

by Jonathan Marcil

August 2024

2 of 36

jonathanmarcil.ca/nsec2024

start with addon.pdf 👇

3 of 36

CONNECTION

plugging the thing, get serial console

4 of 36

SOLVE: PLUGME

315

135

5 = outside

6 = inside

5 of 36

FLASH

playing with the flash memory

6 of 36

SOLVE: first_128

When SRP0 is at 1 (included in 0x9c):

When /WP pin is low the Status Register locked and can not be written to.

When /WP pin is high the Status register is unlocked and can be written to after a Write Enable instruction, WEL=1.

0x9c = 10011100b

SRP = 1

SEC,TB =0

BP2 = 1

BP1 = 1

BP0 = 1

The Block Protect Bits (BP2, BP1, BP0) are read/write bits in the status register that provide Write Protection control and status.

7 of 36

SOLVE: first_128

nsec> raw_toggle

nsec> raw_read_registers

Status Register-1 : 0x9c

Status Register-2 : 0x02

Status Register-3 : 0x60

nsec> raw_write_register1 0x00

TX: 0x06 RX: 0x00

TX: 0x50 RX: 0x00

TX: 0x01 RX: 0x00

nsec> raw_read_registers

Status Register-1 : 0x9c

Status Register-2 : 0x02

Status Register-3 : 0x60

Same value so doesn’t work

8 of 36

SOLVE: first_128

Green light will come on upon successful plug of WP on VCC

PLUG WP

TO VCC

9 of 36

SOLVE: first_128

nsec> raw_write_register1 0x00

TX: 0x06 RX: 0x00

TX: 0x50 RX: 0x00

TX: 0x01 RX: 0x00

nsec> raw_read_registers

Status Register-1 : 0x00

Status Register-2 : 0x02

Status Register-3 : 0x60

nsec> raw_toggle

nsec> write_to_0x48

Writing 0xAA to 0x000048...

0x000048 value is: AA

nsec> read_first_128

SUCCESS: 46 4C 41 47 2D 43 54 46 42 41 44 47…

python

>>> bytes.fromhex("46 4C 41 47 2D 43 54 46 42 41 44 47…

BINGO!

10 of 36

SOLVE: another_128

Status Register-2 : 0x02 = 00000010b

CMP = 0

Status Register-3 : 0x60 = 01100000b

WPS = 0

0x050048

Write protect

0x080048

Not write protect

11 of 36

SOLVE: another_128

SRP = 0

SEC = 0

TB = 1

BP2 = 0

BP1 = 1

BP0 = 1

WEL = 0

BUSY = 0

00101100b

= 0x2c

0x050048

protect = ON == OK

0x080048

protect = OFF == OK

12 of 36

SOLVE: another_128

nsec> raw_write_register1 0x2C

TX: 0x06 RX: 0x00

TX: 0x50 RX: 0x00

TX: 0x01 RX: 0x2c

nsec> raw_read_registers

Status Register-1 : 0x2c

Status Register-2 : 0x02

Status Register-3 : 0x60

nsec> raw_toggle

nsec> read_another_128

SUCCESS!!!

New value

13 of 36

CRYPTO

They are crypto challenges but not challenges about crypto but more like crypto usage challenges, is that clear?

14 of 36

CRYPTO: read the docs

  • Understanding of the crypto chip data and key slots is helpful
  • However the ATECC608B datasheet for the CUSTOM version is under NDA…
    • The chip is the same as the TFLXTLS or TNGTLS version
    • CUSTOM means it’s not provisioned and all configurable
    • I didn’t use the NDA docs to create the challenges
    • I could have changed all slots to something else, but I kept it like TNGTLS
  • ATECC608B is designed to be fully compatible with the ATECC508A devices
    • And this is not under NDA!
    • So lots of details are in that datasheet
  • Bottom line is that you read details from 508A and look at slots in TNGTLS

15 of 36

CRYPTO: read the docs

ATECC608B Slot Configuration Summary

16 of 36

CRYPTO: read the docs

nsec> crypto_print_config 9

AES_Enable: 0x61

SlotConfig[9]: 0x0000

WriteConfig: 0x00

WriteKey: 0x00

IsSecret: 0x00

no_mac: 0x00

readkey: 0x00

KeyConfig[9]: 0x003c

private: 0x00

nsec> crypto_print_config 8

AES_Enable: 0x61

SlotConfig[8]: 0x0000

WriteConfig: 0x00

WriteKey: 0x00

IsSecret: 0x00

no_mac: 0x00

readkey: 0x00

KeyConfig[8]: 0x003c

private: 0x00

17 of 36

SOLVE: read_zone

nsec> crypto_read_zone 8 8

I'll only read block 8 in slot 8 if block 5 in slot 8 starts with 0x42! Exiting...

nsec> crypto_write32_from_hex 8 5 42ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Data written successfully on slot 8 block 5.

nsec> crypto_read_zone 8 8

Good, slot 5 starts with 0x42.

Data read successfully from slot 8: 464c41472d514b414e44434e4432585400000000000000000000000000000000

nsec> crypto_print_config 8

AES_Enable: 0x61

SlotConfig[8]: 0x0000

WriteConfig: 0x00

WriteKey: 0x00

IsSecret: 0x00

no_mac: 0x00

readkey: 0x00

KeyConfig[8]: 0x003c

private: 0x00

Being curious and looking around pays off!

18 of 36

SOLVE: hmac

nsec> crypto_hmac_rnd

Performing HMAC of random number as string 10314711052 with SLOT ID 9...

First 32 chars result: 5AB077645F1305FD054D067C370B1333

Can you generate a HMAC of 138160109146 using SLOT ID 9?

nsec> crypto_read_zone 9 0

Data read successfully from slot 9: 02fe018368d90788ba630006896eac1b63f68bdcb8036de5836b755f7a492cd2

import hmac

import hashlib

message = '138160109146'

private_key_bytes = bytes.fromhex('02fe018368d90788ba630006896eac1b63f68bdcb8036de5836b755f7a492cd2')

str.upper(hmac.new(private_key_bytes, message.encode(), hashlib.sha256).hexdigest())[:32]

nsec> crypto_print_config 9

SlotConfig[9]: 0x0000

WriteConfig: 0x00

IsSecret: 0x00

no_mac: 0x00

KeyConfig[9]: 0x003c

private: 0x00

Don’t overthink it.. yet!

19 of 36

SOLVE: ECDH

crypto_ECDH_premaster_secret [hex chars]

Validate the provided hex string matches

the calculated premaster secret.

Using private key from slot 2

and user provided public key in slot 13.

nsec> crypto_print_config 2

AES_Enable: 0x61

SlotConfig[2]: 0x208f

WriteConfig: 0x02

WriteKey: 0x00

IsSecret: 0x01

no_mac: 0x00

readkey: 0x0f

KeyConfig[2]: 0x0033

private: 0x01

pub_info: 0x01

key_type: 0x04

req_auth: 0x00

req_random: 0x00

lockable: 0x01

SlotLocked(2): true

nsec> crypto_print_config 13

AES_Enable: 0x61

SlotConfig[13]: 0x0000

WriteConfig: 0x00

WriteKey: 0x00

IsSecret: 0x00

no_mac: 0x00

readkey: 0x00

KeyConfig[13]: 0x003c

private: 0x00

pub_info: 0x00

key_type: 0x07

req_auth: 0x00

req_random: 0x00

lockable: 0x01

SlotLocked(13): false

20 of 36

ECDH

my public + your private == your public + my private

* The ECDH command implements the Elliptic Curve Diffie-Hellman algorithm to

* combine an internal private key with an external public key to calculate a

* shared secret.

ECDH

21 of 36

SOLVE: ECDH

nsec> crypto_print_pubkey 2

I (4975497) crypto_atecc:

-----BEGIN PUBLIC KEY-----

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIMX9S1hBPDm7NfCScFg2QUXkBC6z

TAAe4raf3nTI554LrnS3vK95j0Ockrd3SVbVv/KZv+JqwtsIwzOliXzgIg==

-----END PUBLIC KEY-----

nsec> crypto_read_zone 13 0

Data read successfully from slot 13: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

nsec> crypto_read_zone 13 1

Data read successfully from slot 13: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

User provided.. provide it!

Can’t read the private key.. But can read the public key!

22 of 36

SOLVE: ECDH

https://github.com/MicrochipTech/cryptoauthlib/blob/e8f28ec2d41a43b0906fda4a7faa597a08858116/lib/calib/calib_ecdh.c#L106

/** \brief ECDH command with a private key in a slot and the premaster secret

* is returned in the clear.

*

* \param[in] device Device context pointer

* \param[in] key_id Slot of key for ECDH computation

* \param[in] public_key Public key input to ECDH calculation. X and Y

* integers in big-endian format. 64 bytes for P256

* key.

* \param[out] pms Computed ECDH premaster secret is returned here.

* 32 bytes.

*

* \return ATCA_SUCCESS on success

*/

ATCA_STATUS calib_ecdh(ATCADevice device, uint16_t key_id, const uint8_t* public_key, uint8_t* pms)

{

ATCA_STATUS status;

status = calib_ecdh_base(device, ECDH_PREFIX_MODE, key_id, public_key, pms, NULL);

return status;

}

This hints you on how it’s made but really it’s generic ECDH

23 of 36

SOLVE: ECDH

from cryptography.hazmat.primitives import serialization

from cryptography.hazmat.primitives.asymmetric import ec

from cryptography.hazmat.backends import default_backend

from cryptography.hazmat.primitives import hashes

from cryptography.hazmat.primitives.asymmetric import rsa

import os, hmac, hashlib, base64, binascii

public2_key_bytes = base64.b64decode('MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIMX9S1hBPDm7NfCScFg2QUXkBC6zTAAe4raf3nTI554LrnS3vK95j0Ockrd3SVbVv/KZv+JqwtsIwzOliXzgIg==')

public_key2 = serialization.load_der_public_key(public2_key_bytes, backend=default_backend())

print("Generating private key SECP256R1...")

private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())

public_key = private_key.public_key()

binascii.hexlify(public_key.public_numbers().x.to_bytes(32, 'big'))

binascii.hexlify(public_key.public_numbers().y.to_bytes(32, 'big'))

nsec> crypto_write32_from_hex 13 0 e8c72ec3c1bf11dd31e4c171688e9c8e50be48fe7c649154f74838108d89fa5f

nsec> crypto_write32_from_hex 13 1 3cac27b29477ef9a4b8bc82fc5460df3f5482351a1ce146779f370310709a031

Write to slot 13 key

SLOT 2 public key

24 of 36

SOLVE: ECDH

my_premaster = str.upper(binascii.hexlify(private_key.exchange(ec.ECDH(), public_key2)).decode())

nsec> crypto_ECDH_premaster_secret 1920D22DCAE8C6A95C659195CE6A25947ED3B92E8B6322D27FA72B2ECFE02F64

[ADMIN] Internally computed ECDH premaster secret:

19 20 d2 2d ca e8 c6 a9

5c 65 91 95 ce 6a 25 94

7e d3 b9 2e 8b 63 22 d2

7f a7 2b 2e cf e0 2f 64

GOOD: Provided ECDH premaster secret is correct

computed premaster

This is in ADMIN mode on my badge for debugging purpose

25 of 36

SOLVE: bad_nonce

nsec> crypto_bad_nonce

The flag was encrypted with the following order of ATECC608B commands:

1. Nonce: A bad nonce was generated to "initialize TempKey to a specified value".

2. Write: Data slot #8 block #0 was written with a certain value.

3. GenDig: Data slot #8 was used as GENDIG_ZONE_DATA to "performs a SHA256 hash on the source data indicated by zone with the contents of TempKey".

4. AES: The encrypted flag cipher was generated with the AES algorithm using the value of TempKey after the execution of the previous steps.

bad nonce value used:

42424242424242424242424242424242

42424242424242424242424242424242

value that was written to data slot #8 block #0:

004265207374726F6E6720616E642072

65616420646174617368656574732061

resulting encrypted flag cipher: 40878CBD30C22E590EFB1C9448A3B3AA

Can you reproduce and decrypt the cipher?

nsec> crypto_print_config 8

AES_Enable: 0x61

SlotConfig[8]: 0x0000

WriteConfig: 0x00

WriteKey: 0x00

IsSecret: 0x00

no_mac: 0x00

readkey: 0x00

KeyConfig[8]: 0x003c

private: 0x00

pub_info: 0x00

key_type: 0x07

26 of 36

PSA

GOOGLE DOES

NOT FIND IT

27 of 36

SOLVE: bad_nonce

https://microchiptech.github.io/cryptoauthlib/a01439.html

ATCA_STATUS atcab_nonce (const uint8_t *num_in)

Execute a Nonce command in pass-through mode to initialize TempKey to a specified value.

28 of 36

SOLVE: bad_nonce

So what happened is:

// Loads into TempKey

atcab_nonce(4242424242424242424242424242424242424242424242424242424242424242)

// Write into data

atcab_write_zone(ATCA_ZONE_DATA, 8, 004265207374726F6E6720616E64207265616420646174617368656574732061)

// GenDig from data zone using TempKey (outputs in TempKey)

atcab_gendig(GENDIG_ZONE_DATA, 8)

// AES using the TempKey from GenDig

atcab_aes_encrypt(ATCA_TEMPKEY_KEYID, 0, plaintext, ciphertext);

The key is to find out the data structure of TempKey

29 of 36

SOLVE: bad_nonce - GenDig back to the PDFs!

Data structure of TempKey!

30 of 36

SOLVE: bad_nonce - GenDig dig into code

https://github.com/MicrochipTech/cryptoauthlib/blob/e8f28ec2d41a43b0906fda4a7faa597a08858116/lib/calib/calib_command.h#L461C1-L462C1

//!< GenDig zone id data. Use KeyID to specify a slot in the Data zone or a transport key in the hardware array.

#define GENDIG_ZONE_DATA ((uint8_t)2)

https://github.com/MicrochipTech/cryptoauthlib/blob/e8f28ec2d41a43b0906fda4a7faa597a08858116/lib/host/atca_host.h#L280

typedef struct atca_gen_dig_in_out

{

uint8_t zone; //!< [in] Zone/Param1 for the GenDig command

uint16_t key_id; //!< [in] KeyId/Param2 for the GenDig command

uint16_t slot_conf; //!< [in] Slot config for the GenDig command

uint16_t key_conf; //!< [in] Key config for the GenDig command

uint8_t slot_locked; //!< [in] slot locked for the GenDig command

uint32_t counter; //!< [in] counter for the GenDig command

bool is_key_nomac; //!< [in] Set to true if the slot pointed to be key_id has the SotConfig.NoMac bit set

const uint8_t * sn; //!< [in] Device serial number SN[0:8]. Only SN[0:1] and SN[8] are required though.

const uint8_t * stored_value; //!< [in] 32-byte slot value, config block, OTP block as specified by the Zone/KeyId parameters

const uint8_t * other_data; //!< [in] 32-byte value for shared nonce zone, 4-byte value if is_key_nomac is true, ignored and/or NULL otherwise

struct atca_temp_key *temp_key; //!< [inout] Current state of TempKey

} atca_gen_dig_in_out_t;

Not very useful…

31 of 36

bad_nonce - GenDig TempKey in python

from Crypto.Cipher import AES

import hashlib

import binascii

plaintext = b'FLAG-STRING'

def aes_me(data):

key = hashlib.sha256(data).digest()[:16]

cipher = AES.new(key, AES.MODE_ECB)

print(str.upper(binascii.hexlify(cipher.encrypt(plaintext)).decode()))

aes_me(bytes.fromhex('004265207374726F6E6720616E64207265616420646174617368656574732061') + bytes([0x15, 0x02, 0x08, 0x00, 0xee, 0x01, 0x23] + [0x00]*25 + [0x42]*32))

40878CBD30C22E590EFB1C9448A3B3AA

resulting encrypted flag cipher: 40878CBD30C22E590EFB1C9448A3B3AA

32 of 36

bad_nonce - easy mapping!

[0x15, 0x02, 0x08, 0x00, 0xee, 0x01, 0x23] + [0x00]*25 + [0x42]*32)

if 0x02 (Data), then KeyID specifies a slot in the Data zone or a transport key in the hardware array

KeyID = 8

I (637) crypto_atecc_init: Initializing ATECC...

I (647) crypto_atecc_init: LOCK_ZONE_DATA Locked.

I (657) crypto_atecc_init: LOCK_ZONE_CONFIG Locked.

I (667) crypto_atecc_init: ATECC608B detected; Device revision: 6003

I (677) crypto_atecc_init: Serial Number: 0x0123AA3FD87E1140EE

4242424242424242424242424242424242424242424242424242424242424242

33 of 36

SOLVE: bad_nonce

def decrypt_aes_ecb(ciphertext, key):

cipher = AES.new(key, AES.MODE_ECB)

plaintext = cipher.decrypt(ciphertext)

return plaintext

ciphertext = bytes.fromhex('40878CBD30C22E590EFB1C9448A3B3AA')

key = hashlib.sha256(bytes.fromhex('004265207374726F6E6720616E64207265616420646174617368656574732061') + bytes([0x15, 0x02, 0x08, 0x00, 0xee, 0x01, 0x23] + [0x00]*25 + [0x42]*32)).digest()[:16]

decrypt_aes_ecb(ciphertext, key)

34 of 36

THAT’S IT

You’re now a crypto-flash-hardware expert person

🍻Let’s go to the pub?🍻

35 of 36

Thanks!

NorthSec 2024 and the Badge team for this one of a kind opportunity!

Slides on:

https://about.jonathanmarcil.ca

My helpers for today!

Montrehack!

Check out this awesome write up by Padraignix for more details on the solve!

https://blog.quantumlyconfused.com/ctf/2024/05/25/nsec2024-badgelife-addon/

36 of 36

FLAG CHECK

Phospholipid PLUG ME FLAG-PWT4RB47KVA

Phospholipid 0 - FLASH dump FLAG-NSECCTF24DUMPFLASH

Phospholipid 1 - FLASH first_128 FLAG-63QWJK4DCE3

Phospholipid 2 - FLASH another_128 FLAG-C9K3NI1UOVP

Phospholipid 3 - CRYPTO read_zone FLAG-QKANDCND2XT

Phospholipid 4 - CRYPTO hmac FLAG-7A3BEDBA41C8EA1899FEF2D68C7571EF

Phospholipid 5 - CRYPTO ECDH FLAG-97B5C047A6FD010D

Phospholipid 6 - CRYPTO bad_nonce FLAG-SAQR9KRHN4Z