Latest version of the script (I will be waiting for some input if I should go further with this script/openwrt):
To decode CIM v5:
python script.py --action cim --infile cim_v5.cim --outfile cim_v5.tar
To access the shell:
python script.py --action shell --challenge 123456
To stop u-boot (start this before you start the device):
python script.py --action stopboot --serial-port COM5
To stop patch uimage (so that it's accepted by u-boot): (supported devices msm422|msm710|msm430|msm460)
python script.py --action patchheader --infile uImage.img --outfile uImage-patch.img --board-type msm422
import argparse
import hashlib
import re
import serial
import serial.tools.list_ports as port_list
import struct
import zlib
from struct import *
class UBoot:
def __init__(self):
self.header_format = '!7L4B32s'
self.header_size = calcsize(self.header_format)
def update_header(self, indata, sign_as):
mx = 32
indata = bytearray(indata)
indata[mx:mx+32] = b'\x00' * 32
indata[mx:mx+16] = b'5.0.0.0-sr9-007'
if sign_as == 'msm422' or sign_as == 'msm710':
indata[mx+16:mx+17] = b'\x81\xFF'
indata[mx+21:mx+22] = b'\x20'
indata[mx+25:mx+26] = b'\x40'
indata[mx+28:mx+32] = b'\x51\x2D\x37\x9E'
elif sign_as == 'msm430':
indata[mx+16:mx+17] = b'\x82\xFF'
indata[mx+21:mx+22] = b'\x20'
indata[mx+25:mx+26] = b'\x40'
indata[mx+28:mx+32] = b'\x59\xC3\xC9\x8E'
elif sign_as == 'msm460':
indata[mx+16:mx+17] = b'\x83\x01'
indata[mx+21:mx+22] = b'\x20'
indata[mx+25:mx+26] = b'\x40'
indata[mx+28:mx+32] = b'\x59\xC3\xC9\xC1'
hd = self.parse_header(indata[0:64])
indata[4:8] = self.int_to_bytes(self.calculate_crc(hd), 4)
print(self.int_to_bytes(self.calculate_crc(hd), 4))
print(indata[0:64])
return indata
def parse_header(self, header_data):
keys = ['magic', 'headerCrc', 'time', 'size', 'loadAddr', 'entryAddr', 'dataCrc', 'osType', 'arch', 'imageType', 'compression', 'name']
values = unpack(self.header_format, header_data)
hd = dict(zip(keys, values))
if hd['imageType'] == 4:
hd['files'] = getMultiFileLengths(fh, fh.tell())
return hd
def calculate_crc(self, hd):
header = pack(self.header_format, hd['magic'], 0, hd['time'], hd['size'], hd['loadAddr'], hd['entryAddr'],
hd['dataCrc'], hd['osType'], hd['arch'], hd['imageType'], hd['compression'], hd['name'])
return (zlib.crc32(header) & 0xffffffff)
def int_to_bytes(self, n, minlen=0):
if n > 0:
arr = []
while n:
n, rem = n >> 8, n & 0xff
arr.append(rem)
b = bytearray(reversed(arr))
elif n == 0:
b = bytearray(b'\x00')
else:
raise ValueError('Only non-negative values supported')
if minlen > 0 and len(b) < minlen:
b = (minlen-len(b)) * '\x00' + b
return b
class Serial:
def __init__(self):
pass
def list_serial_ports(self):
ports = list(port_list.comports())
for p in ports:
print (p)
def stop_autoboot(self, serial_port):
s = serial.Serial(serial_port, 115200, xonxoff=False, rtscts=False, dsrdtr=False)
s.flushInput()
s.flushOutput()
read_data = ''
found_autoboot = False
print('[*] Looking for Autoboot countdown string on "'+ serial_port +'"')
while True:
bytesToRead = s.inWaiting()
data_raw = s.read(bytesToRead)
if len(data_raw) > 0:
read_data += str(data_raw, 'utf-8', 'ignore')
if re.findall('Autoboot countdown', read_data):
if not found_autoboot:
print('[*] Found Autoboot countdown string')
found_autoboot = True
if found_autoboot:
print('[*] Sending interrupt string')
s.write(b'\x01\x0A')
if re.findall('INTERRUPT', read_data):
break
if re.findall('Unknown command', read_data):
break
print('[*] U-Boot command prompt available. Connect to serial port "' + serial_port + '" using 115200 baud rate')
class File:
def __init__(self):
pass
def simple_read(self, path):
strg = b""
with open(path, "rb") as f:
while byte := f.read():
strg += byte
return strg
def read(self, path):
strg = b""
with open(path, "rb") as f:
header = f.read(8)
if header == b"CIM\xc1\x06\xad\xb0\x15":
print('[*] CIM Header version 6 (AES)')
print('[*] AES Encryption not supported ATM')
exit()
return
else:
print('[*] CIM Header version 5 (RC4)')
f.seek(0, 0)
while byte := f.read():
strg += byte
return strg
def write(self, path, data):
f = open(path, "wb")
f.write(data)
f.close()
class ShellAccess:
def __init__(self):
self.challenge_static = b'\xa7\x25\x57\xd1\x90\x0e\x3d\x6b'
self.sha = hashlib.sha1()
def generate(self, challenge):
self.sha.update(bytes(challenge[:3], 'utf-8'))
self.sha.update(self.challenge_static)
self.sha.update(bytes(challenge[3:], 'utf-8'))
digest = self.sha.digest()
return "%05u" % (digest[3] * digest[13])
class Firmware():
def __init__(self):
self.key = '379AeAB93l550g80'
def crypt_rc4(self, data):
return self.crypt_rc4_pp(data)
def crypt_rc4_pp(self, data):
S = list(range(256))
j = 0
key = self.key
for i in list(range(256)):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]
j, y = (0, 0)
out = bytearray()
for char in data:
j = (j + 1) % 256
y = (y + S[j]) % 256
S[j], S[y] = S[y], S[j]
out.append(char ^ S[(S[j] + S[y]) % 256])
return out
parser = argparse.ArgumentParser(description='Colubris/HPE Helper')
parser.add_argument('--action', help='[cim|shell|stopboot|patchheader]')
parser.add_argument('--infile', help='input file to decrypt/encrypt')
parser.add_argument('--outfile', help='output file to decrypt/encrypt')
parser.add_argument('--challenge', help='challenge issues by en->sh')
parser.add_argument('--serial-port', help='serial port that is used to stop autoboot')
parser.add_argument('--board-type', help='[msm422|msm710|msm430|msm460]')
args = parser.parse_args()
if not args.action:
print('[*] No action given.')
exit()
if args.action == 'cim':
if not args.infile or not args.outfile:
print('[*] Both infile and outfile are needed')
exit()
fl = File()
fw = Firmware()
data = fw.crypt_rc4(fl.read(args.infile))
fl.write(args.outfile, data)
elif args.action == 'shell':
sa = ShellAccess()
print( 'Response: %s' % sa.generate(args.challenge) )
elif args.action == 'stopboot':
s = Serial()
s.stop_autoboot(args.serial_port)
elif args.action == 'patchheader':
u = UBoot()
fl = File()
fc = fl.simple_read(args.infile)
lfc = u.update_header(fc, args.board_type)
fl.write(args.outfile, lfc)