Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ For compression formats, metadata cannot be preserved, as this information in mo
| ---------------------- | ---------------------------------- | ----------------------------------------------- | ----------------------------------------------- |
| Android sparse image | ❌ | [filesystem/android/sparse.py][android-handler] | [`simg2img`][android-extractor] |
| CRAMFS | ✅ | [filesystem/cramfs.py][cramfs-handler] | [`7z`][cramfs-extractor] |
| EROFS | ✅ | [filesystem/android/erofs.py][erofs-handler] | [`fsck.erfos`][erofs-extractor] |
| ExtFS | ✅ | [filesystem/extfs.py][extfs-handler] | [`debugfs`][extfs-extractor] |
| FAT | ✅ | [filesystem/fat.py][fat-handler] | [`7z`][fat-extractor] |
| ISO9660 | ✅ | [filesystem/iso9660.py][iso9660-handler] | [`7z`][iso9660-extractor] |
Expand All @@ -107,12 +108,14 @@ For compression formats, metadata cannot be preserved, as this information in mo
| SquashFS v4 Big Endian | ✅ | [filesystem/squashfs.py][squashfs-handler] | [`sasquatch-v4-be`][squashfs-v4-be-extractor] |
| UBI | ✅ | [filesystem/ubi.py][ubi-handler] | [`ubireader_extract_images`][ubi-extractor] |
| UBIFS | ✅ | [filesystem/ubi.py][ubi-handler] | [`ubireader_extract_files`][ubifs-extractor] |
| YAFFS (1, 2) | ✅ | [filesystem/yaffs.py][yaffs-handler] | [`YAFFSExtractor` custom code][yaffs-extractor] |
| YAFFS (1, 2) | ✅ | [filesystem/yaffs.py][yaffs-handler] | [`YAFFSExtractor` custom code][yaffs-extractor] |

