Skip to content

Instantly share code, notes, and snippets.

@gretel
Last active November 23, 2022 22:37
Embed
What would you like to do?
query, read, encode (using amiitool), write and lock NTAG215 (using uFR Nano hardware) for the purpose of researching Nintendo's Amiibo infrastructure Raw
#!/bin/bash
# This is a companion script to https://github.com/konstantin-kelemen/arduino-amiibo-tools
# The original post this was crafted for was https://games.kel.mn/en/create-amiibo-clones-with-arduino/
# For more info go to https://games.kel.mn/en/companion-script-to-simplify-amiibo-cloning-with-arduino/
#requirements:
#sha1sum (part of coreutils)
#xxd (part of vim)
#hexdump
#amiitool (https://github.com/socram8888/amiitool)
hash xxd 2>/dev/null || { echo >&2 "require xxd but it's not installed. Aborting."; exit 1; }
hash sha1sum 2>/dev/null || { echo >&2 "require sha1sum but it's not installed. Aborting."; exit 1; }
hash hexdump 2>/dev/null || { echo >&2 "require hexdump but it's not installed. Aborting."; exit 1; }
hash ./amiitool 2>/dev/null || { echo >&2 "require amiitool but it's not installed or in the currect directory. Aborting."; exit 1; }
if [ $# -ne 3 ]
then
echo "usage: $0 key_file encrypted_dump_file blank_tagid"
exit
fi
if [ "$(sha1sum "$1" |cut -d' ' -f1)" != "bbdbb49a917d14f7a997d327ba40d40c39e606ce" ]
then
echo "key_file not sane"
exit
fi
base=${2%%.*}
#get the empty tag uid:
taguid=$3
taguid0="$(echo "$taguid" | cut -b1,2)" # Byte 0 (should bx 0x04)
taguid1="$(echo "$taguid" | cut -b3,4)" # Byte 1 (we count from 0)
taguid2="$(echo "$taguid" | cut -b5,6)" # Byte 2
if [ ${#3} -eq 18 ]; then # Check if user provided a long taguid
taguid3="$(echo "$taguid" | cut -b9,10)" # Byte 4
taguid4="$(echo "$taguid" | cut -b11,12)" # Byte 5
taguid5="$(echo "$taguid" | cut -b13,14)" # Byte 6
taguid6="$(echo "$taguid" | cut -b15,16)" # Byte 7
uid="$(echo "$taguid" | cut -b1-16)"
BCC1="$(echo "$taguid" | cut -b17,18)" # Pull out the BCC1 for use later
elif [ ${#3} -eq 14 ]; then # Check if user provided a short taguid
taguid3="$(echo "$taguid" | cut -b7,8)" # Byte 3
taguid4="$(echo "$taguid" | cut -b9,10)" # Byte 4
taguid5="$(echo "$taguid" | cut -b11,12)" # Byte 5
taguid6="$(echo "$taguid" | cut -b13,14)" # Byte 6
# Convert 7byte to 9byte for script
BCC0="$(printf '%02X\n' $(( 0x88 ^ 0x$taguid0 ^ 0x$taguid1 ^ 0x$taguid2 )))" # Calculate the BCC0
BCC1="$(printf '%02X\n' $(( 0x$taguid3 ^ 0x$taguid4 ^ 0x$taguid5 ^ 0x$taguid6 )))" # Calculate the BCC1
uid="$taguid0$taguid1$taguid2$BCC0$taguid3$taguid4$taguid5$taguid6"
fi
if [ ${#uid} -ne 16 ]; then
echo "please pick a valid 7 or 9 byte uid"
exit
fi
# Generate the password from the tag
pw1="$(printf '%02X\n' $(( 0xAA ^ 0x$taguid1 ^ 0x$taguid3 )))"
pw2="$(printf '%02X\n' $(( 0x55 ^ 0x$taguid2 ^ 0x$taguid4 )))"
pw3="$(printf '%02X\n' $(( 0xAA ^ 0x$taguid3 ^ 0x$taguid5 )))"
pw4="$(printf '%02X\n' $(( 0x55 ^ 0x$taguid4 ^ 0x$taguid6 )))"
#decrypt the dump
#echo Using Amiibo Tool to decrypt ${2%%.*}
./amiitool -d -k "$1" -i "$2" -o ${base}_dec.bin || exit 2
#modify the uid record
echo "01D4: $uid" | xxd -r - ${base}_dec.bin
#add password
echo "0214: $pw1$pw2$pw3$pw4" | xxd -r - ${base}_dec.bin #pw
echo "0218: 8080" | xxd -r - ${base}_dec.bin
#set the default values
echo "0208: 000000" | xxd -r - ${base}_dec.bin
echo "0000: $BCC1" | xxd -r - ${base}_dec.bin
echo "0002: 0000" | xxd -r - ${base}_dec.bin
enc_file="${base}_${uid}.bin"
#reencrypt the uid modified dump
#echo Using Amiibo Tool to encrypt ${2%%.*}
./amiitool -e -k "$1" -i ${base}_dec.bin -o ${enc_file} || exit 3
rm ${base}_dec.bin
echo "${enc_file}"
# echo "**** START OF HEXDUMP ****"
# hexdump -v -e " 4/1 \"0x%02X, \" \"\n\"" "enc.bin" > hexdump
# truncate -s -2 hexdump
# echo "" >> hexdump
# echo "" >> hexdump
# cat hexdump
#!/bin/sh
if [ $# -ne 2 ]
then
echo "usage: $0 amiibo.bin uuid"
exit 1
fi
KEY='retail_key.bin'
FILE=$1
UUID=$2
./amiibo.sh ${KEY} ${FILE} ${UUID}
#!/usr/bin/env python3
#
# https://gist.github.com/gretel/dd80c854e22c2afd20f5aebc62015096
# https://www.d-logic.net/nfc-rfid-reader-sdk/products/nano-nfc-rfid-reader/
# https://code.d-logic.net/nfc-rfid-reader-sdk/ufr-lib
#
from ctypes import *
import argparse
import os, threading, time, sys
import pyperclip
import subprocess
import traceback
import ufr_constants, ufr_errors
# oh i love argparse
parser = argparse.ArgumentParser(description='query, read, encode (using amiitool), write and lock NTAG215 (using uFR Nano hardware) for the purpose of researching Nintendo\'s Amiibo infrastructure')
parser.add_argument('-f', '--filename', dest='filename', help='name of the file containing binary data')
parser.add_argument('-r', '--read', dest='read', action='store_true', help='read data from tag')
parser.add_argument('-e', '--encode', dest='encode', action='store_true', help='encode data (calls "encode.sh" when uid is known)')
parser.add_argument('-w', '--write', dest='write', action='store_true', help='write data to tag')
parser.add_argument('-x', '--lock', dest='lock', action='store_true', help='set dynamic and static lock bytes (!)')
parser.add_argument('-l', '--loop', dest='loop', action='store_true', help='do not exit on completion but loop (useful for batch jobs)')
args = parser.parse_args()
class NanoAmii(threading.Thread):
def load_blob(self, filename : str) -> bytearray:
# first argument (required)
# TODO: fail gracefully if argument missing/file nonexistant
self.filename = filename
# read file as binary
print('LOAD', self.filename)
with open(self.filename, 'rb') as binary:
self.data = bytearray(binary.read())
# store length
self.data_len = len(self.data)
# TODO: check for min/max length
print(' read', self.data_len, self.data)
def save_blob(self, filename : str, data : bytes, mode : str = 'wb'):
print('SAVE', repr(filename))
with open(filename, mode) as binary:
binary.write(data)
binary.close()
def main_thread(self):
# loop
while self.run:
self.loop()
def __init__(self):
#print('__init__')
threading.Thread().__init__()
print(sys.argv[0], args)
self.block_len = 0
self.blocks_num = 0
self.card_size_linear = c_uint8()
self.card_size_raw = c_uint8()
self.card_type = None
self.card_uid = (c_ubyte * 10)()
self.connected = False
self.run = True
# TODO: abstraction
self.ufr = cdll.LoadLibrary(os.getcwd() + '/ufr-lib/osx/x86_64/libuFCoder.dylib')
self.ufr.ReaderSoftRestart()
self.data = bytearray()
self.data_len = 0
if(args.write and args.filename == None):
self.abort('filename required')
# start thread
threading.Thread(target = self.main_thread).start()
def open(self) -> int:
reader_fw_bld = c_uint32()
reader_fw_maj = c_uint32()
reader_fw_min = c_uint32()
reader_type = c_uint32()
print('OPEN')
# open device
call_result = self.ufr.ReaderOpen()
if call_result == ufr_constants.DL_OK:
self.ufr.GetReaderType(byref(reader_type))
print(' type', reader_type.value)
self.ufr.GetReaderFirmwareVersion(byref(reader_fw_min), byref(reader_fw_maj))
self.ufr.GetBuildNumber(byref(reader_fw_bld))
print(' firmware %s.%s.%s' % (reader_fw_min.value, reader_fw_maj.value, reader_fw_bld.value))
#self.ufr.ReaderUISignal(ufr_constants.FUNCT_LIGHT_OK, ufr_constants.FUNCT_SOUND_OK)
self.ufr.AutoSleepSet(10)
self.connected = True
else:
print(' error', ufr_errors.UFCODER_ERROR_CODES[call_result])
self.connected = False
return call_result
def encode(self, filename : str, uid) -> int:
print('ENCODE', filename, uid)
try:
output = subprocess.check_output(['bash', 'encode.sh', filename, uid])
args.filename = output.decode('utf-8').strip()
except subprocess.CalledProcessError as e:
self.abort('error on subprocess: %s' % e.output)
print(' filename', args.filename)
self.load_blob(args.filename)
def close(self) -> int:
# reset
self.card_size_linear = c_uint8()
self.card_size_raw = c_uint8()
self.card_type = c_uint8()
self.connected = False
self.card_uid = (c_ubyte * 10)()
# close device
call_result = self.ufr.ReaderClose()
print('CLOSE', hex(call_result))
return call_result
def read_tag(self) -> bytearray:
block = (c_uint8 * self.block_len)()
block_pos = 0
errors = 0
result = bytearray()
print('READ', self.blocks_num, self.block_len)
for r in range(0, self.blocks_num):
block_pos = r
call_result = self.ufr.BlockRead(byref(block), block_pos, ufr_constants.MIFARE_AUTHENT1A, 0)
if(call_result == ufr_constants.DL_OK):
print(' R', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(bytes(block)))
for b in range(0, self.block_len):
result.append(block[b])
else:
errors += 1
print(' !R', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result])
return errors, result
def write_tag(self) -> int:
block = bytes()
block_pos = c_uint8()
call_result = c_uint8()
data_pos = 0
errors = 0
print('WRITE', self.blocks_num, self.block_len)
for block_pos in range(0, self.blocks_num):
if(block_pos < 3 or block_pos == 130):
# skip lock bytes
print(' -W', block_pos)
else:
block = bytes(self.data[data_pos:data_pos + self.block_len])
call_result = self.ufr.BlockWrite(block, block_pos, ufr_constants.MIFARE_AUTHENT1A, 0)
if call_result == ufr_constants.DL_OK:
print(' W', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
else:
errors = 1
print(' !W', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
break
data_pos += 4
return errors
def write_dynlock(self) -> int:
b = 0x01, 0x00, 0x0F, 0xBD
block = bytes(b)
block_pos = 130
call_result = c_uint8()
errors = 0
print('DYNLOCK', block_pos, block)
call_result = self.ufr.BlockWrite(block, block_pos, ufr_constants.MIFARE_AUTHENT1A, 0)
if call_result == ufr_constants.DL_OK:
print(' DL', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
else:
errors = 1
print(' !DL', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
return errors
def write_statlock(self) -> int:
b = 0x0F, 0xE0, 0x0F, 0xE0
block = bytes(b)
block_pos = 2
call_result = c_uint8()
errors = 0
print('STATLOCK', block_pos, block)
call_result = self.ufr.BlockWrite(block, block_pos, ufr_constants.MIFARE_AUTHENT1A, 0)
if call_result == ufr_constants.DL_OK:
print(' SL', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
else:
errors = 1
print(' !SL', block_pos, ufr_errors.UFCODER_ERROR_CODES[call_result], repr(block))
return errors
def query(self) -> bool:
card_size_linear = c_uint8()
card_size_raw = c_uint8()
card_type = c_uint8()
card_uidsize = c_uint8()
card_uid = (c_ubyte * 10)()
# get tag type
call_result = self.ufr.GetDlogicCardType(byref(card_type))
if call_result == ufr_constants.DL_OK:
print('QUERY', ufr_errors.UFCODER_ERROR_CODES[call_result])
print(' type', card_type.value, 'name', ufr_constants.CardName(card_type.value))
self.card_type = card_type.value
else:
return False
# size of memory, length of block
self.blocks_num = ufr_constants.MaxBlock(self.card_type)
self.block_len = ufr_constants.BlockLength(self.card_type)
# get tag identifier (uid)
call_result = self.ufr.GetCardIdEx(byref(card_type), card_uid, byref(card_uidsize))
if call_result == ufr_constants.DL_OK:
# compose uid
c = ''
for n in range(card_uidsize.value):
c = c + format(card_uid[n], '02x')
print(' uid', c)
self.card_uid = c
else:
self.abort('error getting uid of tag')
# copy to clipboard
pyperclip.copy(c)
#self.save_blob('/tmp/nanoamii.last_uid', c, 'w')
call_result = self.ufr.GetCardSize(byref(card_size_linear), byref(card_size_raw))
if call_result == ufr_constants.DL_OK:
self.card_size_raw = card_size_raw.value
self.card_size_linear = card_size_linear.value
print(' size linear', card_size_linear.value, 'raw', card_size_raw.value)
else:
self.abort('error getting memory sizes of tag')
return True
def contact(self):
errors = 0
tag = self.query()
if(tag != True):
return
if(args.read):
read_data = ''
errors, read_data = self.read_tag()
if(errors > 0):
self.abort('error on read')
# TODO error handling
if(args.filename != None):
filename = args.filename
else:
filename = self.card_uid + '.bin'
self.save_blob(filename, read_data)
if(args.encode):
if(args.filename):
self.encode(args.filename, self.card_uid)
else:
self.abort('filename required')
if(args.write):
self.load_blob(args.filename)
errors = self.write_tag()
if(errors > 0):
self.abort('error on write')
if(args.lock):
errors = self.write_dynlock()
if(errors > 0):
self.abort('error on writing dynamic lock bytes')
else:
errors = self.write_statlock()
if(errors > 0):
self.abort('error on writing static lock bytes')
self.ufr.ReaderUISignal(ufr_constants.FUNCT_LIGHT_OK, ufr_constants.FUNCT_SOUND_OK)
if(args.loop != True):
self.run = False
else:
time.sleep(1.5)
def loop(self):
try:
if self.connected != True:
# open
self.open()
elif self.connected:
# connected
result = self.contact()
if result == 0xa4:
# happens on usb disconnection - allow reconnection
self.close()
else:
# ensure
self.close()
except:
# catch exceptions
print('\nEXCEPTION', traceback.format_exc())
sys.exit(1)
finally:
# dont run wild
time.sleep(0.5)
def abort(self, reason):
self.ufr.ReaderUISignal(ufr_constants.FUNCT_LIGHT_ERROR, ufr_constants.FUNCT_SOUND_ERROR)
raise SystemExit(reason)
if __name__ == '__main__':
#print('__main__')
nanoamii = NanoAmii()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment