You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+49-19Lines changed: 49 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,8 +1,11 @@
1
1
# Mem4J — Memory Manipulation Library for Java
2
2
3
-
Mem4J is a Java library that exposes Windows process memory primitives through [JNA](https://github.com/java-native-access/jna). It lets you attach to a running process, resolve module base addresses, follow pointer chains, read and write typed values, and locate addresses by byte signatures — entirely from Java, without writing C++ or maintaining a JNI bridge.
3
+
Mem4J is a Java library that exposes process memory primitives — attaching to a running process, resolving module base addresses, following pointer chains, reading and writing typed values, and locating addresses by byte signatures — entirely from Java, without writing C++ or maintaining a JNI bridge.
4
4
5
-
The library wraps the Win32 APIs `OpenProcess`, `ReadProcessMemory`, `WriteProcessMemory`, `CreateToolhelp32Snapshot`, `Module32First/NextW`, and `Process32NextW` behind a small, opinionated API centered on a `Pointer` abstraction.
5
+
It runs on **both Windows and Linux** behind the same `Pointer` / `Memory` API. The platform-specific layer is selected at runtime via a `NativeAccess` abstraction:
6
+
7
+
- On **Windows** it wraps the Win32 APIs `OpenProcess`, `ReadProcessMemory`, `WriteProcessMemory`, `CreateToolhelp32Snapshot`, `Module32First/NextW`, and `Process32NextW` through [JNA](https://github.com/java-native-access/jna).
8
+
- On **Linux** it uses `/proc/<pid>/maps` for module discovery and `/proc/<pid>/mem` for memory I/O. Process lookup is performed via `/proc/<pid>/comm` and the `/proc/<pid>/exe` symlink.
6
9
7
10
---
8
11
@@ -22,9 +25,9 @@ The library wraps the Win32 APIs `OpenProcess`, `ReadProcessMemory`, `WriteProce
| Java |**11 or higher** (uses `ProcessHandle`, available since Java 9; project targets Java 11) |
25
-
| Operating system |**Windows only** (uses `kernel32.dll`, `user32.dll`, `shell32.dll`) |
26
-
| Architecture | The JVM bitness **must match** the target process. A 32-bit JVM cannot read/write a 64-bit process and vice versa — `ReadProcessMemory`/`WriteProcessMemory` will fail. Use a 64-bit JDK against 64-bit targets. |
27
-
| Privileges |**Administrator**(the library aborts otherwise via `Shell32.IsUserAnAdmin`) |
28
+
| Operating system |**Windows** (`kernel32.dll`, `user32.dll`, `shell32.dll`) **or Linux** (`/proc/<pid>/{maps,mem,comm,exe}` + `libc` for `geteuid`) |
29
+
| Architecture | The JVM bitness **must match** the target process. A 32-bit JVM cannot read/write a 64-bit process and vice versa. Use a 64-bit JDK against 64-bit targets. |
30
+
| Privileges |**Windows:**Administrator (checked via `Shell32.IsUserAnAdmin`). **Linux:**`euid == 0` (root) or the JVM granted `CAP_SYS_PTRACE`. The library aborts otherwise.|
@@ -72,6 +75,20 @@ You can also pin to a branch (e.g. `master-SNAPSHOT`) or a specific commit hash
72
75
73
76
---
74
77
78
+
## Architecture
79
+
80
+
Platform dispatch is centralised in `it.adrian.code.platform.NativeAccess`. The first call to `NativeAccess.get()` inspects `com.sun.jna.Platform` and reflectively loads exactly one backend, so the unused backend's classes (and its native libraries) are never initialised:
81
+
82
+
```
83
+
NativeAccess (abstract)
84
+
├── WindowsAccess → kernel32 / user32 / shell32 via JNA
`Pointer` and `Memory` route all reads, writes, process lookup, and privilege checks through this interface, so the same call sites work on both platforms. The Windows-specific `ProcessUtil.getModule`, `Shell32Util`, `SignatureManager` and `SignatureUtil` remain available unchanged for existing Windows callers.
> **Run this with Administrator privileges.**Without them the library shows a`MessageBox` and calls `System.exit(-1)`.
114
+
> **Privileges required.**On Windows the library aborts via`MessageBox` and `System.exit(-1)` without Administrator rights. On Linux it prints to stderr and exits unless `euid == 0` or the JVM has `CAP_SYS_PTRACE`.
97
115
98
116
---
99
117
100
118
## Usage
101
119
102
120
### Attaching to a process
103
121
104
-
`Pointer.getBaseAddress(processName)` opens a handle with `PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION` (`0x0010 | 0x0020 | 0x0008`) and resolves the base address of the main module that matches `processName`:
122
+
`Pointer.getBaseAddress(processName)` resolves the PID and the main module's base address for the named target. The mechanism is platform-specific:
123
+
124
+
-**Windows:** opens a handle via `OpenProcess` with `PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION` (`0x0010 | 0x0020 | 0x0008`) and locates the module through `CreateToolhelp32Snapshot` + `Module32First/NextW`. Match is against `MODULEENTRY32W.szModule` (e.g. `"game.exe"`).
125
+
-**Linux:** scans `/proc/*/comm` and the `/proc/*/exe` symlink basename to find the PID, then opens `/proc/<pid>/mem` for r/w. The module base is the lowest start address in `/proc/<pid>/maps` whose pathname basename equals the given name (or whose full path matches it).
105
126
106
127
```java
107
-
Pointer base =Pointer.getBaseAddress("game.exe");
128
+
Pointer base =Pointer.getBaseAddress("game.exe"); // Windows
129
+
// or
130
+
Pointer base =Pointer.getBaseAddress("game"); // Linux binary name
108
131
```
109
132
110
-
If the process cannot be found the library opens a `MessageBox`and exits. The returned `Pointer` carries an internal `offset` initialised to `0`.
133
+
If the process cannot be found the library aborts (MessageBox on Windows, stderr on Linux) and calls `System.exit(-1)`. The returned `Pointer` carries an internal `offset` initialised to `0`.
111
134
112
135
### Reading and writing typed values
113
136
@@ -155,6 +178,8 @@ int hp = Memory.readMemory(p, 0L, Integer.class);
155
178
156
179
### Signature (AOB) scanning
157
180
181
+
> ⚠️ **Windows-only.**`SignatureManager` and `SignatureUtil` are coupled to `WinNT.HANDLE`/`Kernel32.ReadProcessMemory`. The cross-platform `Pointer`/`Memory` APIs above work on Linux; AOB scanning currently does not.
182
+
158
183
When offsets shift between builds, byte signatures are more stable. `SignatureManager` scans the target module's address range for a pattern and returns the relative offset of the matched address:
159
184
160
185
```java
@@ -184,12 +209,16 @@ The mask uses `'x'` for "must match exactly" and any other character (typically
|`NativeAccess.get()`| both | Returns the platform-specific backend (`WindowsAccess` or `LinuxAccess`). |
215
+
|`NativeAccess.findPidByName(String)`| both | First PID whose executable name matches. |
216
+
|`NativeAccess.getModuleBaseAddress(pid, name)`| both | Base address of a loaded module / mapped binary. |
217
+
|`NativeAccess.getModuleSize(pid, name)`| both | Mapped size of the module (max end − min start across mappings on Linux). |
218
+
|`NativeAccess.isPrivileged()`| both | Admin on Windows, `euid == 0` on Linux. |
219
+
|`ProcessUtil.getProcessPidByName(String)`| both | Thin wrapper around `NativeAccess.findPidByName`. |
220
+
|`ProcessUtil.getModule(int pid, String name)`| Windows | Returns the `MODULEENTRY32W` for the named module (case-insensitive). Throws on Linux. |
221
+
|`Shell32Util.isUserWindowsAdmin()`| Windows | Returns `true` if the current process has Administrator rights; `false` on Linux. |
193
222
194
223
---
195
224
@@ -243,11 +272,12 @@ The read/write primitives map to fixed-width writes/reads in the target process,
243
272
244
273
## Limitations & caveats
245
274
246
-
-**Windows-only.**The library directly imports `kernel32`/`user32`/`shell32`. There is no Linux/macOS fallback.
275
+
-**macOS is not supported.**Only Windows and Linux backends ship. The factory throws `UnsupportedOperationException` on other platforms.
247
276
-**Bitness must match.** A 32-bit JVM cannot operate on a 64-bit target (or vice versa). Use the appropriate JDK distribution.
248
-
-**No anti-cheat / kernel bypass.** Memory access is performed through the standard documented Win32 API. Targets protected by anti-tamper drivers or Protected Process Light (PPL) will reject `OpenProcess` with `ERROR_ACCESS_DENIED`.
249
-
-**Process attachment is by executable name only.** If two processes share the same `szExeFile`, the first match wins.
277
+
-**No anti-cheat / kernel bypass.** Memory access goes through documented OS APIs. On Windows, targets protected by anti-tamper drivers or Protected Process Light (PPL) reject `OpenProcess` with `ERROR_ACCESS_DENIED`. On Linux, processes marked non-dumpable or owned by another user with no `CAP_SYS_PTRACE` cannot be opened.
278
+
-**Process attachment is by executable name only.** If two processes share the same name, the first match wins.
250
279
-**`indirect64()` assumes a 64-bit pointer.** There is no `indirect32()` variant; on 32-bit targets you would need to extend the API.
280
+
-**AOB scanning is Windows-only.**`SignatureManager` / `SignatureUtil` use `WinNT.HANDLE` directly. A cross-platform implementation on top of `NativeAccess` is on the roadmap.
251
281
-**The library calls `System.exit(-1)`** on missing privileges or missing process. This is intentional for the typical "trainer" use case but may be inconvenient when embedding Mem4J inside a larger application.
0 commit comments