Skip to content

rosetta-frida

Translate real (unobfuscated) Java class, method, and field names to the per-version obfuscated names your target app actually uses — at Frida-attach time.

Write one hook. Ship a map per release. Stop rewriting Frida scripts every time the obfuscator rotates.

  • Write once


    Hooks reference real names. The library translates to obfuscated names through a per-version map loaded at attach time. The same source compiles unchanged across every app version that has a matching map.

  • Per-version maps


    On-disk strict JSON — human-readable and machine- round-trippable. Comment-bearing YAML and TypeScript-module authoring inputs convert to canonical JSON via the CLI.

  • Health-checked at attach


    Before your hooks run, rosetta verifies the loaded map matches the running app (package, version, class lookups, AIDL descriptors, anchor strings). Wrong-map disasters surface as a single readable error, not a cascade of nulls.

  • CLI included


    rosetta init, pull, validate (+ --deep), convert, patch, extract, inspect, diff, merge, types — scaffold maps, pull verified maps from the community repo at build time, swap them into compiled bundles, diff/merge/deep-validate maps, generate real-name type stubs, and audit what every bundle actually targets.

The 30-second pitch

import { rosetta, type RosettaMap } from 'rosetta-frida';
import sampleMap from './maps/com.example.app/30405.json' with { type: 'json' };

const map = sampleMap as unknown as RosettaMap;

Java.perform(() => {
    // Open a session. Auto-detects the running app + version,
    // validates them against the loaded map, runs a health check.
    rosetta.session({ map });

    // Hook by real name. rosetta translates to the obfuscated name
    // (e.g. `aaaa.c`) under the hood.
    rosetta.hook(
        'com.example.app.IRemoteService$Stub.requestTicket',
        function (bundle, callback) {
            send({ stage: 'requestTicket', keys: bundle.keySet() });
            return rosetta.proceed(bundle, callback);
        },
    );
});

When the obfuscator rotates aaaa → aaab in the next release, you do not change a single line of the hook. You ship a new map for 3.5.0 and the same script targets the new version.

Why this exists

Large commercial Android apps rotate obfuscation on every minor release. The class anchors that worked for 1.2.x do not survive 1.3.x. The method letters inside often stay stable — but the class names at the left side of the table rotate, breaking every hook in the field.

Today the recovery loop is:

  1. Static-analysis pass (jadx + diff against the previous version) to discover the new obfuscated names.
  2. Patch every Frida script that hard-codes those names.
  3. Re-compile every bundle.
  4. Re-test on a device.

rosetta-frida decouples what we want to hook (real name) from how it is spelled today (obfuscated name) by introducing per-version mapping files and a lookup layer that consults them at attach time.

See Concepts for the full motivating story.

What's in V1.0

  • Three-tier API. Tier 1 is the declarative rosetta.hook(...) and rosetta.field(...) shorthand for the common 90% of hooks. Tier 2 is the Java.use-shaped surface for users who want the familiar Klass.method.overload(...).implementation = fn shape with name translation. Tier 3 is the low-level escape hatch: rosetta.map.resolveClass(...), rosetta.events.on(...), runtime overrides.
  • Session lifecycle. In-process auto-detect of app + version (via PackageManager.getPackageInfo, no ADB required), registry-bundle picking with optional fuzzy fallback, attach-time health check (class resolution, AIDL descriptors, anchor strings; fails fast in strict mode).
  • Strict validation. A Zod validator rejects malformed maps with structured {path, message} error reports — no silent corruption when a map drifts. The validator tracks the canonical map schema owned by rosetta-maps.
  • Comment-bearing authoring input. YAML supported via rosetta convert. Maps are pure data; TS/JS inputs are not supported.
  • PEM-style marker block. Maps embed into the compiled bundle between -----BEGIN ROSETTA MAP----- / -----END ROSETTA MAP----- comments. Swap maps without recompiling via rosetta patch.
  • CLI tooling. init, pull, validate (+ --deep), convert, patch, extract, inspect, diff, merge, types. pull fetches the verified map for an (app, version_code) from the community rosetta-maps repo at build time (never on the device).
  • Sample map and sample hook. A 15-class anonymized example map covering AIDL stubs, callbacks, overloads, fields, enums, anonymous inner classes.
  • 100% coverage. Every line of the runtime exercised (see the repository's CI for the current test count).

What is not in V1.0:

  • Runtime self-healing / discovery (deferred to V2).
  • A populated public maps corpus (the rosetta-maps repo is scaffolded and rosetta pull already fetches from it at build time; V1 still ships the example map in-repo as the worked reference).
  • rosetta diff, merge, types, migrate CLI commands and the validate --deep semantic checks (deferred to V1.5; migrate still pending). The build-time community-registry fetch shipped in V1.0 as rosetta pull.
  • Native (JNI / ELF symbol) mapping (deferred to V2+).

See Changelog for the full V1.0 changelog and the Roadmap for what's next (purpose + benefit of each upcoming item).

Getting started

Install rosetta-frida Read the quick start

Where this fits

flowchart LR
    A[sigmatcher] -->|emit| M[rosetta map (JSON)]
    B[jadx + hand-author] -->|emit| M
    M -->|imported at build time| C[frida-compile]
    C --> D[hook.bundle.js
marker block embedded] D --> F[Frida runtime] F --> G[(running Android app)]

rosetta-frida is the consumer of maps. It does not analyze APKs (sigmatcher does that). It does not replace Frida (your existing controller — Python frida, frida CLI, frida-node — loads the compiled bundle unchanged).

What rosetta-frida is not

  • Not a deobfuscator. It consumes maps. Use jadx / sigmatcher / hand-authoring to produce them.
  • Not a hook framework. It doesn't define what a "hook" is — Frida does. rosetta-frida just makes Java.use smarter.
  • Not a host-side orchestrator. It's a library that runs inside the Frida JS script. Your Python / Node / CLI controller stays unchanged.
  • Not iOS / desktop / non-Android Frida (V1.0). Future versions may add native-side mapping and non-Android targets.
  • Not an auto-discoverer of obfuscated names (V1.0). Maps are the only source of translation. Self-healing runtime discovery is on the V2+ roadmap.

Status

V1.0 is functionally complete and exercised by a full test suite at 100% line/branch/function/statement coverage. Today, install it by cloning and building from source; once the first release is tagged it will also be available from npm (npm install rosetta-frida) — see Installation. Publishing is tag-driven: a v* version tag triggers a release that rebuilds, re-runs the coverage gate, and publishes with npm provenance. For the milestone overview, see the Roadmap.