commit 47681943eccd0b479095e9ddc2d16f679fc8dd80 Author: Luxferre <3335173-suborg@users.noreply.gitlab.com> Date: Sat Dec 18 14:20:30 2021 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0e8c99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ac29f2 --- /dev/null +++ b/README.md @@ -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. diff --git a/fdls/sc6531efm/nor_fdl.bin b/fdls/sc6531efm/nor_fdl.bin new file mode 100644 index 0000000..b2e9096 Binary files /dev/null and b/fdls/sc6531efm/nor_fdl.bin differ diff --git a/fdls/sc6531efm/nor_fdl1.bin b/fdls/sc6531efm/nor_fdl1.bin new file mode 100644 index 0000000..87a0748 Binary files /dev/null and b/fdls/sc6531efm/nor_fdl1.bin differ diff --git a/fdls/sc770x/fdl1.bin b/fdls/sc770x/fdl1.bin new file mode 100644 index 0000000..72a006d Binary files /dev/null and b/fdls/sc770x/fdl1.bin differ diff --git a/fdls/sc770x/fdl2.bin b/fdls/sc770x/fdl2.bin new file mode 100644 index 0000000..3091821 Binary files /dev/null and b/fdls/sc770x/fdl2.bin differ diff --git a/fdls/ums9117/fdl1.bin b/fdls/ums9117/fdl1.bin new file mode 100644 index 0000000..79c1f97 Binary files /dev/null and b/fdls/ums9117/fdl1.bin differ diff --git a/fdls/ums9117/fdl2.bin b/fdls/ums9117/fdl2.bin new file mode 100644 index 0000000..aee9a99 Binary files /dev/null and b/fdls/ums9117/fdl2.bin differ diff --git a/unicmd.py b/unicmd.py new file mode 100644 index 0000000..d4263d3 --- /dev/null +++ b/unicmd.py @@ -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) diff --git a/unidump.py b/unidump.py new file mode 100644 index 0000000..bb2f2a7 --- /dev/null +++ b/unidump.py @@ -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 ') + 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)