-
-
Save thefkboss/d83510411cb272b21e901fd084289cd7 to your computer and use it in GitHub Desktop.
JFFS2 scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# | |
# tool to parse JFFS2 images | |
# and more importantly, guess the erase block size | |
# | |
# 2015.10.19 darell tan | |
# | |
from struct import unpack | |
from argparse import ArgumentParser | |
import os | |
import sys | |
JFFS2_MAGIC = 0x1985 | |
def detect_endian(f): | |
d = f.read(2) | |
f.seek(-2, 1) | |
if unpack('<H', d)[0] == JFFS2_MAGIC: | |
return '<' | |
elif unpack('>H', d)[0] == JFFS2_MAGIC: | |
return '>' | |
return None | |
class Node: | |
def __init__(self, f, endian): | |
self.f = f | |
self.pos = f.tell() | |
elems = self.parse_node(endian) | |
if elems is None: | |
raise IOError, 'parse error' | |
self.magic, self.nodetype, self.totlen, self.hdr_crc = elems | |
assert self.magic == JFFS2_MAGIC or \ | |
self.magic == self.nodetype == 0xffff, 'invalid JFFS2 magic' | |
if self.is_free(): | |
self.f.seek(-12 + 4, 1) | |
while self.f.read(4) == '\xff' * 4: | |
pass | |
# check for EOF | |
if self.f.read(1) != '': | |
self.f.seek(-5, 1) # put the bytes back | |
self.totlen = self.f.tell() - self.pos | |
else: | |
self.f.seek(self.pad_len(self.totlen - 12), 1) | |
@staticmethod | |
def pad_len(x): | |
return (x + 3) & ~3 | |
def parse_node(self, endian): | |
d = self.f.read(12) | |
if d == '': | |
raise EOFError | |
# could be just padding. if so extend it. | |
if len(d) < 12 and d == '\xff' * len(d): | |
d += '\xff' * (12 - len(d)) | |
return unpack(endian + 'HHII', d) | |
def __str__(self): | |
return '%08x %04x %04x %d' % (self.pos, self.magic, | |
self.nodetype, self.totlen) | |
def is_free(self): | |
return self.magic == self.nodetype == 0xffff | |
def parse_jffs2(f): | |
e = detect_endian(f) | |
assert e is not None, 'JFFS2 magic not present' | |
nodes = [] | |
try: | |
while True: | |
nodes.append(Node(f, e)) | |
except EOFError: | |
pass | |
return nodes | |
def get_block_size(start_addrs): | |
diffs = [b - a for a, b in zip(start_addrs[:-1], start_addrs[1:])] | |
diffs = sorted(diffs) | |
# get the lowest common denominator | |
while len(diffs) > 1: | |
if diffs[-1] / diffs[0]: | |
del diffs[-1] | |
return diffs | |
def main(): | |
ap = ArgumentParser() | |
ap.add_argument('--pad', '-p', type=int, nargs='?', default=None, | |
help='Pads the image file to block size (specify 0 to auto-detect)') | |
ap.add_argument('--verbose', '-v', action='store_true', help='Dumps all found nodes') | |
ap.add_argument('image_file', help='JFFS image file') | |
args = ap.parse_args() | |
f = open(args.image_file, 'r+b') | |
nodes = parse_jffs2(f) | |
if args.verbose: | |
print ' offset magic type len' # header | |
for n in nodes: | |
print n | |
# find the start addresses | |
start_addrs = [] | |
prev_free = False | |
for n in nodes: | |
if not n.is_free() and prev_free: | |
start_addrs.append(n.pos) | |
prev_free = n.is_free() | |
blocksizes = get_block_size(start_addrs) | |
for bs in blocksizes: | |
print 'block size', bs | |
# pad the image? | |
if args.pad is not None: | |
f.seek(0, 2) # seek to end | |
filesize = f.tell() | |
assert args.pad > 0 or len(blocksizes) == 1, \ | |
'ambiguous block size - must specify' | |
block_sz = args.pad if args.pad > 0 else blocksizes[0] | |
if filesize % block_sz == 0: | |
print 'file size %d already conforms to block size %d' % \ | |
(filesize, block_sz) | |
else: | |
target_filesize = (1 + (filesize - 1) // block_sz) * block_sz | |
print 'padding image to %d (block size %d)...' % (target_filesize, block_sz) | |
f.write('\xff' * (target_filesize - filesize)) | |
f.close() | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# mounts jffs2 images | |
# | |
# 2015.10.18 darell tan | |
# | |
set -e | |
JFFS=$1 | |
MOUNTPOINT=$2 | |
ERASESZ=$3 | |
DEV=`losetup -f` | |
if [ ! -f "$JFFS" ]; then | |
echo "JFFS2 image $JFFS not found" | |
exit 1 | |
elif [ -z "$MOUNTPOINT" -o ! -d "$MOUNTPOINT" ]; then | |
echo "mountpoint $MOUNTPOINT not specified or doesn't exist" | |
exit 1 | |
elif [ -z "$ERASESZ" ]; then | |
echo "specify erase size" | |
exit 1 | |
fi | |
rmblock2mtd() { | |
if grep ^block2mtd /proc/modules >/dev/null; then | |
rmmod block2mtd | |
fi | |
} | |
cleanup() { | |
[ -e "$DEV" ] && losetup -d $DEV | |
rmblock2mtd 2>/dev/null | |
} | |
trap cleanup EXIT | |
# setup loop device | |
losetup $DEV $JFFS | |
rmblock2mtd | |
modprobe block2mtd block2mtd="$DEV,$ERASESZ" | |
modprobe mtdblock || true | |
mount -t jffs2 /dev/mtdblock0 $MOUNTPOINT | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
############################################################################### | |
### | |
### (c) 2012 by Ollopa / www.isysop.com | |
### | |
### Python script to pack and unpack U-Boot uImage files | |
### | |
### | |
import os, time, zlib | |
from struct import * | |
from os import fstat | |
from optparse import OptionParser, OptionGroup | |
### 64-byte header structure: | |
### uint32 magic_number | |
### uint32 header_crc | |
### uint32 timestamp | |
### uint32 uImage_size | |
### uint32 load_address | |
### uint32 entry_address | |
### uint32 data_crc | |
### uint8 os_type | |
### uint8 architecture | |
### uint8 image_type | |
### uint8 compression_type | |
### uint8 image_name[32] | |
HEADER_FORMAT = '!7L4B32s' ### (Big-endian, 7 ULONGS, 4 UCHARs, 32-byte string) | |
HEADER_SIZE=calcsize(HEADER_FORMAT) ### Should be 64-bytes | |
### Image types, from 0 to 14 | |
imageType = [['INVALID', ''], | |
['Standalone', 'standalone'], | |
['Kernel', 'kernel'], | |
['RAMDisk', 'ramdisk'], | |
['Multi-File', 'multi'], | |
['Firmware', 'firmware'], | |
['Script', 'script'], | |
['Filesystem', 'filesystem'], | |
['Flat Device Tree Blob', 'flat_dt'], | |
['Kirkwood Boot', 'kwbimage'], | |
['Freescale IMXBoot', 'imximage'], | |
['Davinci UBL', 'ublimage'], | |
['OMAP Config Header', 'omapimage'], | |
['Davinci AIS', 'aisimage'], | |
['Kernel (any load address)', 'kernel_noload']] | |
### OS types from 0 to 22 | |
osType = [['INVALID', ''], | |
['OpenBSD', 'openbsd'], | |
['NetBSD', 'netbsd'], | |
['FreeBSD', 'freebsd'], | |
['4.4BSD', '4_4bsd'], | |
['Linux', 'linux'], | |
['SVR4', 'svr4'], | |
['Esix', 'esix'], | |
['Solaris', 'solaris'], | |
['Irix', 'irix'], | |
['SCO', 'sco'], | |
['Dell', 'dell'], | |
['NCR', 'ncr'], | |
['LynxOS', 'lynxos'], | |
['VxWorks', 'vxworks'], | |
['pSOS', 'psos'], | |
['QNX', 'qnx'], | |
['U-Boot Firmware', 'u-boot'], | |
['RTEMS', 'rtems'], | |
['Unity OS', 'unity'], | |
['INTEGRITY', 'integrity'], | |
['OSE', 'ose']] | |
### CPU Architecture types from 0 to 21 | |
archType = [['INVALID', ''], | |
['Alpha', 'alpha'], | |
['ARM', 'arm'], | |
['Intel x86', 'x86'], | |
['IA64', 'ia64'], | |
['MIPS', 'mips'], | |
['MIPS64', 'mips64'], | |
['PowerPC', 'ppc'], | |
['IBM S390', 's390'], | |
['SuperH', 'sh'], | |
['Sparc', 'sparc'], | |
['Sparc64', 'sparc64'], | |
['M68k', 'm68k'], | |
['MicroBlaze', 'microblaze'], | |
['Nios-II', 'nios2'], | |
['Blackfin', 'blackfin'], | |
['AVR32', 'avr32'], | |
['ST200', 'st200'], | |
['NDS32', 'nds32'], | |
['OpenRISC 1000', 'or1k']] | |
### Compression types from 0 to 4 | |
compressType = [['uncompressed', 'none'], | |
['gzip compressed', 'gzip'], | |
['bzip2 compressed', 'bzip2'], | |
['lzma compressed', 'lzma'], | |
['lzo compressed', 'lzo']] | |
################################################################################ | |
### | |
### fromTable(table, index) - Returns string from lookup table | |
### | |
### parameters: table: name of table to search | |
### index: index of string to fetch | |
### | |
### Returns: Name fromt table or "unknown" message | |
### | |
def fromTable(table, index): | |
if index < len(table): | |
string = table[index][0] | |
else: | |
string = "Unknown:" + str(index) | |
return string | |
################################################################################ | |
### | |
### searchTable(table, value) - Searches a table for value, returns index | |
### | |
### parameters: table: name of table to search | |
### value: data to search for in table | |
### | |
### Returns: index of value if found, -1 otherwise | |
### | |
def searchTable(table, value): | |
index = -1 | |
for i in range(len(table)): | |
if table[i][1] == value: | |
index = i | |
break | |
return index | |
################################################################################ | |
### | |
### parseHeader(fh, offset=0) - Parse uImage header located at offset in file | |
### | |
### Parameters: fh: file handle | |
### offset: Optional location of header within file | |
### | |
### Returns: Dictionary of header information | |
### | |
def parseHeader(fh, offset=0): | |
### Save current position and seek to start position | |
startpos = fh.tell() | |
fh.seek(offset) | |
try: | |
block = fh.read(HEADER_SIZE) | |
except IOError: | |
print "File read error" | |
exit(1) | |
### Names of fields in the image header | |
keys = ['magic', 'headerCrc', 'time', 'size', 'loadAddr', 'entryAddr', | |
'dataCrc', 'osType', 'arch', 'imageType', 'compression', 'name'] | |
### Unpack the header into a dictionary of (key,value) pairs | |
values = unpack(HEADER_FORMAT, block) | |
hd = dict(zip(keys, values)) | |
### if Multi-file image, append file information | |
if hd['imageType'] == 4: | |
hd['files'] = getMultiFileLengths(fh, fh.tell()) | |
### Restore saved file position | |
fh.seek(startpos) | |
return hd | |
################################################################################ | |
### | |
### calculateHeaderCrc(hd) - Calculates the crc for a header | |
### | |
### Parameters: hd: Dictionary of header | |
### | |
### Returns: crc32 value | |
### | |
def calculateHeaderCrc(hd): | |
### Re-pack the list into a binary string | |
### Must calclate header CRC with CRC field set to 0. | |
header = pack(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) | |
################################################################################ | |
### | |
### crc32File(fh, start, length) - Calculates crc of data segment in a file | |
### | |
### Parameters: fh: file handle | |
### start: start position (optional) | |
### length: length of segment (optional) | |
### | |
def crc32File(fh, start=0, length=float('inf')): | |
BLOCKSIZE = 1024*512 | |
crc32 = 0 | |
byteCount = 0 | |
### Save current position and seek to start position | |
startpos = fh.tell() | |
fh.seek(start) | |
block = fh.read(BLOCKSIZE) | |
while block != "": | |
byteCount += len(block) | |
if byteCount > length: | |
extra = byteCount - length | |
block = block[:-extra] | |
crc32 = zlib.crc32(block, crc32) | |
if byteCount >= length: | |
break; | |
block = fh.read(BLOCKSIZE) | |
### Restore saved file position | |
fh.seek(startpos) | |
return (crc32 & 0xffffffff) | |
################################################################################ | |
### | |
### getMultiFileLengths(fh) - returns list of file lengths in multi-file image | |
### | |
### Parameters: fh: file handle | |
### offset: location of file lengths | |
### | |
def getMultiFileLengths(fh, offset=HEADER_SIZE): | |
lengthList = [] | |
### Save current position and seek to multi-file table location | |
startpos = fh.tell() | |
fh.seek(offset) | |
block = fh.read(4) | |
while block != "": | |
length = unpack('!L', block)[0] | |
if length == 0: | |
break | |
lengthList.append(length) | |
block = fh.read(4) | |
### Restore saved file position | |
fh.seek(startpos) | |
return lengthList | |
################################################################################ | |
### | |
### dumpHeader(hd) - Dumps header in text form to stdout | |
### | |
### Parameters: hd: header dictionary | |
def dumpHeader(hd): | |
### Dump header information and verify CRCs | |
if hd['magic'] != 0x27051956: | |
print "Invalid magic number! This is not a valid uImage file." | |
print "Magic: expected 0x27051956, but found %#08x" % hd['magic'] | |
return | |
print "Image name:\t", | |
print hd['name'].rstrip('\o') | |
print "Created:\t", time.ctime(hd['time']) | |
print "Image type:\t", | |
print fromTable(archType, hd['arch']), | |
print fromTable(osType, hd['osType']), | |
print fromTable(imageType, hd['imageType']), | |
print "(" + fromTable(compressType, hd['compression']) + ")" | |
print "Data size:\t%u Bytes" % hd['size'] | |
print "Load Address:\t%#08x" % hd['loadAddr'] | |
print "Entry Point:\t%#08x" % hd['entryAddr'] | |
print "Header CRC:\t%#08x ..." % hd['headerCrc'], | |
if hd['headerCrc'] == calculateHeaderCrc(hd): | |
print "OK" | |
else: | |
print "Mismatch! Calculated CRC: %#08x" % str(calculatedHeadedCrc(hd)) | |
print "Data CRC:\t%#08x" % hd['dataCrc'] ###, | |
###### Verify Data CRC | |
###print "%#08x" % crc32File(fh) | |
if hd['imageType'] == 4: | |
print "Contents:" | |
for index, length in enumerate(hd['files']): | |
print " Image %u: %u bytes" % (index,length) | |
return | |
################################################################################ | |
### | |
### dumpToFile(fh, offset, len, filename) - Dumps segment of fh to filename | |
### | |
### Parameters: fh: Source file handle | |
### offset: Start position in source file | |
### length: Length of segment to copy | |
### filename: Name of new file to write contents to | |
### | |
def dumpToFile(fh, offset, length, filename): | |
### Save current position and seek to start location | |
startpos = fh.tell() | |
fh.seek(offset) | |
BLOCKSIZE = 1024*512 | |
df = open(filename, "wb") | |
while True: | |
if BLOCKSIZE > length: | |
BLOCKSIZE = length | |
block = fh.read(BLOCKSIZE) | |
df.write(block) | |
length -= BLOCKSIZE | |
if block == "": | |
break; | |
df.close() | |
### Restore saved file position | |
fh.seek(startpos) | |
return | |
################################################################################ | |
### | |
### imageList(image) - lists contents of image to stdout | |
### | |
### Parameters: image: filename of image to list | |
### | |
def imageList(image): | |
try: | |
size = os.path.getsize(image) | |
except IOError: | |
print "inavlid filename:", image | |
exit(1) | |
if size < HEADER_SIZE: | |
print "File too small! Not a uImage file." | |
exit(1) | |
f = open(image, "rb") | |
try: | |
data = f.read(HEADER_SIZE) | |
except IOError: | |
print "File read error" | |
f.close() | |
exit(1) | |
d=parseHeader(f) | |
dumpHeader(d) | |
### Dump multi-file headers as well | |
if d['imageType'] == 4: | |
### Next image begins after multi-file table | |
f.seek(HEADER_SIZE+4+(4*len(d['files']))) | |
for index, length in enumerate(d['files']): | |
print "Multi-File Image %u: Header" % (index) | |
print "--------------------------" | |
mfd = parseHeader(f, f.tell()) | |
dumpHeader(mfd) | |
### Dump child uImage file | |
#dumpToFile(f, f.tell(), length, mfd['name'].rstrip('\0')+".uImage") | |
### Dump payload from child uImage | |
#dumpToFile(f, f.tell()+HEADER_SIZE, mfd['size'], mfd['name'].rstrip('\0')) | |
f.seek(length,1) | |
f.close() | |
################################################################################ | |
### | |
### imageExtract(image) - extracts contents of image to file(s) | |
### | |
### Parameters: image: filename of image to extract | |
### | |
def imageExtract(image): | |
try: | |
size = os.path.getsize(image) | |
except IOError: | |
print "inavlid filename", image | |
exit(1) | |
if size < HEADER_SIZE: | |
print "File too small! Not a uImage file." | |
exit(1) | |
f = open(image, "rb") | |
try: | |
data = f.read(HEADER_SIZE) | |
except IOError: | |
print "File read error" | |
f.close() | |
exit(1) | |
d=parseHeader(f) | |
### Check for multi-file image | |
if d['imageType'] == 4: | |
### Next image begins after multi-file table | |
f.seek(HEADER_SIZE+4+(4*len(d['files']))) | |
filenames = [] | |
for index, length in enumerate(d['files']): | |
mfd = parseHeader(f, f.tell()) | |
filename = mfd['name'].rstrip('\0') | |
if filename == "": | |
filename = "image"+index | |
### Prevent overwriting files with the same name | |
if filename in filenames: | |
suffix = 1 | |
while filename+"_"+str(suffix) in filenames: | |
suffix += 1 | |
filename = filename + "_" + str(suffix) | |
filenames.append(filename) | |
### Dump child uImage file | |
print filename+".uImage" | |
dumpToFile(f, f.tell(), length, filename+".uImage") | |
### Dump payload from child uImage | |
print filename | |
dumpToFile(f, f.tell()+HEADER_SIZE, mfd['size'], filename) | |
f.seek(length,1) | |
else: | |
filename = d['name'].rstrip('\0') | |
if filename == "": | |
filename = "image0" | |
print d['name'].rstrip('\0') | |
dumpToFile(f, HEADER_SIZE, d['size'], filename) | |
f.close() | |
################################################################################ | |
### | |
### imageCreate(options, image) - creates uImage with provided options | |
### | |
### Parameters: image: filename of image to create | |
### options: uImage header options from command-line | |
### | |
def imageCreate(options, image): | |
ifh = open(image, 'w+b') | |
os = searchTable(osType, options.osType) | |
arch = searchTable(archType, options.architecture) | |
it = searchTable(imageType, options.imageType) | |
le = int(options.loadaddr, 0) | |
ep = int(options.entryaddr, 0) | |
comp = searchTable(compressType, options.compression) | |
dcrc = 0 | |
hcrc = 0 | |
size = 0 | |
data = str() | |
### unix timestamp (or set to 0 to be like Palm) | |
ctime = int(time.time()) | |
### Check if multi-image | |
if it == 4: | |
lenTable = [] | |
for filename in options.filespec.split(":"): | |
dfh = open(filename, 'rb') | |
lenTable.append(fstat(dfh.fileno()).st_size) | |
data += dfh.read() | |
dfh.close() | |
lenTable.append(0) | |
fmt = "!" + str(len(lenTable)) + "L" | |
data = pack(fmt, *lenTable) + data | |
else: | |
dfh = open(options.filespec, 'rb') | |
data = dfh.read() | |
dfh.close() | |
size = len(data) | |
dcrc = zlib.crc32(data) & 0xFFFFFFFF | |
header = pack(HEADER_FORMAT, 0x27051956, 0, ctime, size, | |
le, ep, dcrc, os, arch, it, comp, options.imagename) | |
hcrc = zlib.crc32(header) & 0xFFFFFFFF | |
header = pack(HEADER_FORMAT, 0x27051956, hcrc, ctime, size, | |
le, ep, dcrc, os, arch, it, comp, options.imagename) | |
ifh.write(header) | |
ifh.write(data) | |
ifh.close() | |
imageList(image) | |
################################################################################ | |
### | |
### Main program body | |
### | |
def main(): | |
usage = "\n\t%prog -l image" \ | |
"\n\t%prog -c [options] image" \ | |
"\n\t%prog -x image" \ | |
"\n\t%prog -h" | |
parser = OptionParser(usage) | |
parser.add_option("-l", action = "store_true", dest = "imageList", | |
help = "list image contents") | |
parser.add_option("-c", action = "store_true", dest = "imageCreate", | |
help = "create new image") | |
parser.add_option("-x", action = "store_true", dest = "imageExtract", | |
help="extract image contents") | |
group = OptionGroup(parser, "Creation Options", "-c -A arch -O os -T" \ | |
" type -C comp -a addr -e ep -n name -d data_file[:data_file...] image") | |
group.add_option("-A", dest = "architecture", help = "set architecture to 'arch'") | |
group.add_option("-O", dest = "osType", help = "set operating system to 'os'") | |
group.add_option("-T", dest = "imageType", help = "set image type to 'type'") | |
group.add_option("-C", dest = "compression", help = "set compression to 'comp'") | |
group.add_option("-a", dest = "loadaddr", help = "iset load address to 'addr'") | |
group.add_option("-e", dest = "entryaddr", help = "iset entry point to 'ep'") | |
group.add_option("-n", dest = "imagename", help = "set image name to 'name'") | |
group.add_option("-d", dest = "filespec", help = "image data from 'datafile'") | |
parser.set_defaults(osType="linux", loadaddr="0", entryaddr="0", imagename="", | |
compression="none", architecture="x86") | |
parser.add_option_group(group) | |
(options, args) = parser.parse_args() | |
if len(args) != 1: | |
parser.error("incorrect number of arguments") | |
image = args[0] | |
if options.imageList: | |
if options.imageCreate or options.imageExtract: | |
parser.error("-l, -c, and -x are mutually exclusive") | |
imageList(image) | |
elif options.imageExtract: | |
if options.imageList or options.imageCreate: | |
parser.error("-l, -c, and -x are mutually exclusive") | |
imageExtract(image) | |
elif options.imageCreate: | |
if options.imageList or options.imageExtract: | |
parser.error("-l, -c, and -x are mutually exclusive") | |
if not options.imageType: | |
parser.error("Must specify image type") | |
if not options.filespec: | |
parser.error("Must specify data file") | |
imageCreate(options, image) | |
return | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment