1import io
2from typing import Optional
3
4from structlog import get_logger
5
6from unblob.extractors.command import Command
7
8from ....file_utils import Endian
9from ....models import (
10 File,
11 HandlerDoc,
12 HandlerType,
13 Reference,
14 Regex,
15 StructHandler,
16 ValidChunk,
17)
18
19logger = get_logger()
20
21CHUNK_TYPE_RAW = 0xCAC1
22CHUNK_TYPE_FILL = 0xCAC2
23CHUNK_TYPE_DONT_CARE = 0xCAC3
24CHUNK_TYPE_CRC32 = 0xCAC4
25
26VALID_CHUNK_TYPES = [
27 CHUNK_TYPE_RAW,
28 CHUNK_TYPE_FILL,
29 CHUNK_TYPE_DONT_CARE,
30 CHUNK_TYPE_CRC32,
31]
32
33
34class SparseHandler(StructHandler):
35 NAME = "sparse"
36
37 # magic (0xed26ff3a)
38 # major version (0x1)
39 # minor version (any)
40 # file header size (0x1C in v1.0)
41 # chunk header size (0XC in v1.0)
42 PATTERNS = [Regex(r"\x3A\xFF\x26\xED\x01\x00[\x00-\xFF]{2}\x1C\x00\x0C\x00")]
43
44 C_DEFINITIONS = r"""
45 typedef struct sparse_header {
46 uint32 magic; /* 0xed26ff3a */
47 uint16 major_version; /* (0x1) - reject images with higher major versions */
48 uint16 minor_version; /* (0x0) - allow images with higer minor versions */
49 uint16 file_hdr_sz; /* 28 bytes for first revision of the file format */
50 uint16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */
51 uint32 blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */
52 uint32 total_blks; /* total blocks in the non-sparse output image */
53 uint32 total_chunks; /* total chunks in the sparse input image */
54 uint32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" */
55 /* as 0. Standard 802.3 polynomial, use a Public Domain */
56 /* table implementation */
57 } sparse_header_t;
58
59 typedef struct chunk_header {
60 uint16 chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */
61 uint16 reserved1;
62 uint32 chunk_sz; /* in blocks in output image */
63 uint32 total_sz; /* in bytes of chunk input file including chunk header and data */
64 } chunk_header_t;
65 """
66 HEADER_STRUCT = "sparse_header_t"
67
68 EXTRACTOR = Command("simg2img", "{inpath}", "{outdir}/raw.image")
69
70 DOC = HandlerDoc(
71 name="Android Sparse",
72 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.",
73 handler_type=HandlerType.FILESYSTEM,
74 vendor="Google",
75 references=[
76 Reference(
77 title="Android Sparse Image Format Documentation",
78 url="https://formats.kaitai.io/android_sparse/",
79 ),
80 Reference(
81 title="simg2img Tool",
82 url="https://github.com/anestisb/android-simg2img",
83 ),
84 ],
85 limitations=[],
86 )
87
88 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
89 header = self.parse_header(file, Endian.LITTLE)
90
91 count = 0
92 while count < header.total_chunks:
93 chunk_header = self.cparser_le.chunk_header_t(file)
94 if chunk_header.chunk_type not in VALID_CHUNK_TYPES:
95 logger.warning("Invalid chunk type in Android sparse image. Aborting.")
96 return None
97 file.seek(chunk_header.total_sz - len(chunk_header), io.SEEK_CUR)
98 count += 1
99
100 return ValidChunk(
101 start_offset=start_offset,
102 end_offset=file.tell(),
103 )