1 of 57

Hardware Hacking for Everyone

by Jonathan Marcil

October 2024

2 of 57

INTRO

What are we doing?

3 of 57

Hardware Hacking Simplified

  • Hardware in your hand; it doesn’t bite nor shock*
  • No soldering required
  • No external hacking tools needed
    • The hardware we give you ARE the tools

*: almost, could happen, I mean, don’t lick it?

4 of 57

Hardware Workshop Simplified

  • Abstraction of generic low level functionality
  • Focus of low level functionalities related to target of learning & hacking
  • Console commands to explore
    • This workshop will be guided

5 of 57

Simplified doesn’t mean simple

  • Content if this workshop can be still considered advanced
  • Many details are extremely particular to the hardware chips
  • Odds of finding hacking guide outside of write ups are very thin
  • Industry experts can still learn from it

6 of 57

Comprehension you won’t need to get flags

  • ESP32 microcontrollers
    • CPU running on the badge you have
  • GPIO (and some protocol like SPI / I2C)
    • Protocole
  • Flash memory
  • Crypto devices used

  • You don’t need the high level understanding, but you’ll learn about the flash and crypto device!

7 of 57

Workshop is a CTF

  • Guiding on which steps and commands to do
  • Will give solutions as we move on
  • A PDF document is your first entry point
  • “PDF Datasheets reading CTF”
  • Have fun finding flags

8 of 57

REMEMBER TO GIVE BACK ALL ITEMS AT THE END

9 of 57

LET’S GO

Let’s do this!

10 of 57

jonathanmarcil.ca/nsec2024

start with addon.pdf 👇

11 of 57

CONNECTION

plugging the thing, get serial console

12 of 57

WHAT TO DO NOW:

Get addon.pdf from jonathanmarcil.ca/nsec2024 and follow instructions on page 6&7 to get console access

13 of 57

WHAT TO DO NOW:�

Continue reading addon.pdf to get the wires connected

14 of 57

SOLVE: PLUGME

315

135

5 = outside

6 = inside

15 of 57

FLASH

playing with the flash memory

16 of 57

WHAT TO DO NOW:

run commands�(this is one challenge):

help

read_first_128�write_to_0x48

17 of 57

WHAT TO DO NOW:

Read datasheet W25Q64JV.pdf

Figure out what to do with raw_toggle

�Hint: WP, registers, you have 6 cables

18 of 57

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.

19 of 57

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

20 of 57

SOLVE: first_128

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

PLUG WP

TO VCC

21 of 57

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!

22 of 57

WHAT TO DO NOW:

command:

read_another_128�Read the same pdf more

23 of 57

SOLVE: another_128

Status Register-2 : 0x02 = 00000010b

CMP = 0

Status Register-3 : 0x60 = 01100000b

WPS = 0

0x050048

Write protect

0x080048

Not write protect

24 of 57

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

25 of 57

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

26 of 57

CRYPTO

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

27 of 57

WHAT TO DO NOW:

command:

crypto_print_config

crypto_print_pubkey�crypto_read_zone�Read the ATECC PDFs

28 of 57

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

29 of 57

CRYPTO: read the docs

ATECC608B Slot Configuration Summary

30 of 57

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

31 of 57

WHAT TO DO NOW:

explore slot 8 with command:

crypto_read_zone

help crypto_write32_from_hex

32 of 57

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!

33 of 57

WHAT TO DO NOW:

command:

crypto_hmac_rnd

solve with python hmac/hashlib

34 of 57

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!

35 of 57

WHAT TO DO NOW:

commands:

crypto_ECDH_premaster_secret�crypto_print_pubkey�crypto_read_zone

think on how ECDH works

36 of 57

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

37 of 57

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

38 of 57

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!

39 of 57

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

40 of 57

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

41 of 57

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

42 of 57

WHAT TO DO NOW:

commands:

crypto_bad_nonce�

This most likely we won’t have time.. deep knowledge of ATECC chip required (read libs + docs)

43 of 57

PSA

GOOGLE DOES

NOT FIND IT

44 of 57

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

45 of 57

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.

46 of 57

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

47 of 57

SOLVE: bad_nonce - GenDig back to the PDFs!

Data structure of TempKey!

48 of 57

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…

49 of 57

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

50 of 57

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

51 of 57

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)

52 of 57

THAT’S IT

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

53 of 57

WHAT HAVE WE LEARNED

Real world works with those chips

54 of 57

Hardware hacking

  • Plugging things differently is pretty much the hardware part we learned today
  • A lot of software skills can be used in hardware
  • Handled it in your hands without breaking it*
  • Datasheets rule the world of hardware
  • Info about hardware can be hard to find online

*: hopefully.. ok who licked it?

55 of 57

Winbond Flash

  • Write protect can be bypassed physically
  • Flash memory chip also contain registers
  • SPI Protocol and chip pins are what you were hooking up on the main badge
    • That’s how you connect a flash chip to a cpu!
  • 🟢 GREEN MEANS GOOD 😂

56 of 57

Microchip Crypto Chip

  • Chip contains flash memory slots
    • Some slots are protected differently
    • Manufacturer can change how slots behave
  • Functions on the chip are secure, but their utilisation could be flawed
  • Some things are not well documented.. and libraries exists and can help learning behavior

57 of 57

Thanks!

NorthSec 2024 and the Badge team for the platform!

Slides soon on:

https://about.jonathanmarcil.ca

Our outstanding helper Padraignix for today!

Montrehack!

Hackfest (esp. vn & Pat)

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/