[android-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/android/sparse.py
[android-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/android/sparse.py#L61
[cramfs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/cramfs.py
[cramfs-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/cramfs.py#L45
[erofs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/android/erfos.py
[erofs-extractor]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/android/erfos.py#L45
[extfs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/extfs.py
[extfs-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/extfs.py#L68
[fat-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/fat.py
Expand Down
1 change: 1 addition & 0 deletions install-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ apt-get update
apt-get install --no-install-recommends -y \
android-sdk-libsparse-utils \
curl \
erofs-utils \
lz4 \
lziprecover \
lzop \
Expand Down
39 changes: 1 addition & 38 deletions overlay.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,5 @@ final: prev:
nativeCheckInputs = (super.nativeCheckInputs or [ ]) ++ [ final.which ];
});

unblob =
let
pyproject_toml = (builtins.fromTOML (builtins.readFile ./pyproject.toml));
version = pyproject_toml.project.version;
in
(prev.unblob.override { e2fsprogs = final.e2fsprogs-nofortify; }).overridePythonAttrs (super: rec {
inherit version;

src = final.nix-filter {
root = ./.;
include = [
"Cargo.lock"
"Cargo.toml"
"pyproject.toml"
"python"
"rust"
"tests"
"README.md"
];
};

dependencies = (super.dependencies or [ ]) ++ [ final.python3.pkgs.pyzstd ];

# remove this when packaging changes are upstreamed
cargoDeps = final.rustPlatform.importCargoLock {
lockFile = ./Cargo.lock;
};

nativeBuildInputs = with final.rustPlatform; [
cargoSetupHook
maturinBuildHook
];

# override disabling of 'test_all_handlers[filesystem.extfs]' from upstream
pytestFlagsArray = [
"--no-cov"
];
});
unblob = final.callPackage ./package.nix { };
}
131 changes: 131 additions & 0 deletions package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
lib,
python3,
fetchFromGitHub,
makeWrapper,
e2fsprogs-nofortify,
erofs-utils,
jefferson,
lz4,
lziprecover,
lzop,
p7zip,
nix-filter,
sasquatch,
sasquatch-v4be,
simg2img,
ubi_reader,
unar,
zstd,
versionCheckHook,
rustPlatform,
}:

let
# These dependencies are only added to PATH
runtimeDeps = [
e2fsprogs-nofortify
erofs-utils
jefferson
lziprecover
lzop
p7zip
sasquatch
sasquatch-v4be
ubi_reader
simg2img
unar
zstd
lz4
];
pyproject_toml = (builtins.fromTOML (builtins.readFile ./pyproject.toml));
version = pyproject_toml.project.version;
in
python3.pkgs.buildPythonApplication rec {
pname = "unblob";
pyproject = true;
disabled = python3.pkgs.pythonOlder "3.9";
inherit version;
src = nix-filter {
root = ./.;
include = [
"Cargo.lock"
"Cargo.toml"
"pyproject.toml"
"python"
"rust"
"tests"
"README.md"
];
};

strictDeps = true;

build-system = with python3.pkgs; [ poetry-core ];

dependencies = with python3.pkgs; [
arpy
attrs
click
cryptography
dissect-cstruct
lark
lief.py
python3.pkgs.lz4 # shadowed by pkgs.lz4
plotext
pluggy
pyfatfs
pyperscan
python-magic
pyzstd
rarfile
rich
structlog
treelib
unblob-native
];

cargoDeps = rustPlatform.importCargoLock {
lockFile = ./Cargo.lock;
};

nativeBuildInputs = with rustPlatform; [
cargoSetupHook
maturinBuildHook
makeWrapper
];

# These are runtime-only CLI dependencies, which are used through
# their CLI interface
pythonRemoveDeps = [
"jefferson"
"ubi-reader"
];

pythonImportsCheck = [ "unblob" ];

makeWrapperArgs = [
"--prefix PATH : ${lib.makeBinPath runtimeDeps}"
];

nativeCheckInputs =
with python3.pkgs;
[
pytestCheckHook
pytest-cov
versionCheckHook
]
++ runtimeDeps;

versionCheckProgramArg = "--version";

pytestFlagsArray = [
"--no-cov"
];

passthru = {
# helpful to easily add these to a nix-shell environment
inherit runtimeDeps;
};

}
3 changes: 2 additions & 1 deletion python/unblob/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
ubi,
yaffs,
)
from .filesystem.android import sparse
from .filesystem.android import erofs, sparse

BUILTIN_HANDLERS: Handlers = (
cramfs.CramFSHandler,
Expand Down Expand Up @@ -118,6 +118,7 @@
engenius.EngeniusHandler,
ecc.AutelECCHandler,
uzip.UZIPHandler,
erofs.EROFSHandler,
)

BUILTIN_DIR_HANDLERS: DirectoryHandlers = (
Expand Down
76 changes: 76 additions & 0 deletions python/unblob/handlers/filesystem/android/erofs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import io
from typing import Optional

from unblob.extractors import Command
from unblob.file_utils import (
Endian,
InvalidInputFormat,
snull,
)
from unblob.models import (
File,
HexString,
StructHandler,
ValidChunk,
)

C_DEFINITIONS = r"""
typedef struct erofs_handler{
uint32_t magic;
uint32_t crc32c;
uint32_t feature_compact;
uint8_t block_size_bs;
uint8_t sb_extslots;
uint16_t root_nid;
uint64_t inos;
uint64_t build_time;
uint32_t build_time_nsec;
uint32_t block_count;
uint32_t meta_blkaddr;
uint32_t xattr_blkaddr;
uint8_t uuid[16];
char volume_name[16];
uint32_t feature_incompact;
char reserved[44];
} erofs_handler_t;
"""

SUPERBLOCK_OFFSET = 0x400


class EROFSHandler(StructHandler):
NAME = "erofs"
PATTERNS = [HexString("e2 e1 f5 e0")] # Magic in little endian
HEADER_STRUCT = "erofs_handler_t"
C_DEFINITIONS = C_DEFINITIONS
EXTRACTOR = Command(
"fsck.erofs",
"--no-preserve",
"--extract={outdir}",
"{inpath}",
)
PATTERN_MATCH_OFFSET = -SUPERBLOCK_OFFSET

def is_valid_header(self, header) -> bool:
try:
snull(header.volume_name).decode("utf-8")
except UnicodeDecodeError:
return False
return (
header.block_count >= 1
and header.build_time > 0
and header.build_time_nsec > 0
and header.block_size_bs >= 9
)

def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
file.seek(start_offset + SUPERBLOCK_OFFSET, io.SEEK_SET)
header = self.parse_header(file, Endian.LITTLE)
if not self.is_valid_header(header):
raise InvalidInputFormat("Invalid erofs header.")

end_offset = (1 << header.block_size_bs) * header.block_count
return ValidChunk(
start_offset=start_offset,
end_offset=end_offset,
)
2 changes: 1 addition & 1 deletion shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let
./flake.lock
./flake.nix
./overlay.nix
./nix
./package.nix
];

lock = builtins.fromJSON (builtins.readFile ./flake.lock);
Expand Down
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Binary file not shown.
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Binary file not shown.
Git LFS file not shown
Loading