diff --git a/frida/poetry.lock b/frida/poetry.lock index 4c93c14..2048f6d 100644 --- a/frida/poetry.lock +++ b/frida/poetry.lock @@ -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" diff --git a/frida/pyproject.toml b/frida/pyproject.toml index 3921e4f..81ef2e8 100644 --- a/frida/pyproject.toml +++ b/frida/pyproject.toml @@ -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" diff --git a/frida/theseus_frida/__init__.py b/frida/theseus_frida/__init__.py index 7a264ec..a247315 100644 --- a/frida/theseus_frida/__init__.py +++ b/frida/theseus_frida/__init__.py @@ -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 diff --git a/frida/theseus_frida/app_exploration.py b/frida/theseus_frida/app_exploration.py index d6ff862..984eed7 100644 --- a/frida/theseus_frida/app_exploration.py +++ b/frida/theseus_frida/app_exploration.py @@ -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(): diff --git a/theseus_autopatcher/.gitignore b/theseus_autopatcher/.gitignore index f514b74..6e91936 100644 --- a/theseus_autopatcher/.gitignore +++ b/theseus_autopatcher/.gitignore @@ -1,2 +1,2 @@ -# Created by venv; see https://docs.python.org/3/library/venv.html -* +dist/ +src/theseus_autopatcher/patcher_86_64_musl diff --git a/theseus_autopatcher/build.sh b/theseus_autopatcher/build.sh new file mode 100644 index 0000000..8e11b3a --- /dev/null +++ b/theseus_autopatcher/build.sh @@ -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 diff --git a/theseus_autopatcher/poetry.lock b/theseus_autopatcher/poetry.lock index c2757de..d1a1994 100644 --- a/theseus_autopatcher/poetry.lock +++ b/theseus_autopatcher/poetry.lock @@ -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" diff --git a/theseus_autopatcher/pyproject.toml b/theseus_autopatcher/pyproject.toml index bc9d6a8..7aecb01 100644 --- a/theseus_autopatcher/pyproject.toml +++ b/theseus_autopatcher/pyproject.toml @@ -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"}] diff --git a/theseus_autopatcher/src/theseus_autopatcher/__init__.py b/theseus_autopatcher/src/theseus_autopatcher/__init__.py index 9278d23..20a28dd 100644 --- a/theseus_autopatcher/src/theseus_autopatcher/__init__.py +++ b/theseus_autopatcher/src/theseus_autopatcher/__init__.py @@ -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", diff --git a/theseus_autopatcher/src/theseus_autopatcher/utils.py b/theseus_autopatcher/src/theseus_autopatcher/utils.py new file mode 100644 index 0000000..ead0cd2 --- /dev/null +++ b/theseus_autopatcher/src/theseus_autopatcher/utils.py @@ -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", + ] + ) diff --git a/theseus_autopatcher/test.sh b/theseus_autopatcher/test.sh new file mode 100644 index 0000000..bd1dcab --- /dev/null +++ b/theseus_autopatcher/test.sh @@ -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}"