Single-header C++17 library for reading and writing the memory of an Android app running under BlueStacks Air on macOS — by the app's own virtual addresses, entirely from the host.
Nothing is injected. No agent runs in the guest. The guest is never modified. The host process walks the guest's ARMv8 page tables directly out of the VM's RAM.
Built with the help of Claude.
#include "memory.h"
mem::Target T;
T.attach(); // find the BlueStacks Air VMM process
T.discover("libil2cpp.so"); // locate the app, resolve its modules
uintptr_t base = T.moduleBase("libil2cpp.so");
int hp = T.read<int>(base + 0x1234);
T.write<int>(base + 0x1234, 999);No dependencies, nothing to link, no build system. Copy memory.h into your project and include it.
curl -O https://raw.githubusercontent.com/ankitrawatgit/Bluestacks-external-memory-rw-macos/main/memory.hThat's the whole library. example.cpp and build.sh in this repo exist only to demonstrate and verify it.
./build.sh && sudo ./memtest- macOS on Apple Silicon
- BlueStacks Air running an ARM64 Android guest with a 4 KB page granule
csrutil enable --without debug, set from Recovery- Your process running as root
Nothing else — no code signing, no entitlements. task_for_pid on another process is gated by SIP's debugging restriction, so lifting that and running as root is sufficient.
Set the SIP flag from Recovery — hold the power button at boot, then Utilities > Terminal:
csrutil enable --without debug
That weakens a real security boundary on your machine. Understand the trade before doing it, and put it back with csrutil enable when you're done.
#include "memory.h"
static mem::Target T;
// --- one-time setup -------------------------------------------------------
if (!T.attach()) { puts(T.error().c_str()); return 1; }
if (!T.discover("libil2cpp.so")) { puts(T.error().c_str()); return 1; }
uintptr_t base = T.moduleBase("libil2cpp.so");
// --- reads and writes -----------------------------------------------------
int hp = T.read<int>(base + 0x1234); // 0 if unmapped
float pos = T.read<float>(addr, -1.0f); // or your own default
T.write<int>(addr, 999);
int out;
if (T.tryRead<int>(addr, out)) { /* checked form */ }
| Method | Notes |
|---|---|
attach(pid) / attach(name = "BlueStacks") |
Opens the VMM task, locates guest RAM |
discover(anchorModule) |
One-time setup, a few seconds. Matches a substring of a SONAME |
moduleBase(name) |
Substring match on SONAME; 0 if not loaded |
modules() |
Every resolved module as {name, base} |
read<T>(va, def = T{}) |
Returns def on failure |
tryRead<T>(va, out) |
Returns false on failure |
write<T>(va, v) |
Goes live immediately |
readBytes / writeBytes |
Bulk; one syscall regardless of size |
readString(va, max) |
NUL-terminated, bounded |
chain(base, {…}, &ok) |
Dereferences between offsets, not after the last |
readChain<T>(base, {…}, def) |
Chain then read |
alive() |
Anchor still an ELF header — detects app restart |
invalidate() / setCache(bool) |
Translation cache control |
setProgress(fn) |
void(const char* stage, float frac) for discover() |
error() |
Why the last failure happened |
regions() / splitRamSuspected() |
Diagnostics, see limitations |
pid(), gpaBase(), pgd(), vaBits(), ramSize(), hostBase() |
Introspection |
T requires POD types (static_asserted). Every public method is mutex-guarded, so a polling thread and a render thread can share one Target.
- 4 KB page granule only. Level count, index width, and the output-address mask are hardcoded for it. Android 15+ images may ship 16 KB pages, which needs real code changes rather than tuning.
This is memory-forensics tooling. Reverse engineering your own applications, malware analysis inside a disposable emulator, debugging a process you can't attach to, and learning how ARMv8 address translation actually works.
Pointing it at software you don't own is your call and your risk. Modifying an online game's memory violates essentially every such game's terms of service and gets accounts banned — external tooling is not invisible, and anomalous values are visible server-side regardless of how they were produced. It also degrades the experience for other players, which is worth weighing separately from whether you get caught.
Don't use this to interfere with systems you don't own or have permission to test.