Skip to content

Instantly share code, notes, and snippets.

/amiibo.py Secret

Created January 13, 2016 17:04
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 anonymous/0a3e16f8f814deb2a056 to your computer and use it in GitHub Desktop.
Save anonymous/0a3e16f8f814deb2a056 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python2
# Use python2 since nfcpy doesn't support python3
import argparse
import binascii
import collections
import struct
import sys
# libusb: http://libusb.info/
# pyusb: https://walac.github.io/pyusb/
# nfcpy: https://nfcpy.readthedocs.org/
import nfc
# PyCryptodome: http://pycryptodome.readthedocs.org/
from Crypto.Cipher import AES
from Crypto.Hash import HMAC
from Crypto.Hash import SHA256
from Crypto.Util import Counter
DUMP_SIZE = 520
PAGE_SIZE = 4
CT = 0x88
PACK = b'\x80\x80'
CC = b'\xF1\x10\xFF\xEE'
STATIC_LOCK = b'\x00\x00\x0F\xE0'
DYNAMIC_LOCK = b'\x01\x00\x0F\x00'
CFG0 = b'\x00\x00\x00\x04'
CFG1 = b'\x5F\x00\x00\x00'
C_PWD_AUTH = b'\x1B'
MasterKeys = collections.namedtuple('MasterKeys',
('hmac_key', 'type_string', 'magic', 'xor_pad'))
DerivedKeys = collections.namedtuple('DerivedKeys',
('aes_key', 'aes_iv', 'hmac_key'))
class FileType(argparse.FileType):
"""Fix for binary stdin/stdout on Windows."""
def __call__(self, string):
f = super(FileType, self).__call__(string)
if string == '-' and 'b' in self._mode and sys.platform == 'win32':
import os, msvcrt
msvcrt.setmode(f.fileno(), os.O_BINARY)
return f
class Drbg(object):
def __init__(self, hmac_key, seed):
self._hmac_key = hmac_key
self._seed = bytes(seed)
self._iteration = 0
self._cache = None
self._pos = 0
def _update(self):
hmac = HMAC.new(self._hmac_key, digestmod=SHA256)
hmac.update(struct.pack('>H', self._iteration))
hmac.update(self._seed)
self._iteration += 1
self._cache = hmac.digest()
self._pos = 0
def get_bytes(self, length):
result = bytearray()
while len(result) < length:
if self._cache is None or self._pos >= len(self._cache):
self._update()
l = min(length - len(result), len(self._cache) - self._pos)
result.extend(memoryview(self._cache)[self._pos:self._pos + l])
self._pos += l
return result
def get_ntag215():
clf = nfc.ContactlessFrontend('usb')
tag = clf.connect(rdwr={'on-connect': lambda Tag: False})
if not isinstance(tag, nfc.tag.tt2_nxp.NTAG215):
raise RuntimeError('Tag is not an NTag215')
return tag
def get_master_keys(keyfile):
hmac_key = keyfile.read(16)
type_string = keyfile.read(14)
keyfile.read(1)
magic_size = ord(keyfile.read(1))
magic = keyfile.read(magic_size)
keyfile.read(16 - magic_size)
xor_pad = keyfile.read(32)
return MasterKeys(hmac_key, type_string, magic, xor_pad)
def get_base_seed(data):
data = memoryview(data)
base_seed = bytearray()
base_seed.extend(data[0x11:0x13])
base_seed.extend(b'\0' * 0x0E)
for _ in xrange(2):
base_seed.extend(data[0x00:0x08])
base_seed.extend(data[0x60:0x80])
return base_seed
def get_seed(master_keys, base_seed):
base_seed = memoryview(base_seed)
seed = bytearray()
seed.extend(master_keys.type_string);
seed.extend(base_seed[0:16 - len(master_keys.magic)])
seed.extend(master_keys.magic)
seed.extend(base_seed[0x10:0x20])
for i in range(0x20):
seed.append(ord(base_seed[0x20 + i]) ^ ord(master_keys.xor_pad[i]))
return seed
def get_derived_keys(master_keys, data):
base_seed = get_base_seed(data)
seed = get_seed(master_keys, base_seed)
drbg = Drbg(master_keys.hmac_key, seed)
aes_key = drbg.get_bytes(16)
aes_iv = drbg.get_bytes(16)
hmac_key = drbg.get_bytes(16)
return DerivedKeys(aes_key, aes_iv, hmac_key)
def set_uid(data, uid):
data[0:3] = uid[0:3]
data[3] = CT ^ uid[0] ^ uid[1] ^ uid[2]
data[4:8] = uid[3:7]
data[8] = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]
def get_pwd(uid):
return bytearray((
0xAA ^ uid[1] ^ uid[3],
0x55 ^ uid[2] ^ uid[4],
0xAA ^ uid[3] ^ uid[5],
0x55 ^ uid[4] ^ uid[6],
))
def is_unlocked(tag):
header = bytearray(tag.read(0))
cfg = bytearray(tag.read(130))
return (header[10] == 0 and header[11] == 0 and cfg[0] == 0 and cfg[1] == 0
and cfg[2] == 0 and cfg[7] >= 135 and cfg[8] & 0b01000000 == 0)
def authenticate(tag, pwd):
"""nfcpy's implementation of authenticate is broken."""
return tag.transceive(C_PWD_AUTH + pwd) == PACK
def cipher(mode, aes_key, aes_iv, data):
ctr = Counter.new(8 * AES.block_size,
initial_value=int(binascii.hexlify(aes_iv), 16))
aes = AES.new(key=bytes(aes_key), mode=AES.MODE_CTR, counter=ctr)
f = getattr(aes, mode)
data[0x014:0x034] = f(bytes(data[0x014:0x034]))
data[0x0A0:0x208] = f(bytes(data[0x0A0:0x208]))
def hmac(hmac_key, data):
hmac = HMAC.new(bytes(hmac_key), digestmod=SHA256)
hmac.update(bytes(data[0x011:0x034]))
hmac.update(bytes(data[0x0A0:0x208]))
hmac.update(bytes(data[0x034:0x054]))
hmac.update(bytes(data[0x000:0x008]))
hmac.update(bytes(data[0x054:0x080]))
return hmac.digest()
def decrypt(master_keys, data):
derived_keys = get_derived_keys(master_keys, data)
cipher('decrypt', derived_keys.aes_key, derived_keys.aes_iv, data)
if not memoryview(data)[0x80:0xA0] == hmac(derived_keys.hmac_key, data):
raise RuntimeError('Signature check failed')
def scan(master_keys):
tag = get_ntag215()
data = bytearray()
for page in xrange(0, DUMP_SIZE // PAGE_SIZE, 4):
data.extend(tag.read(page))
decrypt(master_keys, data)
return data
def encrypt(master_keys, data):
derived_keys = get_derived_keys(master_keys, data)
data[0x80:0xA0] = hmac(derived_keys.hmac_key, data)
cipher('encrypt', derived_keys.aes_key, derived_keys.aes_iv, data)
def restore(master_keys, data):
tag = get_ntag215()
uid = bytearray(tag.identifier)
set_uid(data, uid)
pwd = get_pwd(uid)
unlocked = is_unlocked(tag)
if not unlocked:
if not authenticate(tag, pwd):
raise RuntimeError('Tag auth failed')
locked = bytearray()
for page in xrange(0x0D, 0x20, 4):
locked.extend(tag.read(page))
data[0x34:0x80] = locked
encrypt(master_keys, data)
for page in xrange(0x04, 0x0D):
tag.write(page, data[page * PAGE_SIZE:(page + 1) * PAGE_SIZE])
if unlocked:
for page in xrange(0x0D, 0x20):
tag.write(page, data[page * PAGE_SIZE:(page + 1) * PAGE_SIZE])
for page in xrange(0x20, 0x82):
tag.write(page, data[page * PAGE_SIZE:(page + 1) * PAGE_SIZE])
if unlocked:
tag.write(0x03, CC)
tag.write(0x02, STATIC_LOCK)
tag.write(0x82, DYNAMIC_LOCK)
tag.write(0x85, pwd)
tag.write(0x86, PACK + b'\0\0')
tag.write(0x83, CFG0)
tag.write(0x84, CFG1)
if __name__ == '__main__':
parent_parser_infile = argparse.ArgumentParser(add_help=False)
parent_parser_infile.add_argument('-i', '--infile', default='-',
type=FileType('rb'),
help='input file; if not specified, stdin will be used')
parent_parser_outfile = argparse.ArgumentParser(add_help=False)
parent_parser_outfile.add_argument('-o', '--outfile', default='-',
type=FileType('wb'),
help='output file; if not specified, stdout will be used')
parser = argparse.ArgumentParser()
parser.add_argument('-k', '--keyfile', required=True,
type=FileType('rb'),
help='key set file; for retail amiibo, use "retail unfixed" key set')
subparsers = parser.add_subparsers(dest='command')
parser_scan = subparsers.add_parser('scan',
parents=(parent_parser_outfile,), help='scan and decrypt amiibo')
parser_scan = subparsers.add_parser('decrypt',
parents=(parent_parser_infile, parent_parser_outfile),
help='decrypt and check amiibo dump')
parser_scan = subparsers.add_parser('encrypt',
parents=(parent_parser_infile, parent_parser_outfile),
help='encrypt and sign amiibo dump')
parser_scan = subparsers.add_parser('restore',
parents=(parent_parser_infile,), help='encrypt and restore amiibo')
args = parser.parse_args()
master_keys = get_master_keys(args.keyfile)
if hasattr(args, 'infile'):
data = args.infile.read(DUMP_SIZE)
data = bytearray(data)
if args.command == 'scan':
data = scan(master_keys)
elif args.command == 'decrypt':
decrypt(master_keys, data)
elif args.command == 'encrypt':
encrypt(master_keys, data)
elif args.command == 'restore':
restore(master_keys, data)
if hasattr(args, 'outfile'):
args.outfile.write(data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment