Skip to content

Commit 4333278

Browse files
rxpha3lqkaiser
authored andcommitted
feat(handler): add partclone handler
Partclone is a utility used for backing up and restoring partitions. Many cloning tools (such as Clonezilla) rely on it to create block-level images that include filesystem metadata. Right now only partclone version 2 is supported. end offset is computed from data available in partclone's super block. Extraction is performed by `partclone.restore`, which is part of `partclone` package on Debian based systems and Nix.
1 parent 0435446 commit 4333278

File tree

11 files changed

+101
-0
lines changed

11 files changed

+101
-0
lines changed

Diff for: docs/formats.md

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ unblob supports more than 30 formats. You can see their code in
2222
| CAB |||| [archive/cab.py][cab-handler] | [`7z`][cab-extractor] |
2323
| CPIO |||| [archive/cpio.py][cpio-handler] | unblob extractor |
2424
| DMG |||| [archive/dmg.py][dmg-handler] | [`7z`][dmg-extractor] |
25+
| PARTCLONE |||| [archive/partclone.py][partclone-hanlder] | [`partclone`][partclone-extractor] |
2526
| RAR |||| [archive/rar.py][rar-handler] | [`unar`][rar-extractor] |
2627
| 7ZIP |||| [archive/sevenzip.py][7zip-handler] | [`7z`][7zip-extractor] |
2728
| StuffIt |||| [archive/stuffit.py][stuffit-handler] | [`unar`][stuffit-extractor] |
@@ -39,6 +40,8 @@ unblob supports more than 30 formats. You can see their code in
3940
[cpio-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/archive/cpio.py
4041
[dmg-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/archive/dmg.py
4142
[dmg-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/archive/dmg.py#L67-L69
43+
[partclone-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/archive/partclone.py
44+
[partclone-extractor]: https://github.com/onekey-sec/unblob/blob/b21b6dc291583af6b7ec9b7c3d63ee8302328841/python/unblob/handlers/archive/partclone.py#L44
4245
[rar-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/archive/rar.py
4346
[rar-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/archive/rar.py#L32
4447
[7zip-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/archive/sevenzip.py

Diff for: install-deps.sh

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ apt-get install --no-install-recommends -y \
99
lziprecover \
1010
lzop \
1111
p7zip-full \
12+
partclone \
1213
unar \
1314
xz-utils \
1415
libmagic1 \

Diff for: package.nix

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
lziprecover,
1010
lzop,
1111
p7zip,
12+
partclone,
1213
nix-filter,
1314
sasquatch,
1415
sasquatch-v4be,
@@ -28,6 +29,7 @@ let
2829
lziprecover
2930
lzop
3031
p7zip
32+
partclone
3133
sasquatch
3234
sasquatch-v4be
3335
ubi_reader

Diff for: python/unblob/handlers/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
cab,
77
cpio,
88
dmg,
9+
partclone,
910
rar,
1011
sevenzip,
1112
stuffit,
@@ -116,6 +117,7 @@
116117
zlib.ZlibHandler,
117118
engenius.EngeniusHandler,
118119
ecc.AutelECCHandler,
120+
partclone.PartcloneHandler,
119121
)
120122

121123
BUILTIN_DIR_HANDLERS: DirectoryHandlers = (

Diff for: python/unblob/handlers/archive/partclone.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import binascii
2+
import io
3+
from math import ceil
4+
from typing import Optional
5+
6+
from unblob.extractors import Command
7+
from unblob.file_utils import File, InvalidInputFormat, get_endian
8+
from unblob.models import Regex, StructHandler, ValidChunk
9+
10+
C_DEFINITIONS = r"""
11+
typedef struct partclone_header{
12+
char magic[16];
13+
char partclone_version[14];
14+
char image_version_txt[4];
15+
char endian[2];
16+
char fs_type[16];
17+
uint64 fs_size;
18+
uint64 fs_total_block_count;
19+
uint64 fs_used_block_count_superblock;
20+
uint64 fs_used_block_count_bitmap;
21+
uint32 fs_block_size;
22+
uint32 feature_size;
23+
uint16 image_version;
24+
uint16 number_of_bits_for_CPU;
25+
uint16 checksum_mode;
26+
uint16 checksum_size;
27+
uint32 blocks_per_checksum;
28+
uint8 reseed_checksum;
29+
uint8 bitmap_mode;
30+
uint32 crc32;
31+
} partclone_header_t;
32+
"""
33+
34+
HEADER_STRUCT = "partclone_header_t"
35+
BIG_ENDIAN_MAGIC = 0xC0DE
36+
ENDIAN_OFFSET = 34
37+
38+
39+
class PartcloneHandler(StructHandler):
40+
NAME = "partclone"
41+
PATTERNS = [Regex(r"partclone-image\x00\d+\.\d+\.\d+.*?0002(\xde\xc0|\xc0\xde)")]
42+
HEADER_STRUCT = HEADER_STRUCT
43+
C_DEFINITIONS = C_DEFINITIONS
44+
EXTRACTOR = Command(
45+
"partclone.restore",
46+
"-W",
47+
"-s",
48+
"{inpath}",
49+
"-o",
50+
"{outdir}/partclone.restored",
51+
"-L",
52+
"/dev/stdout",
53+
)
54+
55+
def is_valid_header(self, header) -> bool:
56+
calculated_crc = binascii.crc32(header.dumps()[0:-4])
57+
return (
58+
header.crc32 ^ 0xFFFFFFFF
59+
) == calculated_crc # partclone does not final XOR
60+
61+
def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
62+
file.seek(start_offset + ENDIAN_OFFSET, io.SEEK_SET) # go to endian
63+
endian = get_endian(file, BIG_ENDIAN_MAGIC, endian_len=2)
64+
file.seek(start_offset, io.SEEK_SET) # go to beginning of file
65+
header = self.parse_header(file, endian)
66+
67+
if not self.is_valid_header(header):
68+
raise InvalidInputFormat("Invalid partclone header.")
69+
70+
end_offset = start_offset + len(header) # header
71+
end_offset += header.checksum_size # checksum size
72+
end_offset += ceil(header.fs_total_block_count / 8) # bitmap, as bytes
73+
74+
if header.checksum_mode != 0:
75+
checksum_blocks = ceil(
76+
header.fs_used_block_count_bitmap / header.blocks_per_checksum
77+
)
78+
end_offset += checksum_blocks * header.checksum_size
79+
80+
end_offset += header.fs_used_block_count_bitmap * header.fs_block_size # Data
81+
return ValidChunk(start_offset=start_offset, end_offset=end_offset)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:0e63b5b8ec0ab6dfc4a4254d72e26b8c1b7ee8b6ceb61fe67bea1105b0d60156
3+
size 69930
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e8fb4fbc359454b017521504eddf0e2955c5808280337b73ad9f897a5f501285
3+
size 40123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:6be413ccd078c706d4f7dd64d4e29fe917fd188f22202becf906b0b79aa9d645
3+
size 1474560

Diff for: tests/integration/archive/partclone/__output__/floppy-144m.img_extract/partclone.restored_extract/lost+found/.gitkeep

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:557ad6d9db9ea8ed1f749d8da063d661c78951e318f3d5f23e517b8b93a205d6
3+
size 565248

Diff for: tests/integration/archive/partclone/__output__/fs_dev0.partclone.img_extract/partclone.restored_extract/lost+found/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)