read and write memory in a macOS process by injecting a small dylib into it. ships as a static + dynamic C library, a DYLD_INTERPOSE shim that retrofits existing mach_vm_* tools, and a CLI wrapper around the same primitives.
two ways the payload gets in:
DYLD_INSERT_LIBRARIESif you spawn the target. simple, but hardened runtime strips it on most signed appsLC_LOAD_DYLIBrewrite if you can write to the binary on disk. ad-hoc resigns, works on hardened apps
curl -fsSL https://github.com/LabGuy94/vm_stowaway/releases/latest/download/vm_stowaway-macos-universal.tar.gz | sudo tar xz -C /usr/local --strip-components=1
or from source (needs xcode CLI tools):
curl -fsSL https://raw.githubusercontent.com/LabGuy94/vm_stowaway/master/install.sh | bash
# or:
git clone https://github.com/LabGuy94/vm_stowaway && cd vm_stowaway && make && sudo make install
installs libvm_stowaway.{a,dylib}, libvm_stowaway_payload.dylib, libvm_stowaway_machshim.dylib, <prefix>/include/vm_stowaway.h, and the vm_stowaway CLI. macOS 11+, universal (arm64 + x86_64).
#include <vm_stowaway.h>link with -lvm_stowaway. the payload dylib has to exist somewhere the controller can find it at runtime — by default it looks next to its own binary, then via the dyld search path; pass vm_stowaway_launch_opts_t.payload_path to override.
spawn a target and rewrite a global:
char err[256];
vm_stowaway_t *h = vm_stowaway_launch("./mytarget", argv, NULL, err, sizeof err);
if (!h) { fprintf(stderr, "%s\n", err); return 1; }
uint64_t addr = vm_stowaway_resolve(h, NULL, "g_secret");
uint64_t val = 0xdeadbeef;
vm_stowaway_write(h, addr, &val, sizeof val);
vm_stowaway_close(h);attach to a running, already-patched binary:
vm_stowaway_t *h = vm_stowaway_attach(pid, NULL, 5, err, sizeof err);call a function inside the target (up to 6 u64 args, return value in ret):
uint64_t ret = 0;
uint64_t args[] = { 0x10 };
vm_stowaway_call(h, vm_stowaway_resolve(h, "libsystem_malloc", "malloc"),
args, 1, &ret);software breakpoint, blocking wait, continue:
uint32_t bp;
vm_stowaway_break_set(h, addr, &bp);
uint32_t fired; uint64_t tid, pc;
vm_stowaway_break_wait(h, -1, &fired, &tid, &pc);
/* inspect regs/memory here */
vm_stowaway_break_cont(h, tid);mach-o patcher, standalone (no controller handle needed):
vm_stowaway_patch_opts_t opts = { .resign = 1 };
vm_stowaway_patch("/path/to/binary", "@rpath/libvm_stowaway_payload.dylib",
&opts, err, sizeof err);the full surface is in include/vm_stowaway.h:
- handle:
_launch,_attach,_close,_pid,_find_pid,_remote_info,_last_error - memory:
_read,_write,_resolve,_images,_regions,_scan - task/threads:
_dyld_info,_threads,_thread_get_state/_set_state,_allocate/_deallocate - code:
_call,_break_set/_wait/_clear/_cont - patcher:
_patch/_unpatch,_scan_hijacks/_hijack_drop - bundle scanners:
_scan_apps(hardened + permissive),_scan_electron,_find_app_bundle,_unharden,_grant_task_allow - system toggles:
_amfi_bypass_set/_get,_libval_disable_set/_get
examples in examples/controller_example.c (launch + read/write) and examples/attach_example.c (attach + call).
ctypes wrapper in python/. install the native library first, then:
pip install ./pythonimport vm_stowaway as vms
with vms.launch("./mytarget") as h:
addr = h.resolve("g_secret")
h.write_u32(addr, 0x1337)
print(h.read_u32(addr))
with vms.attach(vms.find_pid("Some.app")) as h:
print(h.call(h.resolve("getpid", image="libsystem_c")))
vms.patch("/path/to/binary", "@rpath/libvm_stowaway_payload.dylib")
for app in vms.scan_apps(permissive_only=True):
print(app.path)VM_STOWAWAY_LIB=... to override the dylib search path. full API in python/vm_stowaway/__init__.py — every public C function is exposed.
if you already have a memory-inspection tool that calls task_for_pid + the mach_vm_* family, libvm_stowaway_machshim.dylib is a drop-in: route those calls through the payload sitting inside the target, no SIP off / debug entitlements / taskport.allow needed.
DYLD_INSERT_LIBRARIES=/usr/local/lib/libvm_stowaway_machshim.dylib \
VM_STOWAWAY_TARGET_PID=1234 \
your_memory_toolenv:
VM_STOWAWAY_TARGET_PID— pid the tool is meant to inspect. required.task_for_pid(self, pid, &t)for this pid returns a sentinel port; every interposed mach call on that port is routed through the payloadVM_STOWAWAY_SOCK— override the unix socket path (default/tmp/vm_stowaway.<pid>.sock)VM_STOWAWAY_DEBUG— set to anything to log interposed calls to stderr
interposed: task_for_pid, pid_for_task, mach_vm_{read,read_overwrite,write,region,region_recurse,allocate,deallocate,protect}, task_{info,threads,suspend,resume,set_info}, thread_{get_state,set_state,info,suspend,resume,terminate}, mach_port_{deallocate,mod_refs,destroy}, plus the legacy vm_* aliases. anything not interposed falls through.
vm_stowaway wrap --pid <pid> -- <cmd> ... is the CLI shortcut that sets the env vars and execs.
same primitives wrapped for shell:
vm_stowaway launch ./mytarget
vm_stowaway patch ./mytarget "$PWD/build/libvm_stowaway_payload.dylib"
vm_stowaway unpatch ./mytarget libvm_stowaway_payload # undo a patch
# target = pid OR process name
vm_stowaway resolve mytarget some_global
vm_stowaway read 1234 mytarget+0x1234 64 --syms
vm_stowaway write 1234 0x10000c000 deadbeef
vm_stowaway scan 1234 0x100000000 0x200000000 --i32 1337
# cheat-engine style: snapshot then filter
vm_stowaway diff start 1234 0x100000000 0x300000000 --i32 0
vm_stowaway diff filter 1234 changed
# call a function inside the target
vm_stowaway call 1234 0x19ed1215c # = getpid()
vm_stowaway call 1234 libsystem_c.dylib+0xabc 1 2 3
# breakpoints (BRK/INT3 via mach exception ports)
vm_stowaway break set 1234 main+0x40
vm_stowaway break wait 1234
vm_stowaway break cont 1234 <tid>
# find apps already shipping with disable-library-validation + allow-dyld-environment
# (i.e. ones plain `launch` works against without any binary modification)
vm_stowaway scan-targets
# find Mach-O hijack candidates (writable paths where dropping a dylib makes
# the target load it as a missing dependency)
vm_stowaway scan-hijacks /Applications/Some.app/Contents/MacOS/Some
vm_stowaway hijack /Applications/Some.app/Contents/MacOS/Some --pick 0
# copy a hardened-runtime .app and ad-hoc resign it without hardened runtime
vm_stowaway unharden /Applications/Some.app /tmp/Some-open.app
# re-sign a bundle with com.apple.security.get-task-allow, so any same-uid
# process can task_for_pid the running target without root
vm_stowaway grant-task-allow /tmp/Some-open.app # in place
vm_stowaway grant-task-allow /Applications/Some.app /tmp/X # copy first
# system-wide toggles (need root + SIP off; AMFI flip also needs a reboot)
vm_stowaway amfi-bypass on|off|status # amfi_get_out_of_my_way=1 boot-arg
vm_stowaway disable-libval on|off|status # /Library/Preferences/...libraryvalidation
# DYLD_INTERPOSE shim — see "shim" above
vm_stowaway wrap --pid 1234 -- /Applications/BitSlicer.app/Contents/MacOS/BitSlicer
vm_stowaway --help for the rest.