rust-deobf 0.1.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rust_deobf/__init__.py +190 -0
- rust_deobf/__main__.py +9 -0
- rust_deobf/_version.py +10 -0
- rust_deobf/bench_emu.py +902 -0
- rust_deobf/cli.py +792 -0
- rust_deobf/data/libfunc_sigs.json +1391 -0
- rust_deobf/data/libfunc_sigs_std.json.gz +0 -0
- rust_deobf/data/runtime_sigs.json +234 -0
- rust_deobf/data/rust_versions.json +474 -0
- rust_deobf/decompiler_bridge.py +1771 -0
- rust_deobf/decompiler_selfcheck.py +260 -0
- rust_deobf/decompress.py +1153 -0
- rust_deobf/detect_vm.py +175 -0
- rust_deobf/disasm.py +193 -0
- rust_deobf/dump_unpacked.py +369 -0
- rust_deobf/emu_backend.py +273 -0
- rust_deobf/export_rules.py +618 -0
- rust_deobf/floss_export.py +186 -0
- rust_deobf/gen_runtime_sigs.py +257 -0
- rust_deobf/gen_sigdb.py +1912 -0
- rust_deobf/ghidra_annotate.py +591 -0
- rust_deobf/ghidra_dump_annotations.py +23 -0
- rust_deobf/llm_triage.py +285 -0
- rust_deobf/mcp_server.py +610 -0
- rust_deobf/recover.py +514 -0
- rust_deobf/recover_antianalysis.py +431 -0
- rust_deobf/recover_apihash.py +254 -0
- rust_deobf/recover_arm64.py +344 -0
- rust_deobf/recover_artifacts.py +335 -0
- rust_deobf/recover_async.py +3132 -0
- rust_deobf/recover_behavior.py +167 -0
- rust_deobf/recover_blobs.py +649 -0
- rust_deobf/recover_callsite.py +659 -0
- rust_deobf/recover_cfg.py +558 -0
- rust_deobf/recover_cryptify.py +199 -0
- rust_deobf/recover_crypto.py +305 -0
- rust_deobf/recover_dynamic.py +271 -0
- rust_deobf/recover_embedded.py +336 -0
- rust_deobf/recover_emulate.py +3834 -0
- rust_deobf/recover_enums.py +403 -0
- rust_deobf/recover_go.py +390 -0
- rust_deobf/recover_idioms.py +3458 -0
- rust_deobf/recover_imports.py +311 -0
- rust_deobf/recover_lang.py +254 -0
- rust_deobf/recover_libfuncs.py +1118 -0
- rust_deobf/recover_rust_meta.py +507 -0
- rust_deobf/recover_rust_strings.py +713 -0
- rust_deobf/recover_stackstrings.py +341 -0
- rust_deobf/recover_types.py +427 -0
- rust_deobf/soc_triage.py +387 -0
- rust_deobf/unpack.py +219 -0
- rust_deobf/unpack_emulate.py +922 -0
- rust_deobf-0.1.0rc2.dist-info/METADATA +992 -0
- rust_deobf-0.1.0rc2.dist-info/RECORD +58 -0
- rust_deobf-0.1.0rc2.dist-info/WHEEL +5 -0
- rust_deobf-0.1.0rc2.dist-info/entry_points.txt +2 -0
- rust_deobf-0.1.0rc2.dist-info/licenses/LICENSE +201 -0
- rust_deobf-0.1.0rc2.dist-info/top_level.txt +1 -0
rust_deobf/__init__.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Ve (VAHPEM) <ve0912084386@gmail.com>
|
|
3
|
+
"""rust_deobf - the installed form of rust-deobf's scripts/ directory.
|
|
4
|
+
|
|
5
|
+
pyproject.toml maps the `rust_deobf` package onto scripts/ unchanged, so every script keeps working
|
|
6
|
+
both ways: `python3 scripts/recover.py` from a clone, and `rust-deobf recover` / `import rust_deobf`
|
|
7
|
+
after `pip install rust-deobf`. This file only runs in the second case (nothing imports `scripts`
|
|
8
|
+
as a package in a clone), and keeps the scripts' existing wiring safe there:
|
|
9
|
+
|
|
10
|
+
* The scripts import each other by bare name (`import recover_emulate`) after putting their own
|
|
11
|
+
directory on sys.path. `import rust_deobf.recover_emulate` / `from rust_deobf import recover`
|
|
12
|
+
therefore return *that same* bare module object (one copy, one `Image` class), not a second copy
|
|
13
|
+
under a dotted name.
|
|
14
|
+
* Library mode (a host program imports rust_deobf): the package directory is moved to the END of
|
|
15
|
+
sys.path before every top-level import, so its bare module names (recover, unpack, disasm, ...)
|
|
16
|
+
can never shadow the host program's own modules or another installed package. The rust-deobf
|
|
17
|
+
CLI process turns this off (cli.py), because there the toolkit's own modules must win.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib
|
|
22
|
+
import importlib.abc
|
|
23
|
+
import importlib.machinery
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
from ._version import __version__
|
|
29
|
+
|
|
30
|
+
__all__ = ["__version__"]
|
|
31
|
+
|
|
32
|
+
_HERE = str(Path(__file__).resolve().parent)
|
|
33
|
+
|
|
34
|
+
# module stems that are NOT bare-imported siblings: package plumbing (cli imports _version
|
|
35
|
+
# relatively; it is the console-script target, so it lives only under its dotted name)
|
|
36
|
+
_PLUMBING = {"cli"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _siblings(directory: str) -> frozenset:
|
|
40
|
+
try:
|
|
41
|
+
names = os.listdir(directory)
|
|
42
|
+
except OSError:
|
|
43
|
+
return frozenset()
|
|
44
|
+
return frozenset(n[:-3] for n in names
|
|
45
|
+
if n.endswith(".py") and not n.startswith("_") and n[:-3] not in _PLUMBING)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _norm(p) -> str | None:
|
|
49
|
+
if not isinstance(p, str):
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
return os.path.normcase(os.path.abspath(p))
|
|
53
|
+
except (OSError, ValueError):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _PackageDirs(importlib.abc.MetaPathFinder):
|
|
58
|
+
"""One finder for rust_deobf and (when installed) rust_deobf_premium.
|
|
59
|
+
|
|
60
|
+
- `<package>.<sibling>` resolves to the bare `<sibling>` module (see the module docstring).
|
|
61
|
+
- in library mode, every top-level import first moves the registered package directories to the
|
|
62
|
+
end of sys.path (the scripts re-insert them at index 0 whenever one of them is imported)."""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self.library_mode = True
|
|
66
|
+
self.packages: dict = {} # package name -> (dir, frozenset of sibling stems)
|
|
67
|
+
self._spellings: set = set() # sys.path strings that name a registered dir
|
|
68
|
+
self._norms: set = set()
|
|
69
|
+
|
|
70
|
+
def register(self, package: str, directory: str) -> None:
|
|
71
|
+
self.packages[package] = (directory, _siblings(directory))
|
|
72
|
+
self._spellings.add(directory)
|
|
73
|
+
n = _norm(directory)
|
|
74
|
+
if n:
|
|
75
|
+
self._norms.add(n)
|
|
76
|
+
|
|
77
|
+
def _ours(self, entry) -> bool:
|
|
78
|
+
if entry in self._spellings:
|
|
79
|
+
return True
|
|
80
|
+
n = _norm(entry)
|
|
81
|
+
if n in self._norms:
|
|
82
|
+
if isinstance(entry, str) and os.path.isabs(entry): # "" (cwd) may change
|
|
83
|
+
self._spellings.add(entry)
|
|
84
|
+
return True
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def demote(self) -> None:
|
|
88
|
+
"""Move every registered package dir to the end of sys.path (each kept once)."""
|
|
89
|
+
path = sys.path
|
|
90
|
+
if not self._spellings or not path:
|
|
91
|
+
return
|
|
92
|
+
hits = [i for i, e in enumerate(path) if self._ours(e)]
|
|
93
|
+
if not hits:
|
|
94
|
+
return
|
|
95
|
+
n = len(hits)
|
|
96
|
+
if hits == list(range(len(path) - n, len(path))) and len({_norm(path[i]) for i in hits}) == n:
|
|
97
|
+
return # already at the end, no duplicates
|
|
98
|
+
keep, seen = [], set()
|
|
99
|
+
for i in hits:
|
|
100
|
+
k = _norm(path[i])
|
|
101
|
+
if k not in seen:
|
|
102
|
+
seen.add(k)
|
|
103
|
+
keep.append(path[i])
|
|
104
|
+
drop = set(hits)
|
|
105
|
+
path[:] = [e for i, e in enumerate(path) if i not in drop] + keep
|
|
106
|
+
|
|
107
|
+
def ensure_on_path(self, directory: str, front: bool) -> None:
|
|
108
|
+
present = any(_norm(e) == _norm(directory) for e in sys.path)
|
|
109
|
+
if front:
|
|
110
|
+
if not (sys.path and _norm(sys.path[0]) == _norm(directory)):
|
|
111
|
+
sys.path.insert(0, directory)
|
|
112
|
+
elif not present:
|
|
113
|
+
sys.path.append(directory)
|
|
114
|
+
|
|
115
|
+
def find_spec(self, fullname, path, target=None):
|
|
116
|
+
if path is None: # a top-level import
|
|
117
|
+
if self.library_mode:
|
|
118
|
+
self.demote()
|
|
119
|
+
return None
|
|
120
|
+
pkg, _, short = fullname.rpartition(".")
|
|
121
|
+
entry = self.packages.get(pkg)
|
|
122
|
+
if entry is None or short not in entry[1]:
|
|
123
|
+
return None
|
|
124
|
+
directory = entry[0]
|
|
125
|
+
return importlib.machinery.ModuleSpec(
|
|
126
|
+
fullname, _AliasLoader(self, short, directory),
|
|
127
|
+
origin=os.path.join(directory, short + ".py"))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _AliasLoader(importlib.abc.Loader):
|
|
131
|
+
"""Loads `<package>.<short>` as the bare module `<short>` (the one the scripts import)."""
|
|
132
|
+
|
|
133
|
+
def __init__(self, finder: _PackageDirs, short: str, directory: str) -> None:
|
|
134
|
+
self.finder, self.short, self.directory = finder, short, directory
|
|
135
|
+
self._spec = None
|
|
136
|
+
|
|
137
|
+
def create_module(self, spec):
|
|
138
|
+
self.finder.ensure_on_path(self.directory, front=not self.finder.library_mode)
|
|
139
|
+
mod = importlib.import_module(self.short)
|
|
140
|
+
origin = getattr(mod, "__file__", None) or ""
|
|
141
|
+
if _norm(os.path.dirname(origin)) != _norm(self.directory):
|
|
142
|
+
raise ImportError(
|
|
143
|
+
f"{spec.name}: the bare module name {self.short!r} resolves to {origin or mod!r}, "
|
|
144
|
+
f"not to {self.directory}: another top-level module of that name shadows it",
|
|
145
|
+
name=spec.name)
|
|
146
|
+
self._spec = getattr(mod, "__spec__", None)
|
|
147
|
+
return mod
|
|
148
|
+
|
|
149
|
+
def exec_module(self, module) -> None:
|
|
150
|
+
# module_from_spec() overwrote __spec__ with the alias spec: keep the bare module's own
|
|
151
|
+
if self._spec is not None:
|
|
152
|
+
module.__spec__ = self._spec
|
|
153
|
+
if self.finder.library_mode: # the module's own sys.path.insert(0, ...) undone now
|
|
154
|
+
self.finder.demote()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _finder() -> _PackageDirs:
|
|
158
|
+
for f in sys.meta_path:
|
|
159
|
+
if type(f).__name__ == "_PackageDirs" and getattr(f, "_rust_deobf", False):
|
|
160
|
+
return f
|
|
161
|
+
f = _PackageDirs()
|
|
162
|
+
f._rust_deobf = True
|
|
163
|
+
sys.meta_path.insert(0, f)
|
|
164
|
+
return f
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def register_package_dir(package: str, directory: str) -> None:
|
|
168
|
+
"""Used by rust_deobf_premium: alias its modules and keep its dir in the same sys.path policy."""
|
|
169
|
+
_finder().register(package, str(directory))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def ensure_package_dirs_on_path() -> None:
|
|
173
|
+
"""Every registered package dir on sys.path: appended (library mode) or first (CLI)."""
|
|
174
|
+
f = _finder()
|
|
175
|
+
for directory, _ in list(f.packages.values()):
|
|
176
|
+
f.ensure_on_path(directory, front=not f.library_mode)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def set_library_mode(on: bool) -> None:
|
|
180
|
+
"""True (default on import): keep the package dirs last on sys.path. The CLI sets False and puts
|
|
181
|
+
the package dir first, like `python3 scripts/<tool>.py` does."""
|
|
182
|
+
f = _finder()
|
|
183
|
+
f.library_mode = bool(on)
|
|
184
|
+
if on:
|
|
185
|
+
f.demote()
|
|
186
|
+
else:
|
|
187
|
+
ensure_package_dirs_on_path()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
register_package_dir(__name__, _HERE)
|
rust_deobf/__main__.py
ADDED
rust_deobf/_version.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Ve (VAHPEM) <ve0912084386@gmail.com>
|
|
3
|
+
"""The rust-deobf version: the single source of truth for both distributions.
|
|
4
|
+
|
|
5
|
+
Read by setup.py (core) and premium/setup.py at build time, by `rust-deobf --version` at run time,
|
|
6
|
+
and checked against the git tag by .github/workflows/release.yml. A plain file (not git-tag
|
|
7
|
+
derived) so a git clone, a shallow CI checkout, a GitHub zip download and an sdist all agree.
|
|
8
|
+
Bump it together with a new CHANGELOG.md section; see "Releasing" in CLAUDE.md.
|
|
9
|
+
"""
|
|
10
|
+
__version__ = "0.1.0rc2"
|