Skip to content

Instantly share code, notes, and snippets.

@obfusk
Created April 16, 2021 13:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obfusk/f9b68f58ef7afcd41ad5b0b51d82274b to your computer and use it in GitHub Desktop.
Save obfusk/f9b68f58ef7afcd41ad5b0b51d82274b to your computer and use it in GitHub Desktop.
fast way to get the minSdkVersion from an APK
#!/usr/bin/python3
# encoding: utf-8
import os
import struct
import sys
# import zipfile
import zlib
from collections import namedtuple
from androguard.core.bytecodes import axml
ZipData = namedtuple("ZipData", ("cd_offset", "eocd_offset", "cd_and_eocd"))
class ZipError(Exception):
pass
# def get_android_manifest(apkfile):
# with zipfile.ZipFile(apkfile, "r") as apk:
# with apk.open("AndroidManifest.xml") as manifest:
# return manifest.read()
def get_android_manifest(apkfile):
zdata = zip_data(apkfile)
with open(apkfile, "rb") as fhi:
fhi.seek(zdata.cd_offset)
while True:
hdr = fhi.read(46)
if hdr[:4] != b"\x50\x4b\x01\x02":
break
n, m, k = struct.unpack("<HHH", hdr[28:34])
hdr += fhi.read(n + m + k)
name = hdr[46:46 + n]
if name == b"AndroidManifest.xml":
header_offset = int.from_bytes(hdr[42:46], "little")
compress_size = int.from_bytes(hdr[20:24], "little")
compress_type = int.from_bytes(hdr[10:12], "little")
if compress_type != 8:
raise ZipError("Expected deflate compression")
fhi.seek(header_offset)
lhr = fhi.read(30)
if lhr[:4] != b"\x50\x4b\x03\x04":
raise ZipError("Expected local file header signature")
ln, lm = struct.unpack("<HH", lhr[26:30])
lhr += fhi.read(ln + lm)
return zlib.decompress(fhi.read(compress_size), -15)
raise RuntimeError("AndroidManifest.xml not found")
def zip_data(apkfile, count=1024):
with open(apkfile, "rb") as fh:
fh.seek(-count, os.SEEK_END)
data = fh.read()
pos = data.rfind(b"\x50\x4b\x05\x06")
if pos == -1:
raise ZipError("Expected end of central directory record (EOCD)")
fh.seek(pos - len(data), os.SEEK_CUR)
eocd_offset = fh.tell()
fh.seek(16, os.SEEK_CUR)
cd_offset = int.from_bytes(fh.read(4), "little")
fh.seek(cd_offset)
cd_and_eocd = fh.read()
return ZipData(cd_offset, eocd_offset, cd_and_eocd)
def get_minsdkversion_from_binary_android_manifest(data):
result = 1
parser = axml.AXMLParser(data)
while parser.is_valid():
event = next(parser)
if event == axml.END_DOCUMENT:
break
# FIXME: depth == 2 ?!
if event == axml.START_TAG and parser.name == "uses-sdk" \
and parser.namespace == "":
for i in range(parser.getAttributeCount()):
if parser.getAttributeName(i) == "minSdkVersion":
# FIXME: VALUE_TYPE_STRING -> getMinSdkVersionForCodename()
val = parser.getAttributeValueData(i)
result = max(result, val)
# return result
return result
if __name__ == "__main__":
# from androguard.core.bytecodes.apk import APK
for apkfile in sys.argv[1:]:
data = get_android_manifest(apkfile)
print(get_minsdkversion_from_binary_android_manifest(data))
# apk = APK(apkfile)
# print(apk.get_min_sdk_version())
# vim: set tw=80 sw=4 sts=4 et fdm=marker :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment