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 = subprocess.run(command, check=True, capture_output=True).stdout

            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]
        subprocess.run(command, check=True, capture_output=True)
        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",
                ]
                subprocess.run(command, check=True, capture_output=True)
                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,
            ]
            subprocess.run(command, check=True, capture_output=True)
            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_components/apk.py
def __init__(self):
    super().__init__(
        "/usr/local/bin/uber-apk-signer.jar",
        "https://github.com/patrickfav/uber-apk-signer",
        install_check_arg="",
    )

is_tool_installed(self)

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_components/apk.py
def is_tool_installed(self) -> bool:
    try:
        retcode = subprocess.call(
            ("java", "-jar", "/usr/local/bin/uber-apk-signer.jar", "--help"),
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
    except FileNotFoundError:
        return False

    return 0 == retcode