Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/dlink/shrs.py: 62%

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

53 statements  

1import hashlib 

2import io 

3from pathlib import Path 

4from typing import Optional 

5 

6from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 

7from structlog import get_logger 

8 

9from unblob.file_utils import File, InvalidInputFormat 

10from unblob.models import ( 

11 Endian, 

12 Extractor, 

13 HandlerDoc, 

14 HandlerType, 

15 HexString, 

16 Reference, 

17 StructHandler, 

18 StructParser, 

19 ValidChunk, 

20) 

21 

22logger = get_logger() 

23 

24C_DEFINITIONS = r""" 

25 

26 typedef struct dlink_shrs_header { 

27 char magic[4]; /* SHRS */ 

28 uint32 file_size; /* Length of decrypted firmware in bytes */ 

29 uint32 file_size_no_padding; /* Length of decrypted firmware - padding */ 

30 char iv[16]; /* Length of AES 128 cbc IV */ 

31 char decrypted_key_digest[64]; /* SHA512 64 byte message digest of decrypted firmware + key */ 

32 char decrypted_digest[64]; /* SHA512 64 byte message digest of decrypted firmware */ 

33 char encrypted_digest[64]; /* SHA512 64 byte message digest of encrypted firmware */ 

34 char unused[512]; /* 512 unused NULL bytes (0xdc to 0x2dc) */ 

35 char signature_1[512]; /* 512 byte Signature 1 */ 

36 char signature_2[512]; /* 512 byte Signature 2 */ 

37 } dlink_header_t; 

38""" 

39 

40KEY = bytes.fromhex("c05fbf1936c99429ce2a0781f08d6ad8") 

41sha512 = hashlib.sha512() 

42 

43 

44class SHRSExtractor(Extractor): 

45 def __init__(self): 

46 self._struct_parser = StructParser(C_DEFINITIONS) 

47 

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

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

50 with File.from_path(inpath) as file: 

51 header = self._struct_parser.parse( 

52 "dlink_header_t", file, endian=Endian.BIG 

53 ) 

54 cipher = Cipher(algorithms.AES(KEY), modes.CBC(header.iv)) 

55 decryptor = cipher.decryptor() 

56 outfile = outpath.open("wb") 

57 ciphertext = file.read(1024) 

58 while ciphertext and len(ciphertext) % 16 == 0: 

59 plaintext = decryptor.update(ciphertext) 

60 outfile.write(plaintext) 

61 ciphertext = file.read(1024) 

62 outfile.write(decryptor.finalize()) 

63 outfile.close() 

64 

65 

66class SHRSHandler(StructHandler): 

67 NAME = "shrs" 

68 

69 PATTERNS = [HexString("53 48 52 53")] 

70 

71 C_DEFINITIONS = C_DEFINITIONS 

72 HEADER_STRUCT = "dlink_header_t" 

73 EXTRACTOR = SHRSExtractor() 

74 

75 DOC = HandlerDoc( 

76 name="D-Link SHRS", 

77 description="SHRS is a D-Link firmware format with a custom header containing metadata, SHA-512 digests, and AES-CBC encryption parameters. The firmware data is encrypted using a fixed key and IV stored in the header.", 

78 handler_type=HandlerType.ARCHIVE, 

79 vendor="D-Link", 

80 references=[ 

81 Reference( 

82 title="Breaking the D-Link DIR3060 Firmware Encryption - Recon - Part 1", 

83 url="https://0x00sec.org/t/breaking-the-d-link-dir3060-firmware-encryption-recon-part-1/21943", # Replace with actual reference if available 

84 ) 

85 ], 

86 limitations=[], 

87 ) 

88 

89 def is_valid_header(self, header, file: File) -> bool: 

90 if header.file_size < len(header): 

91 return False 

92 # we're exactly past the header, we compute the digest 

93 digest = hashlib.sha512(file.read(header.file_size_no_padding)).digest() 

94 # we seek back to where we were 

95 file.seek(-header.file_size_no_padding, io.SEEK_CUR) 

96 return digest == header.encrypted_digest 

97 

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

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

100 

101 if not self.is_valid_header(header, file): 

102 raise InvalidInputFormat("Invalid SHRS header.") 

103 

104 cipher = Cipher(algorithms.AES(KEY), modes.CBC(header.iv)) 

105 decryptor = cipher.decryptor() 

106 read_bytes = 0 

107 

108 while read_bytes < header.file_size: 

109 read_bytes += len(decryptor.update(file.read(1024))) 

110 end_offset = file.tell() 

111 

112 return ValidChunk(start_offset=start_offset, end_offset=end_offset)