Proc API

This API lets scripts inspect and modify the memory of external processes via a process userdata. Handles are created with ref_process(...) and released with deref_process(process).


🔑 Process Handles

Userdata Type

All functions operate on a process userdata created by ref_process.

-- Get a process handle by PID
local proc = ref_process(1234)

-- Get a process handle by name
local proc = ref_process("notepad.exe")

If the process is not found, ref_process returns nil.


Referencing / Releasing

process = ref_process(pid_or_name)   -- global
deref_process(process)               -- global
  • ref_process(pid)pid is a number (Windows PID).

  • ref_process("name.exe") – search by executable name.

  • deref_process(process) – releases the reference; the userdata becomes invalid.

  • You must also call deref_process if process:alive() returns false.

Each Lua script keeps its own list of referenced processes internally. Always call deref_process(proc) when you’re done.


🧬 Process Info

  • base_address() – Image base address of the process.

  • peb() – Address of the process’ PEB.

  • pid() – Process ID.

  • alive() - Determines whether the process is currently alive and running.


📖 Scalar Reads

All addresses are virtual addresses in the target process.

Unsigned Integers

Reads unsigned integers from addr.


Signed Integers

Reads signed integers from addr.


Floating Point

Reads floating point values from addr.


✏️ Scalar Writes

Unsigned Integers

Writes an unsigned integer at addr. Returns true on success.


Signed Integers

Writes a signed integer at addr.


Floating Point

Writes float / double.


🔤 Strings

Read and write null-terminated strings from the remote process.

Read Strings

  • rs – Reads up to max_chars characters from addr.

  • rws – Reads a wide string and converts it to a Lua UTF-8 string.

  • If nothing valid is read, an empty string is returned.


Write Strings

  • ws – Writes text bytes plus a terminating \0.

  • wws – Converts text to UTF-16 and writes plus wide \0.

Returns true on success.


📦 Raw Memory (byte tables)

Use Lua tables of bytes (1..N, values 0–255) for bulk reads / writes.

  • rvm – Returns a new table of length size filled with bytes from addr.

  • wvm – Writes all bytes in byte_table to addr. Returns true if all bytes are written.


🧮 SIMD Helpers

Convenience helpers for 16/32/64-byte SIMD vectors (raw bytes).

All values are plain bytes (0–255). wXXX returns false if the table is too short or the write fails.


📦 Modules & Pattern Scanning

Module Lookup

  • name – Module name, e.g. "notepad.exe", "client.dll".

  • Returns base, size on success.

  • Returns nil if the module is not found.


Code Pattern Scan

  • search_start – Starting address.

  • search_size – Number of bytes to scan.

  • signature – Pattern string, e.g. "48 8B ?? ?? ?? 89" (same format as your internal pattern scanner).

Returns:

  • addr – Address of the first match, or 0 if not found.


🔍 Example: Scanning Notepad (Lua)


proc:read_struct

Reads a single C-style structure from the target process using a descriptor table.


Signature


Parameters

base_address

Absolute address of the struct in the remote process.

descriptor

A Lua table describing each struct field:


Supported Types

Type
Size
Notes

u8

1

unsigned 8-bit

u16

2

unsigned 16-bit

u32

4

unsigned 32-bit

u64

8

unsigned 64-bit

i8

1

signed 8-bit

i16

2

signed 16-bit

i32

4

signed 32-bit

i64

8

signed 64-bit

f32

4

float

f64

8

double


Returns

A Lua table containing the decoded fields:


Example – Reading IMAGE_DOS_HEADER


📘 proc:read_struct_array

Reads an array of structures from the target process.


Signature


Parameters

base_address

Address of the first element in the array.

count

Number of array elements to read.

struct_size

Size in bytes of each array element.

descriptor

Same descriptor table used with read_struct.


Returns

A 1-based Lua array, where each element is a struct table:


Example – Reading a Player Array


proc:get_all_tebs()

Returns a Lua table of TEB addresses (uint64) for threads in the target process.

Syntax

Returns

  • { teb1, teb2, ... } where each teb is a virtual address in the remote process

  • {} if none found / enumeration fails


Virtual Memory Allocation

These functions let you allocate and manage RWX memory inside the target process. They are intended for advanced use; you are fully responsible for tracking and freeing any allocations you create.

🔴 Important Memory Management Notes (MUST READ!)

These points are critical for using alloc_vm safely:

  • Control Flow Guard If the target process has Control Flow Guard (CFG) enabled and you intend to execute code from this region, CFG may block all jumps and calls into the allocated memory. To avoid this, CFG must be fully disabled for the target process. Be aware that disabling CFG reduces the security of the device.

  • Allocations are not automatically freed. Every allocation you create must be manually released using free_vm(). If you lose track of an allocation, the memory will remain reserved until the target process exits.

  • Allocations persist for the lifetime of the target process. Memory created with alloc_vm is automatically released only when the target process itself closes.

  • Closing the Perception overlay will clear all allocations. When the overlay is closed, Perception will clean up every RWX allocation for all processes currently referenced. If you need an allocation to stay alive, do not close the overlay while that memory is being used.

  • Repeated unfreed allocations can exhaust physical memory. If you repeatedly allocate memory without freeing it, these leaks will accumulate and can eventually consume all available physical RAM, potentially degrading system performance or causing instability. If you are writing an injector make sure your inject frees the memory

  • If you are writing an injector, you must free allocated memory. Any injector, loader, or external tool that allocates memory through alloc_vm is responsible for freeing it once the memory is no longer needed. Failing to do so will leave permanent physical memory leaks until the target process exits.


