Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java Field Differential Probe

A C++ DLL that automatically discovers which byte offsets of a Java object correspond to semantically meaningful fields — by observing how memory changes over time rather than relying on field names.

The Insight

Field names in obfuscated Java bytecode are randomized (a, bX, ZzP) and change between builds. But field behavior doesn't change: a rotation field still oscillates, a position field still tracks movement, a counter still increments monotonically.

This tool exploits that invariant: it identifies fields by their temporal behavior signature, not their name.

Algorithm

┌────────────────────────────────────────────────────────────────┐
│ Phase 1: SNAPSHOT                                              │
│   Read all doubles (offsets 16-256) and floats (128-600)       │
│   from target object → store as baseline                       │
│                                                                │
│ Phase 2: WAIT                                                  │
│   Sleep 3 seconds (allow state change: movement, tick, etc.)   │
│                                                                │
│ Phase 3: DIFF                                                  │
│   Re-read all values → compare vs baseline                     │
│   Collect candidates where |Δ| > threshold                     │
│   Sort ascending by |Δ| (smaller = more specific)              │
│                                                                │
│ Phase 4: VALIDATE (per candidate)                              │
│   Observe value every 100ms for up to 60 ticks                 │
│   Count "direction changes" (delta sign flips)                 │
│                                                                │
│   Classification:                                              │
│   ┌──────────────────────────────────────────────────────────┐ │
│   │ ≥2 dir changes in 15 ticks → CONFIRMED (oscillating)    │ │
│   │ 0 dir changes in 50 ticks  → REJECTED  (monotonic)      │ │
│   │ ≥1 dir change at timeout   → CONFIRMED (slow oscillator)│ │
│   │ 0 dir changes at timeout   → REJECTED                   │ │
│   └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘

Why Sort by |Δ|?

Smaller deltas are more discriminating. A rotation value that changed by 0.3° is more likely to be the exact field you want than a timer that jumped by 15000. Sorting candidates by ascending |Δ| puts the most specific fields first.

Why Direction Changes?

Direction changes separate two fundamentally different field types:

Behavior Example Direction Changes
Oscillating Pitch, yaw, position XYZ ≥2 in short window
Monotonic Tick counter, distance walked 0 (only increases)
Static Max health, entity type ID 0 (never changes)

A field that oscillates (changes direction multiple times) is definitively a dynamic state field — exactly what you want for position/rotation discovery.

Critical Finding: Pitch is double, Not float

This is a non-obvious discovery documented extensively in the source project's research notes.

In many Java game engines, camera rotation values are stored as double (8 bytes) internally, even though the rendering pipeline uses float. Reading them as float via Unsafe.getFloat returns garbage because it only reads the first 4 bytes of the 8-byte value.

Always scan for BOTH double and float candidates. This tool does both in parallel, with separate offset ranges for each.

Build

set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_251
build.bat

Usage

  1. Start a Java application with mutable state (game, simulation, GUI app)
  2. Inject field_probe.dll
  3. Move/interact during the 3-second snapshot window to generate deltas
  4. Read results at %TEMP%\field_probe\

Output

probe.log — detailed phases with validation timeline:

═══ Phase 1: SNAPSHOT ═══
  Scanning doubles [16-256] + floats [128-600]
  847 offsets recorded
  23 non-zero values

═══ Phase 3: DIFF ═══
  5 candidates with |Δ| > 0.5 (sorted ascending by |Δ|):
    [0] dbl @128  before=45.3210  after=47.8901  |Δ|=2.5691
    [1] dbl @136  before=-12.500  after=-14.200  |Δ|=1.7000
    [2] dbl @144  before=64.0000  after=64.0000  |Δ|=0.0000
    [3] flt @200  before=100.000  after=100.000  |Δ|=0.0000

═══ Phase 4: VALIDATION ═══
    [0] dbl @128 → CONFIRMED (oscillating, 3 dir changes in 12 ticks)
    [1] dbl @136 → CONFIRMED (oscillating, 2 dir changes in 15 ticks)
    [2] flt @200 → REJECTED (monotonic, 0 dir changes in 50 ticks)

═══ RESULTS ═══
  dbl @128  ✓ CONFIRMED  range=[43.12, 49.87]  dirChanges=5
  dbl @136  ✓ CONFIRMED  range=[-16.50, -10.20]  dirChanges=3
  flt @200  ✗ REJECTED   range=[100.00, 115.00]  dirChanges=0

probe.json — machine-readable:

{
  "version": "1.0",
  "candidates": [
    {"offset": 128, "type": "double", "status": "confirmed", "delta": 2.569, "min": 43.12, "max": 49.87, "dir_changes": 5},
    {"offset": 136, "type": "double", "status": "confirmed", "delta": 1.700, "min": -16.50, "max": -10.20, "dir_changes": 3},
    {"offset": 200, "type": "float", "status": "rejected", "delta": 15.0, "min": 100.0, "max": 115.0, "dir_changes": 0}
  ]
}

Practical Applications

  1. Reverse engineering obfuscated objects — find position/rotation fields without deobfuscation
  2. Automated field mapping — run once per build to generate offset tables
  3. Behavioral classification — distinguish state fields from constants without bytecode analysis
  4. Fuzzing — identify which fields are actively written to by the application

Configuration

All parameters are tunable at the top of field_probe.cpp:

static const double DIFF_THRESHOLD    = 0.5;     // minimum |Δ| to register as changed
static const int    SNAPSHOT_DELAY_MS = 3000;     // wait between snapshot and diff
static const int    VALIDATION_TICKS  = 60;       // observation window length
static const int    VALIDATION_TICK_MS = 100;     // ms per observation tick

Limitations

  • Requires user interaction during the snapshot window — the target object must actually change state
  • Cannot distinguish two fields of the same type that change identically (e.g., two position coordinates that move together)
  • Does not identify field names — only offsets and behavioral signatures
  • Thread safety: reads are non-atomic (no memory barrier). Values may be partially written during read. This is acceptable for classification but not for precise value extraction.

Legal

For security research, reverse engineering education, and JVM internals study in controlled environments.

Do not deploy against systems you do not own or have explicit authorization to test.

License

MIT — see LICENSE.

Related

About

Discovers Java object field offsets by behavioral analysis: snapshot-diff-validate cycle identifies oscillating fields vs monotonic accumulators without knowing field names.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages