Added initial code for fwutils.
This commit is contained in:
parent
d6876b0b95
commit
d97c763e93
|
@ -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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
34
README.md
34
README.md
|
@ -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
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
kaitaistruct>=0.9
|
|
@ -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,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()
|
|
@ -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
|
Loading…
Reference in New Issue