1import io
2from pathlib import Path
3
4from structlog import get_logger
5
6from unblob.extractor import carve_chunk_to_file
7from unblob.file_utils import Endian, File, InvalidInputFormat, StructParser
8from unblob.models import (
9 Chunk,
10 Extractor,
11 HandlerDoc,
12 HandlerType,
13 HexString,
14 Reference,
15 StructHandler,
16 ValidChunk,
17)
18
19logger = get_logger()
20
21C_DEFINITIONS = r"""
22 #define FIXED_HEADER_LEN 40
23 typedef struct chk_header {
24 uint32 magic; /* "2A 23 24 5E" */
25 uint32 header_len;
26 uint8 reserved[8];
27 uint32 kernel_chksum;
28 uint32 rootfs_chksum;
29 uint32 kernel_len;
30 uint32 rootfs_len;
31 uint32 image_chksum;
32 uint32 header_chksum;
33 char board_id[header_len - FIXED_HEADER_LEN]; /* upto MAX_BOARD_ID_LEN */
34 } chk_header_t;
35"""
36
37
38class CHKExtractor(Extractor):
39 def __init__(self):
40 self._struct_parser = StructParser(C_DEFINITIONS)
41
42 def extract(self, inpath: Path, outdir: Path):
43 with File.from_path(inpath) as file:
44 header = self._struct_parser.parse("chk_header_t", file, Endian.BIG)
45
46 file.seek(header.header_len, io.SEEK_SET)
47
48 self._dump_file(file, outdir, Path("kernel"), header.kernel_len)
49 self._dump_file(file, outdir, Path("rootfs"), header.rootfs_len)
50
51 def _dump_file(self, file: File, outdir: Path, path: Path, length: int):
52 if not length:
53 return
54
55 start = file.tell()
56 chunk = Chunk(start_offset=start, end_offset=start + length)
57 carve_chunk_to_file(outdir.joinpath(path), file, chunk)
58
59
60class NetgearCHKHandler(StructHandler):
61 NAME = "chk"
62
63 PATTERNS = [HexString("2a 23 24 5e")]
64
65 C_DEFINITIONS = C_DEFINITIONS
66 HEADER_STRUCT = "chk_header_t"
67 EXTRACTOR = CHKExtractor()
68
69 DOC = HandlerDoc(
70 name="Netgear CHK",
71 description="Netgear CHK firmware files consist of a custom header containing metadata and checksums, followed by kernel and root filesystem partitions. The header includes fields for partition sizes, checksums, and a board identifier.",
72 handler_type=HandlerType.ARCHIVE,
73 vendor="Netgear",
74 references=[
75 Reference(
76 title="CHK Image Format Image Builder Tool for the R7800 Series",
77 url="https://github.com/Getnear/R7800/blob/master/tools/firmware-utils/src/mkchkimg.c",
78 )
79 ],
80 limitations=[],
81 )
82
83 def is_valid_header(self, header) -> bool:
84 if header.header_len != len(header):
85 return False
86 try:
87 header.board_id.decode("utf-8")
88 except UnicodeDecodeError:
89 return False
90 return True
91
92 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
93 header = self.parse_header(file, endian=Endian.BIG)
94
95 if not self.is_valid_header(header):
96 raise InvalidInputFormat("Invalid CHK header.")
97
98 return ValidChunk(
99 start_offset=start_offset,
100 end_offset=start_offset
101 + header.header_len
102 + header.kernel_len
103 + header.rootfs_len,
104 )