Skip to content

Commit

Permalink
Merge pull request ReFirmLabs#267 from boblivion/pfsextraction
Browse files Browse the repository at this point in the history
Added PFS/0.9 extraction support
  • Loading branch information
devttys0 authored Sep 22, 2017
2 parents 40ae0ae + 06c2053 commit fd3c32d
Showing 1 changed file with 115 additions and 0 deletions.
115 changes: 115 additions & 0 deletions src/binwalk/plugins/unpfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import os
import errno
import struct
import binwalk.core.plugin
import binwalk.core.compat
from binwalk.core.common import BlockFile as open

class PFSCommon(object):

def _make_short(self, data, endianess):
"""Returns a 2 byte integer."""
data = binwalk.core.compat.str2bytes(data)
return struct.unpack('%sH' % endianess, data)[0]

def _make_int(self, data, endianess):
"""Returns a 4 byte integer."""
data = binwalk.core.compat.str2bytes(data)
return struct.unpack('%sI' % endianess, data)[0]

class PFS(PFSCommon):
"""Class for accessing PFS meta-data."""
HEADER_SIZE = 16

def __init__(self, fname, endianess='<'):
self.endianess = endianess
self.meta = open(fname, 'rb')
header = self.meta.read(self.HEADER_SIZE)
self.file_list_start = self.meta.tell()

self.num_files = self._make_short(header[-2:], endianess)
self.node_size = self._get_fname_len() + 12

def _get_fname_len(self, bufflen=128):
"""Returns the number of bytes designated for the filename."""
buff = self.meta.peek(bufflen)
strlen = buff.find('\0')
for i, b in enumerate(buff[strlen:]):
if b != '\0':
return strlen+i
return bufflen

def _get_node(self):
"""Reads a chunk of meta data from file and returns a PFSNode."""
data = self.meta.read(self.node_size)
return PFSNode(data, self.endianess)

def get_end_of_meta_data(self):
"""Returns integer indicating the end of the file system meta data."""
return self.HEADER_SIZE + self.node_size * self.num_files

def entries(self):
"""Returns file meta-data entries one by one."""
self.meta.seek(self.file_list_start)
for i in range(0, self.num_files):
yield self._get_node()

def __enter__(self):
return self

def __exit__(self, type, value, traceback):
self.meta.close()

class PFSNode(PFSCommon):
"""A node in the PFS Filesystem containing meta-data about a single file."""

def __init__(self, data, endianess):
self.fname, data = data[:-12], data[-12:]
self._decode_fname()
self.inode_no = self._make_int(data[:4], endianess)
self.foffset = self._make_int(data[4:8], endianess)
self.fsize = self._make_int(data[8:], endianess)

def _decode_fname(self):
"""Extracts the actual string from the available bytes."""
self.fname = self.fname[:self.fname.find('\0')]
self.fname = self.fname.replace('\\', '/')

class PFSExtractor(binwalk.core.plugin.Plugin):
"""
Extractor for known PFS/0.9 File System Formats.
"""
MODULES = ['Signature']

def init(self):
if self.module.extractor.enabled:
self.module.extractor.add_rule(regex='^pfs filesystem',
extension='pfs',
cmd=self.extractor)

def _create_dir_from_fname(self, fname):
try:
os.makedirs(os.path.dirname(fname))
except OSError as e:
if e.errno != errno.EEXIST:
raise e

def extractor(self, fname):
fname = os.path.abspath(fname)
try:
with PFS(fname) as fs:
# The end of PFS meta data is the start of the actual data
data = open(fname, 'rb')
data.seek(fs.get_end_of_meta_data())
for entry in fs.entries():
self._create_dir_from_fname(entry.fname)
outfile = open(entry.fname, 'wb')
outfile.write(data.read(entry.fsize))
outfile.close()
data.close()
except KeyboardInterrupt as e:
raise e
except Exception as e:
return False

return True

0 comments on commit fd3c32d

Please sign in to comment.