Proc API
The Proc API lets AngelScript access and edit memory of external processes using a lightweight handle type proc_t.
🔑 Process Handles
Referencing Processes
proc_t ref_process(uint pid)
proc_t ref_process(const string &in name)pid– Windows process ID.name– Executable name ("notepad.exe","cs2.exe", etc).
Returns a proc_t handle, or 0 if the process could not be opened.
Releasing Handles
void proc_t::deref()Decrements the internal reference and clears the handle.
After calling deref(), the handle becomes invalid (further calls are no-ops).
You must also call deref() if proc_t::alive() returns false.
🧬 Process Info
uint64 proc_t::base_address() const
uint64 proc_t::peb() const
uint proc_t::pid() const
bool proc_t::alive() constbase_address()– Image base (module base) of the process.peb()– Address of the process' PEB.pid()– Process ID.alive()– Determines whether the process is currently alive and running.
Address Validity
Check if an address is mapped inside target process.
📖 Scalar Reads
All addresses are virtual addresses in the target process.
Unsigned Integers
Reads unsigned integer values at addr.
Signed Integers
Reads signed integer values at addr.
Floating-Point
Reads float / double from addr.
✏️ Scalar Writes
Unsigned Integers
Writes unsigned integer values.
Returns true on success.
Signed Integers
Writes signed integer values.
Floating-Point
Writes float / double.
🔤 Strings
All string helpers assume null-terminated memory on the remote side (for reads) and write a terminating null when writing.
Reading Strings
rs– Reads an ANSI/UTF-8 style string fromaddr.rws– Reads a UTF-16 wide string, converts to UTF-8string.
max_chars is a hard cap to avoid walking off invalid memory.
Writing Strings
ws– Writestextas ANSI/UTF-8 bytes plus trailing\0.wws– Convertstextto UTF-16 and writes wide string plus trailingL'\0'.
Returns true on success.
📦 Raw Memory (byte arrays)
Uses array<uint8> for bulk reads/writes.
rvm– Readssizebytes starting ataddrintoout_buf.out_bufis resized tosize.wvm– Writes bytes fromin_buftoaddr. Returnstrueif all bytes were written.
🧮 SIMD Helpers
Convenience wrappers around 16/32/64-byte vectors (__m128/__m256/__m512).
All data is exposed as raw bytes.
r128/r256/r512– Read 16/32/64 bytes into the output array (array is resized).w128/w256/w512– Write 16/32/64 bytes from the input array. Returnsfalseif the array is too small or the write fails.
Thread Environment Blocks (TEB)
array<uint64>@ proc_t::get_all_tebs() const
Returns a list of TEB (Thread Environment Block) addresses for all threads currently discovered in the target process.
Signature
Returns
array<uint64>—{ teb1, teb2, ... }Each entry is a virtual address of a TEB in the target process.
Returns null if no threads are found or enumeration fails.
📦 Modules & Pattern Scans
Module Lookup
Looks up a module by name (e.g. "notepad.exe", "client.dll"):
On success:
module_base– Base address of the module.module_size– Size in bytes.Returns
true.
Returns
falseif the module is not found.
Code Pattern Search
Scans [search_start, search_start + search_size) for a textual pattern:
signature– Pattern string, e.g."48 8B ?? ?? ?? 89"(exact format is the same as used internally by your pattern scanner).find_code_patternreturns the address of the first match, or0if not found.find_all_code_patternspopulatesresultwith all matching addresses.
🔍 Example: Scanning Notepad
Process Struct Helpers
These helpers let you describe a C-style structure in a dictionary and read it.
📦 Struct Descriptor (common concept)
A struct descriptor is a dictionary where each entry:
Key = field name (string)
Value = another
dictionarywith:"offset"– byte offset from struct base (int64)"type"– field type string ("u16","i32","f32", etc.)(optional)
"max_chars"for string fields
Example:
Supported field types
"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
32-bit float
"f64"
8
64-bit double
"string"
var
UTF-8 C-string in target
"wstring"
var
UTF-16 string → UTF-8
For "string" / "wstring", you can add "max_chars" to limit how many characters are read (default ~256, clamped to 1..8192).
📘 bool proc_t::read_struct(uint64 addr, dictionary &out result, const dictionary &in desc)
bool proc_t::read_struct(uint64 addr, dictionary &out result, const dictionary &in desc)Reads one structure instance from the target process into an existing dictionary.
Signature
Parameters
addrAbsolute address of the struct in the remote process.resultDictionary that will be filled with the decoded fields. (Existing contents are cleared before filling.)descStruct descriptordictionarydescribed above.
Return
trueon successfalseon failure (invalid process handle, invalid descriptor, etc.)
Stored value types
To keep things simple (and consistent with your JSON API):
All integer fields are stored as
int64All float fields are stored as
doubleString fields are stored as script
string
So you can safely read with:
You can also read into uint64 etc. – the dictionary will convert as needed.
Example – Read one player struct
📘 bool proc_t::read_struct_array(uint64 base, uint count, uint size, array<dictionary>@ &out result, const dictionary &in desc)
bool proc_t::read_struct_array(uint64 base, uint count, uint size, array<dictionary>@ &out result, const dictionary &in desc)Reads an array of structures into an array<dictionary>.
Signature
Parameters
baseAddress of the first element.countNumber of elements to read.sizeSize in bytes of each element in the array (sizeof(struct)).resultarray<dictionary>that will be resized tocount. Each element will be filled with that struct's data. If an element isnullinternally, it is created.descSame struct descriptor as forread_struct.
Return
trueon successfalseif the process is invalid,count/sizeis 0, etc.
Example – Player list
🧪 Full Example Script – Read IMAGE_DOS_HEADER from notepad.exe
IMAGE_DOS_HEADER from notepad.exeThis is a complete AngelScript file you can drop into your environment. It:
Builds a DOS header descriptor
Uses
proc_t.read_structto read it fromnotepad.exePrints all key fields and the computed NT header address
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.
:red_circle: 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_vmis 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_vmis 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_t::alloc_vm(uint size)
Allocates a block of read–write–execute (RWX) memory in the target process and returns its base address.
Parameters
size— Size of the allocation in bytes.
Returns
Base address of the allocated region on success.
0on failure.
Notes
The allocated region is not associated with any module and is not discoverable through higher-level helpers (e.g. module lookup, pattern scan helpers, etc.). 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 from this region, CFG may block jumps/calls into it unless the target process allows it.
bool proc_t::free_vm(uint64 address)
Frees a region previously allocated with alloc_vm.
Parameters
address— Base address previously returned byalloc_vm.
Returns
trueif the region was successfully freed.falseif the address is invalid or the free fails.
🔗 Export Lookup – get_proc_address
Resolves the address of an exported symbol inside a module in the target process.
module_baseBase address of the module inside the target process. Typically obtained from:proc_t::base_address(), orproc_t::get_module("module_name.dll", base, size).
export_nameName of the exported function or symbol, e.g."Sleep","CreateFileW".
Returns
Absolute virtual address of the exported symbol in the target process.
0if the export is not found or the arguments are invalid.
Notes
This walks the module's export table only – it does not search imports or runtime-resolved addresses.
The address is directly usable for:
Call stubs / shellcode in the remote process.
Manual thunking (e.g. "jmp [resolved_addr]" from your injected code).
You are responsible for ensuring the returned address is valid / executable before calling into it.
Example
🧷 Import Table (IAT) Slot Lookup – 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 directly – it returns the address of the pointer stored in .rdata / IAT. That pointer can then be read or patched (for IAT hooks, trampolines, etc.).
module_baseBase address of the module whose imports you want to inspect/patch (usually the main EXE or a specific DLL), obtained frombase_address()orget_module(...).import_nameName of the imported function as it appears in the import table, e.g."Sleep","MessageBoxW".
Returns
Address of the IAT entry (pointer-sized slot) for the requested import.
0if the import cannot be found or the arguments are invalid.
How to use
Read current target of the import:
Install a basic IAT hook (patch the pointer):
Notes
This only sees imports that are present in the module's static import table.
Imports resolved manually at runtime (e.g. via
GetProcAddressinto custom memory) will not show up here.Ideal for:
Classic IAT hooks (redirecting calls to your own stub).
Finding the call site used by a module to invoke a given API, so your shellcode can jump through the same IAT entry.
Pointer Array Helpers
array<uint64>@ proc_t::read_pointer_array(uint64 base, uint count, int offset_delta) const
Reads an array of 64-bit pointers from the target process.
This is a convenience helper for common layouts like:
pointer lists (
T**, vtable arrays, entity pointer arrays)"pointer + fixed offset" patterns
Parameters
base— Address of the first pointer (each element isuint64, so step = 8 bytes).count— Number of pointers to read.offset_delta— Value added to each pointer after reading (can be negative).
Return
Returns
array<uint64>@containingcountelements.Returns
nullifbaseis invalid,countis 0, or the process handle is invalid.
Example
Virtual Memory Analysis
These APIs allow querying and iterating virtual memory regions.
bool proc_t::virtual_query(uint64 address, uint64 &out region_start, uint64 &out region_size, uint &out protection, bool &out heap_likely) const
bool proc_t::virtual_query(uint64 address, uint64 &out region_start, uint64 &out region_size, uint &out protection, bool &out heap_likely) constQueries the memory region that contains address.
Parameters
address— Any virtual address inside the target region.region_start— Region start address (output).region_size— Region size in bytes (output).protection— Region protection flags (output).heap_likely— True if the region is likely heap/alloc-like (output).
Returns
trueon successfalseif the address is not mapped or cannot be queried
array<dictionary@>@ proc_t::get_vad_snapshot(bool heap_likely_only = false) const
array<dictionary@>@ proc_t::get_vad_snapshot(bool heap_likely_only = false) constBuilds a snapshot of virtual memory regions and returns it as an array of dictionary handles.
When
heap_likely_only == true, only heap-like regions are returned.When
heap_likely_only == false, all regions are returned.
Returns
array<dictionary@>@— array of dictionaries, one per region. Returnsnullon failure.
Dictionary field types
Each dictionary entry stores fields as int64 (matching the same convention as read_struct). You must use int64 in your .get() calls:
"start"
int64
Region start address
"size"
int64
Region size in bytes
"end"
int64
Region end address
"protection"
int64
Protection flags
"heap_likely"
int64
1 if region is heap-like, 0 otherwise
Example
Memory Scan Helpers
All scan functions return array<uint64>@ containing matching addresses. Returns null on failure (invalid process handle, etc.).
All scan functions accept a heap_only parameter (default false). When true, only heap-like memory regions are scanned.
array<uint64>@ proc_t::scan_u32(uint value, bool heap_only = false) const
array<uint64>@ proc_t::scan_u32(uint value, bool heap_only = false) constScans for a 32-bit unsigned value.
array<uint64>@ proc_t::scan_u64(uint64 value, bool heap_only = false) const
array<uint64>@ proc_t::scan_u64(uint64 value, bool heap_only = false) constScans for a 64-bit unsigned value.
array<uint64>@ proc_t::scan_float(float value, bool heap_only = false) const
array<uint64>@ proc_t::scan_float(float value, bool heap_only = false) constScans for a 32-bit floating-point value.
array<uint64>@ proc_t::scan_double(double value, bool heap_only = false) const
array<uint64>@ proc_t::scan_double(double value, bool heap_only = false) constScans for a 64-bit floating-point value.
array<uint64>@ proc_t::scan_string(const string &in text, bool heap_only = false) const
array<uint64>@ proc_t::scan_string(const string &in text, bool heap_only = false) constScans for a UTF-8 / ANSI byte string in memory.
array<uint64>@ proc_t::scan_wstring(const string &in text, bool heap_only = false) const
array<uint64>@ proc_t::scan_wstring(const string &in text, bool heap_only = false) constScans for a UTF-16 (wide) string in memory. The input is a regular string which is internally converted to UTF-16 before scanning.
array<uint64>@ proc_t::scan_pointer(uint64 target, bool heap_only = false) const
array<uint64>@ proc_t::scan_pointer(uint64 target, bool heap_only = false) constScans for all locations in memory that contain a pointer to target.
🔍 Full Scan Example
Last updated