Skip to content

Instantly share code, notes, and snippets.

@jongwonwoo
Forked from gretel/amiibo.sh
Created March 14, 2021 07:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jongwonwoo/a6e7bf93f0905f13e80e3a45e3f96bd6 to your computer and use it in GitHub Desktop.
Save jongwonwoo/a6e7bf93f0905f13e80e3a45e3f96bd6 to your computer and use it in GitHub Desktop.
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