Skip to content

apk.py

ofrak.core.apk

ApkIdentifier (Identifier)

Identifier for ApkArchive.

Some Apks are recognized by the MagicMimePattern; others are tagged as JavaArchive or ZipArchive. This identifier inspects those files, and tags any with an androidmanifest.xml as an ApkArchive.

identify(self, resource, config=None) async

Perform identification on the given resource.

Users should not call this method directly; rather, they should run Resource.identify.

Parameters:

Name Type Description Default
resource Resource required
config

Optional config for identifying. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak/core/apk.py
async def identify(self, resource: Resource, config=None) -> None:
    async with resource.temp_to_disk(suffix=".zip") as temp_path:
        unzip_cmd = [
            "unzip",
            "-l",
            temp_path,
        ]
        unzip_proc = await asyncio.create_subprocess_exec(
            *unzip_cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, stderr = await unzip_proc.communicate()
        if unzip_proc.returncode:
            raise CalledProcessError(returncode=unzip_proc.returncode, cmd=unzip_cmd)

        if b"androidmanifest.xml" in stdout.lower():
            resource.add_tag(Apk)

ApkPacker (Packer)

Repackages decoded Android APK resources into a complete, signed APK file using apktool for compilation and uber-apk-signer for signing. The process recompiles resources, repackages DEX files, updates the manifest, and creates cryptographic signatures required for Android installation. Use after modifying Android app resources, Smali code, or manifest to create an installable APK.

This unpacker is a wrapper for two tools:

Another helpful overview of the process: https://github.com/vaibhavpandeyvpz/apkstudio.

pack(self, resource, config=ApkPackerConfig(sign_apk=True)) async

Pack disassembled APK resources into an APK.

Parameters:

Name Type Description Default
resource Resource required
config ApkPackerConfig ApkPackerConfig(sign_apk=True)
Source code in ofrak/core/apk.py
async def pack(
    self, resource: Resource, config: ApkPackerConfig = ApkPackerConfig(sign_apk=True)
):
    """
    Pack disassembled APK resources into an APK.

    :param resource:
    :param config:
    """
    apk = await resource.view_as(Apk)
    temp_flush_dir = await apk.flush_to_disk()
    apk_suffix = ".apk"
    with tempfile.NamedTemporaryFile(suffix=apk_suffix, delete_on_close=False) as temp_apk:
        temp_apk.close()
        apk_cmd = [
            "apktool",
            "build",
            "--force-all",
            temp_flush_dir,
            "--output",
            temp_apk.name,
        ]
        apk_proc = await asyncio.create_subprocess_exec(
            *apk_cmd,
        )
        apk_returncode = await apk_proc.wait()
        if apk_proc.returncode:
            raise CalledProcessError(returncode=apk_returncode, cmd=apk_cmd)
        if not config.sign_apk:
            # Close the file handle and reopen, to avoid observed situations where temp.read()
            # was not returning data
            with open(temp_apk.name, "rb") as file_handle:
                new_data = file_handle.read()
        else:
            with tempfile.TemporaryDirectory() as signed_apk_temp_dir:
                java_cmd = [
                    "java",
                    "-jar",
                    _UberApkSignerTool.JAR_PATH,
                    "--apks",
                    temp_apk.name,
                    "--out",
                    signed_apk_temp_dir,
                    "--allowResign",
                ]
                java_proc = await asyncio.create_subprocess_exec(
                    *java_cmd,
                )
                java_returncode = await java_proc.wait()
                if java_proc.returncode:
                    raise CalledProcessError(returncode=java_returncode, cmd=java_cmd)
                signed_apk_filename = (
                    os.path.basename(temp_apk.name)[: -len(apk_suffix)]
                    + "-aligned-debugSigned.apk"
                )
                signed_file_name = os.path.join(
                    signed_apk_temp_dir,
                    signed_apk_filename,
                )
                with open(signed_file_name, "rb") as file_handle:
                    new_data = file_handle.read()
        assert len(new_data) != 0
        resource.queue_patch(Range(0, await resource.get_data_length()), new_data)

ApkPackerConfig (ComponentConfig) dataclass

ApkPackerConfig(sign_apk: bool)

ApkUnpacker (Unpacker)

Decodes Android APK application packages into their component files and resources using apktool (see https://ibotpeaches.github.io/Apktool/). This tool decodes the AndroidManifest.xml back to readable XML, extracts resources (images, layouts, strings) in their original format, converts DEX bytecode to Smali assembly, and preserves the complete directory structure. Use when reverse engineering Android applications, analyzing app behavior, examining resource files, or preparing to modify and repackage an APK. The decoded files are much easier to read and modify than the compiled APK format.

unpack(self, resource, config=None) async

Decode Android APK files.

Parameters:

Name Type Description Default
resource Resource required
config None
Source code in ofrak/core/apk.py
async def unpack(self, resource: Resource, config=None):
    """
    Decode Android APK files.

    :param resource:
    :param config:
    """
    apk = await resource.view_as(Apk)
    async with resource.temp_to_disk() as temp_path:
        with tempfile.TemporaryDirectory() as temp_flush_dir:
            cmd = [
                "apktool",
                "decode",
                "--output",
                temp_flush_dir,
                "--force",
                temp_path,
            ]
            proc = await asyncio.create_subprocess_exec(
                *cmd,
            )
            returncode = await proc.wait()
            if proc.returncode:
                raise CalledProcessError(returncode=returncode, cmd=cmd)
            await apk.initialize_from_disk(temp_flush_dir)

_UberApkSignerTool (ComponentExternalTool) private

__init__(self) special

Initialize self. See help(type(self)) for accurate signature.

Source code in ofrak/core/apk.py
def __init__(self):
    super().__init__(
        _UberApkSignerTool.JAR_PATH,
        "https://github.com/patrickfav/uber-apk-signer",
        install_check_arg="",
    )

is_tool_installed(self) async

Check if a tool is installed by running it with the install_check_arg. This method runs <tool> <install_check_arg>.

Returns:

Type Description
bool

True if the tool command returned zero, False if tool could not be found or returned non-zero exit code.

Source code in ofrak/core/apk.py
async def is_tool_installed(self) -> bool:
    if not os.path.exists(_UberApkSignerTool.JAR_PATH):
        return False

    try:
        cmd = [
            "java",
            "-jar",
            _UberApkSignerTool.JAR_PATH,
            "--help",
        ]
        proc = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL,
        )
        returncode = await proc.wait()
    except FileNotFoundError:
        return False

    return 0 == returncode