204 lines
7.6 KiB
Python
204 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os, sys, struct, time
|
|
from serial import Serial
|
|
|
|
# status constants
|
|
NONE = b''
|
|
CONT = b'\x69'
|
|
ACK = b'\x5A'
|
|
NACK = b'\xA5'
|
|
|
|
class MTreader:
|
|
|
|
def __init__(self, devfile, da_path, stage_1, stage_2):
|
|
self.da_path = da_path
|
|
self.st1_offset, self.st1_size, self.st1_addr = [int(x, 16) for x in stage_1.split(":")]
|
|
self.st2_offset, self.st2_size, self.st2_addr = [int(x, 16) for x in stage_2.split(":")]
|
|
while True:
|
|
try:
|
|
self.s = Serial(devfile, 115200)
|
|
sys.stdout.write("\n")
|
|
break
|
|
except OSError as e:
|
|
sys.stdout.write(".")
|
|
sys.stdout.flush()
|
|
time.sleep(0.1)
|
|
|
|
def crc_word(self, data, chs=0):
|
|
for b in data:
|
|
chs += b
|
|
return chs & 0xFFFF
|
|
|
|
def send(self, data, sz = 0):
|
|
r = ""
|
|
if len(data):
|
|
self.s.write(data)
|
|
if sz > 0:
|
|
r = self.s.read(sz)
|
|
return r
|
|
|
|
def cmd(self, cmd, sz = 0):
|
|
r = ""
|
|
cmdlen = len(cmd)
|
|
if cmdlen > 0:
|
|
assert(self.send(cmd, cmdlen) == cmd)
|
|
if sz > 0:
|
|
r = self.s.read(sz)
|
|
return r
|
|
|
|
def da_read16(self, addr, sz=1):
|
|
r = self.cmd(b'\xA2' + struct.pack(">II", addr, sz), sz*2)
|
|
return struct.unpack(">" + sz * 'H', r)
|
|
|
|
def da_write16(self, addr, val):
|
|
r = self.cmd(b'\xD2' + struct.pack(">II", addr, 1), 2)
|
|
assert(r == b"\0\1")
|
|
r = self.cmd(struct.pack(">H", val), 2)
|
|
assert(r == b"\0\1")
|
|
|
|
def da_write32(self, addr, val):
|
|
r = self.cmd(b'\xD4' + struct.pack(">II", addr, 1), 2)
|
|
assert(r == b"\0\1")
|
|
r = self.cmd(struct.pack(">I", val), 2)
|
|
assert(r == b"\0\1")
|
|
|
|
def da_send_da(self, address, size, data, block=4096):
|
|
r = self.cmd(b'\xD7' + struct.pack(">III", address, size, block), 2)
|
|
assert(r == b"\0\0")
|
|
while data:
|
|
self.s.write(data[:block])
|
|
data = data[block:]
|
|
r = self.cmd(NONE, 4) # checksum
|
|
|
|
def get_da(self, offset, size):
|
|
self.fd.seek(offset)
|
|
data = self.fd.read(size)
|
|
return data
|
|
|
|
def connect(self, timeout = 9.0):
|
|
self.s.timeout = 0.02
|
|
while True:
|
|
self.s.write(b"\xA0")
|
|
if self.s.read(1) == b"\x5F":
|
|
self.s.write(b"\x0A\x50\x05")
|
|
r = self.s.read(3)
|
|
if r == b"\xF5\xAF\xFA":
|
|
break
|
|
else:
|
|
print("BROM connection error")
|
|
exit(2)
|
|
timeout -= self.s.timeout
|
|
if timeout < 0:
|
|
print("Timeout error")
|
|
exit(2)
|
|
self.s.timeout = 1.0
|
|
self.CPU_HW = self.da_read16(0x80000000)[0]
|
|
self.CPU_SW = self.da_read16(0x80000004)[0]
|
|
self.chip = self.da_read16(0x80000008)[0]
|
|
self.CPU_SB = self.da_read16(0x8000000C)[0]
|
|
self.da_write16(0xa0700a28, 0x4010)
|
|
self.da_write16(0xa0700a00, 0xF210)
|
|
self.da_write16(0xa0030000, 0x2200)
|
|
self.da_write16(0xa071004c, 0x1a57)
|
|
self.da_write16(0xa071004c, 0x2b68)
|
|
self.da_write16(0xa071004c, 0x042e)
|
|
self.da_write16(0xa0710068, 0x586a)
|
|
self.da_write16(0xa0710074, 0x0001)
|
|
self.da_write16(0xa0710068, 0x9136)
|
|
self.da_write16(0xa0710074, 0x0001)
|
|
self.da_write16(0xa0710000, 0x430e)
|
|
self.da_write16(0xa0710074, 0x0001)
|
|
self.da_write32(0xa0510000, 0x00000002)
|
|
|
|
def da_start(self):
|
|
assert(os.path.isfile(self.da_path) == True)
|
|
self.fd = open(self.da_path, "rb")
|
|
# Send DA first stage
|
|
offset = self.st1_offset
|
|
size = self.st1_size
|
|
data = self.get_da(offset, size)
|
|
self.da_send_da(self.st1_addr, size, data, 0x400)
|
|
# Send DA second stage
|
|
offset = self.st2_offset
|
|
size = self.st2_size
|
|
data = self.get_da(offset, size)
|
|
self.da_send_da(self.st2_addr, size, data, 0x800)
|
|
offset += size
|
|
# Pass execution to DA
|
|
r = self.cmd(b'\xD5' + struct.pack(">I", self.st1_addr), 2)
|
|
assert r == b"\0\0"
|
|
r = self.cmd(NONE, 4)
|
|
self.send(b"\xa5\x05\xfe\x00\x08\x00\x70\x07\xff\xff\x02\x00\x00\x01\x08", 1) # something undocumented
|
|
#Set flash ID info and other stuff
|
|
for i in range(512):
|
|
data = self.get_da(offset, 36)
|
|
assert(data[:4] != b'\xFF\xFF\0\0')
|
|
offset += 36
|
|
r = self.send(data, 1)
|
|
if r == ACK:
|
|
assert(self.cmd(NONE, 2) == b'\xA5\x69')
|
|
break
|
|
assert(r == CONT)
|
|
r = self.send(b"\0\0\0\0", 256)
|
|
|
|
def get_version(self):
|
|
self.send(b'\xEF', 1)
|
|
r = self.send(b'\xEF', 256)
|
|
r = r[:64].strip(b'\0')
|
|
return r
|
|
|
|
def da_read_flash(self, start, size, outfile, blk_size=1024):
|
|
r = self.send(b'\xD6\0' + struct.pack(">LL", start, size), 1)
|
|
if r == NACK:
|
|
errorcode = self.s.read(4)
|
|
print('Flash read error: %d, exiting' % int.from_bytes(errorcode, 'big'))
|
|
self.da_reset()
|
|
exit(1)
|
|
self.send(struct.pack(">L", blk_size), 0)
|
|
outf = open(outfile, "wb")
|
|
while size > 0:
|
|
chunk = self.s.read(min(size, blk_size))
|
|
size -= len(chunk)
|
|
chksum = struct.unpack(">H", self.s.read(2))[0]
|
|
chksum_ref = self.crc_word(chunk)
|
|
assert(chksum_ref == chksum)
|
|
outf.write(chunk)
|
|
sys.stdout.write(".")
|
|
sys.stdout.flush()
|
|
self.s.write(ACK) # continue with the next packet
|
|
|
|
def da_reset(self):
|
|
r = self.send(b'\xB9', 1)
|
|
r = self.send(b'\xC9\x00', 1)
|
|
r = self.send(b'\xDB\x01\x40\x00\x00\x00\x00', 1)
|
|
|
|
if __name__ == "__main__": # main app start
|
|
from argparse import ArgumentParser
|
|
parser = ArgumentParser(description="MTreader: a simple, no-nonsense MediaTek MT6261 phone ROM reader", epilog="(c) Luxferre 2020 --- No rights reserved")
|
|
parser.add_argument('port', help='Serial port to connect to (/dev/ttyUSB0, /dev/tty.usbmodem14100 etc.)')
|
|
parser.add_argument('file', help='File to write the dump into')
|
|
parser.add_argument('start', type=int, help='start position (in the phone flash memory)')
|
|
parser.add_argument('length', type=int, help='data length')
|
|
parser.add_argument('-da','--agent', default=os.path.dirname(os.path.realpath(__file__))+'/MT6261.bin', help='Path to download agent (DA) binary')
|
|
parser.add_argument('-s1','--stage-1', default='0:0x718:0x70007000', help='Data for first stage DA loading (offset:size:addr format)')
|
|
parser.add_argument('-s2','--stage-2', default='0x718:0x1e5c8:0x10020000', help='Data for second stage DA loading (offset:size:addr format)')
|
|
parser.add_argument('-bs','--block-size', type=int, default=1024, help='Readback block size (in bytes), defaults to 1024')
|
|
args = parser.parse_args()
|
|
if(args.length < 1):
|
|
print('Empty length of data, exiting')
|
|
exit(1)
|
|
print("Turn the phone off, hold the boot key and connect the cable")
|
|
m = MTreader(args.port, args.agent, args.stage_1, args.stage_2)
|
|
m.connect()
|
|
if m.chip != 0x6261:
|
|
print("Warning: chip ID is detected as %04x instead of 6261, readback process might fail!")
|
|
m.da_start()
|
|
print("DA sent and responsive, starting the operation")
|
|
ver = m.get_version().decode('ascii')
|
|
print("Firmware version: %s\n" % ver)
|
|
print("Dumping, don't disconnect...")
|
|
m.da_read_flash(args.start, args.length, args.file, args.block_size)
|
|
print("\nROM dumped, disconnect the cable")
|
|
m.da_reset()
|