Hardware Hacking for Everyone
by Jonathan Marcil
October 2024
INTRO
What are we doing?
Hardware Hacking Simplified
*: almost, could happen, I mean, don’t lick it?
Hardware Workshop Simplified
Simplified doesn’t mean simple
Comprehension you won’t need to get flags
Workshop is a CTF
REMEMBER TO GIVE BACK ALL ITEMS AT THE END
LET’S GO
Let’s do this!
jonathanmarcil.ca/nsec2024
start with addon.pdf 👇
CONNECTION
plugging the thing, get serial console
WHAT TO DO NOW:
Get addon.pdf from jonathanmarcil.ca/nsec2024 and follow instructions on page 6&7 to get console access
WHAT TO DO NOW:�
Continue reading addon.pdf to get the wires connected
SOLVE: PLUGME
315
135
5 = outside
6 = inside
FLASH
playing with the flash memory
WHAT TO DO NOW:
run commands�(this is one challenge):
help
read_first_128�write_to_0x48
WHAT TO DO NOW:
Read datasheet W25Q64JV.pdf
Figure out what to do with raw_toggle
�Hint: WP, registers, you have 6 cables
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.
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
SOLVE: first_128
Green light will come on upon successful plug of WP on VCC
PLUG WP
TO VCC
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!
WHAT TO DO NOW:
command:
read_another_128�Read the same pdf more
SOLVE: another_128
Status Register-2 : 0x02 = 00000010b
CMP = 0
Status Register-3 : 0x60 = 01100000b
WPS = 0
0x050048
Write protect
0x080048
Not write protect
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
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
CRYPTO
They are crypto challenges but not challenges about crypto but more like crypto usage challenges, is that clear?
WHAT TO DO NOW:
command:
crypto_print_config
crypto_print_pubkey�crypto_read_zone�Read the ATECC PDFs
CRYPTO: read the docs
CRYPTO: read the docs
ATECC608B Slot Configuration Summary
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
WHAT TO DO NOW:
explore slot 8 with command:
crypto_read_zone
help crypto_write32_from_hex
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!
WHAT TO DO NOW:
command:
crypto_hmac_rnd
solve with python hmac/hashlib
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!
WHAT TO DO NOW:
commands:
crypto_ECDH_premaster_secret�crypto_print_pubkey�crypto_read_zone
think on how ECDH works
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
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
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!
SOLVE: ECDH
/** \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
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
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
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)
PSA
GOOGLE DOES
NOT FIND IT
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
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.
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
SOLVE: bad_nonce - GenDig back to the PDFs!
Data structure of TempKey!
SOLVE: bad_nonce - GenDig dig into code
//!< 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)
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…
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
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
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)
THAT’S IT
You’re now a crypto-flash-hardware expert person
WHAT HAVE WE LEARNED
Real world works with those chips
Hardware hacking
*: hopefully.. ok who licked it?
Winbond Flash
Microchip Crypto Chip
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/