Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/filesystem/android/sparse.py: 66%

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

29 statements  

1import io 

2 

3from structlog import get_logger 

4 

5from unblob.extractors.command import Command 

6 

7from ....file_utils import Endian 

8from ....models import ( 

9 File, 

10 HandlerDoc, 

11 HandlerType, 

12 Reference, 

13 Regex, 

14 StructHandler, 

15 ValidChunk, 

16) 

17 

18logger = get_logger() 

19 

20CHUNK_TYPE_RAW = 0xCAC1 

21CHUNK_TYPE_FILL = 0xCAC2 

22CHUNK_TYPE_DONT_CARE = 0xCAC3 

23CHUNK_TYPE_CRC32 = 0xCAC4 

24 

25VALID_CHUNK_TYPES = [ 

26 CHUNK_TYPE_RAW, 

27 CHUNK_TYPE_FILL, 

28 CHUNK_TYPE_DONT_CARE, 

29 CHUNK_TYPE_CRC32, 

30] 

31 

32 

33class SparseHandler(StructHandler): 

34 NAME = "sparse" 

35 

36 # magic (0xed26ff3a) 

37 # major version (0x1) 

38 # minor version (any) 

39 # file header size (0x1C in v1.0) 

40 # chunk header size (0XC in v1.0) 

41 PATTERNS = [Regex(r"\x3A\xFF\x26\xED\x01\x00[\x00-\xFF]{2}\x1C\x00\x0C\x00")] 

42 

43 C_DEFINITIONS = r""" 

44 typedef struct sparse_header { 

45 uint32 magic; /* 0xed26ff3a */ 

46 uint16 major_version; /* (0x1) - reject images with higher major versions */ 

47 uint16 minor_version; /* (0x0) - allow images with higer minor versions */ 

48 uint16 file_hdr_sz; /* 28 bytes for first revision of the file format */ 

49 uint16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */ 

50 uint32 blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */ 

51 uint32 total_blks; /* total blocks in the non-sparse output image */ 

52 uint32 total_chunks; /* total chunks in the sparse input image */ 

53 uint32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" */ 

54 /* as 0. Standard 802.3 polynomial, use a Public Domain */ 

55 /* table implementation */ 

56 } sparse_header_t; 

57 

58 typedef struct chunk_header { 

59 uint16 chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ 

60 uint16 reserved1; 

61 uint32 chunk_sz; /* in blocks in output image */ 

62 uint32 total_sz; /* in bytes of chunk input file including chunk header and data */ 

63 } chunk_header_t; 

64 """ 

65 HEADER_STRUCT = "sparse_header_t" 

66 

67 EXTRACTOR = Command("simg2img", "{inpath}", "{outdir}/raw.image") 

68 

69 DOC = HandlerDoc( 

70 name="Android Sparse", 

71 description="Android sparse images are a file format used to efficiently store disk images by representing sequences of zero blocks compactly. The format includes a file header, followed by chunk headers and data, with support for raw, fill, and 'don't care' chunks.", 

72 handler_type=HandlerType.FILESYSTEM, 

73 vendor="Google", 

74 references=[ 

75 Reference( 

76 title="Android Sparse Image Format Documentation", 

77 url="https://formats.kaitai.io/android_sparse/", 

78 ), 

79 Reference( 

80 title="simg2img Tool", 

81 url="https://github.com/anestisb/android-simg2img", 

82 ), 

83 ], 

84 limitations=[], 

85 ) 

86 

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

88 header = self.parse_header(file, Endian.LITTLE) 

89 

90 count = 0 

91 while count < header.total_chunks: 

92 chunk_header = self.cparser_le.chunk_header_t(file) 

93 if chunk_header.chunk_type not in VALID_CHUNK_TYPES: 

94 logger.warning("Invalid chunk type in Android sparse image. Aborting.") 

95 return None 

96 file.seek(chunk_header.total_sz - len(chunk_header), io.SEEK_CUR) 

97 count += 1 

98 

99 return ValidChunk( 

100 start_offset=start_offset, 

101 end_offset=file.tell(), 

102 )