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 )