UPLOAD
This commit is contained in:
parent
e01a8e65f2
commit
f57adf9c1b
@ -1,3 +1,3 @@
|
||||
# PlayReady
|
||||
|
||||
PlayReady stuffs
|
||||
PlayReady stuffs, forks and collections from PlayReady Discord server
|
||||
|
||||
BIN
decbin-pr.zip
Normal file
BIN
decbin-pr.zip
Normal file
Binary file not shown.
464
decrypt_bgc_zgp_pak.py
Normal file
464
decrypt_bgc_zgp_pak.py
Normal file
@ -0,0 +1,464 @@
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from typing import Union, Optional
|
||||
from pathlib import Path
|
||||
from zlib import crc32
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import CMAC
|
||||
|
||||
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
|
||||
|
||||
|
||||
class Pak:
|
||||
def __init__(self, bgroupcert_sizes: dict, zgpriv_sizes: dict):
|
||||
self.mstar_key_bank_magic_id = b"Mstar.Key.Bank"
|
||||
self.secure_store_file_magic_id = b"MSTAR_SECURE_STORE_FILE_MAGIC_ID"
|
||||
self.bgroupcert_sizes = bgroupcert_sizes
|
||||
self.zgpriv_sizes = zgpriv_sizes
|
||||
|
||||
def process_files(self, boot_file: Path, sedata_file: Path, playready_keys: dict) -> None:
|
||||
self.validate_files(boot_file, sedata_file)
|
||||
boot_key = self.get_boot_key(input_file=boot_file).hex()
|
||||
|
||||
boot_keys = {
|
||||
"brand_unknown": {
|
||||
"MODEL_UNKNOWN": boot_key
|
||||
}
|
||||
}
|
||||
|
||||
decryption_bgc_zgp = DecryptionBgcZgp(
|
||||
boot_keys=boot_keys,
|
||||
playready_keys=playready_keys
|
||||
)
|
||||
|
||||
chunks = self.get_chunks_from_sedata(input_file=sedata_file)
|
||||
|
||||
for bgp_security_level, bgp_size in self.bgroupcert_sizes.items():
|
||||
print(f"\nTrying get BGCert - Security Level: {bgp_security_level}")
|
||||
for chunk in chunks:
|
||||
data = self.prepare_data(chunk)
|
||||
|
||||
bgroupcert_data = self.decrypt_bgroupcert(decryption_bgc_zgp, data, bgp_size)
|
||||
if not bgroupcert_data:
|
||||
continue
|
||||
|
||||
print("Firmware:")
|
||||
print(f" - Key : {decryption_bgc_zgp.boot_key.hex()}")
|
||||
print(f" - Brand: {decryption_bgc_zgp.brand}")
|
||||
|
||||
for chunk in chunks:
|
||||
data = self.prepare_data(chunk)
|
||||
|
||||
zgpriv_data = self.decrypt_zgpriv(decryption_bgc_zgp, data, self.zgpriv_sizes[bgp_security_level])
|
||||
if not zgpriv_data:
|
||||
continue
|
||||
|
||||
code = self.process_decrypted_data(decryption_bgc_zgp, bgroupcert_data, zgpriv_data)
|
||||
if code == 0:
|
||||
break
|
||||
|
||||
def get_boot_key(self, input_file: Path, offset: int = 16) -> bytes:
|
||||
data = DecryptionBgcZgp.get_file_data(input_file)
|
||||
|
||||
index = data.find(self.mstar_key_bank_magic_id)
|
||||
if index == -1 or index < offset:
|
||||
raise ValueError("Error: Boot key not found or invalid position")
|
||||
|
||||
return data[index - offset:index]
|
||||
|
||||
def get_chunks_from_sedata(self, input_file: Path, min_size: int = 0, max_size: int = 2000) -> list[dict]:
|
||||
data = DecryptionBgcZgp.get_file_data(input_file)
|
||||
|
||||
chunks = []
|
||||
start_pos = 0
|
||||
|
||||
while True:
|
||||
pos = data.find(self.secure_store_file_magic_id, start_pos)
|
||||
if pos == -1:
|
||||
break
|
||||
|
||||
header_start = max(0, pos - 32)
|
||||
|
||||
header = data[header_start:pos]
|
||||
|
||||
data_start = pos + len(self.secure_store_file_magic_id)
|
||||
|
||||
next_pos = data.find(self.secure_store_file_magic_id, data_start)
|
||||
if next_pos == -1:
|
||||
data_end = len(data)
|
||||
else:
|
||||
data_end = next_pos
|
||||
|
||||
extracted_data = data[data_start:data_end]
|
||||
total_size = len(header) + len(self.secure_store_file_magic_id) + len(extracted_data)
|
||||
|
||||
if min_size <= total_size <= max_size:
|
||||
chunks.append({
|
||||
"position": header_start if len(header) == 32 else pos,
|
||||
"header": header if len(header) == 32 else b"",
|
||||
"has_full_header": len(header) == 32,
|
||||
"data": extracted_data,
|
||||
"total_size": total_size
|
||||
})
|
||||
|
||||
start_pos = data_end
|
||||
|
||||
return chunks
|
||||
|
||||
def prepare_data(self, chunk: dict) -> bytes:
|
||||
magic_id = self.secure_store_file_magic_id
|
||||
header = chunk["header"] if chunk["has_full_header"] else b""
|
||||
return header + magic_id + chunk["data"]
|
||||
|
||||
def process_decrypted_data(
|
||||
self, decryption_bgc_zgp: "DecryptionBgcZgp", bgroupcert_data: bytes, zgpriv_data: bytes
|
||||
) -> int:
|
||||
bgroupcert_tmp_path = Path("bgroupcert.decrypt_bgc_zgp_pak.tmp")
|
||||
zgpriv_tmp_path = Path("zgpriv_temp.decrypt_bgc_zgp_pak.tmp")
|
||||
|
||||
code = 1
|
||||
|
||||
try:
|
||||
bgroupcert_tmp_path.write_bytes(bgroupcert_data)
|
||||
zgpriv_tmp_path.write_bytes(zgpriv_data)
|
||||
|
||||
command = [
|
||||
"pyplayready",
|
||||
"create-device",
|
||||
"-c", str(bgroupcert_tmp_path),
|
||||
"-k", str(zgpriv_tmp_path)
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
prd_path = Path(result.stderr.split("Saved to: ")[-1].strip())
|
||||
folder_path = Path("playready_decryption_bgc_zgp_pak") / prd_path.stem
|
||||
|
||||
if folder_path.exists() and any(item.is_file() for item in folder_path.iterdir()):
|
||||
raise ValueError(f"Error: This certificate already exists in {folder_path}")
|
||||
|
||||
folder_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
bgroupcert_out_path = folder_path / "bgroupcert.dat"
|
||||
bgroupcert_out_path.write_bytes(bgroupcert_data)
|
||||
|
||||
zgpriv_out_path = folder_path / "zgpriv.dat"
|
||||
zgpriv_out_path.write_bytes(zgpriv_data)
|
||||
|
||||
prd_path.rename(folder_path / prd_path.name)
|
||||
|
||||
security_level = decryption_bgc_zgp.get_security_level(bgroupcert_data)
|
||||
|
||||
print("Decryption:")
|
||||
print(f" - BGroupCert Security level: {security_level}")
|
||||
print(f" - BGroupCert Path : {bgroupcert_out_path}")
|
||||
print(f" - ZGPriv Path : {zgpriv_out_path}")
|
||||
print(f" - PRD Path : {folder_path / prd_path.name}")
|
||||
print(f" - Files exported to : {folder_path.parent}")
|
||||
code = result.returncode
|
||||
finally:
|
||||
bgroupcert_tmp_path.unlink(missing_ok=True)
|
||||
zgpriv_tmp_path.unlink(missing_ok=True)
|
||||
return code
|
||||
|
||||
@staticmethod
|
||||
def validate_files(boot_file: Path, sedata_file: Path) -> None:
|
||||
if not boot_file.exists():
|
||||
raise ValueError(f"Error: Boot File not found -> {boot_file}")
|
||||
if not sedata_file.exists():
|
||||
raise ValueError(f"Error: SeData File not found -> {sedata_file}")
|
||||
|
||||
@staticmethod
|
||||
def decrypt_bgroupcert(decryption_bgc_zgp: "DecryptionBgcZgp", data: bytes, size: int):
|
||||
try:
|
||||
decrypted_bgroupcert_data = decryption_bgc_zgp.decrypt_aes_ecb(
|
||||
inp=data,
|
||||
size=size,
|
||||
is_bgroupcert=True
|
||||
)
|
||||
return decryption_bgc_zgp.process_bgroupcert(data=decrypted_bgroupcert_data)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def decrypt_zgpriv(decryption_bgc_zgp: "DecryptionBgcZgp", data: bytes, size: int):
|
||||
try:
|
||||
decrypted_zgpriv_data = decryption_bgc_zgp.decrypt_aes_ecb(
|
||||
inp=data,
|
||||
size=size
|
||||
)
|
||||
return decryption_bgc_zgp.process_zgpriv(data=decrypted_zgpriv_data, is_sl3000=False)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class DecryptionBgcZgp:
|
||||
def __init__(self, boot_keys: dict, playready_keys: dict):
|
||||
self.boot_keys = boot_keys
|
||||
self.playready_keys = playready_keys
|
||||
self.boot_key: Optional[bytes] = None
|
||||
self.brand: Optional[str] = None
|
||||
self.bgroupcert_start_magic = b"CHAI"
|
||||
self.bgroupcert_end_magic = b"\x93\xfa\xc5\xab"
|
||||
|
||||
def decrypt_aes_ecb(self,inp: Union[Path, bytes],size: Optional[int] = None, is_bgroupcert: bool = False) -> bytes:
|
||||
if isinstance(inp, bytes):
|
||||
encrypted_data = inp
|
||||
else:
|
||||
encrypted_data = self.get_file_data(inp)
|
||||
|
||||
for brand, keys in self.boot_keys.items():
|
||||
for model, boot_key in keys.items():
|
||||
if self.boot_key:
|
||||
key = self.boot_key
|
||||
else:
|
||||
key = self.hex_to_bytes(boot_key)
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
|
||||
if size:
|
||||
data = cipher.decrypt(encrypted_data[:size])
|
||||
else:
|
||||
data = cipher.decrypt(encrypted_data)
|
||||
|
||||
if is_bgroupcert and not self.bgroupcert_start_magic in data and not self.bgroupcert_end_magic in data:
|
||||
continue
|
||||
|
||||
if is_bgroupcert:
|
||||
self.boot_key = key
|
||||
self.brand = brand
|
||||
|
||||
return data
|
||||
|
||||
raise ValueError(
|
||||
"Error: Failed to decrypt the bgroupcert. No valid AES key matched or the decrypted data is invalid."
|
||||
)
|
||||
|
||||
def decrypt_zgpriv(self, encrypted_data: bytes) -> bytes:
|
||||
transient_key = self.hex_to_bytes(self.playready_keys["porting_kit"]["transient"])
|
||||
cmac = CMAC.new(transient_key, ciphermod=AES)
|
||||
|
||||
intermediate_key = self.hex_to_bytes(self.playready_keys["porting_kit"]["intermediate"])
|
||||
intermediate_data = (
|
||||
b"\x01" +
|
||||
intermediate_key + # Intermediate key
|
||||
b"\x00" +
|
||||
b"\x00" * 16 + # Context
|
||||
b"\x00\x80"
|
||||
)
|
||||
cmac.update(intermediate_data)
|
||||
|
||||
wrapping_key = cmac.digest()
|
||||
|
||||
wrapped_key = encrypted_data[:48]
|
||||
unwrapped_key = aes_key_unwrap(wrapping_key, wrapped_key)
|
||||
|
||||
return unwrapped_key
|
||||
|
||||
def process_zgpriv(self, data: bytes, is_sl3000: bool) -> bytes:
|
||||
content = self.remove_header(data)
|
||||
|
||||
if is_sl3000:
|
||||
content = self.decrypt_zgpriv(encrypted_data=content)
|
||||
|
||||
if self.brand == "lg" and len(data) == 128:
|
||||
zgpriv_data = content[64:-32]
|
||||
else:
|
||||
zgpriv_data = content[:32]
|
||||
|
||||
return zgpriv_data
|
||||
|
||||
def process_bgroupcert(self, data: bytes) -> bytes:
|
||||
content = self.remove_header(data)
|
||||
|
||||
start_index = content.find(self.bgroupcert_start_magic)
|
||||
end_index = content.find(self.bgroupcert_end_magic)
|
||||
|
||||
if start_index == -1:
|
||||
raise ValueError("Error: BGCert start magic sequence not found.")
|
||||
|
||||
if end_index == -1:
|
||||
raise ValueError("Error: BGCert end magic sequence not found.")
|
||||
|
||||
if end_index < start_index:
|
||||
raise ValueError("Error: BGCert end magic sequence found before the start magic sequence.")
|
||||
|
||||
bgroupcert_data = content[start_index:end_index + len(self.bgroupcert_end_magic)]
|
||||
|
||||
return bgroupcert_data
|
||||
|
||||
@staticmethod
|
||||
def remove_header(data: bytes, header: Union[str, bytes] = "INNER_MSTAR_FILE") -> bytes:
|
||||
if isinstance(header, str):
|
||||
header = header.encode()
|
||||
|
||||
header_index = data.find(header)
|
||||
|
||||
if header_index == -1:
|
||||
return data
|
||||
|
||||
content = data[header_index + len(header):]
|
||||
|
||||
return content
|
||||
|
||||
@staticmethod
|
||||
def hex_to_bytes(data: Union[str, bytes]) -> bytes:
|
||||
if isinstance(data, str):
|
||||
data = bytes.fromhex(data)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_file_data(input_file: Path) -> bytes:
|
||||
with open(input_file, "rb") as f:
|
||||
data = f.read()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_security_level(data: bytes) -> str:
|
||||
if b"SL3000" in data:
|
||||
security_level = "SL3000"
|
||||
elif b"SL2000" in data:
|
||||
security_level = "SL2000"
|
||||
elif b"SL150" in data:
|
||||
security_level = "SL150"
|
||||
else:
|
||||
security_level = "UNKNOWN"
|
||||
return security_level
|
||||
|
||||
def process_files(self, bgroupcert_file: Path, zgpriv_file: Path):
|
||||
if not bgroupcert_file.exists():
|
||||
raise ValueError(f"Error: BGCert File not found -> {bgroupcert_file}")
|
||||
|
||||
if not zgpriv_file.exists():
|
||||
raise ValueError(f"Error: ZGPriv File not found -> {zgpriv_file}")
|
||||
|
||||
decrypted_bgroupcert_data = self.decrypt_aes_ecb(
|
||||
inp=bgroupcert_file,
|
||||
is_bgroupcert=True
|
||||
)
|
||||
bgroupcert_data = self.process_bgroupcert(
|
||||
data=decrypted_bgroupcert_data
|
||||
)
|
||||
|
||||
print("Firmware:")
|
||||
print(f" - Key : {decryption_bgc_zgp.boot_key.hex()}")
|
||||
print(f" - Brand: {decryption_bgc_zgp.brand}")
|
||||
|
||||
security_level = self.get_security_level(bgroupcert_data)
|
||||
|
||||
decrypted_zgpriv_data = self.decrypt_aes_ecb(
|
||||
inp=zgpriv_file
|
||||
)
|
||||
zgpriv_data = self.process_zgpriv(
|
||||
data=decrypted_zgpriv_data,
|
||||
is_sl3000=security_level == "SL3000"
|
||||
)
|
||||
|
||||
bgc_zgp_id = hex(crc32(bgroupcert_data + zgpriv_data))[2:]
|
||||
folder_name = f"playready_decryption_bgc_zgp_pak/{self.brand}_{security_level}_{bgc_zgp_id}"
|
||||
folder_path = Path(folder_name)
|
||||
|
||||
if folder_path.exists():
|
||||
raise ValueError(f"Error: This certificate already exists in {folder_path}")
|
||||
|
||||
folder_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
bgroupcert_out_path = Path(folder_path / "bgroupcert.dat")
|
||||
bgroupcert_out_path.write_bytes(bgroupcert_data)
|
||||
|
||||
zgpriv_out_path = Path(folder_path / "zgpriv.dat")
|
||||
zgpriv_out_path.write_bytes(zgpriv_data)
|
||||
|
||||
command = [
|
||||
"pyplayready", "create-device",
|
||||
"-c", str(bgroupcert_out_path),
|
||||
"-k", str(zgpriv_out_path),
|
||||
"-o", folder_path
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
prd_path = None
|
||||
if result.returncode == 0:
|
||||
prd_path = folder_path / Path(result.stderr.split("Saved to: ")[-1].strip()).name
|
||||
|
||||
print("Decryption:")
|
||||
print(f" - BGroupCert Security level: {security_level}")
|
||||
print(f" - BGroupCert Path : {bgroupcert_out_path}")
|
||||
print(f" - ZGPriv Path : {zgpriv_out_path}")
|
||||
if prd_path:
|
||||
print(f" - PRD Path : {prd_path}")
|
||||
print(f" - Files exported to : {folder_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage:")
|
||||
print(" BGroupCert and ZGPriv")
|
||||
print(f" decrypt_bgc_zgp_pak.py <bgroupcert_file> <zgpriv_file>")
|
||||
print(" Pak")
|
||||
print(f" decrypt_bgc_zgp_pak.py <boot_file> <sedata_file>")
|
||||
sys.exit(1)
|
||||
|
||||
boot_keys = {
|
||||
"vestel": {
|
||||
"MB130": "8981D083B3D53B3DF1AC529A70F244C0",
|
||||
"MB_VARIANT_1": "24490B4CC95F739CE34138478E47139E"
|
||||
},
|
||||
"lg": {
|
||||
"HE_LCD_NC5U_AAADAIAA": "E33AB4C45C2570B8AD15A921F752DEB6",
|
||||
"HE_DTV_W21A_AFADATAA": "0007FF4154534D92FC55AA0FFF0110E0"
|
||||
}
|
||||
}
|
||||
|
||||
playready_keys = {
|
||||
"porting_kit": {
|
||||
"transient": "8B222FFD1E76195659CF2703898C427F",
|
||||
"intermediate": "9CE93432C7D74016BA684763F801E136",
|
||||
}
|
||||
}
|
||||
|
||||
# Pak / SeData
|
||||
bgroupcert_sizes = {
|
||||
"SL2000": 1760,
|
||||
"SL3000": 1440
|
||||
}
|
||||
zgpriv_sizes = {
|
||||
"SL2000": 176,
|
||||
"SL3000": 176
|
||||
}
|
||||
|
||||
print("Process started...")
|
||||
|
||||
if "boot" in sys.argv[1] and "sedata" in sys.argv[2]:
|
||||
pak = Pak(bgroupcert_sizes, zgpriv_sizes)
|
||||
|
||||
boot_file = Path(sys.argv[1])
|
||||
sedata_file = Path(sys.argv[2])
|
||||
|
||||
pak.process_files(boot_file, sedata_file, playready_keys)
|
||||
else:
|
||||
decryption_bgc_zgp = DecryptionBgcZgp(
|
||||
boot_keys=boot_keys,
|
||||
playready_keys=playready_keys
|
||||
)
|
||||
|
||||
bgroupcert_file = Path(sys.argv[1])
|
||||
zgpriv_file = Path(sys.argv[2])
|
||||
|
||||
decryption_bgc_zgp.process_files(bgroupcert_file, zgpriv_file)
|
||||
|
||||
print("\nProcess completed successfully.")
|
||||
|
||||
sys.exit(0)
|
||||
BIN
extract_emmc_bin_0.2.zip
Normal file
BIN
extract_emmc_bin_0.2.zip
Normal file
Binary file not shown.
21
ubidump/LICENSE
Normal file
21
ubidump/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Willem Hengeveld <itsme@xs4all.nl>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
129
ubidump/README.md
Normal file
129
ubidump/README.md
Normal file
@ -0,0 +1,129 @@
|
||||
UBIFS Dumper
|
||||
============
|
||||
|
||||
This tool can be used to view or extract the contents of UBIFS images.
|
||||
|
||||
About UBIFS
|
||||
===========
|
||||
|
||||
UBIFS is a filesystem specifically designed for used on NAND flash chips.
|
||||
NAND flash is organized in _eraseblocks_. _Eraseblocks_ can be erased,
|
||||
appended to, and read. Erasing is a relatively expensive operation, and can
|
||||
be done only a limited number of times.
|
||||
|
||||
An UBIFS image contains four abstraction layers:
|
||||
* eraseblocks
|
||||
* volumes
|
||||
* b-tree nodes
|
||||
* inodes
|
||||
|
||||
Each eraseblock contains info on how often it has been erased, and which volume it belongs to.
|
||||
A volume contains a b-tree database with keys for:
|
||||
* inodes, indexed by inode number
|
||||
* direntries, indexed by inode number + name hash
|
||||
* datablocks, indexed by inode number + block number
|
||||
|
||||
The inodes are basically a standard unix filesystem, with direntries, regular files, symlinks, devices, etc.
|
||||
|
||||
mounting images on linux
|
||||
------------------------
|
||||
|
||||
modprobe nandsim first_id_byte=0x2c second_id_byte=0xac third_id_byte=0x90 fourth_id_byte=0x26
|
||||
nandwrite /dev/mtd0 firmware-image.ubi
|
||||
modprobe ubi mtd=/dev/mtd0,4096
|
||||
mount -t ubifs -o ro /dev/ubi0_0 mnt
|
||||
|
||||
This will mount a ubi image for a device with eraseblock size 0x40000.
|
||||
If your image has a blocksize of 0x20000, use `fourth_id_byte=0x15`, and specify a pagesize of `2048`
|
||||
with the second modprobe line.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
View the contents of the `/etc/passwd` file in the filesystem image `image.ubi`:
|
||||
|
||||
python ubidump.py -c /etc/passwd image.ubi
|
||||
|
||||
List the files in all the volumes in `image.ubi`:
|
||||
|
||||
python ubidump.py -l image.ubi
|
||||
|
||||
View the contents of b-tree database from the volumes in `image.ubi`:
|
||||
|
||||
python ubidump.py -d image.ubi
|
||||
|
||||
Extract an unsupported volume type, so you can analyze it with other tools:
|
||||
|
||||
python ubidump.py -v 0 --saveraw unknownvol.bin image.ubi
|
||||
|
||||
Note that often ubi images contain squashfs volumes, which can be extracted using tools like
|
||||
[unsquashfs](https://github.com/plougher/squashfs-tools) or [rdsquashfs](https://github.com/AgentD/squashfs-tools-ng)
|
||||
|
||||
Install
|
||||
=======
|
||||
|
||||
Install the required python modules using:
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
or as a pip package:
|
||||
|
||||
pip install ubidump
|
||||
|
||||
You may need to manually install your operarating system libraries for lzo first:
|
||||
|
||||
on linux:
|
||||
|
||||
apt install liblzo2-dev
|
||||
|
||||
on MacOS:
|
||||
|
||||
brew install lzo
|
||||
|
||||
maybe you need to build the python library like this:
|
||||
|
||||
LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include/lzo pip3 install python-lzo
|
||||
|
||||
|
||||
When you need zstd compression, you will need to install the `zstandard` module.
|
||||
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
* python2 or python3
|
||||
* python-lzo ( >= 1.09, which introduces the 'header=False' argument )
|
||||
* crcmod
|
||||
* optional: zstandard
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
* add option to select a volume
|
||||
* add option to select a older `master` node
|
||||
* parse the journal
|
||||
* analyze b-tree structure for unused nodes
|
||||
* analyze fs structure for unused inodes, dirents
|
||||
* verify that data block size equals the size mentioned in the inode.
|
||||
* add support for ubifs ( without the ubi layer )
|
||||
* add option to extract a raw volume.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
* the ubifs/mtd tools http://linux-mtd.infradead.org/
|
||||
* git repos can be found [here](http://git.infradead.org/)
|
||||
|
||||
Similar tools
|
||||
=============
|
||||
|
||||
* another python tool [on github](https://github.com/jrspruitt/ubi_reader/)
|
||||
* does not support listing files.
|
||||
* a closed source windows tool [here](http://ubidump.oozoon.de/)
|
||||
* ubi-utils/ubidump.c [on the mtd mailinglist](http://lists.infradead.org/pipermail/linux-mtd/2014-July/054547.html)
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
Willem Hengeveld <itsme@xs4all.nl>
|
||||
|
||||
2
ubidump/requirements.txt
Normal file
2
ubidump/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
python-lzo>=1.11
|
||||
crcmod>=1.7
|
||||
48
ubidump/setup.py
Normal file
48
ubidump/setup.py
Normal file
@ -0,0 +1,48 @@
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name = "ubidump",
|
||||
version = "1.0.0",
|
||||
entry_points = {
|
||||
'console_scripts': ['ubidump=ubidump:main'],
|
||||
},
|
||||
install_requires=[
|
||||
"python-lzo>=1.11",
|
||||
"crcmod>=1.7",
|
||||
],
|
||||
py_modules=['ubidump'],
|
||||
author = "Willem Hengeveld",
|
||||
author_email = "itsme@xs4all.nl",
|
||||
description = "Commandline tool for viewing or extracting UBIFS images.",
|
||||
long_description="""
|
||||
This tool can be used to view or extract the contents of UBIFS images.
|
||||
|
||||
View the contents of the `/etc/passwd` file in the filesystem image `image.ubi`:
|
||||
|
||||
ubidump -c /etc/passwd image.ubi
|
||||
|
||||
List the files in all the volumes in `image.ubi`:
|
||||
|
||||
ubidump -l image.ubi
|
||||
|
||||
View the contents of b-tree database from the volumes in `image.ubi`:
|
||||
|
||||
ubidump -d image.ubi
|
||||
""",
|
||||
|
||||
license = "MIT",
|
||||
keywords = "ubifs commandline",
|
||||
url = "https://github.com/nlitsme/ubidump/",
|
||||
classifiers = [
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Utilities',
|
||||
'Topic :: Software Development :: Version Control :: Git',
|
||||
'Topic :: System :: Filesystems',
|
||||
],
|
||||
)
|
||||
|
||||
1812
ubidump/ubidump.py
Executable file
1812
ubidump/ubidump.py
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user