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