Skip to content

Instantly share code, notes, and snippets.

@nateevans
Forked from gretel/amiibo.sh
Created September 11, 2018 13:38
Show Gist options
  • Select an option

  • Save nateevans/f7ce31eb2479a68c892215c81459c169 to your computer and use it in GitHub Desktop.

Select an option

Save nateevans/f7ce31eb2479a68c892215c81459c169 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
#!/usr/bin/env python3
#
# https://gist.github.com/
# 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 traceback
import ufr_constants, ufr_errors
# oh i love argparse
parser = argparse.ArgumentParser(description='read and write binary blob to nfc read_tag')
parser.add_argument('--filename', dest='filename', help='name of the file containing binary data')
parser.add_argument('--loop', dest='loop', action='store_true', help='do not exit on completion but loop')
parser.add_argument('--write', dest='write', action='store_true', help='write data to tag')
parser.add_argument('--read', dest='read', action='store_true', help='read data from tag')
parser.add_argument('--lock', dest='lock', action='store_true', help='set dynamic and static lock bytes (!)')
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('FILE', 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__()
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 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):
call_result = c_uint8()
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.write):
# write
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