5 Downloads Updated 1 week ago
ollama pull jordimurgo/methodprint-embed:2026-10-27
A 12M-parameter code encoder that maps methods — not whole binaries — into one shared 256-dimensional space across three architectures: Android smali, ARM64, and x86-64.
Model: jordimurgo/methodprint-embed:2026-10-27
Identifying whether a given method comes from a known library, and whether that identification survives obfuscation that renames classes and methods and differences in compiler and optimisation level.
The space is shared but the domains are not interchangeable: an ARM method and a smali method are embedded by the same encoder, yet cross-architecture comparison is explicitly out of scope. Compare smali to smali, ARM64 to ARM64, x86-64 to x86-64.
| Architecture | BERT (6 layers, 4 heads, hidden 256, intermediate 1024) |
| Parameters | 11.59M |
| Output | 256-dimensional, one vector per method |
| Context | 512 tokens, all-content-token mean pooling over non-overlapping windows |
| Quantization | F32 |
| Tokenization | case-sensitive-code-v3 — word-level, closed vocabulary of 26,256 tokens; no subwords |
| License | MIT |
Trained from random initialisation — no third-party weights are involved.
| Domain | Training windows |
|---|---|
Android smali (smali-v3) |
225,463 |
ARM64 (arm-v3) |
124,996 |
x86-64 (x86-v3) |
33,842 |
| Total | 384,301 |
Sources are open: Java/Kotlin libraries compiled from Maven artifacts with D8 in release and debug modes, Debian arm64 and amd64 binaries, and obfuscated variants produced with dProtect and Obfuscapk. No APK from any corpus enters training.
Training is two-stage: masked language modelling, then contrastive learning with compiler-variant positives (the same source built at different optimisation levels must land close) and same-library-different-method negatives.
ollama pull jordimurgo/methodprint-embed:2026-10-27
curl http://localhost:11434/api/embed -d '{
"model": "jordimurgo/methodprint-embed:2026-10-27",
"input": "invoke-virtual {v0}, Ljava/lang/String;->length()I"
}'
Returns one 256-dimensional L2-normalised vector.
llama.cpp:
llama-server -m methodprint-embed-2026-10-27-f32.gguf --embeddings \
--pooling mean --ctx-size 512 --port 8093
The GGUF path adds two tokens that the PyTorch path does without. This is a policy difference, not a weight difference: the tensors are byte-identical.
101 and appends a separator, regardless of the
vocabulary keys. Mean pooling therefore includes two tokens the production encoder
never sees.cos = 0.9999999; with the runtime’s framing the
gap is median cos = 0.993 and top-1 neighbour agreement is 78%.On a frozen smali whole-artifact benchmark, after recalibrating the threshold to the population the clustering step actually uses, this encoder reaches pair precision 1.000, recall 0.929, purity 1.000 over 12 true and 124 cross-identity validation pairs.
That number is real but small. The calibration set holds 12 true clusters, not the ~299 independent samples needed to support a 1% false-accept claim. Treat the result as “the separation exists on this frozen set”, not as a production rate.
On library containment inside larger binaries the encoder does not clear the bar: recall 0.75 with a 0.40 false-containment rate at 0.30 coverage over 12 positives and 81 negatives. Container detection is not a supported claim for this version.
MIT. The weights were trained from random initialisation on public sources, so there are no upstream model terms to inherit.
If you use it, cite the dataset this work builds on where applicable, and publish the list of samples used so the result can be reproduced.