init
This commit is contained in:
commit
43eef100f1
24 changed files with 2041734 additions and 0 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Android class shadowing scanner
|
||||||
|
|
||||||
|
Detect if an Android application is in a situation that may lead to class spoofing.
|
||||||
214
android_class_shadowing_scanner/__init__.py
Normal file
214
android_class_shadowing_scanner/__init__.py
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
import zipfile
|
||||||
|
import io
|
||||||
|
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
import androguard.core.dex # type: ignore
|
||||||
|
from androguard.core.dex import DEX # type: ignore
|
||||||
|
|
||||||
|
# loguru -> logging framework used by androgard
|
||||||
|
from loguru import logger # type: ignore
|
||||||
|
|
||||||
|
from .platform_classes import (
|
||||||
|
PLATFORM_32_CLASSES,
|
||||||
|
SDK_32_CLASSES,
|
||||||
|
PLATFORM_33_CLASSES,
|
||||||
|
SDK_33_CLASSES,
|
||||||
|
PLATFORM_34_CLASSES,
|
||||||
|
SDK_34_CLASSES,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove Androguard logs
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
|
||||||
|
# Patch Androguard
|
||||||
|
class PatchedDomapiApiFlag(IntEnum):
|
||||||
|
NONE = 0
|
||||||
|
CORE_PLATFORM_API = 1
|
||||||
|
TEST_API = 2
|
||||||
|
UNKN_3 = 3
|
||||||
|
UNKN_4 = 4
|
||||||
|
UNKN_5 = 5
|
||||||
|
UNKN_6 = 6
|
||||||
|
UNKN_7 = 7
|
||||||
|
UNKN_8 = 8
|
||||||
|
UNKN_9 = 9
|
||||||
|
INKN_10 = 10
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedRestrictionApiFlag(IntEnum):
|
||||||
|
WHITELIST = 0
|
||||||
|
GREYLIST = 1
|
||||||
|
BLACKLIST = 2
|
||||||
|
GREYLIST_MAX_O = 3
|
||||||
|
GREYLIST_MAX_P = 4
|
||||||
|
GREYLIST_MAX_Q = 5
|
||||||
|
GREYLIST_MAX_R = 6
|
||||||
|
GREYLIST_MAX_7 = 7
|
||||||
|
GREYLIST_MAX_8 = 8
|
||||||
|
GREYLIST_MAX_9 = 9
|
||||||
|
GREYLIST_MAX_10 = 10
|
||||||
|
|
||||||
|
|
||||||
|
androguard.core.dex.HiddenApiClassDataItem.DomapiApiFlag = PatchedDomapiApiFlag
|
||||||
|
androguard.core.dex.HiddenApiClassDataItem.RestrictionApiFlag = (
|
||||||
|
PatchedRestrictionApiFlag
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Entry:
|
||||||
|
# sha256: str
|
||||||
|
nb_duplicate_classes: int
|
||||||
|
nb_platform_32_classes: int
|
||||||
|
nb_platform_non_sdk_32_classes: int
|
||||||
|
nb_sdk_32_classes: int
|
||||||
|
nb_platform_33_classes: int
|
||||||
|
nb_platform_non_sdk_33_classes: int
|
||||||
|
nb_sdk_33_classes: int
|
||||||
|
nb_platform_34_classes: int
|
||||||
|
nb_platform_non_sdk_34_classes: int
|
||||||
|
nb_sdk_34_classes: int
|
||||||
|
has_classes0_dex: bool
|
||||||
|
has_classes1_dex: bool
|
||||||
|
has_classes0X_dex: bool
|
||||||
|
has_classes_dex_over_10: bool
|
||||||
|
has_non_numeric_classes_dex: bool
|
||||||
|
has_non_consecutive_classes_dex: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlatformClassesData:
|
||||||
|
nb_duplicate_classes: int
|
||||||
|
nb_platform_32_classes: int
|
||||||
|
nb_platform_non_sdk_32_classes: int
|
||||||
|
nb_sdk_32_classes: int
|
||||||
|
nb_platform_33_classes: int
|
||||||
|
nb_platform_non_sdk_33_classes: int
|
||||||
|
nb_sdk_33_classes: int
|
||||||
|
nb_platform_34_classes: int
|
||||||
|
nb_platform_non_sdk_34_classes: int
|
||||||
|
nb_sdk_34_classes: int
|
||||||
|
|
||||||
|
|
||||||
|
def scan_classes(apk: zipfile.ZipFile, file_names: set[str]) -> PlatformClassesData:
|
||||||
|
all_classes = set()
|
||||||
|
duplicated_classes = set()
|
||||||
|
platform_32_classes = set()
|
||||||
|
sdk_32_classes = set()
|
||||||
|
platform_non_sdk_32_classes = set()
|
||||||
|
platform_33_classes = set()
|
||||||
|
sdk_33_classes = set()
|
||||||
|
platform_non_sdk_33_classes = set()
|
||||||
|
platform_34_classes = set()
|
||||||
|
sdk_34_classes = set()
|
||||||
|
platform_non_sdk_34_classes = set()
|
||||||
|
for name in file_names:
|
||||||
|
with apk.open(name) as dex_f:
|
||||||
|
dex = DEX(dex_f.read())
|
||||||
|
for clazz in map(lambda c: c.name, dex.get_classes()):
|
||||||
|
if clazz in all_classes:
|
||||||
|
duplicated_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_32_CLASSES:
|
||||||
|
platform_32_classes.add(clazz)
|
||||||
|
if clazz in SDK_32_CLASSES:
|
||||||
|
sdk_32_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_32_CLASSES and clazz not in SDK_32_CLASSES:
|
||||||
|
platform_non_sdk_32_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_33_CLASSES:
|
||||||
|
platform_33_classes.add(clazz)
|
||||||
|
if clazz in SDK_33_CLASSES:
|
||||||
|
sdk_33_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_33_CLASSES and clazz not in SDK_33_CLASSES:
|
||||||
|
platform_non_sdk_33_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_34_CLASSES:
|
||||||
|
platform_34_classes.add(clazz)
|
||||||
|
if clazz in SDK_34_CLASSES:
|
||||||
|
sdk_34_classes.add(clazz)
|
||||||
|
if clazz in PLATFORM_34_CLASSES and clazz not in SDK_34_CLASSES:
|
||||||
|
platform_non_sdk_34_classes.add(clazz)
|
||||||
|
all_classes.add(clazz)
|
||||||
|
return PlatformClassesData(
|
||||||
|
nb_duplicate_classes=len(duplicated_classes),
|
||||||
|
nb_platform_32_classes=len(platform_32_classes),
|
||||||
|
nb_platform_non_sdk_32_classes=len(platform_non_sdk_32_classes),
|
||||||
|
nb_sdk_32_classes=len(sdk_32_classes),
|
||||||
|
nb_platform_33_classes=len(platform_33_classes),
|
||||||
|
nb_platform_non_sdk_33_classes=len(platform_non_sdk_33_classes),
|
||||||
|
nb_sdk_33_classes=len(sdk_33_classes),
|
||||||
|
nb_platform_34_classes=len(platform_34_classes),
|
||||||
|
nb_platform_non_sdk_34_classes=len(platform_non_sdk_34_classes),
|
||||||
|
nb_sdk_34_classes=len(sdk_34_classes),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(apk: zipfile.ZipFile) -> Entry:
|
||||||
|
classes_dex = set(
|
||||||
|
filter(
|
||||||
|
lambda name: name.startswith("classes") and name.endswith(".dex"),
|
||||||
|
apk.namelist(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dex_numbers = list(
|
||||||
|
map(
|
||||||
|
int,
|
||||||
|
filter(
|
||||||
|
lambda string: string not in ("", "1")
|
||||||
|
and string.isnumeric()
|
||||||
|
and string[0] != "0",
|
||||||
|
map(
|
||||||
|
lambda name: name.removeprefix("classes").removesuffix(".dex"),
|
||||||
|
classes_dex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dex_numbers.sort()
|
||||||
|
|
||||||
|
has_non_numeric_classes_dex = False
|
||||||
|
for name in classes_dex:
|
||||||
|
if name == "classes.dex":
|
||||||
|
continue
|
||||||
|
if not name.removeprefix("classes").removesuffix(".dex").isnumeric():
|
||||||
|
has_non_numeric_classes_dex = True
|
||||||
|
|
||||||
|
has_non_consecutive_classes_dex = False
|
||||||
|
if "classes.dex" in classes_dex and dex_numbers:
|
||||||
|
has_non_consecutive_classes_dex = True
|
||||||
|
last_number = 1
|
||||||
|
for i in range(len(dex_numbers)):
|
||||||
|
if dex_numbers[i] == 0 or dex_numbers[i] == 1:
|
||||||
|
continue
|
||||||
|
# the list is sorted
|
||||||
|
if dex_numbers[i] != last_number + 1:
|
||||||
|
has_non_consecutive_classes_dex = True
|
||||||
|
break
|
||||||
|
|
||||||
|
platform_classes_data = scan_classes(apk, classes_dex)
|
||||||
|
|
||||||
|
return Entry(
|
||||||
|
**asdict(platform_classes_data),
|
||||||
|
has_classes0_dex="classes0.dex" in classes_dex,
|
||||||
|
has_classes1_dex="classes1.dex" in classes_dex,
|
||||||
|
has_classes0X_dex=any(
|
||||||
|
map(lambda name: name.startswith("classes0"), classes_dex)
|
||||||
|
),
|
||||||
|
has_classes_dex_over_10=any(map(lambda x: x >= 10, dex_numbers)),
|
||||||
|
has_non_numeric_classes_dex=has_non_numeric_classes_dex,
|
||||||
|
has_non_consecutive_classes_dex=has_non_consecutive_classes_dex,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import sys
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
with open(sys.argv[1], "rb") as file:
|
||||||
|
apk = file.read()
|
||||||
|
|
||||||
|
with zipfile.ZipFile(io.BytesIO(apk)) as apk:
|
||||||
|
entry = analyze(apk)
|
||||||
|
|
||||||
|
pprint.pprint(entry)
|
||||||
36854
android_class_shadowing_scanner/android-32/classes.txt
Normal file
36854
android_class_shadowing_scanner/android-32/classes.txt
Normal file
File diff suppressed because it is too large
Load diff
193306
android_class_shadowing_scanner/android-32/fields.txt
Normal file
193306
android_class_shadowing_scanner/android-32/fields.txt
Normal file
File diff suppressed because it is too large
Load diff
306531
android_class_shadowing_scanner/android-32/methods.txt
Normal file
306531
android_class_shadowing_scanner/android-32/methods.txt
Normal file
File diff suppressed because it is too large
Load diff
5181
android_class_shadowing_scanner/android-32/sdk_classes.txt
Normal file
5181
android_class_shadowing_scanner/android-32/sdk_classes.txt
Normal file
File diff suppressed because it is too large
Load diff
26743
android_class_shadowing_scanner/android-32/sdk_fields.txt
Normal file
26743
android_class_shadowing_scanner/android-32/sdk_fields.txt
Normal file
File diff suppressed because it is too large
Load diff
52719
android_class_shadowing_scanner/android-32/sdk_methods.txt
Normal file
52719
android_class_shadowing_scanner/android-32/sdk_methods.txt
Normal file
File diff suppressed because it is too large
Load diff
42485
android_class_shadowing_scanner/android-33/classes.txt
Normal file
42485
android_class_shadowing_scanner/android-33/classes.txt
Normal file
File diff suppressed because it is too large
Load diff
207512
android_class_shadowing_scanner/android-33/fields.txt
Normal file
207512
android_class_shadowing_scanner/android-33/fields.txt
Normal file
File diff suppressed because it is too large
Load diff
331724
android_class_shadowing_scanner/android-33/methods.txt
Normal file
331724
android_class_shadowing_scanner/android-33/methods.txt
Normal file
File diff suppressed because it is too large
Load diff
5387
android_class_shadowing_scanner/android-33/sdk_classes.txt
Normal file
5387
android_class_shadowing_scanner/android-33/sdk_classes.txt
Normal file
File diff suppressed because it is too large
Load diff
28072
android_class_shadowing_scanner/android-33/sdk_fields.txt
Normal file
28072
android_class_shadowing_scanner/android-33/sdk_fields.txt
Normal file
File diff suppressed because it is too large
Load diff
54977
android_class_shadowing_scanner/android-33/sdk_methods.txt
Normal file
54977
android_class_shadowing_scanner/android-33/sdk_methods.txt
Normal file
File diff suppressed because it is too large
Load diff
48935
android_class_shadowing_scanner/android-34/classes.txt
Normal file
48935
android_class_shadowing_scanner/android-34/classes.txt
Normal file
File diff suppressed because it is too large
Load diff
230283
android_class_shadowing_scanner/android-34/fields.txt
Normal file
230283
android_class_shadowing_scanner/android-34/fields.txt
Normal file
File diff suppressed because it is too large
Load diff
374815
android_class_shadowing_scanner/android-34/methods.txt
Normal file
374815
android_class_shadowing_scanner/android-34/methods.txt
Normal file
File diff suppressed because it is too large
Load diff
5837
android_class_shadowing_scanner/android-34/sdk_classes.txt
Normal file
5837
android_class_shadowing_scanner/android-34/sdk_classes.txt
Normal file
File diff suppressed because it is too large
Load diff
29820
android_class_shadowing_scanner/android-34/sdk_fields.txt
Normal file
29820
android_class_shadowing_scanner/android-34/sdk_fields.txt
Normal file
File diff suppressed because it is too large
Load diff
58733
android_class_shadowing_scanner/android-34/sdk_methods.txt
Normal file
58733
android_class_shadowing_scanner/android-34/sdk_methods.txt
Normal file
File diff suppressed because it is too large
Load diff
41
android_class_shadowing_scanner/platform_classes.py
Normal file
41
android_class_shadowing_scanner/platform_classes.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
local_dir = Path(__file__).parent
|
||||||
|
|
||||||
|
PLATFORM_32_CLASSES = set()
|
||||||
|
SDK_32_CLASSES = set()
|
||||||
|
PLATFORM_33_CLASSES = set()
|
||||||
|
SDK_33_CLASSES = set()
|
||||||
|
PLATFORM_34_CLASSES = set()
|
||||||
|
SDK_34_CLASSES = set()
|
||||||
|
|
||||||
|
with (local_dir / "android-32" / "classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
PLATFORM_32_CLASSES.add(class_name)
|
||||||
|
with (local_dir / "android-32" / "sdk_classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
SDK_32_CLASSES.add(class_name)
|
||||||
|
with (local_dir / "android-33" / "classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
PLATFORM_33_CLASSES.add(class_name)
|
||||||
|
with (local_dir / "android-33" / "sdk_classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
SDK_33_CLASSES.add(class_name)
|
||||||
|
with (local_dir / "android-34" / "classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
PLATFORM_34_CLASSES.add(class_name)
|
||||||
|
with (local_dir / "android-34" / "sdk_classes.txt").open() as file:
|
||||||
|
for line in file:
|
||||||
|
class_name = line.strip()
|
||||||
|
if class_name:
|
||||||
|
SDK_34_CLASSES.add(class_name)
|
||||||
1543
poetry.lock
generated
Normal file
1543
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
pyproject.toml
Normal file
18
pyproject.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "android-class-shadowing-scanner"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Detect if an Android application is in a situation that may lead to class spoofing."
|
||||||
|
authors = ["Jean-Marie Mineau <jean-marie.mineau@inria.fr>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.12"
|
||||||
|
|
||||||
|
|
||||||
|
androguard = "^4.1.2"
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
scan = 'android_class_shadowing_scanner.__init__:main'
|
||||||
Loading…
Add table
Add a link
Reference in a new issue