Added pycham Tool by Chema García to official repository thus making it an official tool for the Chameleon

This commit is contained in:
Simon Küppers
2014-10-03 16:03:06 +02:00
parent 13fc6740b4
commit 9ee7066015
6 changed files with 625 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
Copyright (c) 2014, Chema García
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of pycham nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+11
View File
@@ -0,0 +1,11 @@
pyCham
======
A tool to manage the settings and configurations of Chameleon-Mini
![](https://raw.githubusercontent.com/sch3m4/pycham/master/img/screenshot.png)
References
----------
http://hackaday.com/2013/12/28/chameleon-emulates-contactless-smart-cards/
https://github.com/emsec/ChameleonMini
Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env python
#
# Written by Chema Garcia
# @sch3m4
# chema@safetybits.net || http://safetybits.net
#
from pyCham import *
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python
#
# Written by Chema Garcia
# @sch3m4
# chema@safetybits.net || http://safetybits.net
#
import serial
import time
from xmodem import XMODEM
from StringIO import StringIO
class pyCham():
# reconnection delay
RECON_DELAY = 7
# Chameleon-Mini ID
DEVICE_ID = 'USB VID:PID=03eb:2044 SNR=435503430313FFFF91FF71000100'
# commands config
COMMAND_GET_VERSION = 'version?'
COMMAND_GET_CONFIGS = 'config'
COMMAND_GET_CURRENT_CONFIG = 'config?'
COMMAND_GET_CURRENT_SETTING = 'setting?'
COMMAND_GET_BUTTON = 'button'
COMMAND_GET_CURRENT_BUTTON = 'button?'
COMMAND_GET_CURRENT_UID = 'uid?'
COMMAND_GET_CURRENT_UID_SIZE = 'uidsize?'
COMMAND_GET_CURRENT_MEMSIZE = 'memsize?'
COMMAND_GET_RO = 'readonly?'
COMMAND_SET_UID = 'uid='
COMMAND_SET_CONFIG = 'config='
COMMAND_SET_RO = 'readonly='
COMMAND_SET_BUTTON = 'button='
COMMAND_SET_SETTING = 'setting='
COMMAND_UPLOAD = 'upload'
COMMAND_DOWNLOAD = 'download'
COMMAND_RESET = 'reset'
#COMMAND_UPGRADE = 'upgrade' # not enabled
COMMAND_CLEAR = 'clear'
INSTANT_COMMANDS = [COMMAND_GET_VERSION,COMMAND_GET_CONFIGS,COMMAND_GET_CURRENT_CONFIG,COMMAND_GET_CURRENT_UID_SIZE, COMMAND_GET_CURRENT_UID , COMMAND_GET_RO,COMMAND_GET_CURRENT_MEMSIZE,COMMAND_GET_BUTTON,COMMAND_GET_CURRENT_BUTTON,COMMAND_GET_CURRENT_SETTING,COMMAND_CLEAR,COMMAND_RESET]
COMMANDS = { COMMAND_RESET: 'Resets the Chameleon',
COMMAND_SET_CONFIG: 'Sets the active configuration',
COMMAND_SET_UID: 'Sets a new UID (hex)',
COMMAND_SET_RO: 'Switches the state of the read-only mode',
COMMAND_UPLOAD: 'Uploads a memory dump upto the memory size',
COMMAND_DOWNLOAD: 'Downloads a memory dump',
COMMAND_SET_BUTTON: 'Sets the current button press action for BUTTON0',
COMMAND_SET_SETTING: 'Sets the active setting',
COMMAND_CLEAR: 'Clears the entire memory of the currently activated setting' }
# response commands
RESPONSE_OK_TEXT = 101
RESPONSE_OK = 0
RESPONSE_WAITING = 1
RESPONSE_ERROR = 2
RESPONSES = {RESPONSE_OK : [100,RESPONSE_OK_TEXT] , RESPONSE_WAITING : [110] , RESPONSE_ERROR : [200,201,202] }
# serial port configuration
SERIAL_TO = 15
SERIAL_BD = 38400
def __init__(self):
self.SERIAL = None
self.SERIAL_PORT = None
def setSerial(self,port):
self.SERIAL_PORT = port
def openSerial(self):
if self.SERIAL_PORT is None:
return None
try:
self.SERIAL = serial.Serial(self.SERIAL_PORT,baudrate=self.SERIAL_BD,timeout=self.SERIAL_TO)
except Exception,e:
print "Error: Cannot open serial port: %s" % e
return None
return self.SERIAL
def getCommands(self):
return self.COMMANDS
def getInstantCommands(self):
return self.INSTANT_COMMANDS
def close(self):
try:
self.SERIAL.close()
except:
pass
def __check_retcode__ ( self , retcode ):
"""
Checks the return code and returns one of the following return codes: RESPONSE_OK | RESPONSE_WAITING | RESPONSE_ERROR
"""
code = retcode.split(':')[0]
ret = None
for r in self.RESPONSES:
if int(code) in self.RESPONSES[r]:
ret = r
break
return ret
def execute(self,command='version?'):
"""
Executes an action and returns the pair (return_code,return_value)
"""
self.SERIAL.write ( command + '\r' )
retval = ''
retcode = self.SERIAL.readline()
if int(retcode.split(':')[0]) == self.RESPONSE_OK_TEXT:
retval = self.SERIAL.readline()
return (retcode.strip(),retval.strip())
def downloadToFile ( self , tofile='downloaded.bin' , memsize = 1024 ):
def __read_byte ( size , timeout = 1 ):
return self.SERIAL.read(size)
def __write_byte ( data , timeout = 1 ):
return self.SERIAL.write(data)
time.sleep(0.1)
buffer = StringIO()
stream = open(tofile, 'wb')
modem = XMODEM(__read_byte,__write_byte).recv(buffer,crc_mode=0,quiet=1)
contents = buffer.getvalue()
stream.write(contents[:memsize])
stream.close()
buffer.close()
try:
self.execute ( self.COMMAND_RESET )
except:
pass
return
def uploadFromFile ( self , fromfile = 'upload.bin' ):
def __read_byte ( size , timeout = 1 ):
return self.SERIAL.read(size)
def __write_byte ( data , timeout = 1 ):
return self.SERIAL.write(data)
time.sleep(0.1)
stream = open(fromfile, 'rb')
buffer = StringIO(stream.read())
stream.close()
modem = XMODEM(__read_byte,__write_byte).send(buffer,quiet=1)
buffer.close()
try:
self.execute ( self.COMMAND_RESET )
except:
pass
return
def getVersion(self):
"""
Returns the version of Chameleon
"""
code,val = self.execute ( self.COMMAND_GET_VERSION )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getConfigurations(self):
"""
Returns all profiles available
"""
code,val = self.execute ( self.COMMAND_GET_CONFIGS )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurSettings(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_SETTING )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurConfig(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_CONFIG )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getButtonActions(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_BUTTON )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurButtonActions(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_BUTTON )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getReadOnly(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_RO )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurrentUID(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_UID )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurrentUIDSize(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_UID_SIZE )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
def getCurrentMemSize(self):
"""
Returns the current settings
"""
code,val = self.execute ( self.COMMAND_GET_CURRENT_MEMSIZE )
retcode = self.__check_retcode__ ( code )
if retcode == self.RESPONSE_WAITING: # volver a leer
print "TODO"
elif retcode == self.RESPONSE_ERROR: # raise an error
print "TODO"
elif retcode == self.RESPONSE_OK:
return val
else:
print "Unknown Error"
return None
+264
View File
@@ -0,0 +1,264 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
#
# Written by Chema Garcia
# @sch3m4
# chema@safetybits.net || http://safetybits.net
#
import os
import time
import sys
import argparse
import serial.tools.list_ports
from colorama import init as coloramaInit
from colorama import Fore, Back
from lib import pyCham
cham = pyCham()
def showBanner(showFooter = False):
print "██████╗ ██╗ ██╗ ██████╗██╗ ██╗ █████╗ ███╗ ███╗"
print "██╔══██╗╚██╗ ██╔╝██╔════╝██║ ██║██╔══██╗████╗ ████║ by Chema Garcia"
print "██████╔╝ ╚████╔╝ ██║ ███████║███████║██╔████╔██║ @sch3m4"
print "██╔═══╝ ╚██╔╝ ██║ ██╔══██║██╔══██║██║╚██╔╝██║ chema@safetybits.net"
print "██║ ██║ ╚██████╗██║ ██║██║ ██║██║ ╚═╝ ██║ https://github.com/sch3m4/pycham"
print "╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ v0.2b"
if showFooter is True:
print "a Python interface to Chameleon Mini"
print ""
def locateDevice ( devid ):
'''
Returns the serial port path of the arduino if found, or None if it isn't connected
'''
retval = None
for port in serial.tools.list_ports.comports():
if len(port[2]) >= len(devid) and port[2][:len(devid)] == devid:
retval = port[0]
break
return retval
def auxMenu(options):
os.system('clear')
showBanner()
ret = None
while True:
cont = 1
for opt in options:
print Fore.RED + "%s" % cont + Fore.RESET + ")\t" + "%s" % opt
cont += 1
print "0) Exit"
try:
print ""
opt = int(raw_input ( "Value: " ).strip())
except:
continue
if opt > len ( options ):
continue
if int(opt) == 0:
ret = None
else:
ret = options[opt-1]
break
return ret
def showMenu():
cmds = cham.getCommands()
instantcmds = cham.getInstantCommands()
map = {}
finish = False
while finish is False:
print "Getting device version..."
version = cham.getVersion()
print "Getting available configurations..."
configurations = cham.getConfigurations()
print "Getting the currently activated setting..."
settings = cham.getCurSettings()
print "Getting current config..."
config = cham.getCurConfig()
print "Getting button actions..."
bactions = cham.getButtonActions()
print "Getting all available button actions..."
bcuractions = cham.getCurButtonActions()
print "Getting readonly status..."
rostatus = cham.getReadOnly()
print "Getting current UID..."
curuid = cham.getCurrentUID()
print "Getting current UID size..."
curuidsize = cham.getCurrentUIDSize()
print "Getting current memory size..."
curmemsize = cham.getCurrentMemSize()
os.system('clear')
showBanner()
print "-==== Global Configurations ====-"
print Fore.RESET + " = " + Fore.MAGENTA + "+ Version: " + Fore.GREEN + "%s" % version
print Fore.RESET + " = " + Fore.MAGENTA + "+ Configs: " + Fore.GREEN + "%s" % configurations
print Fore.RESET + " = " + Fore.MAGENTA + "+ Button actions: " + Fore.GREEN + "%s" % bactions
print Fore.RESET + " = " + Fore.MAGENTA + "+ ReadOnly status: " + Fore.GREEN + "%s" % rostatus
print Fore.RESET + " = " + Fore.YELLOW + "+ Current button action: " + Fore.GREEN + "%s" % bcuractions
print Fore.RESET + " = " + Fore.YELLOW + "+ Currrent config: " + Fore.GREEN + "%s" % config
print Fore.RESET + " = " + Fore.YELLOW + "+ Current setting: " + Fore.GREEN + "%s" % settings
print Fore.RESET + " = " + Fore.YELLOW + "+ Current UID: " + Fore.GREEN + "%s" % curuid
print Fore.RESET + " = " + Fore.YELLOW + "+ Current UID size: " + Fore.GREEN + "%s" % curuidsize
print Fore.RESET + " = " + Fore.YELLOW + "+ Current memory size: " + Fore.GREEN + "%s" % curmemsize
print Fore.RESET + "-===============================-\n"
options = cmds.keys()
cont = 1
for opt in options:
print Fore.RED + "%s" % cont + Fore.RESET + ")\t%s" % cmds[opt]
cont += 1
if len(map.keys()) != len(options):
map[cont] = opt
print "0)\tExit"
correct = False
reload = False
while correct is False and reload is False:
try:
print ""
print Fore.BLUE + "NOTE:" + Fore.RESET + " Press enter to reload the menu"
sel = raw_input("Action: ")
sel = int(sel)
except ValueError:
if len(sel.strip()) == 0:
reload = True
except:
continue
if reload is True or sel <= len ( options ):
correct = True
if reload is True:
continue
if sel == 0:
return
invalid = False
unknown = False
cmd = map[sel+1]
if cmd in instantcmds:
cham.execute ( cmd )
elif cmd == pyCham.COMMAND_SET_UID:
uid = ''
while len ( uid ) == 0:
uid = raw_input ( "Enter the new UID value: " ).strip()
if uid is None:
unknown = True
else:
cmd += uid
elif cmd == pyCham.COMMAND_SET_CONFIG:
print "TODO"
elif cmd == pyCham.COMMAND_SET_RO:
if int(rostatus) == 0:
cmd += '1'
elif int(rostatus) == 1:
cmd += '0'
else:
invalid = True
elif cmd == pyCham.COMMAND_SET_BUTTON:
opt = auxMenu ( bactions.split(',') )
if opt is None:
unknown = True
else:
cmd += opt
elif cmd == pyCham.COMMAND_SET_SETTING:
opt = auxMenu ( ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16'] )
if opt is None:
unknown = True
else:
cmd += opt
elif cmd == pyCham.COMMAND_DOWNLOAD:
path = '.'
while os.path.isdir(path) is True and os.path.exists(path):
path = raw_input ( "Enter destination file path: " ).strip()
cham.execute ( cmd )
time.sleep(1)
cham.downloadToFile ( path , int(curmemsize) )
print "Restarting..."
# finish = True
elif cmd == pyCham.COMMAND_UPLOAD:
path = ''
while os.path.isfile(path) is False:
path = raw_input ( "Enter source file path: " ).strip()
cham.execute ( cmd )
time.sleep(1)
cham.uploadFromFile ( path )
# finish = True
print "Restarting..."
else:
unknown = True
print "Unknown option!"
# this should not happen
if invalid is True:
print "Invalid value!"
elif unknown is False and finish is False:
cham.execute ( cmd )
raw_input("Press ENTER to continue")
return finish
def main():
showBanner(True)
if len(sys.argv) < 2:
print "[i] Trying to autodetect Chameleon-Mini"
time.sleep(0.5)
args = {'serial': locateDevice ( pyCham.DEVICE_ID ) }
if args['serial'] is not None:
print "[i] Chameleon-Mini found at " + Fore.GREEN + "%s" % args['serial'] + Fore.RESET
time.sleep(1)
else:
parser = argparse.ArgumentParser()
parser.add_argument('serial', metavar='tty', type=str , help='Serial port of Chameleon' )
args = vars(parser.parse_args())
if args['serial'] is None or not os.path.exists ( args['serial' ] ):
print "[e] Error: Chameleon serial port not found. Rerun this script with -h parameter to view help."
sys.exit(-1)
coloramaInit()
finish = False
while finish is False:
cham.setSerial ( args['serial'] )
if cham.openSerial() is None:
print "[e] Error opening serial port"
sys.exit(-2)
finish = showMenu()
cham.close()
cham.close()
return finish
if __name__ == "__main__":
finish = False
while finish is False:
try:
finish = main()
except serial.SerialException:
print "[e] Connection closed"
for i in range(0,pyCham.RECON_DELAY):
val = pyCham.RECON_DELAY - i
print '[+] Reconnection in %d seconds\r' % val,
sys.stdout.flush()
time.sleep(1)