Files
reverse-skills-max/vendor-new/apk-reverse/docs/tool-verification/EXTENSION-rasc.md

142 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# EXTENSION — rasc (the Rust ASC) measured against the Python one
Evidence for `skills/apk-reverse/references/rasc-and-droidsaw.md`. Every number below has the command
that produced it.
## What was measured
| Claim | Grade | Result |
|---|---|---|
| `rasc` builds from the `rust` branch on this host with no prebuilt artifact | **observed** | rustc 1.98.1 (gnu host) + MinGW-w64 gcc 16.1.0; `cargo build --release` **117 s**; binary 2,236,416 B |
| It is faster than the Python `droidasc` on the same archives | **observed** | 4.0–5.6× for `classes`, up to 16.2× for `findrefs` (tables below) |
| It returns the same answers | **observed** | **identical class-definition sets**, 30,768 / 30,768 with **0 differences in either direction**; `findrefs string` identical on one sample, `type` a superset (390 vs 384) |
| Its `getclass` decompiles ordinary classes correctly | **observed** | 16 of 17 JADX-judged classes matched literal-for-literal, 0 reorderings |
| **Its outer-class view of an enum does not inline the constant bodies, and says nothing** | **observed — and the first write-up of this row over-claimed; see §The blind spot** | JADX inlines the three anonymous subclasses; `rasc` prints a bare constant list. Both tools decompile `PasswordConverter$1/2/3` **fully** (404–407 B, containing `convert`/`getType`), so what is missing is an inlining step, not the code |
| Its own benchmark's 8.0× geometric mean reproduces here | **not reproduced, and explained** | that figure is from a 343 MiB / 567k-class archive; on a 34.8 MB / 30,768-class app the ratio is 4×. Both are real; the small-archive figure is what a normal task hits |
## Environment
Host Windows 11. Device not involved. Toolchain installed for this pass, into the gitignored
`tools/_work/`:
```
rustup-init (x86_64-pc-windows-gnu) -> rustc 1.98.1, cargo 1.98.1
winget install BrechtSanders.WinLibs.POSIX.UCRT -> gcc 16.1.0 (x86_64-w64-mingw32, UCRT)
```
The 32-bit MinGW already on this host (`C:\MinGW`, gcc 6.3.0, target `mingw32`) **cannot** link a
64-bit binary — `gcc -m64` reports `sorry, unimplemented: 64-bit mode not compiled in`, and the Rust
GNU target needs a 64-bit linker. E:\vs2022 has no VC tools (`cl.exe` / `link.exe` absent), and the
WSL2 Ubuntu distribution is registered but `HCS_E_SERVICE_NOT_AVAILABLE`, so no Linux path either.
```
$ python skills/apk-reverse/scripts/rasc_build.py --check
toolchain : git=E:\Git\cmd\git.EXE cargo=...\rust\cargo\bin\cargo.EXE rustc=...\cargo\bin\rustc.EXE gcc=C:\MinGW\bin\gcc.EXE(mingw32)
rasc : E:\apk-reverse\tools\_work\rust\target\release\rasc.exe rasc 0.1.0
result : ok
```
## Speed and agreement
Fastest of 3 runs (1 run on the largest), same machine, `PYTHONUTF8=1` for the Python side.
```
$ python tools/_work/compare_asc.py tools/_work/bench/apks/UnCrackable-Level3.apk --repeat 3
scenario rasc s py s speedup result agreement
classes 0.025 0.138 5.6 equal rasc=1396 py=1396
manifest 0.017 - - not comparable
findrefs string 0.022 0.341 15.6 overlap 100% rasc=3 py=3
findrefs type 0.022 0.358 16.2 overlap 98% rasc=390 py=384
```
Rounded to whole numbers in the reference file (4.7–14.7× there vs 5.6–16.2× here) because these are
two independent runs of the same measurement; both are in the observed range, and the run-to-run
spread on a sub-100 ms command is the honest resolution.
On a real 34.8 MB application (`MT-Manager.apk`, 30,768 classes):
```
$ python -c "... rasc classes vs droidasc listclass, set comparison ..."
rasc : 0.048 s 30768 classes
droidasc : 0.190 s 30768 classes
speedup : 4.0x
identical set : True
only in rasc : 0 only in droidasc: 0
```
`python skills/apk-reverse/scripts/rasc_build.py --verify tools/_phone-modules/MT-Manager.apk` runs
the same comparison and reports `{'result': 'ok', 'rasc_classes': 30768, 'droidasc_classes': 30768,
'only_rasc': 0, 'only_droidasc': 0}`, so the check travels with the kit rather than living in a note.
**A defect found on the Python side during this work:** `droidasc listclass` exits 1 with
`'gbk' codec can't encode character` on an app whose classes contain non-Latin identifiers, when the
console encoding is not UTF-8. With `PYTHONUTF8=1` it completes. `rasc` was unaffected. That is a
one-variable difference in the environment, not a property of either tool, and it is recorded because
it cost a round here.
## The blind spot, verified rather than reported
The `rasc` repository ships `bench/jadx_parity.py`, which treats a string literal JADX has and `rasc`
does not as lost code, and refuses to judge a class JADX itself could not decompile cleanly. Its own
class sampling takes every-nth entry from `rasc classes`, which on a real app lands mostly on
obfuscated names where JADX produces nothing — 8 of 10 picked that way were unjudgeable here, so the
sample measures the sampler. `tools/_work/parity_fair_sample.py` filters to app-like classes first
(ASCII, dotted, no inner class, not `R`/`BuildConfig`) and feeds those to the unmodified harness:
```
$ python tools/_work/parity_fair_sample.py tools/_phone-modules/MT-Manager.apk 20
classes in APK: 30768
app-like candidates: ...
judged 17 class(es) where JADX was clean; literals rasc lost: 1; reorderings: 0
```
The one finding, checked by hand with `tools/_work/verify_parity_finding.py`:
```
class : org.bouncycastle.crypto.PasswordConverter
jadx literals: ['ASCII', 'PKCS12', 'UTF8']
rasc literals: []
missing : ['ASCII', 'PKCS12', 'UTF8']
'ASCII' appears in rasc output as an identifier: True
...
VERDICT: form difference (enum constants printed as a list, literals not re-emitted)
```
**The automated verdict understates it, and the hand check then overstated it. Both corrections are
kept.** The harness counts literals, and the literals in JADX's output belong to the anonymous
subclasses it inlines; `rasc`'s outer-class view prints a bare constant list. The first write-up here
said "the method bodies are gone". **That was wrong, and the way it was wrong is worth recording: the
subclasses were never queried.** They exist as their own classes and both tools decompile them fully:
```
$ rasc getclass <apk> 'Lorg/bouncycastle/crypto/PasswordConverter$1;' -> 404 B, has convert(/getType(
$ droidasc getclass <apk> 'Lorg/bouncycastle/crypto/PasswordConverter$1;' -> 460 B, has both
$ ... $2 and $3 the same shape, 406-407 B vs 462-463 B
```
So the accurate finding is narrower than the first version and narrower than the harness's phrasing:
**`rasc` does not inline enum-constant bodies into the outer class view, and it does not say so** —
the bodies are one `getclass` away, on the subclass JADX itself names in a comment
(`// from class: ...PasswordConverter.1`). Neither does the Python tool inline; `droidasc`'s outer
listing is just richer (2,170 B vs 314 B, with `$VALUES`, `$values()` and the constructors explicit).
The general lesson this left behind, and the reason the episode stays in the record: a harness that
counts one artefact (literals) can point at a real difference while misdescribing it, and a hand
check that stops at the first comparison can convert that into an overstated claim. The corrective was
to query the classes the tool we were comparing against had named — which cost one command.
## Not established
- **No whole-APK parity run.** The comparison is on class sets, sampled `getclass` output and one
`findrefs` family per sample. `bench/corpus_parity.py` and `bench/jadx_parity.py` exist for a fuller
run and were used on one archive, not on a corpus.
- **`findrefs` on a large corpus was not run to completion.** On the 34.8 MB app, `findrefs string
http` returned 0 rows on both sides — a query with no hits is not a parity result, and a query that
does hit was only compared on the small archive.
- **`getclass` was judged on 17 classes of one app.** The enum blind spot is confirmed; whether other
class shapes (inner classes, synthetic bridges, `invokedynamic`) diverge the same way is untested.
- **No device or Android-side use.** `rasc` reads archives on the host; nothing here exercises it as a
device tool, and the reference file makes no claim about that.
- **The 8.0× upstream figure was not reproduced**, because the archive it was measured on (343 MiB,
567k classes) is not available here. The direction and the smaller ratio were measured.