Skip to content

apk.py

ofrak_components.apk

ApkIdentifier (Identifier)

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_components/apk.py
async def identify(self, resource: Resource, config=None) -> None:
    await resource.run(MagicMimeIdentifier)
    magic = resource.get_attributes(Magic)
    if magic is not None and magic.mime in ["application/java-archive", "application/zip"]:
        with tempfile.NamedTemporaryFile(suffix=".zip") as temp_file:
            temp_file.write(await resource.get_data())
            temp_file.flush()

            command = ["unzip", "-l", temp_file.name]
            filenames = _run_command(command, IdentifierError)

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

ApkPacker (Packer)

Pack decoded APK resources into an 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_components/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) as temp_apk:
        command = ["apktool", "build", "--force-all", temp_flush_dir, "--output", temp_apk.name]
        _run_command(command, PackerError)
        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:
                command = [
                    "java",
                    "-jar",
                    "/usr/local/bin/uber-apk-signer.jar",
                    "--apks",
                    temp_apk.name,
                    "--out",
                    signed_apk_temp_dir,
                    "--allowResign",
                ]
                _run_command(command, PackerError)
                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)

Decode Android APK files.

This unpacker is a wrapper for apktool. See https://ibotpeaches.github.io/Apktool/.

unpack(self, resource, config=None) async

Decode Android APK files.

Parameters:

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

    :param resource:
    :param config:
    """
    apk = await resource.view_as(Apk)
    data = await resource.get_data()
    with tempfile.NamedTemporaryFile() as temp_file:
        temp_file.write(data)
        temp_file.flush()
        with tempfile.TemporaryDirectory() as temp_flush_dir:
            command = [
                "apktool",
                "decode",
                "--output",
                temp_flush_dir,
                "--force",
                temp_file.name,
            ]
            _run_command(command, UnpackerError)
            await apk.initialize_from_disk(temp_flush_dir)