This commit is contained in:
Jean-Marie Mineau 2025-04-04 15:08:28 +02:00
parent ba02e70dcc
commit a6a0740c61
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
11 changed files with 466 additions and 171 deletions

160
frida/poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "alembic"
@ -18,7 +18,7 @@ SQLAlchemy = ">=1.3.0"
typing-extensions = ">=4"
[package.extras]
tz = ["backports.zoneinfo ; python_version < \"3.9\"", "tzdata"]
tz = ["backports.zoneinfo", "tzdata"]
[[package]]
name = "androguard"
@ -132,6 +132,25 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "coloredlogs"
version = "15.0.1"
description = "Colored terminal output for Python's logging module"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
markers = "extra == \"grodd\""
files = [
{file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
{file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
]
[package.dependencies]
humanfriendly = ">=9.1"
[package.extras]
cron = ["capturer (>=2.4)"]
[[package]]
name = "contourpy"
version = "1.3.1"
@ -267,7 +286,7 @@ files = [
]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
[[package]]
name = "fonttools"
@ -330,18 +349,18 @@ files = [
]
[package.extras]
all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"]
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""]
interpolatable = ["munkres", "pycairo", "scipy"]
lxml = ["lxml (>=4.0)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr ; sys_platform == \"darwin\""]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""]
woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"]
unicode = ["unicodedata2 (>=15.1.0)"]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
[[package]]
name = "frida"
@ -472,6 +491,44 @@ files = [
docs = ["Sphinx", "furo"]
test = ["objgraph", "psutil"]
[[package]]
name = "grodd-runner"
version = "0.1.0"
description = "Grodd runner is a python program that tries to browse all activities of an Android application."
optional = true
python-versions = ">=3.11,<4.0.0"
groups = ["main"]
markers = "extra == \"grodd\""
files = []
develop = false
[package.dependencies]
coloredlogs = ">=15.0.1,<16.0.0"
networkx = ">=3.4.2,<4.0.0"
uiautomator = ">=1.0.2,<2.0.0"
[package.source]
type = "git"
url = "ssh://git@gitlab.inria.fr/CIDRE/malware/grodd-runner.git"
reference = "HEAD"
resolved_reference = "323cabc4e92a89fc9bcd23231c11764a6e423a52"
[[package]]
name = "humanfriendly"
version = "10.0"
description = "Human friendly output for text interfaces using Python"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
markers = "extra == \"grodd\""
files = [
{file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
{file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
]
[package.dependencies]
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
[[package]]
name = "ipython"
version = "8.32.0"
@ -498,7 +555,7 @@ traitlets = ">=5.13.0"
[package.extras]
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
black = ["black"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"]
kernel = ["ipykernel"]
matplotlib = ["matplotlib"]
nbconvert = ["nbconvert"]
@ -636,7 +693,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"]
[[package]]
name = "lxml"
@ -1213,7 +1270,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions ; python_version < \"3.10\""]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
@ -1309,6 +1366,22 @@ files = [
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyreadline3"
version = "3.5.4"
description = "A python implementation of GNU readline."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "sys_platform == \"win32\" and extra == \"grodd\""
files = [
{file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"},
{file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"},
]
[package.extras]
dev = ["build", "flake8", "mypy", "pytest", "twine"]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@ -1457,25 +1530,25 @@ files = [
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
[package.extras]
aiomysql = ["aiomysql (>=0.2.0) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
aiosqlite = ["aiosqlite ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\"", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17) ; python_version >= \"3\""]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2) ; python_version >= \"3\"", "mariadb (>=1.0.1,!=1.1.2) ; python_version >= \"3\""]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)", "mariadb (>=1.0.1,!=1.1.2)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql", "pymssql"]
mssql-pyodbc = ["pyodbc", "pyodbc"]
mypy = ["mypy (>=0.910) ; python_version >= \"3\"", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0) ; python_version >= \"3\"", "mysqlclient (>=1.4.0,<2) ; python_version < \"3\""]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mysql-connector = ["mysql-connector-python", "mysql-connector-python"]
oracle = ["cx_oracle (>=7) ; python_version >= \"3\"", "cx_oracle (>=7,<8) ; python_version < \"3\""]
oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg ; python_version >= \"3\"", "asyncpg ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0) ; python_version >= \"3\"", "pg8000 (>=1.16.6,!=1.29.0) ; python_version >= \"3\""]
postgresql-asyncpg = ["asyncpg", "asyncpg", "greenlet (!=0.4.17)", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)", "pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1) ; python_version < \"3\"", "pymysql ; python_version >= \"3\""]
sqlcipher = ["sqlcipher3_binary ; python_version >= \"3\""]
pymysql = ["pymysql", "pymysql (<1)"]
sqlcipher = ["sqlcipher3_binary"]
[[package]]
name = "stack-data"
@ -1525,6 +1598,40 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "uiautomator"
version = "1.0.2"
description = "Python Wrapper for Android UiAutomator test tool"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"grodd\""
files = [
{file = "uiautomator-1.0.2.tar.gz", hash = "sha256:48a41c36f8347b643ff215d41b73ab2b4f542a0e3f7b110b85f7952b70742744"},
]
[package.dependencies]
urllib3 = ">=1.7.1"
[[package]]
name = "urllib3"
version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"grodd\""
files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "wcwidth"
version = "0.2.13"
@ -1647,9 +1754,12 @@ files = [
]
[package.extras]
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[extras]
grodd = ["grodd-runner"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.13,<4.0.0"
content-hash = "5f31562f74268b8fd66962ef54ae7ac09e50583c27137cccf77230d2c1461c2d"
content-hash = "bc0db0a87a5eb37d349706b948be90506f3bd5925e6262df600cbdffa3b116fe"

View file

@ -14,6 +14,8 @@ dependencies = [
]
[project.optional-dependencies]
grodd = ["grodd-runner @ git+ssh://git@gitlab.inria.fr/CIDRE/malware/grodd-runner.git"]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

View file

@ -234,7 +234,7 @@ FRIDA_SERVER_BIN = Path(__file__).parent / "frida-server-16.7.0-android-x86_64.x
FRIDA_SERVER_ANDROID_PATH = "/data/local/tmp/frida-server"
def setup_frida(device_name: str, env: dict[str, str]) -> frida.core.Device:
def setup_frida(device_name: str, env: dict[str, str], adb: str) -> frida.core.Device:
if device_name != "":
device = frida.get_device(device_name)
env["ANDROID_SERIAL"] = device_name
@ -250,21 +250,21 @@ def setup_frida(device_name: str, env: dict[str, str]) -> frida.core.Device:
# Start server
proc: subprocess.CompletedProcess[str] | subprocess.CompletedProcess[bytes] = (
subprocess.run(
["adb", "shell", "whoami"],
[adb, "shell", "whoami"],
encoding="utf-8",
stdout=subprocess.PIPE,
env=env,
)
)
if proc.stdout.strip() != "root":
proc = subprocess.run(["adb", "root"], env=env)
proc = subprocess.run([adb, "root"], env=env)
# Rooting adb will disconnect the device
if device_name != "":
device = frida.get_device(device_name)
else:
device = frida.get_usb_device()
perm = subprocess.run(
["adb", "shell", "stat", "-c", "%a", FRIDA_SERVER_ANDROID_PATH],
[adb, "shell", "stat", "-c", "%a", FRIDA_SERVER_ANDROID_PATH],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@ -287,7 +287,7 @@ def setup_frida(device_name: str, env: dict[str, str]) -> frida.core.Device:
subprocess.run(
[
"adb",
adb,
"push",
str((tmpd / "frida-server").absolute()),
FRIDA_SERVER_ANDROID_PATH,
@ -296,9 +296,9 @@ def setup_frida(device_name: str, env: dict[str, str]) -> frida.core.Device:
)
if need_perm_resset:
subprocess.run(
["adb", "shell", "chmod", "755", FRIDA_SERVER_ANDROID_PATH], env=env
[adb, "shell", "chmod", "755", FRIDA_SERVER_ANDROID_PATH], env=env
)
subprocess.Popen(["adb", "shell", FRIDA_SERVER_ANDROID_PATH], env=env)
subprocess.Popen([adb, "shell", FRIDA_SERVER_ANDROID_PATH], env=env)
# The server take some time to start
# time.sleep(3)
t = spinner()
@ -313,23 +313,39 @@ def setup_frida(device_name: str, env: dict[str, str]) -> frida.core.Device:
time.sleep(0.3)
def collect_runtime(apk: Path, device_name: str, file_storage: Path, output: TextIO):
def collect_runtime(
apk: Path,
device_name: str,
file_storage: Path,
output: TextIO,
adb_path: Path | None = None,
android_sdk_path: Path | None = None,
):
env = dict(os.environ)
if adb_path is not None:
adb = str(adb_path)
elif adb_path is None and android_sdk_path is None:
adb = "adb"
elif not (android_sdk_path / "platform-tools" / "adb").exists():
adb = "adb"
else:
adb = str(android_sdk_path / "platform-tools" / "adb")
if not file_storage.exists():
file_storage.mkdir(parents=True)
if not file_storage.is_dir():
print("[!] file_storage must be a directory")
exit()
device = setup_frida(device_name, env)
device = setup_frida(device_name, env, adb)
app = get_apkid(apk)[0]
if device.enumerate_applications([app]):
# Uninstall the APK if it already exist
subprocess.run(["adb", "uninstall", app], env=env)
subprocess.run(["adb", "install", str(apk.absolute())], env=env)
subprocess.run([adb, "uninstall", app], env=env)
subprocess.run([adb, "install", str(apk.absolute())], env=env)
with FRIDA_SCRIPT.open("r") as file:
jsscript = file.read()
@ -380,7 +396,7 @@ def collect_runtime(apk: Path, device_name: str, file_storage: Path, output: Tex
# time.sleep(0.3)
# print(f"[*] Classloader list received" + " " * 20)
explore_app()
explore_app(app, device=device.id, android_sdk=android_sdk_path)
# Try to find the Main class loader
main_class_loader: str | None = None

View file

@ -1,5 +1,28 @@
def explore_app():
manual_exploration()
from pathlib import Path
try:
from grodd_runner import grodd_runner # type: ignore
USE_GRODD = True
except ModuleNotFoundError:
USE_GRODD = False
def explore_app(
package: str,
device: str = "emulator-5554",
android_sdk: Path | None = None,
):
if USE_GRODD:
grodd_runner(
"grodd", device, timeout=300, package=package, android_sdk=android_sdk
)
else:
print(
"\033[31mGrodd is not available, you need to explore the app manually\033[0m"
)
manual_exploration()
def manual_exploration():

View file

@ -1,2 +1,2 @@
# Created by venv; see https://docs.python.org/3/library/venv.html
*
dist/
src/theseus_autopatcher/patcher_86_64_musl

View file

@ -0,0 +1,11 @@
#!/usr/bin/bash
#rustup target add x86_64-unknown-linux-musl
#doas pacman -S musl
FOLDER=$(dirname "$(realpath $0)")
env --chdir "${FOLDER}/../patcher" cargo build --release --target=x86_64-unknown-linux-musl
cp "${FOLDER}/../patcher/target/x86_64-unknown-linux-musl/release/patcher" "${FOLDER}/src/theseus_autopatcher/patcher_86_64_musl"
env --chdir "${FOLDER}" poetry build

View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "alembic"
@ -213,6 +213,24 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "coloredlogs"
version = "15.0.1"
description = "Colored terminal output for Python's logging module"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
files = [
{file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
{file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
]
[package.dependencies]
humanfriendly = ">=9.1"
[package.extras]
cron = ["capturer (>=2.4)"]
[[package]]
name = "contourpy"
version = "1.3.1"
@ -336,10 +354,10 @@ files = [
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""]
pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
@ -406,7 +424,7 @@ files = [
]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
[[package]]
name = "fonttools"
@ -469,18 +487,18 @@ files = [
]
[package.extras]
all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"]
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""]
interpolatable = ["munkres", "pycairo", "scipy"]
lxml = ["lxml (>=4.0)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr ; sys_platform == \"darwin\""]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""]
woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"]
unicode = ["unicodedata2 (>=15.1.0)"]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
[[package]]
name = "frida"
@ -611,6 +629,42 @@ files = [
docs = ["Sphinx", "furo"]
test = ["objgraph", "psutil"]
[[package]]
name = "grodd-runner"
version = "0.1.0"
description = "Grodd runner is a python program that tries to browse all activities of an Android application."
optional = true
python-versions = ">=3.11,<4.0.0"
groups = ["main"]
files = []
develop = false
[package.dependencies]
coloredlogs = ">=15.0.1,<16.0.0"
networkx = ">=3.4.2,<4.0.0"
uiautomator = ">=1.0.2,<2.0.0"
[package.source]
type = "git"
url = "ssh://git@gitlab.inria.fr/CIDRE/malware/grodd-runner.git"
reference = "HEAD"
resolved_reference = "323cabc4e92a89fc9bcd23231c11764a6e423a52"
[[package]]
name = "humanfriendly"
version = "10.0"
description = "Human friendly output for text interfaces using Python"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
files = [
{file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
{file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
]
[package.dependencies]
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
[[package]]
name = "ipython"
version = "9.0.2"
@ -785,7 +839,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"]
[[package]]
name = "lxml"
@ -1347,7 +1401,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions ; python_version < \"3.10\""]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
@ -1456,6 +1510,22 @@ files = [
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyreadline3"
version = "3.5.4"
description = "A python implementation of GNU readline."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "sys_platform == \"win32\""
files = [
{file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"},
{file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"},
]
[package.extras]
dev = ["build", "flake8", "mypy", "pytest", "twine"]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@ -1604,25 +1674,25 @@ files = [
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
[package.extras]
aiomysql = ["aiomysql (>=0.2.0) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
aiosqlite = ["aiosqlite ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\"", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17) ; python_version >= \"3\""]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2) ; python_version >= \"3\"", "mariadb (>=1.0.1,!=1.1.2) ; python_version >= \"3\""]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)", "mariadb (>=1.0.1,!=1.1.2)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql", "pymssql"]
mssql-pyodbc = ["pyodbc", "pyodbc"]
mypy = ["mypy (>=0.910) ; python_version >= \"3\"", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0) ; python_version >= \"3\"", "mysqlclient (>=1.4.0,<2) ; python_version < \"3\""]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mysql-connector = ["mysql-connector-python", "mysql-connector-python"]
oracle = ["cx_oracle (>=7) ; python_version >= \"3\"", "cx_oracle (>=7,<8) ; python_version < \"3\""]
oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg ; python_version >= \"3\"", "asyncpg ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\"", "greenlet (!=0.4.17) ; python_version >= \"3\""]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0) ; python_version >= \"3\"", "pg8000 (>=1.16.6,!=1.29.0) ; python_version >= \"3\""]
postgresql-asyncpg = ["asyncpg", "asyncpg", "greenlet (!=0.4.17)", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)", "pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1) ; python_version < \"3\"", "pymysql ; python_version >= \"3\""]
sqlcipher = ["sqlcipher3_binary ; python_version >= \"3\""]
pymysql = ["pymysql", "pymysql (<1)"]
sqlcipher = ["sqlcipher3_binary"]
[[package]]
name = "stack-data"
@ -1691,6 +1761,38 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "uiautomator"
version = "1.0.2"
description = "Python Wrapper for Android UiAutomator test tool"
optional = true
python-versions = "*"
groups = ["main"]
files = [
{file = "uiautomator-1.0.2.tar.gz", hash = "sha256:48a41c36f8347b643ff215d41b73ab2b4f542a0e3f7b110b85f7952b70742744"},
]
[package.dependencies]
urllib3 = ">=1.7.1"
[[package]]
name = "urllib3"
version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = true
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "wcwidth"
version = "0.2.13"
@ -1813,9 +1915,9 @@ files = [
]
[package.extras]
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.13,<4.0.0"
content-hash = "8e014dd037fc84f3479bd8821c65d1afa5672d3c3c99f39a4024d5197d7b26bb"
content-hash = "1bcf2198a037383d3d29aea2059caec389c58b17d89d17eb4ce8d5d9c71fa8b0"

View file

@ -11,6 +11,10 @@ requires-python = ">=3.13,<4.0.0"
[tool.poetry.dependencies]
theseus-frida = { path = "../frida" }
grodd-runner = {git = "ssh://git@gitlab.inria.fr/CIDRE/malware/grodd-runner.git", optional = true}
[tool.poetry.extras]
grodd = ["grodd-runner"]
[tool.poetry]
packages = [{include = "theseus_autopatcher", from = "src"}]

View file

@ -7,87 +7,7 @@ from shutil import which
from theseus_frida import collect_runtime
def spinner(symbs: str = "◜◠◝◞◡◟"):
while True:
for s in symbs:
yield s
def get_android_sdk_path() -> Path | None:
if "ANDROID_HOME" in os.environ:
return Path(os.environ["ANDROID_HOME"])
default = Path.home() / "Android" / "Sdk"
if default.exists():
return default
return None
def get_build_tools_path(toolname: str) -> Path | None:
def score_version(name: str):
score = []
for n in name.split("."):
if n.isdecimal():
score.append(int(n))
else:
score.append(-1)
return score
path = which(toolname)
if path is not None:
return Path(path)
path = which(toolname + ".exe")
if path is not None:
return Path(path)
sdk = get_android_sdk_path()
if sdk is None:
return None
tools = sdk / "build-tools"
if not tools.exists():
return None
options = []
for d in tools.iterdir():
if (d / toolname).exists():
options.append(d / toolname)
if (d / (toolname + ".exe")).exists():
options.append(d / (toolname + ".exe"))
if not options:
return None
return max(options, key=lambda d: score_version(d.parent.name))
def get_keytool_path() -> Path | None:
path = which("keytool")
if path is not None:
return Path(path)
path = which("keytool.exe")
if path is not None:
return Path(path)
else:
return None
def gen_keystore(keytool: Path, storepath: Path):
print(f"{str(storepath)} does not exist, creating it.")
subprocess.run(
[
str(keytool),
"-genkeypair",
"-validity",
"1000",
"-dname",
"CN=SomeKey,O=SomeOne,C=FR",
"-keystore",
str(storepath),
"-alias",
"SignKey",
"-keyalg",
"RSA",
"-v",
]
)
from .utils import *
PATCHER_BIN_PATH = Path(__file__).parent / "patcher_86_64_musl"
@ -100,30 +20,24 @@ def patch_apk(
apksigner: Path,
keystore: Path,
):
def dbg(l):
print(" ".join(l))
return l
subprocess.run(
dbg(
[
str(PATCHER_BIN_PATH.absolute()),
"--runtime-data",
str(runtime_data.absolute()),
"--path",
str(apk.absolute()),
"--out",
str(apkout.absolute()),
"-k",
str(keystore.absolute()),
"-z",
str(zipalign.absolute()),
"-a",
str(apksigner.absolute()),
"--code-loading-patch-strategy",
"model-class-loaders",
]
)
[
str(PATCHER_BIN_PATH.absolute()),
"--runtime-data",
str(runtime_data.absolute()),
"--path",
str(apk.absolute()),
"--out",
str(apkout.absolute()),
"-k",
str(keystore.absolute()),
"-z",
str(zipalign.absolute()),
"-a",
str(apksigner.absolute()),
"--code-loading-patch-strategy",
"model-class-loaders",
]
)
@ -228,6 +142,7 @@ def main():
device_name=args.device,
file_storage=tmpd / "dex",
output=fp,
android_sdk_path=get_android_sdk_path(),
)
patch_apk(
runtime_data=tmpd / "runtime.json",

View file

@ -0,0 +1,87 @@
import os
import argparse
import subprocess
import tempfile
from pathlib import Path
from shutil import which
def spinner(symbs: str = "-\\|/"):
while True:
for s in symbs:
yield s
def get_android_sdk_path() -> Path | None:
if "ANDROID_HOME" in os.environ:
return Path(os.environ["ANDROID_HOME"])
default = Path.home() / "Android" / "Sdk"
if default.exists():
return default
return None
def get_build_tools_path(toolname: str) -> Path | None:
def score_version(name: str):
score = []
for n in name.split("."):
if n.isdecimal():
score.append(int(n))
else:
score.append(-1)
return score
path = which(toolname)
if path is not None:
return Path(path)
path = which(toolname + ".exe")
if path is not None:
return Path(path)
sdk = get_android_sdk_path()
if sdk is None:
return None
tools = sdk / "build-tools"
if not tools.exists():
return None
options = []
for d in tools.iterdir():
if (d / toolname).exists():
options.append(d / toolname)
if (d / (toolname + ".exe")).exists():
options.append(d / (toolname + ".exe"))
if not options:
return None
return max(options, key=lambda d: score_version(d.parent.name))
def get_keytool_path() -> Path | None:
path = which("keytool")
if path is not None:
return Path(path)
path = which("keytool.exe")
if path is not None:
return Path(path)
else:
return None
def gen_keystore(keytool: Path, storepath: Path):
print(f"{str(storepath)} does not exist, creating it.")
subprocess.run(
[
str(keytool),
"-genkeypair",
"-validity",
"1000",
"-dname",
"CN=SomeKey,O=SomeOne,C=FR",
"-keystore",
str(storepath),
"-alias",
"SignKey",
"-keyalg",
"RSA",
"-v",
]
)

View file

@ -0,0 +1,25 @@
#!/usr/bin/bash
FOLDER=$(dirname "$(realpath $0)")
if adb devices | grep -q 'emulator-'; then
echo 'Emulator already started'
else
echo 'Emulator no started'
QT_QPA_PLATFORM=xcb alacritty -e ~/Android/Sdk/emulator/emulator -avd root34 &
fi
env --chdir "${FOLDER}" poetry build
TMP=$(mktemp -d)
python -m venv "${TMP}"
source "${TMP}/bin/activate"
pip install "${FOLDER}/dist/theseus_autopatcher-0.1.0-py3-none-any.whl"
#source .venv/bin/activate
adb wait-for-device
theseus-autopatch -a "${FOLDER}/../test_apks/dynloading/build/test_dynloading.apk" -o /tmp/patched_dynloading.apk -k "${FOLDER}/../test_apks/dynloading/ToyKey.keystore"
rm -rf "${TMP}"