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