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

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

48 statements  

1from pathlib import Path 

2from typing import Optional 

3 

4from structlog import get_logger 

5 

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

7from unblob.models import ( 

8 Extractor, 

9 HandlerDoc, 

10 HandlerType, 

11 HexString, 

12 Reference, 

13 StructHandler, 

14 ValidChunk, 

15) 

16 

17logger = get_logger() 

18 

19C_DEFINITIONS = r""" 

20 typedef struct engenius_header { 

21 uint32 unknown_1; 

22 uint32 vendor_id; 

23 uint32 product_id; 

24 char version[20]; 

25 uint32 length; 

26 uint32 unknown_2; 

27 char checksum[16]; 

28 char padding[32]; 

29 uint32 unknown_3; 

30 char magic[4]; 

31 char reg_dom[8]; 

32 uint32 major_version; 

33 uint32 minor_version; 

34 uint32 micro_version; 

35 uint32 release_date; 

36 uint32 c_major_version; 

37 uint32 c_minor_version; 

38 uint32 c_micro_version; 

39 uint32 model_len; 

40 char model[model_len]; 

41 } engenius_header_t; 

42""" 

43 

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

45XOR_KEY_LEN = len(XOR_KEY) 

46 

47 

48def decrypter(reference): 

49 def decrypt(value, offset): 

50 nonlocal reference 

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

52 

53 return decrypt 

54 

55 

56class EngeniusExtractor(Extractor): 

57 def __init__(self): 

58 self._struct_parser = StructParser(C_DEFINITIONS) 

59 

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

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

62 

63 with File.from_path(inpath) as f: 

64 engenius_header = self._struct_parser.parse( 

65 "engenius_header_t", f, Endian.BIG 

66 ) 

67 logger.debug( 

68 "engenius_header_t", 

69 engenius_header=engenius_header, 

70 size=len(engenius_header), 

71 _verbosity=3, 

72 ) 

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

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

75 decrypted = bytearray() 

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

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

78 outfile.write(decrypted) 

79 

80 

81class EngeniusHandler(StructHandler): 

82 NAME = "engenius" 

83 

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

85 

86 C_DEFINITIONS = C_DEFINITIONS 

87 HEADER_STRUCT = "engenius_header_t" 

88 EXTRACTOR = EngeniusExtractor() 

89 PATTERN_MATCH_OFFSET = -0x5C 

90 

91 DOC = HandlerDoc( 

92 name="Engenius", 

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

94 handler_type=HandlerType.ARCHIVE, 

95 vendor="Engenius", 

96 references=[ 

97 Reference( 

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

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

100 ) 

101 ], 

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

103 ) 

104 

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

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

107 return False 

108 try: 

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

110 except UnicodeDecodeError: 

111 return False 

112 return True 

113 

114 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]: 

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

116 

117 if not self.is_valid_header(header): 

118 raise InvalidInputFormat("Invalid Engenius header.") 

119 

120 return ValidChunk( 

121 start_offset=start_offset, 

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

123 )