-
Notifications
You must be signed in to change notification settings - Fork 112
Dump your backup firmware
To save a restorable backup of your cam you need 2 files:
- sys_xxx
- home_xxx
where xxx is the suffix of your model (i.e. y203c)
The sys file is the root file system and the home file is the home partition (mounted on /home). Both files are jffs2 images. To save these files without a previous hack, you need to extract them through a serial connection. So you need some manual skills because you have to solder 3 wires.
- Open the cam removing screws.
- Find the 3 uart pads. Where are they? It depends on the model and it's not simple to identify them. Example:
- Connect the pads to a ttl-rs232/usb adapter (rx, tx and GND).
- Open putty with logging enabled.
- Switch on the cam. Now, if the connection is ok, you will see the uboot and kernel logs on the screen. Otherwise, restart from point 2.
- Wait for loading to complete.
- Stop the logging and save the boot log (eventually cleaned up from your data, if present).
Now you have a physical connection to the cam and you have a dump of the boot log (you could analyze it to get some info).
In the log you will find the mtd partitions list. Example:
Creating 6 MTD partitions on "NOR_FLASH":
0x000000000000-0x000000050000 : "BOOT"
0x000000050000-0x0000001d0000 : "KERNEL"
0x0000001d0000-0x0000003b0000 : "ROOTFS"
0x0000003b0000-0x000000fe0000 : "HOME"
0x000000fe0000-0x000000ff0000 : "vd1"
0x000000ff0000-0x000001000000 : "conf"
The partitions that interest us are obviously "ROOTFS" (mtd2) and "HOME" (mtd3) so we have to write down the informations regarding the starting address (1st column), the ending address (2nd column) and the length (the difference) because we will need them later.
Before you proceed to dump the partitions, prepare a python script named dump.py with the following content (thanks to @IonAgorria):
#!/usr/bin/env python3
__author__ = 'Ion Agorria'
import argparse
from datetime import datetime
from time import time
from serial import Serial
def visible(text):
text.replace("\n", "\\n")
text.replace("\r", "\\r")
return text
def parse_line(line, size):
# Break down each part
if len(line) != size:
raise Exception("Line size %s doesn't match %s: %s" % (len(line), size, visible(line)))
i = 0
addr = line[i:i + 8]
i += 8
assert line[i:i + 2] == ": "
i += 2
hex_data = line[i:i + 8]
i += 8
assert line[i:i + 1] == " "
i += 1
hex_data += line[i:i + 8]
i += 8
assert line[i:i + 1] == " "
i += 1
hex_data += line[i:i + 8]
i += 8
assert line[i:i + 1] == " "
i += 1
hex_data += line[i:i + 8]
i += 8
assert line[i:i + 4] == " "
i += 4
text = line[i:i + 16]
i += 16
assert line[i:i + 2] == "\r\n"
#Convert hex string into actual data
data = []
for i in range(0, len(hex_data), 2):
piece = hex_data[i:i + 2]
data.append(int(piece, 16))
#For redundancy check if data matches text, but only for printable characters (non dot)
# for i, piece in enumerate(data):
# if text[i] != ".":
# assert chr(piece) == text[i]
return int(addr, 16), data, text
def write(serial, opts, data):
if opts.debug:
print("Debug write: " + visible(data))
data = bytes(data, "ascii")
serial.write(data)
def dump(serial, opts, log):
data = []
finish = False
start_addr = opts.start
last_addr = start_addr - 0x10
do_reset = False
# Restore the previous dump
if opts.previous:
loaded = False
last_percentage = 0
print("Info: loading from " + opts.previous)
with open(opts.previous, 'r') as previous:
lines = previous.readlines()
count = len(lines)
i = 0
for line in lines:
#Fix lines with only \n
if line[-2:] != "\r\n":
line = line[:-1] + "\r\n"
addr, line_data, text = parse_line(line, opts.size)
#Check if we skipped some line
if last_addr != addr - 0x10:
raise Exception("Possible skip or repetition, last address 0x%x doesn't match with previous address 0x%x" % (last_addr, addr - 0x10))
last_addr = addr
#Check if we reach end
if addr > opts.end:
print("Info: Reached specified end address")
finish = True
break
#Discard if not start
if addr < opts.start:
print("Warning: address %s is lower than start address! discarding" % addr)
continue
#Store line data and log
data.append(bytes(line_data))
if not opts.ignore_log:
log.write(line)
log.flush()
#Print percentage
i += 1
percentage = int(round(i / count * 100))
if last_percentage != percentage:
print("Info: (%i%%) %i/%i " % (percentage, count, i))
last_percentage = percentage
#If we reach here everything went fine
loaded = True
if not loaded:
raise Exception("Dump was not restored completely, something went wrong")
start_addr = last_addr + 0x10
last_addr = start_addr - 0x10
print("Info: finished loading previous")
if not finish:
# Send the initial command
write(serial, opts, "md %s %s\n" % (hex(start_addr), hex(opts.step * 4)))
do_reset = opts.reset
while not finish:
#Read response
chunk = serial.readlines()
if opts.debug:
print("Debug read: " + str(chunk))
assert len(chunk) > 0
#Remove first one, its the command that we sent
chunk = chunk[1:]
#Iterate each line in chunk
for line in chunk:
line = line.decode("ascii")
if opts.debug:
print("line: " + str(line))
#Adquire new chunk by sending newline
if line == 'MStar # ':
if opts.debug:
print("Debug: detected prompt, sending newline")
write(serial, opts, "\n")
continue
addr, line_data, text = parse_line(line, opts.size)
#Check if we skipped some line
if last_addr != addr - 0x10:
raise Exception("Possible skip or repetition, last address 0x%x doesn't match with previous address 0x%x" % (last_addr, addr - 0x10))
last_addr = addr
#Check if we reach end
if addr > opts.end:
print("Info: Reached specified end address")
finish = True
break
#Discard if not start
if addr < opts.start:
print("Warning: address %s is lower than start address! discarding" % addr)
continue
#Print current line
hex_addr = hex(addr).upper()[2:]
while not len(hex_addr) == 8:
hex_addr = "0" + hex_addr
hex_data = ""
for line_byte in line_data:
line_byte = hex(line_byte).upper()[2:]
if len(line_byte) == 1:
line_byte = "0" + line_byte
hex_data += " " + line_byte
print("0x%s %s |%s|" % (hex_addr, hex_data, text))
#Store line data and log
data.append(bytes(line_data))
log.write(line)
log.flush()
if do_reset:
print("Info: sending reset as requested")
write(serial, opts, "reset\n")
return data
def main():
# Args parse
parser = argparse.ArgumentParser()
parser.add_argument("port", help="Serial port of device")
parser.add_argument("baud", type=int, help="Serial baud rate")
parser.add_argument("start", help="Start address in dec or hex (with 0x), must be multiple of 16")
parser.add_argument("end", help="End address in dec or hex (with 0x), must be multiple of 16")
parser.add_argument("--step", type=int, default=256, help="Number of lines per dump chunk")
parser.add_argument("--size", type=int, default=67, help="Total size of each line including spaces and newlines")
parser.add_argument("--timeout", type=float, default=0.1, help="Timeout in secs for serial")
parser.add_argument("--previous", help="Previous log to continue from")
parser.add_argument("--ignore-log", action='store_true', help="Previous log is not saved on new log")
parser.add_argument('--debug', action='store_true', help='Enables debug mode')
parser.add_argument('--reset', action='store_true', help='Sends "reset" after finishing')
opts = parser.parse_args()
# Args conversion
if opts.start[0:2] == "0x":
opts.start = int(opts.start, 16)
else:
opts.start = int(opts.start)
if opts.end[0:2] == "0x":
opts.end = int(opts.end, 16)
else:
opts.end = int(opts.end)
# Args check
if opts.start % 16 != 0:
raise Exception("start argument is not multiple of 16")
if opts.start < 0:
raise Exception("start argument is too low")
if opts.end % 16 != 0:
raise Exception("end argument is not multiple of 16")
if opts.end <= opts.start:
raise Exception("end argument is too low")
if opts.step <= 0:
raise Exception("step argument is too low")
if opts.size <= 0:
raise Exception("size argument is too low")
if opts.timeout <= 0:
raise Exception("timeout argument is too low")
if opts.debug:
print("Debug mode enabled")
name = datetime.fromtimestamp(time()).strftime("%Y-%m-%dT%H%M%S") + "_" + hex(opts.start) + "_" + hex(opts.end)
#Prepare to dump
serial = Serial(port=opts.port, baudrate=opts.baud, timeout=opts.timeout)
log = open(name + ".log", "w")
try:
data = dump(serial, opts, log)
except Exception as e:
raise e
finally:
serial.close()
log.close()
#Write to file
file = open(name + ".img", "wb")
try:
for line in data:
file.write(line)
except Exception as e:
raise e
finally:
file.close()
main()
And another python script named swap.py with the following content:
#!/usr/bin/env python3
import os
import sys, getopt
def main(argv):
inputfile = ''
outputfile = ''
try:
opts, args = getopt.getopt(argv,"hi:o:",["ifile=","ofile="])
except getopt.GetoptError:
print('swap.py -i <inputfile> -o <outputfile>')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print('swap.py -i <inputfile> -o <outputfile>')
sys.exit()
elif opt in ("-i", "--ifile"):
inputfile = arg
elif opt in ("-o", "--ofile"):
outputfile = arg
print('Input file is "', inputfile)
print('Output file is "', outputfile)
fi = open(inputfile, 'rb')
fo = open(outputfile, 'wb')
while True:
a = fi.read(4)
if (not a):
break
fo.write(a[::-1])
fi.close
fo.close
if __name__ == "__main__":
main(sys.argv[1:])
- Open putty with logging enabled.
- Switch on the cam.
- Stop the boot at the U-Boot prompt (should be "MStar #") pressing "Enter".
- Run the following commands to initialize the SPI bus and copy the partition content to RAM. Copy from offset 0x1d0000, length 0x1e0000 to RAM at address 0x21000000:
sf probe
sf read 0x21000000 0x1d0000 0x1e0000
- Close putty.
- Open a python 3.x console and run the script with the following options:
py dump.py --timeout 1 --step 64 comX 115200 0x21000000 0x211dfff0
Where comX is the port of your serial adapter, 0x21000000 is the RAM start address and 0x211dfff0 is the RAM end address (-16 bytes).
- Save the file as sys_xxx, where xxx is the suffix of your model (i.e. y203c)
- Open putty with logging enabled.
- Switch on the cam.
- Stop the boot at the U-Boot prompt (should be "MStar #") pressing "Enter".
- Run the following commands to initialize the SPI bus and copy the partition content to RAM. Copy from offset 0x3b0000, length 0xc30000 to RAM at address 0x21000000:
sf probe
sf read 0x21000000 0x3b0000 0xc30000
- Close putty.
- Open a python 3.x console and run the script with the following options:
py dump.py --timeout 1 --step 64 com4 115200 0x21000000 0x21c2fff0
Where comX is the port of your serial adapter, 0x21000000 is the RAM start address and 0x21c2fff0 is the RAM end address (-16 bytes).
- Save the file as home_xxx, where xxx is the suffix of your model (i.e. y203c)
The files dumped with the previous procedure have one last problem. Before they can be mounted as jffs2 filesystem or used for a restore, they must be reordered because the endianness is wrong. Run the swap.py script:
py swap.py -i sys_xxx -o sys_xxx.swapped
py swap.py -i home_xxx -o home_xxx.swapped
Please note that rootfs partition contains a file /etc/back.bin with a backup of your configuration (WiFi SSID, password, etc...). If you want to clean it, unpack the file system using jefferson and repack it using mkfs.jffs2.
- jefferson sys_xxx.swapped -d outdir
- Clean...
- mkfs.jffs2 -l -e 0x10000 -r outdir -o sys_xxx.final
If you want to reduce the file size unpack and repack home_xxx also.
- jefferson home_xxx.swapped -d outdir
- mkfs.jffs2 -l -e 0x10000 -r outdir -o home_xxx.final