A couple of improvements
- make TLS session persistent: once open, you can save TLS state with tls.save() and restore it next time by calling tls.load() instad of tls.open(). State is saved in tls.dict file. - a small standalone script added which can be used to keep an open file descriptor to the device. Without it tls.load() wont work. - identify() function added - added methods to associate blobs with fingers. Windows seems to be using these blobs to save certificates for the users to establish trust with potentially removable devices (it is sort of documented in winbio specs)
This commit is contained in:
parent
a7e203f958
commit
ef6ce24a26
6 changed files with 282 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
__pycache__
|
||||
tls.dict
|
||||
|
|
|
|||
57
db97.py
57
db97.py
|
|
@ -23,6 +23,12 @@ class User():
|
|||
def __repr__(self):
|
||||
return '<User: dbid=%04x identity=%s fingers=%s>' % (self.dbid, repr(self.identity), repr(self.fingers))
|
||||
|
||||
def subtype_to_string(s):
|
||||
if s < 0xf5 or s > 0xfe:
|
||||
return 'Unknown'
|
||||
|
||||
return 'WINBIO_FINGER_UNSPECIFIED_POS_%02d' % (s - 0xf5 + 1)
|
||||
|
||||
def parse_user_storage(rsp):
|
||||
assert_status(rsp[:2])
|
||||
rsp=rsp[2:]
|
||||
|
|
@ -136,6 +142,24 @@ e3d473d5f1897e05db677c7ff6e260048255fdbe35fae42c791ea57ac0f2334e6ab5c650f59f3b27
|
|||
eb1e84deb687d2dc3d5cf4b03dbe35d165e
|
||||
''')
|
||||
|
||||
class DbRecord():
|
||||
def __init__(self):
|
||||
self.dbid = 0
|
||||
self.type = 0
|
||||
self.storage = 0
|
||||
self.value = None
|
||||
self.children = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<DbRecord: dbid=%d type=%d storage=%d value=%s children=%s>' % (
|
||||
self.dbid,
|
||||
self.type,
|
||||
self.storage,
|
||||
repr(self.value),
|
||||
repr(self.children)
|
||||
)
|
||||
|
||||
|
||||
class Db():
|
||||
def __init__(self, tls):
|
||||
self.tls = tls
|
||||
|
|
@ -163,9 +187,34 @@ class Db():
|
|||
else:
|
||||
return parse_user(rsp)
|
||||
|
||||
def get_record_value(self, dbid):
|
||||
rsp = self.tls.app(pack('<BH', 0x49, dbid))
|
||||
assert_status(rsp)
|
||||
|
||||
rec = DbRecord()
|
||||
rec.dbid, rec.type, rec.storage, sz = unpack('<xxHHHHxx', rsp[:12])
|
||||
rec.value = rsp[12:12+sz]
|
||||
|
||||
return rec
|
||||
|
||||
def get_record_children(self, dbid):
|
||||
rsp = self.tls.app(pack('<BH', 0x46, dbid))
|
||||
assert_status(rsp)
|
||||
|
||||
rec = DbRecord()
|
||||
rec.dbid, rec.type, rec.storage, sz, cnt = unpack('<xxHHHHHxx', rsp[:14])
|
||||
rsp = rsp[14:]
|
||||
rec.children=[]
|
||||
for i in range(0, cnt):
|
||||
dbid, typ = unpack('<HH', rsp[i:i+4])
|
||||
rec.children += [{ 'dbid': dbid, 'type': typ }]
|
||||
|
||||
return rec
|
||||
|
||||
def del_record(self, dbid):
|
||||
assert_status(self.tls.app(pack('<BH', 0x48, dbid)))
|
||||
|
||||
|
||||
def new_record(self, parent, typ, storage, data):
|
||||
assert_status(self.tls.app(b'\x45'))
|
||||
assert_status(self.tls.app(wtf_hardcoded))
|
||||
|
|
@ -189,6 +238,12 @@ class Db():
|
|||
rec = self.new_record(userid, 0xb, stg.dbid, template)
|
||||
return rec
|
||||
|
||||
def new_data(self, parent, data):
|
||||
stg = self.get_user_storage(name='StgWindsor')
|
||||
data = pack('<HH', 1, len(data)) + data
|
||||
rec = self.new_record(parent, 0x8, stg.dbid, data)
|
||||
return rec
|
||||
|
||||
def flush_changes(self):
|
||||
assert_status(self.tls.app(b'\x1a'))
|
||||
|
||||
|
|
@ -198,5 +253,5 @@ class Db():
|
|||
for u in usrs:
|
||||
print('%2d: User %s with %d fingers:' % (u.dbid, repr(u.identity), len(u.fingers)))
|
||||
for f in u.fingers:
|
||||
print(' %2d: %02x (WINBIO_FINGER_UNSPECIFIED_POS_%02d)' % (f['dbid'], f['subtype'], f['subtype'] - 0xf5 + 1))
|
||||
print(' %2d: %02x (%s)' % (f['dbid'], f['subtype'], subtype_to_string(f['subtype'])))
|
||||
|
||||
|
|
|
|||
40
holdthedoor.py
Normal file
40
holdthedoor.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# There seems to be either a bug or a feature which renders the scanner unusable if
|
||||
# there were too many attemps to establish TLS connection with it. While device
|
||||
# seems to magically fix itself after a while (8 hours?) it is extremely annoying
|
||||
# when hacking.
|
||||
#
|
||||
# It is probably not that bad if all communication is done by a single service process which
|
||||
# only initiates TLS connection once during system startup and then simply serves
|
||||
# requests via DBus, DCOM or whatever. It is not that nice when you have a
|
||||
# standalone program which you are hacking and restarting all the time.
|
||||
#
|
||||
# So, to workaround the problem I'm trying to save and restore the TLS state
|
||||
# between the prototype invocation. This works fine with one exception. As soon as
|
||||
# you close the last file descriptor associated with a USB device, the kernel automatically
|
||||
# resets the device, effectively killing the established TLS state.
|
||||
#
|
||||
# This script helps to work around this last problem. It keeps an open descriptor which
|
||||
# prevents kernel from resetting the device configuration. It does not interfere with
|
||||
# the main process and does not hold the claim on the inface. It just sits there
|
||||
# doing nothing until you decide to quit.
|
||||
#
|
||||
# The same can be achived by running something like "read 4</dev/bus/usb/001/011" from
|
||||
# a command line, but in this case you need to figure out what is the current bus/device
|
||||
# number yourself.
|
||||
|
||||
import usb.core
|
||||
from usb.util import claim_interface, release_interface
|
||||
from time import sleep
|
||||
|
||||
dev = usb.core.find(idVendor=0x138a, idProduct=0x0097)
|
||||
|
||||
# make sure we at least opened device descriptor
|
||||
claim_interface(dev, 0)
|
||||
|
||||
sleep(0.2)
|
||||
|
||||
# release the iface, but keep the device open
|
||||
release_interface(dev, 0)
|
||||
|
||||
# sit here, until the user press enter
|
||||
raw_input()
|
||||
135
prototype.py
135
prototype.py
|
|
@ -14,10 +14,17 @@ db = Db(tls)
|
|||
|
||||
def open97():
|
||||
usb.open()
|
||||
usb.send_init()
|
||||
tls.open()
|
||||
tls.save()
|
||||
#usb.trace_enabled = True
|
||||
#tls.trace_enabled = True
|
||||
|
||||
def load97():
|
||||
#usb.trace_enabled = True
|
||||
#tls.trace_enabled = True
|
||||
usb.open()
|
||||
tls.load()
|
||||
|
||||
def glow_start_scan():
|
||||
cmd=unhexlify('3920bf0200ffff0000019900200000000099990000000000000000000000000020000000000000000000000000ffff000000990020000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
||||
|
|
@ -27,9 +34,40 @@ def glow_end_enroll():
|
|||
cmd=unhexlify('39f4010000f401000001ff002000000000ffff0000000000000000000000000020000000000000000000000000f401000000ff0020000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
||||
assert_status(tls.app(cmd))
|
||||
|
||||
# FIXME this must be very specific to a particular device:
|
||||
def start_enroll_prg():
|
||||
cmd=unhex('''
|
||||
# FIXME this must be very specific to a particular device and constructed on the fly from multiple hardcoded tables:
|
||||
identify_prg=unhex('''
|
||||
02980000002300000020000800002000800000010032007000000000802020050024200000502077362820010030200100082170000c210
|
||||
000482102004c210000582000005c20000060200000682005006c20012970200121742001887820018084202000942001809c200902a020
|
||||
0b19b4200000b8203b04bc201400c0200200c4200100c82002003300100000000080cc200000f503d0200000a1013200440000000080dc2
|
||||
0e803e0206401e420d002e8200001f0200500f8200500fc200000b8203b0000080400140800000808000008080000140830000808000014
|
||||
0831001c081a0032000c0000000080501101004c1126003400080310071d10071d10071d10071d10071c01065810080101000007c8078c0
|
||||
6100000204f80007f000003070107010c07032c08fc80095a800afc08fb800b5a095b800afb08fa800b5b095c800afa08f9800b5c095d80
|
||||
0af908f8800b5d095e800af808f7800b5e095f800af708f6800b5f0960800af608f5800b600961800af508f4800b610962800af408f3800
|
||||
b620963800af308f2800b630964800af208f1800b640965800af108f0800b650966800af008ef800b660967800aef08ee800b670968800a
|
||||
ee08ed800b68096c800aed08ec800b6c096d800aec08eb800b6d096e800aeb08ea800b6e096f800aea08e9800b6f0970800ae908e8800b7
|
||||
00971800ae808e7800b710972800ae708e6800b720973800ae608e5800b730974800ae508e4800b740975800ae408e3800b750976800ae3
|
||||
08e2800b760977800ae208e1800b770978800ae108e0800b780979800ae008df800b79097a800adf08de800b7a097b800ade08dd800b7b0
|
||||
97c800add08dc800b7c097d800adc08db800b7d097e800adb08da800b7e097f800ada08d9800b7f0980800ad908d8800b800981800ad808
|
||||
d7800b810982800ad708d6800b820983800ad608d5800b830984800ad508d4800b840985800ad408d3800b850986800ad308d2800b86098
|
||||
7800ad208d1800b870988800ad108d0800b880989800ad008cf800b89098a800acf08ce800b8a098b800ace08cd800b8b098c800acd08cc
|
||||
800b8c098d800acc08cb800b8d098e800acb08ca800b8e098f800aca08c9800b8f0990800ac908c8800b900991800ac808c7800b9109928
|
||||
00ac708c6800b920993800ac608c5800b930994800ac508c4800b940995800ac408c3800b950996800ac308c2800b960997800ac208c180
|
||||
0b970998800ac108c0800b980999800ac008bf800b99099a800abf08be800b9a099b800abe08bd800b9b099c800abd08bc800b9c099d800
|
||||
abc08bb800b9d099e800abb08ba800b9e099f800aba08b9800b9f09a0800ab908b8800ba00801800ab808b7800a010802800ab708b6800a
|
||||
020803800ab608b5800a030804802003070404020000000000002f000400900000002900040000000000350004001000000017000000260
|
||||
02800fbb20f00f2220f00300000006001020040010a00018000000a0200000b19000050c360ea010910002e001c00020018002300000090
|
||||
0090004d01000090017c013c323232640a02013000cc0103000000ff0000001d000003ff00000025000003ff00000022000003101112131
|
||||
415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b
|
||||
4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081828
|
||||
38485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f2b23203c2d182e1e30182e1c321d341d341e321c301e1e241e201f
|
||||
201d1c321a301e1c211e21341f1e202024201f1e20201f212221221d221e23341e1d1e1d20341f1d193b341c1d1e35201e201c20221f341
|
||||
c1e1e1c221f201d21201e1c1f34242221201f20221f201e241e241d2020221e2420231d221e211e1f1e1e341c321e3220301d2d302f2d2c
|
||||
2b23223a211c7e7f807f8080808080808080808080808080808080808180818081808180808080808180818080808180818081808180818
|
||||
08180818081808180808081808180808081807f808081808080818081808180808081808180818081808180818080808180818081808180
|
||||
81808180818081808180818081808180818081808080808080808080807f807f807f807f7f7e7e
|
||||
''')
|
||||
# The following blog has only 1 byte difference from the above
|
||||
enroll_prg=unhex('''
|
||||
02980000002300000020000800002000800000010032007000000000802020050024200000502077362820010030200100082170000c210
|
||||
000482102004c210000582000005c20000060200000682005006c20012970200121742001887820018084202000942001809c200902a020
|
||||
0b19b4200000b8203b04bc201400c0200200c4200100c82002003300100000000080cc200000f503d0200000a1013200440000000080dc2
|
||||
|
|
@ -60,8 +98,9 @@ c1e1e1c221f201d21201e1c1f34242221201f20221f201e241e241d2020221e2420231d221e211e1
|
|||
08180818081808180808081808180808081807f808081808080818081808180808081808180818081808180818080808180818081808180
|
||||
81808180818081808180818081808180818081808080808080808080807f807f807f807f7f7e7e
|
||||
''')
|
||||
assert_status(tls.app(cmd))
|
||||
|
||||
def start_scan(cmd):
|
||||
assert_status(tls.app(cmd))
|
||||
|
||||
def wait_for_finger():
|
||||
while True:
|
||||
|
|
@ -84,8 +123,8 @@ def wait_till_finished():
|
|||
def stop_prg():
|
||||
return tls.app(unhexlify('5100200000'))
|
||||
|
||||
def capture_enroll():
|
||||
start_enroll_prg()
|
||||
def capture(prg):
|
||||
start_scan(prg)
|
||||
|
||||
b=usb.wait_int()
|
||||
if b[0] != 0:
|
||||
|
|
@ -176,7 +215,7 @@ def enroll(identity, subtype):
|
|||
while True:
|
||||
glow_start_scan()
|
||||
|
||||
err = capture_enroll()
|
||||
err = capture(enroll_prg)
|
||||
if err != 0:
|
||||
print('Error %08x, try again' % err)
|
||||
continue
|
||||
|
|
@ -206,3 +245,85 @@ def enroll(identity, subtype):
|
|||
|
||||
return recid
|
||||
|
||||
def parse_dict(x):
|
||||
rc={}
|
||||
|
||||
while len(x) > 0:
|
||||
(t, l), x = unpack('<HH', x[:4]), x[4:]
|
||||
rc[t], x = x[:l], x[l:]
|
||||
|
||||
return rc
|
||||
|
||||
|
||||
def identify():
|
||||
glow_start_scan()
|
||||
err = capture(identify_prg)
|
||||
if err != 0:
|
||||
raise Exception('Capture failed: %08x' % err)
|
||||
|
||||
try:
|
||||
# which finger?
|
||||
stg_id=0 # match against any storage
|
||||
usr_id=0 # match against any user
|
||||
cmd=pack('<BBBHHHHH', 0x5e, 2, 0xff, stg_id, usr_id, 1, 0,0)
|
||||
rsp=tls.app(cmd)
|
||||
assert_status(rsp)
|
||||
|
||||
b = usb.wait_int()
|
||||
if b[0] != 3:
|
||||
raise Exception('Identification failed: %s' % hexlify(b).decode())
|
||||
|
||||
rsp = tls.app(unhexlify('6000000000'))
|
||||
assert_status(rsp)
|
||||
rsp = rsp[2:]
|
||||
|
||||
finally:
|
||||
# finish
|
||||
assert_status(tls.app(unhexlify('6200000000')))
|
||||
|
||||
(l,), rsp = unpack('<H', rsp[:2]), rsp[2:]
|
||||
if l != len(rsp):
|
||||
raise Exception('Response size does not match')
|
||||
|
||||
rsp=parse_dict(rsp)
|
||||
|
||||
|
||||
#for k in rsp:
|
||||
# print('%04x: %s (%d)' % (k, hexlify(rsp[k]).decode(), len(rsp[k])))
|
||||
|
||||
#0001: 09000000 (4)
|
||||
#0003: f500 (2)
|
||||
#0004: 8dee792532d3432d41c872fd4d6d590fbc855ad449cf2753cd919eb9c94675c6 (32)
|
||||
#0005: 0000000000000000000000000000000000000000000000000000000000000000 (32)
|
||||
#0008: 0a00 (2)
|
||||
#0002: 010b0000 (4)
|
||||
#0006: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 (40)
|
||||
usrid, subtype, hsh, fingerid = rsp[1], rsp[3], rsp[4], rsp[8]
|
||||
usrid, = unpack('<L', usrid)
|
||||
subtype, = unpack('<H', subtype)
|
||||
fingerid, = unpack('<H', fingerid)
|
||||
|
||||
usr = db.get_user(usrid)
|
||||
finger_record = db.get_record_children(fingerid)
|
||||
|
||||
# Device won't let you add more than one data blob
|
||||
if len(finger_record.children) > 1:
|
||||
raise Exception('Expected only one child record for finger')
|
||||
|
||||
print('Recognised finger %02x (%s) from user %s' % (subtype, subtype_to_string(subtype), repr(usr.identity)))
|
||||
print('Template hash: %s' % hexlify(hsh).decode())
|
||||
|
||||
if len(finger_record.children) > 0:
|
||||
if finger_record.children[0]['type'] != 8:
|
||||
raise Exception('Expected data blob as a finger child')
|
||||
|
||||
blob_id = finger_record.children[0]['dbid']
|
||||
blob = db.get_record_value(blob_id).value
|
||||
|
||||
tag, sz = unpack('<HH', blob[:4])
|
||||
val = blob[4:4+sz]
|
||||
|
||||
print('Data blob associated with the finger: %04x: %s' % (tag, hexlify(val).decode()))
|
||||
|
||||
return rsp
|
||||
|
||||
|
|
|
|||
22
tls97.py
22
tls97.py
|
|
@ -11,6 +11,7 @@ from fastecdsa.point import Point
|
|||
from fastecdsa.keys import gen_private_key, get_public_key
|
||||
from fastecdsa.ecdsa import sign
|
||||
from fastecdsa.encoding.der import DEREncoder
|
||||
import pickle
|
||||
|
||||
|
||||
# Info about the host computer
|
||||
|
|
@ -128,6 +129,27 @@ class Tls():
|
|||
self.encryption_key = key_block[0x40:0x40+0x20]
|
||||
self.decryption_key = key_block[0x60:0x60+0x20]
|
||||
|
||||
def save(self):
|
||||
with open('tls.dict', 'wb') as f:
|
||||
pickle.dump({
|
||||
'sign_key': self.sign_key,
|
||||
'validation_key': self.validation_key,
|
||||
'encryption_key': self.encryption_key,
|
||||
'decryption_key': self.decryption_key,
|
||||
'secure_rx': self.secure_rx,
|
||||
'secure_tx': self.secure_tx
|
||||
}, f)
|
||||
|
||||
def load(self):
|
||||
with open('tls.dict', 'rb') as f:
|
||||
d=pickle.load(f)
|
||||
self.sign_key = d['sign_key']
|
||||
self.validation_key = d['validation_key']
|
||||
self.encryption_key = d['encryption_key']
|
||||
self.decryption_key = d['decryption_key']
|
||||
self.secure_rx = d['secure_rx']
|
||||
self.secure_tx = d['secure_tx']
|
||||
|
||||
def decrypt(self, c):
|
||||
iv, c = c[:0x10], c[0x10:]
|
||||
aes=AES.new(self.decryption_key, AES.MODE_CBC, iv)
|
||||
|
|
|
|||
41
usb97.py
41
usb97.py
|
|
@ -2,6 +2,9 @@
|
|||
import re
|
||||
import usb.core
|
||||
from binascii import *
|
||||
from util import assert_status
|
||||
from struct import unpack
|
||||
from usb.util import claim_interface, release_interface
|
||||
|
||||
def unhex(x):
|
||||
return unhexlify(re.sub('\W', '', x))
|
||||
|
|
@ -17,6 +20,22 @@ e024ca6b9a48332c1a69a5a3fdd24b964cf7e7c55229bb0b48a6e339eb2c42d07ec850a4ee780660
|
|||
69192ef2e16591df26394791a4ecb994a24f5a7f70f1eb2604e6bfb67a452cb74ead8b0d9808f890ac386750cbac06ee03a852145094053
|
||||
b2b074b9905de5cdbf2272b67e51f159164778d6d2ef7a1ccb81df9f896ddb38ce11e8140cf6cb9c82
|
||||
''')
|
||||
init_hardcoded_clean_slate=unhex('''
|
||||
06020000012c40c9d271378bc0912ef5dced69bd81b7fc16972c7b46e621af54a00e2cc6baca6eb83ea30222dfc6c925262006ae93412ea
|
||||
cf482f2034ee7b13297474b7e1e91f279cac2ccb7195443e4dd3328cfd292ade073fcc2eaa8f07b77231130ba997f921b9be7b4fb6cc691
|
||||
0d2976b3e050913b27dbe73afd6e964260b9435ebab5117e71f7cb68464d4b6f8afc7e1a421f671f5854a1d0c8ab93ed3b88b2bc1a42875
|
||||
e40b15f0e78496ac40e4a4a7fd39a97534ce1866479e0384f0789bbfc2fea0ce982bf7a9df97d60b237edbe1b26c9791043a96b81e435d6
|
||||
de5971c758d374905df95b0cddabfbf531749ba191f07a6f5e2722852f137a53513a9ec6ab30c3f09aa6ce21b391e55cf81dcda6422011b
|
||||
f163317a9a4382546141d45f2274bd660103bd3af705f3ed12e493bc4f834d5d7f162e2c3405cf857b00129789a3353bf7fab7796e267e3
|
||||
062d55660dbbb857911ac8e871c460dd31c56a86a5631475f0f2ee5e9ce2af0faec0931a640ba2394025f29ffeca3a7e99c15a78ce1f1f7
|
||||
808cedd7601b9b6382d72ca873257d4f6af70e29e22afea15e36e0282b8f0bfc68ffa3417d212b8bbe11bb73b363a19872e6e947d45de30
|
||||
fbc493ca083a0a4650615d8628606362081ca6df5d67527971d1776ad76a7a28c932f0317b59cb4a82a14b2bcb7b01fb662be1496d24d91
|
||||
9140ec80068b21a818daa2fb8e05f63edbd4bd5797c74a28b3e7cf81c9045248584977711341fca3f08ba91ff853b62dc24ce4bba4ed57f
|
||||
47bd458545d805b6bb14fe0cde01440b60bf7be937f6444a8e2a10ed8fa9ddb8604bb95fe411b97112e78dbf5a4a0f004669c93765a9f38
|
||||
665cb55f5658895c1c06a7aedf694bfb3afa9b8b1dea5ab85c821ac20b0663b95023642fda36ad78e3e00140b966f404f7e55f0b416ea43
|
||||
b4c74c39900830abc6906a1004bef1b5b7dbbbeb5ec1b22604ac86429b9f56511b746a7124c449b8c9498f49144abc2d64f6a114f1d7f91
|
||||
aa41249faeef4d838e280cb5d6fc19cfe86c75f
|
||||
''')
|
||||
|
||||
class Usb():
|
||||
def __init__(self):
|
||||
|
|
@ -25,15 +44,25 @@ class Usb():
|
|||
def open(self):
|
||||
self.dev = usb.core.find(idVendor=0x138a, idProduct=0x0097)
|
||||
self.dev.default_timeout = 5000
|
||||
|
||||
def send_init(self):
|
||||
self.dev.set_configuration()
|
||||
|
||||
# TODO analyse responses
|
||||
self.cmd(unhexlify('01'))
|
||||
self.cmd(unhexlify('19'))
|
||||
self.cmd(unhexlify('4302'))
|
||||
# TODO is this one always the same?
|
||||
self.cmd(init_hardcoded)
|
||||
# TODO analyse responses, detect hardware type
|
||||
assert_status(self.cmd(unhexlify('01')))
|
||||
assert_status(self.cmd(unhexlify('19')))
|
||||
|
||||
rsp=self.cmd(unhexlify('4302'))
|
||||
|
||||
assert_status(self.cmd(init_hardcoded))
|
||||
|
||||
(err,), rsp = unpack('<H', rsp[:2]), rsp[2:]
|
||||
if err != 0:
|
||||
print('Clean slate')
|
||||
self.cmd(init_hardcoded_clean_slate)
|
||||
|
||||
self.cmd(unhexlify('3e'))
|
||||
# why twice?
|
||||
self.cmd(unhexlify('3e'))
|
||||
|
||||
def cmd(self, out):
|
||||
|
|
|
|||
Loading…
Reference in a new issue