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