Functions

uint64 proc:alloc_vm(size)

Allocates a block of read–write–execute (RWX) memory inside the target process and returns its base address.

Parameters

  • size — Size of the allocation in bytes.

Returns

  • Base address of the allocation on success.

  • 0 on failure.

Notes

  • Allocated memory is not tied to any module and is not discoverable through helpers like module queries or pattern scans. You must store and manage the returned address yourself.

  • The memory is RWX. If the target process uses Control Flow Guard (CFG) and you intend to execute code from this region, CFG may block jumps/calls unless the target allows it.


bool proc:free_vm(address)

Frees a region previously allocated with proc:alloc_vm().

Parameters

  • address — The exact base address returned by alloc_vm.

Returns

  • true if the region was successfully freed.

  • false if the address is invalid or the free fails.


🔗 Export Lookup – process:get_proc_address

Resolves the address of an exported function or symbol inside a module in the target process.

  • module_base Base address of the module inside the target process. Typically obtained from:

    • process:base_address(), or

    • local base, size = process:get_module("module_name.dll").

  • export_name Name of the exported function/symbol, e.g. "Sleep", "CreateFileW", "DllMain".

Returns

  • Absolute virtual address of the exported symbol in the target process (as a Lua integer).

  • 0 if the export is not found or the arguments are invalid.

Notes

  • This walks the module’s export table only – it does not look at imports or IAT.

  • The returned address is suitable for:

    • Remote call stubs / shellcode.

    • Manual thunks that jump directly to an API inside the target module.

Example


🧷 Import Table (IAT) Slot Lookup – process:get_import_rdata_address

Resolves the address of the import table entry (IAT slot) for a given imported function inside a module.

This does not return the function’s address itself – it returns the address of the pointer stored in .rdata / IAT. You can then read or patch that pointer for IAT hooks.

  • module_base Base address of the module whose imports you want to inspect/patch (usually the main EXE or a specific DLL), e.g. from process:get_module(...).

  • import_name Name of the imported function (as it appears in the import table), e.g. "Sleep", "MessageBoxW".

Returns

  • Address of the IAT entry (pointer-sized slot) as a Lua integer.

  • 0 if the import cannot be found or arguments are invalid.

Typical usage

  • Read the current imported function pointer:

  • Install a simple IAT hook (patch the slot to point to your stub):

Notes

  • Only sees functions present in the module’s static import table.

  • Manually resolved APIs (e.g. GetProcAddress into some custom region) will not show up here.

  • Designed for IAT hooks and for shellcode that wants to call through the same import slot as the module.


Virtual Memory Analysis

proc:virtual_query(address) -> (start:uint64, size:uint64, protection:int, heap_likely:bool)|nil

Queries the virtual memory region containing the given address.

Parameters:

  • address — a uint64 virtual address.

Returns:

  • start — region base address (uint64)

  • size — region size in bytes (uint64)

  • protection — region protection flags (int)

  • heap_likelytrue if the region is heuristically likely to be heap/alloc-like (bool)

  • nil if the address is not mapped or cannot be queried.


proc:get_vad_snapshot(heap_likely_only:bool) -> table

Builds a snapshot of virtual memory regions.

Parameters:

  • heap_likely_only — when true, only heap-like regions are returned; when false, all regions are returned.

Returns:

A 1-based Lua array of region tables:

Each region table contains:

  • start (uint64)

  • size (uint64)

  • end (uint64)

  • protection (int)

  • heap_likely (bool)


Memory Scanning Helpers

These helpers scan the target process memory by iterating over the snapshot of virtual memory regions. All scanning functions return a 1-based Lua table of matching virtual addresses:


proc:scan_string(text:string, heap_only:bool|nil) -> table

Scan for occurrences of an ASCII/multibyte constant string in memory.

  • text — the pattern to search for.

  • heap_only — optional bool filter restricting scan to heap-like regions (false if nil).


proc:scan_wstring(text:string, heap_only:bool|nil) -> table

Scan for occurrences of a UTF-16 (wide) string.

  • text — the wide string to search for.

  • heap_only — optional bool filter.


proc:scan_pointer(target:uint64, heap_only:bool|nil) -> table

Find all references to a pointer value.

  • target — the pointer value to search for.

  • heap_only — optional bool filter.


proc:scan_u64(value:uint64, heap_only:bool|nil) -> table

Scan for a 64-bit unsigned integer value in memory.

  • value — the uint64 value to search for.

  • heap_only — optional bool filter.


proc:scan_u32(value:int, heap_only:bool|nil) -> table

Scan for a 32-bit unsigned integer value in memory.

  • value — the uint32 value to search for.

  • heap_only — optional bool filter.


proc:scan_float(value:float, heap_only:bool|nil) -> table

Scan for a 32-bit float value in memory.

  • value — float pattern to search for.

  • heap_only — optional bool filter.


proc:scan_double(value:float, heap_only:bool|nil) -> table

Scan for a 64-bit double value in memory.

  • value — double pattern to search for.

  • heap_only — optional bool filter.

Last updated