android_sparse.py
ofrak.core.android_sparse
AndroidSparseImage (GenericBinary)
An Android sparse image format binary blob.
Android sparse images are used to efficiently store and flash system images by only including blocks that contain data, skipping empty/unused blocks.
get_file(self)
async
Get the unpacked raw image resource.
Returns:
| Type | Description |
|---|---|
Resource |
The raw (non-sparse) image resource |
Source code in ofrak/core/android_sparse.py
async def get_file(self) -> Resource:
"""
Get the unpacked raw image resource.
:return: The raw (non-sparse) image resource
"""
return await self.resource.get_only_child()
AndroidSparseImagePacker (Packer)
Pack (convert) a raw image back into Android sparse image format using img2simg.
pack(self, resource, config=None)
async
Pack the raw image back into sparse format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The sparse image resource to pack |
required |
Source code in ofrak/core/android_sparse.py
async def pack(self, resource: Resource, config=None):
"""
Pack the raw image back into sparse format.
:param resource: The sparse image resource to pack
"""
sparse_view = await resource.view_as(AndroidSparseImage)
raw_child_r = await sparse_view.get_file()
raw_data = await raw_child_r.get_data()
with tempfile.NamedTemporaryFile(
suffix=".img", mode="wb", delete_on_close=False
) as raw_file:
raw_file.write(raw_data)
raw_file.close()
with tempfile.NamedTemporaryFile(
suffix=".simg", mode="rb", delete_on_close=False
) as sparse_file:
sparse_file.close()
# TODO: detect block size in the unpacker rather than default to 4096
# See https://github.com/redballoonsecurity/ofrak/issues/665
cmd = [
"img2simg",
raw_file.name,
sparse_file.name,
"4096",
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
if proc.returncode:
raise CalledProcessError(returncode=proc.returncode, cmd=cmd, stderr=stderr)
with open(sparse_file.name, "rb") as sparse_fh:
sparse_data = sparse_fh.read()
original_size = await resource.get_data_length()
resource.queue_patch(Range(0, original_size), sparse_data)
await resource.save()
AndroidSparseImageUnpacker (Unpacker)
Unpack (convert) an Android sparse image to raw image format using simg2img.
unpack(self, resource, config=None)
async
Unpack the Android sparse image by converting it to raw format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The sparse image resource to unpack |
required |
Source code in ofrak/core/android_sparse.py
async def unpack(self, resource: Resource, config=None):
"""
Unpack the Android sparse image by converting it to raw format.
:param resource: The sparse image resource to unpack
"""
async with resource.temp_to_disk() as sparse_path:
with tempfile.NamedTemporaryFile(
suffix=".img", mode="rb", delete_on_close=False
) as raw_file:
raw_file.close()
cmd = [
"simg2img",
sparse_path,
raw_file.name,
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
if proc.returncode:
raise CalledProcessError(returncode=proc.returncode, cmd=cmd, stderr=stderr)
with open(raw_file.name, "rb") as raw_fh:
raw_data = raw_fh.read()
await resource.create_child(tags=(GenericBinary,), data=raw_data)
_AndroidSparseImageTool (ComponentExternalTool)
private
Custom tool checker for Android sparse image tools.
These tools don't support standard help flags and always return non-zero exit codes, so we check for the presence of usage output instead.
__init__(self, tool, apt_package, brew_package)
special
Initialize self. See help(type(self)) for accurate signature.
Source code in ofrak/core/android_sparse.py
def __init__(self, tool: str, apt_package: str, brew_package: str):
super().__init__(
tool, "https://github.com/anestisb/android-simg2img", "", apt_package, brew_package
)
is_tool_installed(self)
async
Check if the tool is installed by running it with no arguments and checking for usage output.
Returns:
| Type | Description |
|---|---|
bool |
True if tool outputs usage information, False otherwise |
Source code in ofrak/core/android_sparse.py
async def is_tool_installed(self) -> bool:
"""
Check if the tool is installed by running it with no arguments and checking for usage output.
:return: True if tool outputs usage information, False otherwise
"""
try:
cmd = [self.tool]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, stderr = await proc.communicate()
except FileNotFoundError:
return False
# Check if usage information is present in output (tool returns 255, but that's OK)
if b"usage:" in stderr.lower():
return True
return False
_match_sparse_magic(data)
private
Check if data starts with Android sparse image magic bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
bytes |
Binary data to check |
required |
Returns:
| Type | Description |
|---|---|
bool |
True if data matches sparse image format |
Source code in ofrak/core/android_sparse.py
def _match_sparse_magic(data: bytes) -> bool:
"""
Check if data starts with Android sparse image magic bytes.
:param data: Binary data to check
:return: True if data matches sparse image format
"""
if len(data) < 4:
return False
return data[:4] == SPARSE_HEADER_MAGIC