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.
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.
┌────────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
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.
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.
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.
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_251
build.bat- Start a Java application with mutable state (game, simulation, GUI app)
- Inject
field_probe.dll - Move/interact during the 3-second snapshot window to generate deltas
- Read results at
%TEMP%\field_probe\
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}
]
}- Reverse engineering obfuscated objects — find position/rotation fields without deobfuscation
- Automated field mapping — run once per build to generate offset tables
- Behavioral classification — distinguish state fields from constants without bytecode analysis
- Fuzzing — identify which fields are actively written to by the application
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- 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.
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.
MIT — see LICENSE.
- jvm-oop-calibrator — Calibrate compressed OOP range (prerequisite for object reference filtering)
- hotspot-class-mirror-scanner — Enumerate static fields of a class mirror
- jni-function-table-inspector — Detect JNI function table hooks