Lua module for reading the memory of Windows processes
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src
.gitignore
CMakeLists.txt
LICENSE
README.md
appveyor.yml
memreader-1.0.0-1.rockspec

README.md

memreader

Build status

memreader is a Lua module for reading the memory of Windows processes.

local memreader = require('memreader')

-- Find a window by title and open a handle to its process
local window = memreader.findwindow("Your Window Title")
local process = memreader.openprocess(window.pid)

-- Read the first 8 bytes from the start of the main module's memory
local data_rel = process:readrelative(0, 8)

-- Do the same thing, but using the absolute address this time
local address = process.base
local data_abs = process:read(address, 8)

-- Data is returned as a string of the specified length (not null-terminated)
assert(#data_rel == 8 and #data_abs == 8)
assert(data_rel == data_abs)

Installation

With Luarocks:

luarocks install memreader

Building

To build memreader, you'll need to install cmake, some version of Visual Studio, and have a Lua .lib file that can be found by cmake (preferably built with the same compiler you're using to build memreader).

Using cmake-gui

  • Run cmake-gui
  • Browse to memreader directory and set the build directory (typically just add /build to the memreader directory path)
  • Click Configure
  • Select the Generator and hit Finish
  • Hit Generate and then Open Project to open the project in Visual Studio
  • Build the project in Visual Studio as normal

Using cmake

Open a command line in the memreader directory and do the following:

mkdir build
cd build
cmake ..
cmake --build . --config Release

If needed, you can specify a generator by doing cmake -G "Visual Studio 14 2015 Win64" .. instead of cmake ..

API Reference

memreader.debugprivilege([state = true])

Attempts to adjust the access token of the Lua process to set the SeDebugPrivilege privilege (needed to access the memory of processes owned by other accounts). Calling this may or may not be necessary depending on how Lua is spawned, your use case, etc.

On success, returns true; otherwise, returns nil, errmsg.

memreader.processes()

Returns an iterator for all the processes in the system, in pid, name pairs.

Example:

local process
for pid, name in memreader.processes() do
  if name == "target.exe" then
    process = memreader.openprocess(pid)
    break
  end
end

Relevant WinAPI docs: CreateToolhelp32Snapshot, Process32Next

memreader.findwindow(title)

Finds a window by title. If found, returns a memreader.window usertype; otherwise, returns nil, errmsg.

Note: If there are multiple windows with the same title, this will only return the first (in arbitrary order). findwindow is faster than iterating with processes, but is not as precise or thorough.

Relevant WinAPI docs: FindWindow

memreader.openprocess(pid)

Attempts to open a handle to the process with the given process ID using the flags PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ. On success, returns a memreader.process usertype; otherwise, returns nil, errmsg.

Relevant WinAPI docs: OpenProcess

memreader.process

A usertype for process handles.

Fields (read-only):

  • process.pid: The process ID (e.g. 280)
  • process.name: The name of the process' main module (e.g. lua.exe)
  • process.path: The full path to the process' main module (e.g. C:\lua.exe)
  • process.base: The base address of the process' main module, as a memreader.address usertype (e.g. 0000000076EA0000)

process:read(address, nbytes)

Reads the specified number of bytes starting at the given address (can be either a number or a memreader.address) and returns that memory as a string (not null-terminated). On failure, returns nil, errmsg.

process:readrelative(offset, nbytes)

Like process:read(), except that offset is added to the process' main module's base address to determine the address to start from.

-- The following are exactly equivalent
process:read(process.base + 0x40, 4)
process:readrelative(0x40, 4)

process:modules()

Returns an iterator for all the modules of the process, as memreader.module usertypes.

Example:

for module in process:modules() do
  print(module.name, module.base)
end

process:exitcode()

Returns the exit code of the process (if it has exited). If the process is still running, then it will instead return nil. On failure, returns nil, errmsg.

process:version()

Retrieves the file and product version info embedded in the process' main module and returns it as a table. On failure, returns nil, errmsg.

Structure of the returned table:

{ 
  file = { major=1, minor=0, build=3, revision=105 },
  product = { major=3, minor=0, build=0, revision=0 }
}

memreader.module

A usertype for process modules.

Fields (read-only):

  • module.name: The name of the module (e.g. lua51.dll)
  • module.path: The full path to the module (e.g. C:\lua51.dll)
  • module.base: The base address of the module, as a memreader.address usertype (e.g. 0000000076EA0000)
  • module.size: The size (in bytes) of the module (e.g. 32768)

memreader.address

A usertype for an address in memory (LPVOID). Can be manipulated by adding/subtracting it with numbers or other memreader.address instances.

Example:

local a = process.base
local b = a + 0x40
local c = b - a

memreader.window

A usertype for window handles.

Fields (read-only):

  • window.pid: The process ID of the window's main thread (e.g. 280)
  • window.title: The title of the window (e.g. Your Window Title)