Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/engeniustech/engenius.py: 49%

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

47 statements  

1from pathlib import Path 

2 

3from structlog import get_logger 

4 

5from unblob.file_utils import Endian, File, InvalidInputFormat, StructParser 

6from unblob.models import ( 

7 Extractor, 

8 HandlerDoc, 

9 HandlerType, 

10 HexString, 

11 Reference, 

12 StructHandler, 

13 ValidChunk, 

14) 

15 

16logger = get_logger() 

17 

18C_DEFINITIONS = r""" 

19 typedef struct engenius_header { 

20 uint32 unknown_1; 

21 uint32 vendor_id; 

22 uint32 product_id; 

23 char version[20]; 

24 uint32 length; 

25 uint32 unknown_2; 

26 char checksum[16]; 

27 char padding[32]; 

28 uint32 unknown_3; 

29 char magic[4]; 

30 char reg_dom[8]; 

31 uint32 major_version; 

32 uint32 minor_version; 

33 uint32 micro_version; 

34 uint32 release_date; 

35 uint32 c_major_version; 

36 uint32 c_minor_version; 

37 uint32 c_micro_version; 

38 uint32 model_len; 

39 char model[model_len]; 

40 } engenius_header_t; 

41""" 

42 

43XOR_KEY = b"\xac\x78\x3c\x9e\xcf\x67\xb3\x59" 

44XOR_KEY_LEN = len(XOR_KEY) 

45 

46 

47def decrypter(reference): 

48 def decrypt(value, offset): 

49 nonlocal reference 

50 return value ^ XOR_KEY[(offset - reference) % XOR_KEY_LEN] 

51 

52 return decrypt 

53 

54 

55class EngeniusExtractor(Extractor): 

56 def __init__(self): 

57 self._struct_parser = StructParser(C_DEFINITIONS) 

58 

59 def extract(self, inpath: Path, outdir: Path): 

60 outpath = outdir.joinpath(f"{inpath.name}.decrypted") 

61 

62 with File.from_path(inpath) as f: 

63 engenius_header = self._struct_parser.parse( 

64 "engenius_header_t", f, Endian.BIG 

65 ) 

66 logger.debug( 

67 "engenius_header_t", 

68 engenius_header=engenius_header, 

69 size=len(engenius_header), 

70 _verbosity=3, 

71 ) 

72 decrypt = decrypter(f.find(XOR_KEY)) 

73 with outpath.open("wb") as outfile: 

74 decrypted = bytearray() 

75 for offset in range(f.tell(), engenius_header.length): 

76 decrypted.append(decrypt(f[offset], offset)) 

77 outfile.write(decrypted) 

78 

79 

80class EngeniusHandler(StructHandler): 

81 NAME = "engenius" 

82 

83 PATTERNS = [HexString("12 34 56 78 61 6c 6c")] 

84 

85 C_DEFINITIONS = C_DEFINITIONS 

86 HEADER_STRUCT = "engenius_header_t" 

87 EXTRACTOR = EngeniusExtractor() 

88 PATTERN_MATCH_OFFSET = -0x5C 

89 

90 DOC = HandlerDoc( 

91 name="Engenius", 

92 description="Engenius firmware files contain a custom header with metadata, followed by encrypted data using an XOR cipher.", 

93 handler_type=HandlerType.ARCHIVE, 

94 vendor="Engenius", 

95 references=[ 

96 Reference( 

97 title="enfringement - Tools for working with EnGenius WiFi hardware.", 

98 url="https://github.com/ryancdotorg/enfringement", # Replace with actual reference if available 

99 ) 

100 ], 

101 limitations=["Does not support all firmware versions."], 

102 ) 

103 

104 def is_valid_header(self, header) -> bool: 

105 if header.length <= len(header): 

106 return False 

107 try: 

108 header.model.decode("utf-8") 

109 except UnicodeDecodeError: 

110 return False 

111 return True 

112 

113 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

114 header = self.parse_header(file, endian=Endian.BIG) 

115 

116 if not self.is_valid_header(header): 

117 raise InvalidInputFormat("Invalid Engenius header.") 

118 

119 return ValidChunk( 

120 start_offset=start_offset, 

121 end_offset=start_offset + len(header) + header.length, 

122 )