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

44 statements  

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 )