Static reconstruction of a packed Android VM and its managed/native bridge.
Read the disclaimer first. This is independent research, not an official project or an endorsement by any vendor, application developer, or government body. No vendor authorization is claimed.
Published repository: ArchLinuxAddict/sckit-vmp-research (public).
This repository is a sanitized research release of a completed Phase VI–XI
investigation. It documents the static structure of the SckitVMP container,
the recovered string encoding, the method-record layout, the native
libsnehakimakichut.so execution pipeline, and the observed Android runtime
behavior of one preserved application build.
The release deliberately does not include the third-party APK, DEX files, native libraries, the VMP asset, packet captures, screenshots, device logs, or full extracted inventories. Those inputs remain private and are not redistributed. Selected derived tables and checksums are included so the analysis can be evaluated without republishing proprietary binaries or raw personal/environmental data.
What is established:
- a reproducible structural parser for the container format (when a lawfully obtained matching input is supplied);
- the XOR/modified-UTF-8 string encoding and the class/method record layout;
- the distinction between method-table IDs and VM opcodes;
- the native bridge/registration and inner-invoke/dispatch addresses;
- an address-level opcode-handler table (159 opcodes, 116 unique handlers);
- observed 500/root, 900/Frida, and 300/VPN behavior and activation UI context.
What is not established:
- the exact detector predicate or method for the 500/900/300 outcomes;
- a complete method-payload-to-opcode decoder;
- the exact activation protocol, backend schema, or
STATUS: ONLINEmeaning; - TLS application payloads or a complete detector map.
- Project status and scope
- Executive summary
- Research motivation and questions
- Project history: Phases VI–XI
- Research environment and toolchain
- Methodology
- Architecture overview
- SckitVMP container reconstruction
- String decoding
- Method records and code offsets
- Managed-to-native bridge and pipeline
- IR dispatch and opcode inventory
- Detection observations
- Activation-flow analysis
- Network forensics and TLS limitations
- Evidence confidence levels
- Repository tree and file guide
- Prerequisites and environment setup
- Reproduction instructions
- Automated tests and validation
- Known limitations and unresolved questions
- Intellectual-property and redistribution
- Responsible-research scope and disclaimers
- Contributing
- Security reporting
- Future work
- Acknowledgments and third-party tools
- License explanation
- Final status and report links
The investigation began with observed behavior, not with a source tree. A preserved Android build displayed environment-dependent gates:
- a rooted device produced Error Code 500 / Root Detected;
- a Frida-instrumented run produced Error Code 900 / Frida Detected;
- a PCAPdroid VPN run produced Error Code 300 / VPN Detected.
The subsequent work separated the application's Java bridge, the native
libsnehakimakichut.so runtime, and the SckitVMP asset. Phase XI reconstructed
the container as an 8-byte SckitVMP header, six (offset, count) section
descriptors, two reserved words, a method-code region, and six data sections. It
then recovered the string transform: each encoded byte is XORed with
(byte_index & 0xA3) and decoded as modified UTF-8. All 6,004 string records in
the preserved asset decode, with their UTF-16 length hints matching.
The method table contains 1,845 records. VMPPBridge.invoke(id) is a method
selector into that table, not an opcode. In particular, invoke(1839) maps to
LoginDialog$100000002.run, the animation callback already identified in the
earlier phases. The native invoke stub at 0x13eb98 bounds-checks the ID and
calls inner_invoke at 0x12824c; 0x20-byte IR records are dispatched through a
tag table at 0x10b660 and a 159-entry relative jump table at 0x108ff0. The
table resolves to 116 unique handler targets.
The most important limitation is equally clear: the 16-byte method-code record
header and 724 method-ID matches are confirmed, but the variable payload after
the header has not been decoded into the IR opcode stream. The detection
predicates behind 500/900/300, the Please don't try to crack me!!! exception,
RcIsRooted, and the activation protocol remain unresolved. The repository
therefore publishes a verified structural/native model, not a claimed complete
detector bypass or activation implementation.
The project asked:
- Can a packed Android VM's container be reconstructed from its native parser and a preserved asset without executing modified production software?
- Are the managed invocation IDs opcodes, record offsets, or method selectors?
- How do method records, code records, and the native interpreter relate?
- Can the observed root/Frida/VPN outcomes be attributed to a specific VMP method, native function, or predicate?
- What can activation call sites establish without a key or server access?
- Which conclusions are observations, correlations, interpretations, or unresolved hypotheses?
The answers are intentionally bounded. A method mapping is not a detector map; a temporal network correlation is not causation; a string name is not proof of a predicate.
The public reports preserve phase identity. Earlier phases I–V established the target provenance, the APK/DEX inventory, the embedded engine artifacts, and the runtime harness. Phases VI–XI produced the native/VMP focus documented here.
| Phase | Objective | Methods | Principal results | Unresolved at phase end |
|---|---|---|---|---|
| VI | Native/VMP baseline | Ghidra, JADX, Frida, string bridge | Shield loader/JNI paths, isPmsHooked @ 0x165e48, VMP library structure, string-bridge behavior |
Exact root/Frida method; full VMP semantics |
| VII | Detection inventory | Static API scans, runtime A/B, owned fixture | Root/Frida/PMS/PairIP/activation layers separated; 1839 correction began | Detector implementation; signature/tamper paths |
| VIII | Unrooted differential | Unrooted device, permission flow, fixture validation | Root gate absent on unrooted run; VIP dialog reached; 1839 confirmed as animation callback | Exact detector; activation key |
| IX | Network/VPN/activation | tcpdump, PCAPdroid, ss, DNS/SNI correlation |
App traffic before root error; VPN 300 observed; Firebase/API context | TLS payloads; causality; activation schema |
| X | Call-site inventory | DEX regex/AST inventory, categories | 607 app sites + 117 log sites; 62 classes; 1839 retained as callback | Native record/opcode mapping; detector IDs |
| XI | Binary reconstruction | Ghidra, parser, table extraction, tests | Container sections, XOR/mutf8 strings, 1,845 method records, 724/724 code-ID matches, 159/116 dispatch table | Method payload decoder; exact predicates; activation protocol |
Phase XI corrected two earlier presentation errors without altering the private
historical records: the six header pairs are (offset, count) rather than
(offset, length), and the first string length is a decoded UTF-16-unit count,
not a byte length. The Phase X manifest's stale hashes for two inventories and
its executive summary are documented in the Phase X audit.
| Component | Version/use |
|---|---|
| Linux x86_64 | Analysis host |
| Python | 3.14.8 (standard library only for public scripts) |
| Ghidra | 12.2 PUBLIC, headless |
| Java | OpenJDK 27 |
llvm-objdump |
23.1.3 |
| JADX | 1.5.6 (private Phase VI–X work) |
| Rizin | 0.9.1 (private native inspection) |
| Android runtime | Two private test devices, Android 15/API 35 and Android 16/API 36 |
| Frida | 17.24.3 (private runtime observation) |
The toolchain was used read-only against the preserved third-party artifacts. No APK, DEX, native library, or VMP asset is included in this repository.
The project uses four evidence classes:
- Static observation — bytes, offsets, strings, ELF/Java structures, and decompiled control flow. Reproducible from a lawfully obtained matching input.
- Runtime observation — behavior recorded on private test devices. Cited as observation, not as a universal implementation.
- Correlation — two observed events (for example, DNS/TCP before a UI error) are placed on a timeline. Correlation does not prove causation.
- Hypothesis/interpretation — a mechanism that fits evidence but lacks a demonstrated predicate or path. It is labelled PROBABLE/UNRESOLVED.
The public data favors structural evidence. Runtime evidence is summarized with device/environment details removed. See methodology and limitations.
flowchart LR
A["Android app Java"] --> B["VMPPBridge\ninvoke(int,Object[])\ndecryptString(int)"]
B --> C["libsnehakimakichut.so\nJNI_OnLoad @0x138a58"]
C --> D["SckitVMP asset\nparser / section tables"]
D --> E["method records\n1,845 entries"]
D --> F["string table\n6,004 records"]
B --> G["native invoke @0x13eb98\nbounds + method selection"]
G --> H["inner_invoke @0x12824c\nexception/frame setup"]
H --> I["0x20-byte IR dispatch\n@0x1286b8"]
I --> J["159 opcodes\n116 handler targets"]
B --> K["decryptString @0x13f4ac\nstring index 0..6003"]
Addresses are Ghidra addresses with image base 0x100000; subtract 0x100000
for ELF-relative addresses. The diagram separates confirmed paths from the
unresolved method-payload decoder. See architecture.
The recovered top-level layout for the preserved 562,963-byte asset is:
| Offset | Meaning |
|---|---|
0x00 |
8-byte SckitVMP magic |
0x08 |
six (u32 offset, u32 count) pairs |
0x38 |
two reserved u32 (zero in the preserved sample) |
0x40 |
method-code region start |
| section 0 | 6,004 variable-length strings |
| section 1 | 677-entry class table |
| section 2 | 353 exception-type vectors |
| section 3 | 937 three-word reference records |
| section 4 | 1,845 five-word method records |
| section 5 | 22,118-byte blob; role unresolved |
The exact offsets and spans are in vmp_format_reconstruction.md. Section 5 is explicitly not called bytecode: no method-code offset points into it, and no consumer was demonstrated.
Each string record is:
uleb128 decoded UTF-16 code units
uleb128 encoded byte length
encoded bytes
Native FUN_001313f4 was shown to XOR byte i with i & mask; the VMP string
call uses mask 0xA3 at ELF 0x38fc4 / Ghidra 0x138fc4. The result is decoded
as modified UTF-8. In the preserved asset, all 6,004/6,004 records decode and
all decoded UTF-16 unit counts match the first field.
The full decoded string inventory is intentionally withheld because it contains third-party application text and implementation details. Public examples are limited to structural descriptors, the documented UI/status strings, and the selected method records needed to reproduce the findings.
Each of the 1,845 section-4 records has five u32 fields:
| Field | Interpretation | Status |
|---|---|---|
| 0 | class-table index | CONFIRMED |
| 1 | method-name string index | CONFIRMED |
| 2 | exception-vector index, 0xffffffff = none |
CONFIRMED |
| 3 | auxiliary value | OBSERVED; semantics unresolved |
| 4 | method-code record offset, 0 = no code | CONFIRMED |
Method record index is the invoke ID. 724/724 non-zero code offsets point
to a record whose first u32 equals the method ID. The code header is 16 bytes:
method ID, a flag-bearing word, a packed register/ins-like word, and an
instruction-count-like word. The variable payload after the header is not
decoded; the opcode-sequence mapping is therefore unresolved.
Selected records are published in data/method-record-excerpt.csv; the full inventory remains private.
The Java bridge declares native invoke(int,Object[]), decryptString(int),
initApp, initApp2, and decrypt(byte[]). The preserved native registration
table contains four entries:
invokeat0x13eb98;- an initialization body at
0x13f0a0used by two registration slots; decryptStringat0x13f4ac.
invoke uses JNI array accessors, bounds-checks the ID against 1,845, performs a
hash lookup, selects method metadata with a 0x40-byte stride, and calls
inner_invoke. The method ID is not an opcode. decryptString indexes a
separate string vector (0..6003) and returns a Java string.
The public crosswalk is an excerpt: data/managed-native-crosswalk-excerpt.csv. Full call-site inventories are private. See native_execution_pipeline.md and native_callgraph.md.
inner_invoke consumes 0x20-byte IR records. The opcode field is selected by a
tag-dependent offset table at Ghidra 0x10b660; the opcode (1..159) indexes the
relative jump table at 0x108ff0, dispatched at 0x1286b8. The table was
extracted to data/opcode_dispatch.csv: 159 opcode
entries, 116 unique handler addresses. Seven-opcode families (50–56, 57–63,
64–70, 71–77, 78–84, 85–91) and a partial 92/93/94/96 family are documented.
This is an address-level mapping. No opcode is assigned a Java operation name
without direct evidence. Handler 50–56 performs JNI object-array access
(GetArrayLength/GetObjectArrayElement), supporting an object-array handler
family description, not a complete invoke-* naming. See
opcode_mapping.md.
The following are observed outcomes, not a complete detector map:
| Observation | Status |
|---|---|
| Rooted-device run: Error Code 500 / Root Detected | CONFIRMED behavior |
| Frida-instrumented run: Error Code 900 / Frida Detected | CONFIRMED behavior |
| PCAPdroid VPN run: Error Code 300 / VPN Detected | CONFIRMED behavior |
invoke(1839) = LoginDialog$100000002.run animation callback |
CONFIRMED mapping |
Please don't try to crack me!!! under Frida at 1839/1744 |
CONFIRMED exception; cause unresolved |
RcIsRooted Bundle field |
UNRESOLVED |
isPmsHooked at libScKitShieldV1 0x165e48 |
CONFIRMED static implementation, separate library |
| Exact root/Frida/VPN predicates and VMP IDs | UNRESOLVED |
The decoded VMP table contains root/status UI text (This Device is rooted!,
STATUS:, ONLINE, OFFLINE) but no exact Root Detected, Frida Detected,
VPN Detected, Please don't try to crack me!!!, or RcIsRooted literal. The
VMP inner_invoke security-controlled branch is a candidate gate, not a
proven detector. See detection_correlation.md.
The activation UI obtains Settings.Secure.ANDROID_ID and uses a Firebase RTDB
base URL whose project-specific identifier is withheld from this release. The
recovered method records include fetch (1744), verify (1765),
pushDeviceId (1756), pushExpiryDate (1764), chkExp (1665), hasNet (1641),
and offlineOk (1685), owned by LoginDialog$NetTask or LoginDialog.
This establishes call ownership and UI context only. It does not establish the
request path/schema, key semantics, response fields, STATUS: ONLINE meaning, or
post-activation behavior. No key was guessed, forged, or used; no production
server was manipulated. See
activation_network_context.md.
Private runtime captures showed the app resolving and connecting to a public-sector attendance API host and a Firebase RTDB endpoint during startup. The app sockets were attributed to the target process at capture time. The temporal order places those connections before the observed root-error page, but does not prove that a server response caused the root decision.
Exact IP addresses, capture timestamps, device identifiers, PIDs/UIDs, raw packets, and project-specific Firebase identifiers are withheld. SNI/host metadata was visible; TLS application payloads and certificates were not decrypted. No certificate pinning bypass, CA installation, or proxy was used. See network_forensics.md.
| Level | Meaning in this repository |
|---|---|
| CONFIRMED | Reproduced from bytes/structure or directly observed runtime behavior; cited. |
| PROBABLE | Supported by converging evidence but no complete predicate/path. |
| CORRELATED | Events share a timeline/context; causation is not established. |
| UNRESOLVED | Not enough evidence; retained as an explicit question. |
Examples: the container layout and string decoder are CONFIRMED from the asset and native code; the 1839 method mapping is CONFIRMED; the 500/900/300 predicates are UNRESOLVED; network timing is CORRELATED. See findings.
.
├── README.md
├── DISCLAIMER.md
├── CONTRIBUTING.md
├── SECURITY.md
├── .gitignore
├── docs/
│ ├── overview.md
│ ├── methodology.md
│ ├── architecture.md
│ ├── phase-history.md
│ ├── findings.md
│ ├── limitations.md
│ ├── reproduction.md
│ ├── ethics-and-scope.md
│ └── publication-audit.md
├── reports/
│ ├── phase6/ sanitized native/VMP baseline
│ ├── phase7/ sanitized detection inventory
│ ├── phase8/ sanitized differential/VMP reports
│ ├── phase9/ sanitized network/VPN report
│ ├── phase10/ sanitized call-site report
│ └── phase11/ reconstructed format/pipeline/crosswalk reports
├── data/
│ ├── opcode_dispatch.csv
│ ├── method-record-excerpt.csv
│ ├── managed-native-crosswalk-excerpt.csv
│ ├── private-source-hashes.json
│ ├── public-evidence-summary.json
│ └── public-files.json
├── scripts/phase11/ parser and opcode-table extractor
├── tests/ self-contained public tests
└── examples/minimal-vmp/ synthetic boundary example
Full raw reports, inventories, logs, screenshots, captures, APKs, DEX files, libraries, VMP assets, and extracted application strings are not in this tree. They remain private; see publication audit.
The public checks require only:
- Python 3.10+ (tested with 3.14);
- Git;
- a POSIX shell.
No Ghidra, JADX, Frida, Android device, or proprietary input is required for the public tests. The full parser can be exercised only if you independently and lawfully obtain a matching VMP asset and native library; those files are not provided here.
# from a local checkout of the verified repository
cd sckit-vmp-research
python3 --versionDo not download the proprietary application or libraries from unauthorized sources. The analysis documents the exact preserved hashes so a lawful holder can verify an independently obtained copy; it does not provide a download path.
Self-contained checks:
python3 -m unittest discover -s tests -v
python3 scripts/check_public_release.pyParse the synthetic boundary example:
python3 scripts/phase11/vmp_format.py examples/minimal-vmp/minimal.vmpIf you have a matching lawful VMP input, the parser and tests can be pointed at
it with an environment variable; see reproduction. The
native opcode extractor requires the matching libsnehakimakichut.so; the
repository includes the expected table shape in data/opcode_dispatch.csv but
not the library.
Every command in this README corresponds to a script or test in the repository. The private runtime/device experiments cannot be reproduced without the private captures and an authorized test environment; they are documented as observations, not as public commands.
The private project's final Phase XI run was:
- Phase XI: 24 tests, 24 passed;
- combined private suite: 117 tests run, 115 passed, 2 skipped, 0 failed.
The public release is intentionally smaller and does not contain the
proprietary inputs. Its current local result is 14 tests run, 14 passed, 0
skipped, 0 failed (python3 -m unittest discover -s tests -v). The public
checks validate the synthetic container, string primitives, opcode CSV shape,
documentation links, data schemas, and the absence of excluded
binary/secret material. The audit is in
publication audit.
- The 16-byte method-code payload has not been decoded into the 0x20-byte IR; no method ID has a complete opcode sequence.
- The 22,118-byte section-5 blob and 937 reference records have no proven consumer.
- The exact 500/900/300 predicates and the anti-crack exception throw site are unresolved.
RcIsRootedhas no confirmed producer/consumer.- Activation request/response,
STATUS: ONLINE, and post-activation behavior are unresolved without an authorized key/account. - TLS application payloads and certificate details were unavailable.
- The Phase X manifest has a documented stale-hash mismatch for two inventories and its executive summary; the original was preserved and the public audit records the inconsistency.
- Runtime results depend on the specific preserved build, devices, and environment and do not generalize automatically.
See limitations and unresolved questions.
The original third-party application, its DEX/native code, the SckitVMP asset,
and captured traffic are not redistributed. Their names and identifiers are
used nominatively for analysis. No ownership of third-party software is claimed
or transferred.
The original analysis text and scripts in this repository are published without a grant of a blanket license because ownership/redistribution rights for all derived and third-party-adjacent material have not been independently established. See license explanation. Do not assume that this repository's presence grants permission to redistribute excluded third-party artifacts.
This repository is for understanding binary formats, managed/native execution, evidence handling, and reproducible analysis. It does not provide or endorse:
- modified/repacked production APKs;
- root, Frida, VPN, integrity, licensing, or anti-tamper bypass instructions;
- activation-key guessing/forging, token theft, or server manipulation;
- instructions for unauthorized access to systems or data.
Technical access to an APK or device does not itself establish authorization to modify, redistribute, or circumvent it. Read the full DISCLAIMER.md.
Contributions should improve reproducibility, evidence quality, or documentation without adding proprietary binaries, personal data, credentials, captures, or bypass material. Every claim should identify its evidence class. See CONTRIBUTING.md. Security/privacy reports should follow SECURITY.md.
Do not open a public issue containing a secret, device identifier, private capture, or personal data. If the repository is hosted on GitHub, use Security → Report a vulnerability for a private advisory. No private email address or guaranteed response time is invented. Reports should identify the file and category without repeating the sensitive value. See SECURITY.md.
- Decode the method-code payload into the 0x20-byte IR, starting with a non-security UI method, and validate the same decoder on a second flag value.
- Trace section-5/reference-record consumers and the method header's auxiliary fields.
- Build a controlled, authorized trace that distinguishes a global runtime security state from method-local behavior around the anti-crack exception.
- Obtain vendor-authorized activation test material, if available, to document
the request/response schema and
STATUS: ONLINEsemantics. - Perform independent TLS/certificate analysis only with explicit authorization and without defeating protections.
This work used Ghidra, JADX, Rizin, Frida, Android platform tools, llvm-objdump,
and Python. Their names and trademarks remain with their respective owners.
No tool vendor or application vendor endorsed this research.
The investigation also relied on an independently created test fixture for harness validation; that fixture is not included in this release because its provenance/publication status was not re-established for this repository.
No LICENSE file is included. The rights to grant a repository-wide license
could not be established for all original and derived material, and some
technical descriptions are adjacent to third-party software. Therefore this
release does not assert MIT/GPL/Apache or any other blanket license. The absence
of a license means no permission is granted beyond applicable law and the
limited reading/reproduction described here; it does not override copyright,
contract, privacy, or other obligations. If licensing is later established for
the original analysis only, a scoped license can be added with a clear file
inventory and attribution.
Status: published at
ArchLinuxAddict/sckit-vmp-research
(public, default branch main). The initial release commit
e63475171d3155a42d0b76883a7c82952ca700d3 was pushed and verified; this
documentation update is the follow-up release commit. The public artifact audit
and post-push checks are recorded in
publication-audit.md.
Detailed reports: