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:
apktoolrepacks the APK resources. See https://ibotpeaches.github.io/Apktool/.uber-apk-signersigns the packed APK file. See https://github.com/patrickfav/uber-apk-signer.
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 |
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