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

  • base_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.


📖 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 from addr.

  • rws – Reads a UTF-16 wide string, converts to UTF-8 string.

max_chars is a hard cap to avoid walking off invalid memory.


Writing Strings

  • ws – Writes text as ANSI/UTF-8 bytes plus trailing \0.

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

Returns true on success.


📦 Raw Memory (byte arrays)

Uses array<uint8> for bulk reads/writes.

  • rvm – Reads size bytes starting at addr into out_buf. out_buf is resized to size.

  • wvm – Writes bytes from in_buf to addr. Returns true if 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. Returns false if 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 false if the module is not found.


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).

  • Returns the address of the first match, or 0 if not found.


🔍 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 dictionary with:

    • "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

Type
Size
Meaning

"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)

Reads one structure instance from the target process into an existing dictionary.

Signature

Parameters

  • addr Absolute address of the struct in the remote process.

  • result Dictionary that will be filled with the decoded fields. (Existing contents are cleared before filling.)

  • desc Struct descriptor dictionary described above.

Return

  • true on success

  • false on 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 int64

  • All float fields are stored as double

  • String 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)

Reads an array of structures into an array<dictionary>.

Signature

Parameters

  • base Address of the first element.

  • count Number of elements to read.

  • size Size in bytes of each element in the array (sizeof(struct)).

  • result array<dictionary> that will be resized to count. Each element will be filled with that struct’s data. If an element is null internally, it is created.

  • desc Same struct descriptor as for read_struct.

Return

  • true on success

  • false if the process is invalid, count/size is 0, etc.

Example – Player list


🧪 Full Example Script – Read IMAGE_DOS_HEADER from notepad.exe

This is a complete AngelScript file you can drop into your environment. It:

  1. Builds a DOS header descriptor

  2. Uses proc_t.read_struct to read it from notepad.exe

  3. Prints 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.

🔴 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_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.

    • 0 on 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 by alloc_vm.

  • Returns

    • true if the region was successfully freed.

    • false if 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_base Base address of the module inside the target process. Typically obtained from:

    • proc_t::base_address(), or

    • proc_t::get_module("module_name.dll", base, size).

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

Returns

  • Absolute virtual address of the exported symbol in the target process.

  • 0 if 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_base Base address of the module whose imports you want to inspect/patch (usually the main EXE or a specific DLL), obtained from base_address() or 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) for the requested import.

  • 0 if 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 GetProcAddress into 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 is uint64, 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>@ containing count elements.

  • Returns null if base is invalid, count is 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, uint32 &out protection, bool &out heap_likely) const

Queries 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

  • true on success

  • false if the address is not mapped or cannot be queried


void proc_t::get_vad_snapshot(bool heap_likely_only, array<dictionary>@ &out regions) const

Builds a snapshot of virtual memory regions.

  • When heap_likely_only == true, only heap-like regions are returned.

  • When heap_likely_only == false, all regions are returned.

regions output format:

  • regions[i] is a dictionary with:

    • "start" (uint64)

    • "size" (uint64)

    • "end" (uint64)

    • "protection" (uint32)

    • "heap_likely" (bool)


Memory Scan Helpers

These helpers scan memory by iterating the VAD snapshot.

All scan functions output a list of matching addresses to result.

void proc_t::scan_bytes(const array<uint8> &in pattern, array<uint64> &out result, bool heap_likely_only) const

Scans for an exact byte sequence (pattern).


void proc_t::scan_all_bytes(array<uint64> &out result, bool heap_likely_only) const

Scans for all readable regions and returns their base addresses (useful for building your own multi-pass scanners / range filters).


void proc_t::scan_u32(uint32 value, array<uint64> &out result, bool heap_likely_only) const

Scans for a 32-bit unsigned value.


void proc_t::scan_u64(uint64 value, array<uint64> &out result, bool heap_likely_only) const

Scans for a 64-bit unsigned value.


void proc_t::scan_all_u32(array<uint64> &out result, bool heap_likely_only) const

Collects candidate addresses for 32-bit scanning across regions (fast pre-pass for multi-scan workflows).


void proc_t::scan_all_u64(array<uint64> &out result, bool heap_likely_only) const

Collects candidate addresses for 64-bit scanning across regions (fast pre-pass for multi-scan workflows).

Last updated