This commit is contained in:
Luxferre
2021-12-18 14:20:30 +02:00
commit 47681943ec
10 changed files with 512 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
__pycache__
.DS_Store
+43
View File
@@ -0,0 +1,43 @@
# UniDump: an opensource Unisoc/Spreadtrum phone flash reader
## Dependencies
Python 3.8+ and PyUSB.
## Usage
Run `python unidump.py -h` to see all parameters. But there are several typical scenarios that UniDump officially supports.
**Note**: you need to hold a bootkey pressed when connecting the device for it to be detected correctly. This key can vary across devices. Typically it's Call, Soft Left, Soft Right, Center, 9 or #, but it can be anything else.
### SC6531E/F/M
This is the default target for UniDump, FDLs for it are shipped in the repo and you don't need to configure anything else.
For SC6531E, firmware is usually 4MB long, so you just need to run `python unidump.py your-output-file.bin`.
For SC6531F or SC6531M where firmware can be larger, you need to pass in the length. As with any integer parameter here, you can pass it in hexadecimal format as well.
E.g. for CAT B26 (bootkey is `#`): `python unidump.py -l 0x6b7000 b26.bin`
### UMS9117/UMS9117L (NOT SUPPORTED YET)
For these 4G chipsets you need to use your own FDLs (although you **may** try the ones shipped with UniDump) and the following addresses: 0x6200 for FDL1, 0x80100000 for FDL2.
E.g. for Nokia 225 4G (if you're in the UniDump directory): `python unidump.py -fdl1 fdls/ums9117/fdl1.bin -addr1 0x6200 -fdl2 fdls/ums9117/fdl2.bin -addr2 0x80100000 225.bin`
### SC7701/SC7702/SC7703 (NOT SUPPORTED YET)
First, for these 3G chipsets you need to use your own FDLs (although you **may** try the ones shipped with UniDump) and the following addresses: 0x40000000 for FDL1, 0x0 for FDL2.
E.g. for Nokia 3310 3G (if you're in the UniDump directory): `python unidump.py -fdl1 fdls/sc770x/fdl1.bin -addr1 0x40000000 -fdl2 fdls/sc770x/fdl2.bin -addr2 0 3310-3g.bin`
Second, these phones are connected in a different way: disconnect the cable, remove the battery, run the command, connect the cable, hold the bootkey and then insert the battery.
### SC6531D and lower
For now, SC6531DA and other single-FDL variants of SC6531 chipset are not supported.
## Credits
Created by Luxferre in 2021. All files except the FDL blobs are public domain.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+265
View File
@@ -0,0 +1,265 @@
# Command interface for Unisoc chipsets
# Created by Luxferre in 2021
from struct import pack, unpack
UNICMD_CRC_MISMATCH = 0xd00b
# HDLC frame encoding/decoding (Unisoc modification)
def crc16_xmodem(data: bytes): # xmodem used in boot mode
crc = 0
data = bytearray(data)
msb = crc >> 8
lsb = crc & 255
for c in data:
x = (0xFF & c) ^ msb
x ^= (x >> 4)
msb = (lsb ^ (x >> 3) ^ (x << 4)) & 255
lsb = (x ^ (x << 5)) & 255
return (msb << 8) + lsb
def crc16_fdl(data: bytes): # used in FDL1/2 mode
crc = 0
data = bytearray(data)
l = len(data)
for i in range(0,l,2):
if i+1 == l:
crc += data[i]
else:
crc += (data[i]<<8)|data[i+1]
crc = (crc >> 16) + (crc & 0xffff)
crc += (crc >> 16)
return ~crc & 0xffff
def hdlc_encode(data, fdl = False, nocrc = False):
if nocrc:
crc = 0
else:
if fdl:
crc = crc16_fdl(data)
else:
crc = crc16_xmodem(data)
out = []
for c in data:
if c == 0x7e or c == 0x7d:
out.append(0x7d)
out.append(c ^ 0x20)
else:
out.append(c)
out = bytes(out)
return b'\x7e' + out + pack('>HB', crc, 0x7e)
def hdlc_decode(data, fdl = False, ignoreCrc = False): # HDLC bug in Unisoc: CRC is also encoded!!!
rawdata = bytearray(data[1:-1])
out = []
esc = False
for c in rawdata:
if esc:
if c == 0x5e or c == 0x5d:
out.append(c ^ 0x20)
esc = False
else:
raise Exception("Invalid escape sequence while decoding HDLC frame")
elif c == 0x7d:
esc = True
else:
out.append(c)
decoded = bytes(out)
rawcrc = unpack('>H', decoded[-2:])[0]
decoded = decoded[:-2]
if fdl:
calc_crc = crc16_fdl(decoded)
else:
calc_crc = crc16_xmodem(decoded)
if ignoreCrc: # don't hard assert but return a special error code on CRC mismatch
if rawcrc != calc_crc:
return UNICMD_CRC_MISMATCH
else:
assert rawcrc == calc_crc, "Actual CRC16 value %04x does not match calculated value %04x after decoding HDLC frame" % (rawcrc, calc_crc)
return decoded
def resp_decode(data, fdl = False, ignoreCrc = False):
rawdata = hdlc_decode(data, fdl, ignoreCrc)
if rawdata != UNICMD_CRC_MISMATCH:
respcode, resplen = unpack('>HH', rawdata[:4])
content = rawdata[4:4+resplen]
return respcode, resplen, content
else:
return UNICMD_CRC_MISMATCH, 0, None
# Unisoc command set constants
FLAG_BYTE = 0x7E
BSL_PKT_TYPE_MIN = 0
# PC -> phone commands
BSL_CMD_CONNECT = BSL_PKT_TYPE_MIN
BSL_CMD_START_DATA = 1
BSL_CMD_MIDST_DATA = 2
BSL_CMD_END_DATA = 3
BSL_CMD_EXEC_DATA = 4
BSL_CMD_NORMAL_RESET = 5
BSL_CMD_READ_FLASH = 6
BSL_CMD_READ_CHIP_TYPE = 7
BSL_CMD_READ_NVITEM = 8
BSL_CMD_CHANGE_BAUD = 9
BSL_CMD_ERASE_FLASH = 0xa
BSL_CMD_REPARTITION = 0xb
BSL_CMD_READ_FLASH_TYPE = 0xc
BSL_CMD_READ_FLASH_INFO = 0xd
BSL_CMD_READ_SECTOR_SIZE = 0xf
BSL_CMD_READ_START = 0x10
BSL_CMD_READ_MIDST = 0x11
BSL_CMD_READ_END = 0x12
BSL_CMD_KEEP_CHARGE = 0x13
BSL_CMD_EXTTABLE = 0x14
BSL_CMD_READ_FLASH_UID = 0x15
BSL_CMD_READ_SOFTSIM_EID = 0x16
BSL_CMD_POWER_OFF = 0x17
BSL_CMD_CHECK_ROOT = 0x19
BSL_CMD_READ_CHIP_UID = 0x1a
BSL_CMD_ENABLE_WRITE_FLASH = 0x1b
BSL_CMD_ENABLE_SECUREBOOT = 0x1c
BSL_CMD_READ_RF_TRANSCEIVER_TYPE = 0x24
BSL_CMD_CHECK_BAUD = FLAG_BYTE
BSL_DDR_VERIFY = 0x26
BSL_CMD_END_PROCESS = 0x7F
# Phone -> PC responses
BSL_REP_TYPE_MIN = 0x80
BSL_REP_ACK = BSL_REP_TYPE_MIN
BSL_REP_VER = 0x81
BSL_REP_INVALID_CMD = 0x82
BSL_REP_UNKNOWN_CMD = 0x83
BSL_REP_OPERATION_FAILED = 0x84
BSL_REP_NOT_SUPPORT_BAUDRATE = 0x85
BSL_REP_DOWN_NOT_START = 0x86
BSL_REP_DOWN_MULTI_START = 0x87
BSL_REP_DOWN_EARLY_END = 0x88
BSL_REP_DOWN_DEST_ERROR = 0x89
BSL_REP_DOWN_SIZE_ERROR = 0x8A
BSL_REP_VERIFY_ERROR = 0x8B
BSL_REP_NOT_VERIFY = 0x8C
BSL_PHONE_NOT_ENOUGH_MEMORY = 0x8D
BSL_PHONE_WAIT_INPUT_TIMEOUT = 0x8E
BSL_PHONE_SUCCEED = 0x8F
BSL_PHONE_VALID_BAUDRATE = 0x90
BSL_PHONE_REPEAT_CONTINUE = 0x91
BSL_PHONE_REPEAT_BREAK = 0x92
BSL_REP_READ_FLASH = 0x93
BSL_REP_READ_CHIP_TYPE = 0x94
BSL_REP_READ_NVITEM = 0x95
BSL_REP_INCOMPATIBLE_PARTITION = 0x96
BSL_REP_UNKNOWN_DEVICE = 0x97
BSL_REP_INVALID_DEVICE_SIZE = 0x98
BSL_REP_ILLEGAL_SDRAM = 0x99
BSL_WRONG_SDRAM_PARAMETER = 0x9A
BSL_REP_READ_FLASH_INFO = 0x9B
BSL_REP_READ_SECTOR_SIZE = 0x9C
BSL_REP_READ_FLASH_TYPE = 0x9D
BSL_REP_READ_FLASH_UID = 0x9E
BSL_REP_READ_SOFTSIM_EID = 0x9F
BSL_ERROR_CHECKSUM = 0xA0
BSL_CHECKSUM_DIFF = 0xA1
BSL_WRITE_ERROR = 0xA2
BSL_CHIPID_NOT_MATCH = 0xA3
BSL_FLASH_CFG_ERROR = 0xA4
BSL_REP_DOWN_STL_SIZE_ERROR = 0xA5
BSL_REP_PHONE_IS_ROOTED = 0xA7
BSL_REP_SEC_VERIFY_ERROR = 0xAA
BSL_REP_READ_CHIP_UID = 0xAB
BSL_REP_NOT_ENABLE_WRITE_FLASH = 0xAC
BSL_REP_ENABLE_SECUREBOOT_ERROR = 0xAD
BSL_REP_FLASH_WRITTEN_PROTECTION = 0xB3
BSL_REP_FLASH_INITIALIZING_FAIL = 0xB4
BSL_REP_RF_TRANSCEIVER_TYPE = 0xB5
BSL_REP_UNSUPPORTED_COMMAND = 0xFE
BSL_REP_LOG = 0xFF
BSL_PKT_TYPE_MAX = 0x100
BSL_UART_SEND_ERROR = 0x101
BSL_REP_DECODE_ERROR = 0x102
BSL_REP_INCOMPLETE_DATA = 0x103
BSL_REP_READ_ERROR = 0x104
BSL_REP_TOO_MUCH_DATA = 0x105
BSL_USER_CANCEL = 0x106
BSL_REP_SIZE_ZERO = 0x107
BSL_REP_PORT_ERROR = 0x108
# Unisoc command packet interface
def shape_cmd_packet(command):
return pack('>HH', command, 0)
def shape_data_packet(dtype, data, dlen = 0):
if dlen == 0:
dlen = len(data)
#if dlen&1:
# dlen += 1
packethdr = pack('>HH', dtype, dlen)
return packethdr + data
def cmd_data_start(targetAddr, targetLen):
datahdr = pack('>LL', targetAddr, targetLen)
return shape_data_packet(BSL_CMD_START_DATA, datahdr)
def cmd_data_send(data):
return shape_data_packet(BSL_CMD_MIDST_DATA, data)
def cmd_data_end():
return shape_cmd_packet(BSL_CMD_END_DATA)
def cmd_data_exec(targetAddr):
datahdr = pack('>L', targetAddr)
return shape_data_packet(BSL_CMD_EXEC_DATA, datahdr)
def cmd_connect():
return shape_cmd_packet(BSL_CMD_CONNECT)
def cmd_reset():
return shape_cmd_packet(BSL_CMD_NORMAL_RESET)
def cmd_keep_charge():
return shape_cmd_packet(BSL_CMD_KEEP_CHARGE)
def cmd_sync():
return pack('>H', BSL_CMD_CHECK_BAUD)
def cmd_sync_full(baudrate = 921600):
datahdr = pack('>L', baudrate)
return shape_data_packet(BSL_CMD_CHANGE_BAUD, datahdr)
def cmd_read_chip_type():
return shape_cmd_packet(BSL_CMD_READ_CHIP_TYPE)
def cmd_read_sector_size():
return shape_cmd_packet(BSL_CMD_READ_SECTOR_SIZE)
def cmd_read_flash_type():
return shape_cmd_packet(BSL_CMD_READ_FLASH_TYPE)
def cmd_enable_flash():
return shape_cmd_packet(BSL_CMD_ENABLE_WRITE_FLASH)
def cmd_read_flash_info():
return shape_cmd_packet(BSL_CMD_READ_FLASH_INFO)
def cmd_read_flash(targetAddr, targetLen, offset):
return pack('>HHLLL', BSL_CMD_READ_FLASH, 12, targetAddr, targetLen, offset)
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python
import usb
import sys, time
import os
import unicmd
# global params
UNISOC_VID = 0x1782
UNISOC_PID = 0x4d00
MAX_PKT_SIZE = 1024
bSize = 512 # read block size
genTimeout = 4000
# all main procedures
def connect(vid, pid):
while True:
dev = usb.core.find(idVendor=vid, idProduct=pid)
sys.stdout.write('.')
sys.stdout.flush()
if dev is not None:
print('\nDevice connected')
break
time.sleep(0.1)
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
epIn = usb.util.find_descriptor(
intf,
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_IN)
epOut = usb.util.find_descriptor(
intf,
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
assert epIn is not None
assert epOut is not None
return dev, epIn, epOut
def reqonly(packet, fdlBooted = False, noCrc = False):
packet = unicmd.hdlc_encode(packet, fdlBooted, noCrc)
dev.write(epOut, packet, genTimeout)
def reqresp(packet, fdlBooted = False, noCrc = False):
reqonly(packet, fdlBooted, noCrc)
resp = bytes(dev.read(epIn, bSize, genTimeout))
return resp
def handshake(fdlBooted = False):
resp = reqresp(unicmd.cmd_sync(), fdlBooted)
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
if len(r):
print('>', r.decode())
resp = reqresp(unicmd.cmd_connect(), fdlBooted)
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
if len(r):
print('>', r.decode())
def send_file_to_addr(fname, faddr, fdlBooted = False):
pSize = MAX_PKT_SIZE
f = open(fname, 'rb')
fdata = f.read()
f.close()
flen = len(fdata)
print('Initializing data transfer...')
resp = reqresp(unicmd.cmd_data_start(faddr, flen), fdlBooted)
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
assert rcode == unicmd.BSL_REP_ACK, 'Could not start data transfer, response code is %X' % rcode
print('Starting data transfer...')
while fdata:
buf = fdata[:pSize]
resp = reqresp(unicmd.cmd_data_send(buf), fdlBooted)
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
assert rcode == unicmd.BSL_REP_ACK, 'Something is wrong and response code is %X, block is %s' % (rcode, buf.hex())
fdata = fdata[pSize:]
sys.stdout.write('.')
sys.stdout.flush()
print('\nEnding data transfer...')
resp = reqresp(unicmd.cmd_data_end(), fdlBooted)
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
assert rcode == unicmd.BSL_REP_ACK, 'Could not finalize data transfer, response code is %X' % rcode
print('Data transfer successful')
def read_partdata(baseaddr, size, offset):
t = b''
reqonly(unicmd.cmd_read_flash(baseaddr, size, offset), True)
while True:
xr = bytes(dev.read(epIn, bSize, genTimeout))
t += xr
if len(xr) < bSize:
break
return t
def read_partition(baseaddr, partsize, partoffset, outfile, rbblocksize):
outf = open(outfile, 'wb')
psize = partsize
offset = partoffset
print('Dumping %d bytes from partition 0x%X at offset 0x%X to %s...' % (partsize, baseaddr, partoffset, outfile))
bufsize = rbblocksize
while psize > 0:
if psize < bufsize:
bufsize = psize
resp = read_partdata(baseaddr, bufsize, offset)
rcode, rlen, r = unicmd.resp_decode(resp, True)
outf.write(r)
sys.stdout.write('.')
sys.stdout.flush()
psize -= rlen
offset += rlen
outf.flush()
print('\nPartition dumped!')
def auto_int(x):
return int(x,0)
# main code start
if __name__ == '__main__': # main app start
from argparse import ArgumentParser
rootdir = os.path.dirname(os.path.realpath(__file__))
parser = ArgumentParser(description='UniDump: an opensource Unisoc SC6531E/F/M phone dumper', epilog='(c) Luxferre 2021 --- No rights reserved <https://unlicense.org>')
parser.add_argument('file', help='File to write the dump into')
parser.add_argument('-p','--partid', type=auto_int, default=0x80000003, help='partition ID (defaults to 0x80000003 that can address full flash space on SC6531E/F/M)')
parser.add_argument('-s','--start', type=auto_int, default=0, help='start position (in the partition, defaults to 0)')
parser.add_argument('-l', '--length', type=auto_int, default=0x400000, help='data length in bytes to read, defaults to 0x400000')
parser.add_argument('-bs','--block-size', type=auto_int, default=4096, help='Readback block size (in bytes), defaults to 4096')
parser.add_argument('-dv','--device-vid', type=auto_int, default=UNISOC_VID, help='Override device vendor ID')
parser.add_argument('-dp','--device-pid', type=auto_int, default=UNISOC_PID, help='Override device product ID')
parser.add_argument('-fdl1','--fdl1-file', default=rootdir+'/fdls/sc6531efm/nor_fdl1.bin', help='Path to FDL1, defaults to the generic SC6531E/F/M FDL1 shipped with UniDump')
parser.add_argument('-addr1','--fdl1-addr', type=auto_int, default=0x40004000, help='Address to load FDL1 into, defaults to 0x40004000')
parser.add_argument('-fdl2','--fdl2-file', default=rootdir+'/fdls/sc6531efm/nor_fdl.bin', help='Path to FDL2, defaults to the generic SC6531E/F/M FDL2 shipped with UniDump')
parser.add_argument('-addr2','--fdl2-addr', type=auto_int, default=0x14000000, help='Address to load FDL2 into, defaults to 0x14000000')
args = parser.parse_args()
UNISOC_VID = args.device_vid
UNISOC_PID = args.device_pid
fdl1Addr = args.fdl1_addr
fdl2Addr = args.fdl2_addr
fdl1Name = args.fdl1_file
fdl2Name = args.fdl2_file
outfile = args.file
partitionId = args.partid
readbs = args.block_size
readoffset = args.start
readlen = args.length
print('Using FDL1 %s, loading to 0x%X' % (fdl1Name, fdl1Addr))
print('Using FDL2 %s, loading to 0x%X' % (fdl2Name, fdl2Addr))
# initial connection
print('Connect the device %X:%X while holding the bootkey...' % (UNISOC_VID, UNISOC_PID) )
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
handshake()
def reconnect():
global dev
if dev is not None:
usb.util.dispose_resources(dev)
time.sleep(0.5)
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
print('Boot mode entered')
print('Sending FDL1')
send_file_to_addr(fdl1Name, fdl1Addr)
print('Starting FDL1')
resp = reqresp(unicmd.cmd_data_exec(fdl1Addr))
rcode, rlen, r = unicmd.resp_decode(resp, False)
if rcode == unicmd.BSL_REP_ACK:
print('FDL1 started successfully, reconnecting...')
reconnect()
handshake(True)
print('Protocol set up, sending FDL2')
send_file_to_addr(fdl2Name, fdl2Addr, True)
print('Starting FDL2')
resp = reqresp(unicmd.cmd_data_exec(fdl2Addr), True)
rcode, rlen, r = unicmd.resp_decode(resp, True)
if rcode == unicmd.BSL_REP_ACK:
print('FDL2 started successfully!')
resp = reqresp(unicmd.cmd_sync_full(), True)
rcode, rlen, r = unicmd.resp_decode(resp, True)
assert rcode == unicmd.BSL_REP_ACK, 'Could not set the baudrate, response code is %X' % rcode
print('FDL2 running, may start interacting with flash memory')
read_partition(partitionId, readlen, readoffset, outfile, readbs)
resp = reqresp(unicmd.cmd_reset(), True)
rcode, rlen, r = unicmd.resp_decode(resp, True)
assert rcode == unicmd.BSL_REP_ACK, 'Could not reset the device, response code is %X' % rcode
if dev is not None:
usb.util.dispose_resources(dev)