Getting Started Welcome Setting Up Game Tree GuiRoot Method
Writing a Memory Library Reading and Writing Page Iterator Pattern Scanning Buffer Strategies
Writing a Roblox Library C++ String

Page Iterator

In this guide, we will write a simple memory page allocator and use it to scan for pointers

Paging

Memory is usually divided into different pages. Each page is usually aligned to 4096.
This is so that the OS can assign different protection and states to each page, and allow pages to be paged out or mapped from a file.

Page Iterator

We will be writing a page iterator, allowing us to iterate through all the pages in a process.
Why do we want this?
We need this so we can scan memory effectively.
We don't want to scan unmapped regions as those would make our read function fail.
We also want to minimize the number of calls to our read function as syscalls can take a long time to complete.
With a page iterator, we can iterate through each page, read the entire page, then do the scanning on our own process, making it faster.

Implementation

To do this, we can use the `VirtualQueryEx` function provided by Windows.
However, from the same reason as in the previous chapter, we will use `NtQueryVirtualMemory` instead.

pub extern "ntdll" fn NtQueryVirtualMemory(
    hProcess: ?win32.HANDLE,
    lpAddress: usize,
    memoryInformationClass: u32,
    lpBuffer: ?*win32.MEMORY_BASIC_INFORMATION,
    dwLength: usize,
    outLength: ?*usize,
) callconv(.winapi) NTSTATUS;

// In MemoryAccessor
    pub fn getBasicInformation(this: @This(), address: usize) !win32.MEMORY_BASIC_INFORMATION {
        var mbi: win32.MEMORY_BASIC_INFORMATION = undefined;
        if(NtQueryVirtualMemory(this.process_handle, address, 0, &mbi, @sizeOf(win32.MEMORY_BASIC_INFORMATION), null) < 0)
            return error.QueryVirtualMemoryFailed;
        return mbi;
    }
// End

pub const PageIterator = struct {
    memory_accessor: MemoryAccessor,
    cur: usize,

    pub fn init(memory_accessor: MemoryAccessor) @This() {
        return .{
            .memory_accessor = memory_accessor,
            .cur = 0,
        };
    }

    pub fn next(this: *@This()) ?win32.MEMORY_BASIC_INFORMATION {
        if (this.cur >= std.math.maxInt(usize))
            return null;
        const mbi = this.memory_accessor.getBasicInformation(this.cur) catch return null;
        this.cur = @intFromPtr(mbi.BaseAddress) + mbi.RegionSize;
        return mbi;
    }
};


// Example usage
pub fn main() !void {
    const process_handle = win32.GetCurrentProcess(); // Replace with call to OpenProcess

    // Create memory accessor
    const memory_accessor: MemoryAccessor = .init(process_handle);

    const my_value: u32 = 0xDEADBEEF;

    const my_pointer = &my_value;

    std.debug.print("My pointer is at: {X}\n", .{ @intFromPtr(&my_pointer) });

    // Example scan for pointers to my_value
    const allocator = std.heap.smp_allocator;
    var it: PageIterator = .init(memory_accessor);
    var buffer: std.ArrayList(*const u32) = .empty;
    defer buffer.deinit(allocator);
    while (it.next()) |mbi| {
        // Take only readable pages
        if((mbi.Protect & (win32.PAGE_READONLY | win32.PAGE_READWRITE)) == 0 or (mbi.Protect & win32.PAGE_GUARD) != 0 or (mbi.State & (win32.MEM_COMMIT)) == 0)
            continue;
        try buffer.resize(allocator, mbi.RegionSize/@sizeOf(*const u32));
        memory_accessor.readSlice(@intFromPtr(mbi.BaseAddress), *const u32, buffer.items) catch continue;
        for(buffer.items, 0..) |value, i|
            if(value == my_pointer)
                std.debug.print("Found pointer at: {X}\n", .{ @intFromPtr(mbi.BaseAddress) + i * @sizeOf(*const u32) });
    }

    std.mem.doNotOptimizeAway(my_value);
    std.mem.doNotOptimizeAway(my_pointer);
}

In the example, we create a variable to store a value, and take the address of that variable and store it in another variable (this is called a pointer).
We then print the address of the pointer then scan for the address of the pointer using the address of the first variable.
I got:

My pointer is at: 7FF6CE2471F8
Found pointer at: 8554DFF908
Found pointer at: 7FF6CE2471F8