From f0eca9a597f84952d986c6207dca9c4516e74e18 Mon Sep 17 00:00:00 2001 From: Dom Whewell Date: Thu, 7 Nov 2024 18:53:22 +0000 Subject: [PATCH] Added new module jadx --- bbot/modules/jadx.py | 111 ++++++++++++++++++ .../module_tests/test_module_jadx.py | 50 ++++++++ 2 files changed, 161 insertions(+) create mode 100644 bbot/modules/jadx.py create mode 100644 bbot/test/test_step_2/module_tests/test_module_jadx.py diff --git a/bbot/modules/jadx.py b/bbot/modules/jadx.py new file mode 100644 index 000000000..282547d5f --- /dev/null +++ b/bbot/modules/jadx.py @@ -0,0 +1,111 @@ +from pathlib import Path +from subprocess import CalledProcessError +from bbot.modules.internal.base import BaseModule + + +class jadx(BaseModule): + watched_events = ["FILESYSTEM"] + produced_events = ["FILESYSTEM"] + flags = ["passive"] + meta = { + "description": "Decompile APKs and XAPKs using JADX", + "created_date": "2024-11-04", + "author": "@domwhewell-sage", + } + options = { + "threads": 4, + } + options_desc = { + "threads": "Maximum jadx threads for extracting apk's, default: 4", + } + deps_ansible = [ + { + "name": "Install latest JRE (Debian)", + "package": {"name": ["default-jre"], "state": "present"}, + "become": True, + "when": "ansible_facts['os_family'] == 'Debian'", + }, + { + "name": "Install latest JRE (Arch)", + "package": {"name": ["jre-openjdk"], "state": "present"}, + "become": True, + "when": "ansible_facts['os_family'] == 'Archlinux'", + }, + { + "name": "Install latest JRE (Fedora)", + "package": {"name": ["java-openjdk-headless"], "state": "present"}, + "become": True, + "when": "ansible_facts['os_family'] == 'RedHat'", + }, + { + "name": "Install latest JRE (Alpine)", + "package": {"name": ["openjdk11"], "state": "present"}, + "become": True, + "when": "ansible_facts['os_family'] == 'Alpine'", + }, + { + "name": "Create jadx directory", + "file": {"path": "#{BBOT_TOOLS}/jadx", "state": "directory", "mode": "0755"}, + }, + { + "name": "Download jadx", + "unarchive": { + "src": "https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip", + "include": ["lib/jadx-1.5.0-all.jar", "bin/jadx"], + "dest": "#{BBOT_TOOLS}/jadx", + "remote_src": True, + }, + }, + ] + + allowed_file_types = ["java archive", "android application package"] + + async def setup(self): + self.threads = self.config.get("threads", 4) + return True + + async def filter_event(self, event): + if "file" in event.tags: + if not event.data["magic_description"].lower() in self.allowed_file_types: + return False, f"Jadx is not able to decompile this file type: {event.data['magic_description']}" + else: + return False, "Event is not a file" + return True + + async def handle_event(self, event): + path = Path(event.data["path"]) + output_dir = path.parent / path.name.replace(".", "_") + self.helpers.mkdir(output_dir) + success = await self.decompile_apk(path, output_dir) + + # If jadx was able to decompile the java archive, emit an event + if success: + await self.emit_event( + {"path": str(output_dir)}, + "FILESYSTEM", + tags="folder", + parent=event, + context=f'extracted "{path}" to: {output_dir}', + ) + else: + output_dir.rmdir() + + async def decompile_apk(self, path, output_dir): + command = [ + f"{self.scan.helpers.tools_dir}/jadx/bin/jadx", + "--threads-count", + self.threads, + "--output-dir", + str(output_dir), + str(path), + ] + try: + output = await self.run_process(command, check=True) + except CalledProcessError as e: + self.warning(f"Error decompiling {path}. STDERR: {repr(e.stderr)}") + return False + if not Path(output_dir / "resources").exists() and not Path(output_dir / "sources").exists(): + self.warning(f"JADX was unable to decompile {path}.") + self.warning(output) + return False + return True diff --git a/bbot/test/test_step_2/module_tests/test_module_jadx.py b/bbot/test/test_step_2/module_tests/test_module_jadx.py new file mode 100644 index 000000000..f949580d2 --- /dev/null +++ b/bbot/test/test_step_2/module_tests/test_module_jadx.py @@ -0,0 +1,50 @@ +from pathlib import Path +from .base import ModuleTestBase, tempapkfile + + +class TestJadx(ModuleTestBase): + modules_overrides = ["apkpure", "google_playstore", "speculate", "jadx"] + apk_file = tempapkfile() + + async def setup_after_prep(self, module_test): + await module_test.mock_dns({"blacklanternsecurity.com": {"A": ["127.0.0.99"]}}) + module_test.httpx_mock.add_response( + url="https://play.google.com/store/search?q=blacklanternsecurity&c=apps", + text=""" + + + "blacklanternsecurity" - Android Apps on Google Play + + + + + """, + ) + module_test.httpx_mock.add_response( + url="https://play.google.com/store/apps/details?id=com.bbot.test", + text=""" + + + BBOT + + + + + + + """, + ) + module_test.httpx_mock.add_response( + url="https://d.apkpure.com/b/XAPK/com.bbot.test?version=latest", + content=self.apk_file, + ) + + def check(self, module_test, events): + extract_event = [ + e + for e in events + if e.type == "FILESYSTEM" and "com_bbot_test_xapk" in e.data["path"] and "folder" in e.tags + ] + assert 1 == len(extract_event), "Failed to extract apk" + extract_path = Path(extract_event[0].data["path"]) + assert extract_path.is_dir(), "Destination apk doesn't exist"