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