Skip to content

Commit f7f32fa

Browse files
Antoine Pecoraroqkaiser
Antoine Pecoraro
authored andcommitted
feat(handler): Add support for HP IPKG format
The IPKG files are childs of a BDL file. The header contains some informations such as name, version, revision, etc of the IPKG package. After that header is a table of content that has the name, the offset, the size and a crc32 of each file it contains. This repo https://github.com/tylerwhall/hpbdl contains information that was useful
1 parent f98b06a commit f7f32fa

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c99dd4e215b508395aed42ebfee1e87d64b6bb5551c165934fe1a759e40093ee
3+
size 1345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e6478ea19e2122817e1986151847d3590e114ffd7c5d65d8d91cccea316c1cf2
3+
size 17

unblob/handlers/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .archive import ar, arc, arj, cab, cpio, dmg, rar, sevenzip, stuffit, tar, zip
33
from .archive.dlink import encrpted_img, shrs
44
from .archive.engeniustech import engenius
5-
from .archive.hp import bdl
5+
from .archive.hp import bdl, ipkg
66
from .archive.instar import bneg
77
from .archive.netgear import chk, trx
88
from .archive.qnap import qnap_nas
@@ -64,6 +64,7 @@
6464
qnap_nas.QnapHandler,
6565
bneg.BNEGHandler,
6666
bdl.HPBDLHandler,
67+
ipkg.HPIPKGHandler,
6768
sparse.SparseHandler,
6869
ar.ARHandler,
6970
arc.ARCHandler,

unblob/handlers/archive/hp/ipkg.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import io
2+
from pathlib import Path
3+
from typing import Optional
4+
5+
from dissect.cstruct import Instance
6+
from structlog import get_logger
7+
8+
from unblob.extractor import carve_chunk_to_file, is_safe_path
9+
from unblob.file_utils import Endian, File, InvalidInputFormat, StructParser, snull
10+
from unblob.models import Chunk, Extractor, HexString, StructHandler, ValidChunk
11+
12+
logger = get_logger()
13+
14+
C_DEFINITIONS = r"""
15+
typedef struct ipkg_file_entry {
16+
char name[256];
17+
uint64 offset;
18+
uint64 size;
19+
uint32 crc32;
20+
} ipkg_toc_entry_t;
21+
22+
typedef struct ipkg_header {
23+
char magic[4];
24+
uint16 major;
25+
uint16 minor;
26+
uint32 toc_offset;
27+
uint32 unknown_1;
28+
uint32 toc_entries;
29+
uint32 unknown_2[2];
30+
uint32 always_null;
31+
char file_version[256];
32+
char product_name[256];
33+
char ipkg_name[256];
34+
char signature[256];
35+
} ipkg_header_t;
36+
"""
37+
38+
39+
def is_valid_header(header: Instance) -> bool:
40+
if header.toc_offset == 0 or header.toc_entries == 0:
41+
return False
42+
try:
43+
snull(header.ipkg_name).decode("utf-8")
44+
snull(header.file_version).decode("utf-8")
45+
snull(header.product_name).decode("utf-8")
46+
except UnicodeDecodeError:
47+
return False
48+
return True
49+
50+
51+
class HPIPKGExtractor(Extractor):
52+
def __init__(self):
53+
self._struct_parser = StructParser(C_DEFINITIONS)
54+
55+
def extract(self, inpath: Path, outdir: Path):
56+
entries = []
57+
with File.from_path(inpath) as file:
58+
header = self._struct_parser.parse("ipkg_header_t", file, Endian.LITTLE)
59+
file.seek(header.toc_offset, io.SEEK_SET)
60+
for _ in range(header.toc_entries):
61+
entry = self._struct_parser.parse(
62+
"ipkg_toc_entry_t", file, Endian.LITTLE
63+
)
64+
entry_path = Path(snull(entry.name).decode("utf-8"))
65+
if entry_path.parent.name:
66+
raise InvalidInputFormat("Entry name contains directories.")
67+
if not is_safe_path(outdir, entry_path):
68+
logger.warning(
69+
"Path traversal attempt, discarding.",
70+
outdir=outdir,
71+
)
72+
continue
73+
entries.append(
74+
(
75+
outdir.joinpath(outdir / entry_path.name),
76+
Chunk(
77+
start_offset=entry.offset,
78+
end_offset=entry.offset + entry.size,
79+
),
80+
)
81+
)
82+
83+
for carve_path, chunk in entries:
84+
carve_chunk_to_file(
85+
file=file,
86+
chunk=chunk,
87+
carve_path=carve_path,
88+
)
89+
90+
91+
class HPIPKGHandler(StructHandler):
92+
NAME = "ipkg"
93+
94+
PATTERNS = [HexString("69 70 6B 67 01 00 03 00")]
95+
96+
C_DEFINITIONS = C_DEFINITIONS
97+
HEADER_STRUCT = "ipkg_header_t"
98+
EXTRACTOR = HPIPKGExtractor()
99+
100+
def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
101+
header = self.parse_header(file, endian=Endian.LITTLE)
102+
103+
if not is_valid_header(header):
104+
raise InvalidInputFormat("Invalid IPKG header.")
105+
106+
file.seek(start_offset + header.toc_offset, io.SEEK_SET)
107+
end_offset = -1
108+
for _ in range(header.toc_entries):
109+
entry = self._struct_parser.parse("ipkg_toc_entry_t", file, Endian.LITTLE)
110+
end_offset = max(end_offset, start_offset + entry.offset + entry.size)
111+
112+
return ValidChunk(start_offset=start_offset, end_offset=end_offset)

0 commit comments

Comments
 (0)