Skip to content

Commit

Permalink
Added new module jadx
Browse files Browse the repository at this point in the history
  • Loading branch information
domwhewell-sage committed Nov 7, 2024
1 parent 0f1db8c commit f0eca9a
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
111 changes: 111 additions & 0 deletions bbot/modules/jadx.py
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions bbot/test/test_step_2/module_tests/test_module_jadx.py
Original file line number Diff line number Diff line change
@@ -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="""<!DOCTYPE html>
<html>
<head>
<title>"blacklanternsecurity" - Android Apps on Google Play</title>
</head>
<body>
<a href="/store/apps/details?id=com.bbot.test&pcampaignid=dontmatchme&pli=1"/>
</body>
</html>""",
)
module_test.httpx_mock.add_response(
url="https://play.google.com/store/apps/details?id=com.bbot.test",
text="""<!DOCTYPE html>
<html>
<head>
<title>BBOT</title>
</head>
<body>
<meta name="appstore:developer_url" content="https://www.blacklanternsecurity.com">
</div>
</div>
</body>
</html>""",
)
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"

0 comments on commit f0eca9a

Please sign in to comment.