diff --git a/src/binwalk/plugins/unjffs2.py b/src/binwalk/plugins/unjffs2.py new file mode 100644 index 0000000..6a0b253 --- /dev/null +++ b/src/binwalk/plugins/unjffs2.py @@ -0,0 +1,168 @@ +import os +import sys +import subprocess +import binwalk.core.common +import binwalk.core.compat +import binwalk.core.plugin + +class JFFS2Exception(Exception): + pass + +class JFFS2Entry(object): + + def __init__(self, **kwargs): + for (k, v) in binwalk.core.compat.iterator(kwargs): + setattr(self, k, v) + +class UnJFFS2(object): + + def __init__(self, image, directory, verbose=False): + self.image = image + self.verbose = verbose + self.directory = directory + + if not os.path.exists(self.image): + raise JFFS2Exception("Invalid/non-existant image: '%s'" % self.image) + + try: + os.mkdir(self.directory) + except OSError as e: + raise JFFS2Exception("Failed to create output directory '%s': %s" % (self.directory, str(e))) + + def parse_permission(self, perm_txt): + perm = 0 + + for i in range(0, 3): + if perm_txt[::-1][i] != '-': + perm |= (1 << i) + + return perm + + def parse_permissions(self, permissions): + ftype = permissions[0] + owner = self.parse_permission(permissions[1:4]) + group = self.parse_permission(permissions[4:7]) + other = self.parse_permission(permissions[7:10]) + perms = (owner * 64) + (group * 8) + other + + return (ftype, perms) + + def ls(self): + entries = [] + + # jffs2reader self.image -d / -r + (stdout, stderr) = subprocess.Popen(['jffs2reader', self.image, '-d', '/', '-r'], stdout=subprocess.PIPE).communicate() + + # Handle big endian images + if not stdout and not stderr: + subprocess.call(['jffs2dump', '-b', '-e', self.image + '.le', self.image]) + self.image += '.le' + + # jffs2reader self.image -d / -r + (stdout, stderr) = subprocess.Popen(['jffs2reader', self.image, '-d', '/', '-r'], stdout=subprocess.PIPE).communicate() + + for line in binwalk.core.compat.bytes2str(stdout).splitlines(): + parts = [x for x in line.split(' ') if x] + parts = parts[:5] + [' '.join(parts[5:])] + + uid = int(parts[2]) + guid = int(parts[3]) + + fpath = parts[-1] + symlink = "" + + (ftype, permissions) = self.parse_permissions(parts[0]) + + if ftype == 'l' and '->' in fpath: + (fpath, symlink) = fpath.split('->', 1) + fpath = fpath.strip() + symlink = symlink.strip() + + if fpath.startswith(os.path.sep): + fpath = fpath[1:] + if symlink and symlink.startswith(os.path.sep): + symlink = symlink[1:] + + entries.append(JFFS2Entry(type=ftype, path=fpath, symlink=symlink, uid=uid, guid=guid, permissions=permissions)) + + return entries + + def extract_entry(self, entry): + outfile = os.path.join(self.directory, entry.path) + + if self.verbose: + sys.stderr.write(entry.path + "\n") + + if entry.type == 'd': + try: + os.mkdir(outfile) + except OSError as e: + pass + #sys.stderr.write("Failed to create directory '%s': %s\n" % (outfile, str(e))) + elif entry.type == 'l': + try: + os.symlink(entry.symlink, outfile) + except OSError as e: + pass + #sys.stderr.write("Failed to create symlink '%s -> %s': %s\n" % (outfile, entry.symlink, str(e))) + elif entry.type == '-': + # jffs2reader self.image -f entry.path + (stdout, stderr) = subprocess.Popen(['jffs2reader', self.image, '-f', entry.path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate() + + if stderr: + pass + #sys.stderr.write("jffs2reader error while reading file '%s': %s\n" % (entry.path, binwalk.core.compat.bytes2str(stderr))) + else: + fp = binwalk.core.common.BlockFile(outfile, "wb") + fp.write(stdout) + fp.close() + # TODO: Add support for special device files + #elif entry.type == 'c': + # pass + else: + #sys.stderr.write("Don't know how to handle file type '%c' for '%s'\n" % (entry.type, entry.path)) + return + + # Set file user/group owner + try: + os.chown(outfile, entry.uid, entry.guid) + except OSError as e: + pass + + # Set file permissions + try: + os.chmod(outfile, entry.permissions) + except OSError as e: + pass + + def extract(self): + for entry in self.ls(): + self.extract_entry(entry) + +class UnJFFS2Plugin(binwalk.core.plugin.Plugin): + + MODULES = ['Signature'] + + def init(self): + if self.module.extractor.enabled: + self.module.extractor.add_rule(txtrule=None, + regex='^jffs2 filesystem', + extension='jffs2', + cmd=self.extractor) + + def extractor(self, fname): + fname = os.path.realpath(fname) + outdir = os.path.join(os.path.dirname(fname), 'jffs2-root') + outdir = binwalk.core.common.unique_file_name(outdir) + + try: + UnJFFS2(fname, outdir).extract() + except KeyboardInterrupt as e: + raise e + except Exception as e: + return False + + return True +