1import binascii
2import io
3import struct
4
5from unblob.extractors import Command
6
7from ...file_utils import get_endian, iterate_file
8from ...models import (
9 File,
10 HandlerDoc,
11 HandlerType,
12 HexString,
13 Reference,
14 StructHandler,
15 ValidChunk,
16)
17
18CRAMFS_FLAG_FSID_VERSION_2 = 0x00000001
19BIG_ENDIAN_MAGIC = 0x28_CD_3D_45
20FSID_CRC_OFFSET = 32
21FSID_CRC_SIZE = 4
22
23
24def swap_int32(i):
25 return struct.unpack("<I", struct.pack(">I", i))[0]
26
27
28class CramFSHandler(StructHandler):
29 NAME = "cramfs"
30
31 PATTERNS = [
32 HexString("28 CD 3D 45"), # big endian
33 HexString("45 3D CD 28"), # little endian
34 ]
35
36 C_DEFINITIONS = r"""
37 typedef struct cramfs_header {
38 uint32 magic;
39 uint32 fs_size;
40 uint32 flags;
41 uint32 future;
42 char signature[16];
43 uint32 fsid_crc;
44 uint32 fsid_edition;
45 uint32 fsid_blocks;
46 uint32 fsid_files;
47 char name[16];
48 } cramfs_header_t;
49 """
50 HEADER_STRUCT = "cramfs_header_t"
51
52 EXTRACTOR = Command("7z", "x", "-y", "{inpath}", "-o{outdir}")
53
54 DOC = HandlerDoc(
55 name="CramFS",
56 description="CramFS is a lightweight, read-only file system format designed for simplicity and efficiency in embedded systems. It uses zlib compression for file data and stores metadata in a compact, contiguous structure.",
57 handler_type=HandlerType.FILESYSTEM,
58 vendor=None,
59 references=[
60 Reference(
61 title="CramFS Documentation",
62 url="https://web.archive.org/web/20160304053532/http://sourceforge.net/projects/cramfs/",
63 ),
64 Reference(
65 title="CramFS Wikipedia",
66 url="https://en.wikipedia.org/wiki/Cramfs",
67 ),
68 ],
69 limitations=[],
70 )
71
72 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
73 endian = get_endian(file, BIG_ENDIAN_MAGIC)
74 header = self.parse_header(file, endian)
75 valid_signature = header.signature == b"Compressed ROMFS"
76
77 if valid_signature and self._is_crc_valid(file, start_offset, header):
78 return ValidChunk(
79 start_offset=start_offset,
80 end_offset=start_offset + header.fs_size,
81 )
82 return None
83
84 def _is_crc_valid(
85 self,
86 file: File,
87 start_offset: int,
88 header,
89 ) -> bool:
90 # old cramfs format do not support crc
91 if not (header.flags & CRAMFS_FLAG_FSID_VERSION_2):
92 return True
93
94 file.seek(start_offset, io.SEEK_SET)
95 header_bytes = bytearray(file.read(FSID_CRC_OFFSET + FSID_CRC_SIZE))
96 header_bytes[FSID_CRC_OFFSET : FSID_CRC_OFFSET + FSID_CRC_SIZE] = (
97 b"\x00\x00\x00\x00"
98 )
99 computed_crc = binascii.crc32(header_bytes)
100
101 for chunk in iterate_file(
102 file,
103 start_offset + FSID_CRC_OFFSET + FSID_CRC_SIZE,
104 header.fs_size - (FSID_CRC_OFFSET + FSID_CRC_SIZE),
105 ):
106 computed_crc = binascii.crc32(chunk, computed_crc)
107
108 # some vendors like their CRC's swapped, don't ask why
109 return header.fsid_crc == computed_crc or header.fsid_crc == swap_int32(
110 computed_crc
111 )