Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/dec-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ jobs:
with:
distribution: "oracle"
java-version: "21"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.12.1"
- name: Test the JADX bridge
run: |
gradle --no-daemon -p declib/decompilers/jadx/worker test stageBridge
git diff --exit-code -- declib/decompilers/jadx/worker/bridge/declib-jadx-worker.jar
pytest tests/test_jadx.py -v
- name: Install Ghidra
uses: antoniovazquezblanco/setup-ghidra@v2.0.12
with:
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,27 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.12.1'
- name: Build and test the JADX bridge
run: |
gradle --no-daemon -p declib/decompilers/jadx/worker test stageBridge
git diff --exit-code -- declib/decompilers/jadx/worker/bridge/declib-jadx-worker.jar
- name: Install build
run: pip install build
- name: Build dists
run: python -m build
- name: Verify the JADX bridge is packaged
run: unzip -l dist/*.whl | grep 'declib/decompilers/jadx/worker/bridge/declib-jadx-worker.jar'
- name: Release to PyPI
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
password: ${{ secrets.PYPI_API_TOKEN }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,12 @@ dmypy.json
.pyre/

.idea/
.gradle/

**/*.rep/*
**/*.gpr
**/*.lock
**/*.lock~
**/*.swap
**/*.i64
**/*.DS_STORE
**/*.DS_STORE
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The minimum Python version is **3.10**.
- Binary Ninja: **>= 2.4**
- angr-management: **>= 9.0**
- Ghidra: **>= 12.0** (started in PyGhidra mode)
- JADX: **>= 1.5.6** (headless Java/Android support; see
[the JADX backend guide](./docs/jadx.md)). JADX is optional and is not
bundled with DecLib.

## Usage
DecLib exposes all decompiler API through the abstract class `DecompilerInterface`. The `DecompilerInterface`
Expand Down Expand Up @@ -109,4 +112,3 @@ one of the many BinSync projects.
<br>
<img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/reveng_ai.svg?raw=true" alt="RevEng AI" style="height: 50px; display: inline-block; vertical-align: middle;">
</p>

2 changes: 1 addition & 1 deletion declib/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def main():
"""
)
parser.add_argument(
"--decompiler", choices=["ida", "ghidra", "binja", "angr"], help="""
"--decompiler", choices=["ida", "ghidra", "binja", "angr", "jadx"], help="""
Force a specific decompiler for the server. If not specified, auto-detection will be used.
"""
)
Expand Down
94 changes: 94 additions & 0 deletions declib/api/decompiler_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,100 @@ def list_imports(self) -> List:
"""Return (lifted_addr, name, library) tuples for imported symbols."""
return self._send_request({"type": "method_call", "method_name": "list_imports"})

# Managed-code API (JVM/Dex backends such as JADX). These methods use
# opaque stable references rather than native integer addresses.
def managed_capabilities(self) -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_capabilities",
})

def managed_list_classes(self, filter=None, limit=1000) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_list_classes",
"kwargs": {"filter": filter, "limit": limit},
})

def managed_list_methods(self, class_ref=None, filter=None, limit=2000) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_list_methods",
"kwargs": {
"class_ref": class_ref,
"filter": filter,
"limit": limit,
},
})

def managed_list_fields(self, class_ref=None, filter=None, limit=2000) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_list_fields",
"kwargs": {
"class_ref": class_ref,
"filter": filter,
"limit": limit,
},
})

def managed_class_source(self, ref: str) -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_class_source",
"args": [ref],
})

def managed_method_source(self, ref: str) -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_method_source",
"args": [ref],
})

def managed_class_xrefs(self, ref: str) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_class_xrefs",
"args": [ref],
})

def managed_method_xrefs(self, ref: str, direction="both") -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_method_xrefs",
"args": [ref],
"kwargs": {"direction": direction},
})

def managed_field_xrefs(self, ref: str) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_field_xrefs",
"args": [ref],
})

def managed_list_resources(self, filter=None, limit=2000) -> List[Dict]:
return self._send_request({
"type": "method_call",
"method_name": "managed_list_resources",
"kwargs": {"filter": filter, "limit": limit},
})

def managed_get_resource(self, path: str, max_bytes=1024 * 1024) -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_get_resource",
"args": [path],
"kwargs": {"max_bytes": max_bytes},
})

def managed_get_manifest(self) -> Dict:
return self._send_request({
"type": "method_call",
"method_name": "managed_get_manifest",
})

def backend_eval(self, code: str, mode: str = "eval") -> Dict:
"""UNSAFE: run arbitrary Python in the backend process. See the interface docstring."""
return self._send_request({"type": "method_call", "method_name": "backend_eval", "args": [code], "kwargs": {"mode": mode}})
Expand Down
76 changes: 75 additions & 1 deletion declib/api/decompiler_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
Enum, Struct, FunctionArgument, Context, Decompilation, Typedef
)
from declib.decompilers import SUPPORTED_DECOMPILERS, ANGR_DECOMPILER, \
BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER
BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER, JADX_DECOMPILER

_l = logging.getLogger(name=__name__)

Expand Down Expand Up @@ -668,6 +668,76 @@ def list_imports(self) -> List[Tuple[int, str, str]]:
"""
raise NotImplementedError("Import listing is not implemented for this backend.")

#
# Managed-code API
#

def managed_capabilities(self) -> Dict:
"""Describe managed-code capabilities and the currently loaded input."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_list_classes(
self,
filter: Optional[str] = None,
limit: int = 1000,
) -> List[Dict]:
"""List classes using stable opaque references rather than addresses."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_list_methods(
self,
class_ref: Optional[str] = None,
filter: Optional[str] = None,
limit: int = 2000,
) -> List[Dict]:
"""List methods, preserving the full descriptor in each ``ref``."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_list_fields(
self,
class_ref: Optional[str] = None,
filter: Optional[str] = None,
limit: int = 2000,
) -> List[Dict]:
"""List fields using stable opaque references."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_class_source(self, ref: str) -> Dict:
"""Return decompiled source for a managed-code class."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_method_source(self, ref: str) -> Dict:
"""Return decompiled source for a managed-code method."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_class_xrefs(self, ref: str) -> List[Dict]:
"""Return nodes that reference a managed-code class."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_method_xrefs(self, ref: str, direction: str = "both") -> Dict:
"""Return callers and/or callees for a managed-code method."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_field_xrefs(self, ref: str) -> List[Dict]:
"""Return nodes that reference a managed-code field."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_list_resources(
self,
filter: Optional[str] = None,
limit: int = 2000,
) -> List[Dict]:
"""List resources in an Android/JVM input."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_get_resource(self, path: str, max_bytes: int = 1024 * 1024) -> Dict:
"""Decode a text or binary Android/JVM resource."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def managed_get_manifest(self) -> Dict:
"""Decode and return AndroidManifest.xml."""
raise NotImplementedError("Managed code is not implemented for this backend.")

def backend_eval(self, code: str, mode: str = "eval") -> Dict:
"""UNSAFE escape hatch: run arbitrary Python inside the backend process.

Expand Down Expand Up @@ -1426,6 +1496,10 @@ def discover(
extra_kwargs = {"flat_api": DecompilerInterface._find_global_in_call_frames('__this__')}
if project_dir:
extra_kwargs["project_location"] = project_dir
elif current_decompiler == JADX_DECOMPILER:
from declib.decompilers.jadx.interface import JadxInterface
deci_class = JadxInterface
extra_kwargs = {}
else:
raise ValueError("Please use DecLib with our supported decompiler set!")

Expand Down
Loading
Loading