Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/netgear/chk.py: 77%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

43 statements  

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 )