Added initial code for fwutils.

This commit is contained in:
Jack Hadrill 2022-04-03 23:09:05 +01:00
parent d6876b0b95
commit d97c763e93
8 changed files with 194 additions and 2 deletions

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Module",
"type": "python",
"request": "launch",
"module": "fwutils",
"justMyCode": true,
"args": ["x", "../spa50x-30x-7-6-2f.bin"]
}
]
}

View File

@ -1,3 +1,35 @@
# fwutils-spa504g # SPA504G firmware utilities
Firmware utilities for the Cisco SPA504G SIP phone. Firmware utilities for the Cisco SPA504G SIP phone.
## Installation
This utility requires Python 3.
```bash
$ python3 -m venv venv
$ . ./venv/bin/activate
$ pip install -e .
```
## Usage
The utility will extract files to a new `extracted` directory, in the same location as the specified file.
```
usage: fwutils [-h] {x} ...
Firmware utility for the SPA504G SIP phone.
positional arguments:
{x} Extract a file.
options:
-h, --help show this help message and exit
```
### Example
```
$ fwutils x ./spa50x-30x-7-6-2f.bin
```

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
kaitaistruct>=0.9

18
setup.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
from setuptools import setup, find_packages
with open("requirements.txt") as f:
requirements = f.readlines()
setup(
name="fwutils",
version="0.1",
description="SPA504G firmware utilities.",
author="Butlersaurus",
packages=find_packages("src"),
package_dir={"": "src"},
include_package_data=True,
entry_points={"console_scripts": ["fwutils = fwutils.__main__:main"]},
install_requires=requirements
)

0
src/fwutils/__init__.py Normal file
View File

54
src/fwutils/__main__.py Normal file
View File

@ -0,0 +1,54 @@
import argparse
from io import BytesIO
import mmap
import pathlib
import zlib
from fwutils.definitions.spa504g import Spa504g
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Firmware utility for the SPA504G SIP phone.")
subparsers = parser.add_subparsers(help="Extract a file.")
extract_parser = subparsers.add_parser("x")
extract_parser.add_argument("file", type=pathlib.Path, nargs="?", help="The file to extract.")
args = parser.parse_args()
if not args.file:
parser.error("You must specify a file.")
return args
def main():
args = parse_args()
file: pathlib.Path = args.file
file_handle = open(file, "rb")
with mmap.mmap(file_handle.fileno(), 0, access=mmap.ACCESS_READ) as buf:
firmware = Spa504g.from_io(BytesIO(buf))
output_dir = file.parent.absolute() / "extracted"
output_dir.mkdir(parents=True, exist_ok=True)
for module in firmware.modules:
output_file = file.stem
output_file += "_"
output_file += hex(module.offset)
output_file += "_"
output_file += hex(module.offset + module.length)
output_file += file.suffix
output_file = pathlib.Path(output_file)
with open(output_dir / output_file, "wb") as f:
f.write(module.body)
if module.compressed == 0: # 0 == True
decompressed_data = zlib.decompress(module.body, 15)
output_file = output_file.stem + "_decompressed" + output_file.suffix
with open(output_dir / output_file, "wb") as f:
f.write(decompressed_data)
f.close()
if __name__ == "__main__":
main()

View File

View File

@ -0,0 +1,71 @@
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
from pkg_resources import parse_version
import kaitaistruct
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO
if parse_version(kaitaistruct.__version__) < parse_version('0.9'):
raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))
class Spa504g(KaitaiStruct):
def __init__(self, _io, _parent=None, _root=None):
self._io = _io
self._parent = _parent
self._root = _root if _root else self
self._read()
def _read(self):
self.header = Spa504g.Header(self._io, self, self._root)
self.modules = [None] * (self.header.module_count)
for i in range(self.header.module_count):
self.modules[i] = Spa504g.Module(self._io, self, self._root)
class Header(KaitaiStruct):
def __init__(self, _io, _parent=None, _root=None):
self._io = _io
self._parent = _parent
self._root = _root if _root else self
self._read()
def _read(self):
self.magic = self._io.read_bytes(16)
if not self.magic == b"\x53\x6B\x4F\x73\x4D\x6F\x35\x20\x66\x49\x72\x4D\x77\x41\x72\x45":
raise kaitaistruct.ValidationNotEqualError(b"\x53\x6B\x4F\x73\x4D\x6F\x35\x20\x66\x49\x72\x4D\x77\x41\x72\x45", self.magic, self._io, u"/types/header/seq/0")
self.signature = self._io.read_bytes(32)
self.digest = self._io.read_bytes(16)
self.random_sequence = self._io.read_bytes(16)
self.header_length = self._io.read_u4be()
self.module_header_length = self._io.read_u4be()
self.file_length = self._io.read_u4be()
self.version = (self._io.read_bytes(32)).decode(u"utf8")
self.module_count = self._io.read_u4be()
self.padding = self._io.read_bytes((self.header_length - 128))
class Module(KaitaiStruct):
def __init__(self, _io, _parent=None, _root=None):
self._io = _io
self._parent = _parent
self._root = _root if _root else self
self._read()
def _read(self):
self.sector_id = self._io.read_u2be()
self.compressed = self._io.read_u2be()
self.length = self._io.read_u4be()
self.offset = self._io.read_u4be()
self.digest = self._io.read_bytes(16)
self.padding = self._io.read_bytes((self._parent.header.module_header_length - 28))
@property
def body(self):
if hasattr(self, '_m_body'):
return self._m_body if hasattr(self, '_m_body') else None
_pos = self._io.pos()
self._io.seek(self.offset)
self._m_body = self._io.read_bytes(self.length)
self._io.seek(_pos)
return self._m_body if hasattr(self, '_m_body') else None