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

8.3 KiB
Raw Blame History

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.