-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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