This is the regenny wiki.
What is regenny?
regenny is a reverse engineering tool to interactively reconstruct structures and generate usable C++ header files. Header file generation is done by the sister project sdkgenny.
For those familiar with other tools, it is similar to ReClass.NET. It is written in C++.
Structures are defined in a plaintext .genny
file rather than manually inserting types into a tree view. This allows for the structures to be version controlled, shared between users, and are human readable from a normal text editor.
It has a Lua scripting interface to easily read and manipulate the structures externally without compiling a separate program. This allows for easy testing, quick prototyping, or even writing a full blown tool in Lua. The Lua bindings can be used in a separate Lua installation/embedding, they are not tied to regenny.
Precompiled builds can currently be downloaded from the Actions page.
Getting started
To get started using ReGenny, create a project by clicking File->New
on the top menu bar and entering a name in the dialog box. This will create a .json
project file and a .genny
file.
After that, you can click Action->Attach
to attach to a running process and then you can edit the .genny
file created earlier to start reverse engineering structures.
Once you're finished, you can click Action->Generate SDK
to generate corresponding C++ files.
ReGenny is a very powerful tool has many other features, but this should give you a good idea on how to get started with reverse engineering using ReGenny.
What is a .genny
file?
.genny
files are defined in a C/C++-like syntax. They provide the definitions of the types to be viewed in the GUI, generated, and/or reflected upon by the scripting API.
Structures can be given an explicit size, and fields within the structures can be given explicit offsets without manual padding, and out of order.
Simple C structures can sometimes be directly pasted into a .genny
file.
Example
// Explicitly defined types.
// Format is type {type_name} {type_size} [[{metadata}]]
// the metadata determines how the type is displayed in the GUI and read by the Lua API.
type int 4 [[i32]]
type float 4 [[f32]]
type char 1
// Forward declared structure.
struct Bar{};
// Simple structure. Size is explicitly defined. It doesn't need to be, though.
struct Foo 0x100 {
int a @ 4 // Explicit offset
char* b @ 8 [[utf8*]]
int c // implicit offset, it's 0x10, inferred from the previous member
int d @ 0x70
float e @ 0x80
Bar* bar @ 0x90 // Pointer to a forward declared structure
};
struct Bar {
Foo* parent
int b
int c
};
The above image is slightly out of date. There is no longer a text editor inside ReGenny itself. .genny
files must be opened within ReGenny and edited with an external text editor now. Reason being ImGui's text editor functionality was not that great and caused frustrating issues.
The corresponding generated C++ structure
Foo.hpp
#pragma once
struct Bar;
#pragma pack(push, 1)
struct Foo {
char pad_0[0x4];
int a; // 0x4
// Metadata: utf8*
char* b; // 0x8
int c; // 0x10
char pad_14[0x5c];
int d; // 0x70
char pad_74[0xc];
float e; // 0x80
char pad_84[0xc];
Bar* bar; // 0x90
char pad_98[0x68];
}; // Size: 0x100
#pragma pack(pop)
Bar.hpp
#pragma once
struct Foo;
#pragma pack(push, 1)
struct Bar {
Foo* parent; // 0x0
int b; // 0x8
int c; // 0xc
}; // Size: 0x10
#pragma pack(pop)
Accessing this structure from Lua
Before using this script, the structure in the GUI has to be set to Foo
, regenny must be attached to a process, and the address of the structure must be set in the GUI.
-- An overlay is a special Lua type that allows access to the memory of a structure.
-- It has index and newindex metamethods that access to the members of the structure
-- seamlessly, with no magic numbers/offsets, completely externally.
local foo = regenny:overlay()
if not foo or foo:type():name() ~= "Foo" then
print("Overlay is not a Foo, cannot use this script.")
return
end
-- Dynamically read the structure memory from the target process.
print(foo.a) -- prints the value of foo.a
print(foo.b) -- prints the value of foo.b, will be a string
-- Setter demonstrations
-- These actually set the values within the target process's structure behind the scenes.
foo.c = 0x12345 -- sets foo.c to 0x12345
foo.e = 3.14159 -- sets foo.e to 3.14159
local bar = foo.bar -- gets the value of foo.bar
-- Because pointers can be null.
if bar ~= nil then
bar.b = 0x1337 -- sets bar.b to 0x1337
bar.c = bar.b + 0x100 -- sets bar.c to bar.b + 0x100
print(bar.parent:ptr() == foo:address()) -- prints true if bar.parent is equal to foo
if bar.parent:ptr() ~= foo:address() then
-- sets bar.parent to foo
-- pointers can be explicitly written to with a raw integer value
bar.parent = foo:address()
end
end
In addition to simply reading/modifying structures, the Lua API allows for more advanced features such as:
- Performing reflection on the structure types
- Reading and writing to the process memory
- Allocating memory in the process
- Parsing another
.genny
file separately - Generating C++ header files from a new
.genny
file or SDK instance
Supported types
- Namespaces
- Enums
- Structs
- Classes
- Function prototypes
- Static function prototypes
- Bitfields
- Arrays
- Multi-dimensional arrays
- Pointers
- Namespaces can be nested within each other
- Structs, enums and classes can be nested within other structs/classes
- Bring your own external types
Type metadata
Types and variables can have a metadata attached to them. This metadata is used to determine how the type is displayed in the GUI and read by the Lua API.
For types, the format is type {type_name} {size} [[{metadata}]]
.
For variables, the format is {type_name} {variable_name} [[{metadata}]]
.
Example:
type int 4 [[i32]] // i32 metadata, will be displayed as a signed 32-bit integer in the GUI
type float 4 [[f32]] // f32 metadata, will be displayed as a 32-bit float in the GUI
type char 1 // no metadata
struct Foo {
// will be displayed as a signed 32-bit integer in the GUI, implicit metadata
int a;
// utf8* metadata, will be displayed as a utf8 string in the GUI
// must be explicitly specified, as the type is a pointer to 1 byte
char* b [[utf8*]]
// f32 metadata, will be displayed as a 32-bit float in the GUI
float c
// metadata does not need to be specified for this pointer
int* d
};
Address
ReGenny parses addresses in a few different ways. Here you can find some examples.
For these examples, I'm going to be using numbers in the hexidecimal format (base 16), but decimal numbers (base 10) are also supported.
Absolute addresses
Inputting an absolute address will display whatever is at that address in the current virtual address space.
For example, on Windows if your process is based at 0x180000000
, then you can input the address 0x180000000
to see the PE (MZ) header and 0x180001000
to see the start of code (or whatever else is after the PE header, assuming the PE header is 0x1000
bytes in size).
Relative addresses
Relative addresses can also be used. The syntax looks something like this: <module_name>+offset->offset->offset
.
Module names can contain full paths or partial paths, as long as it ends with the input path.
For example, say we want to get the base address of a module loaded as C:\Windows\System32\kernel32.dll
, we could do one of the following:
<kernel32.dll>+0x0
(Partial ending)<System32\kernel32.dll>
(Partial ending)<C:\Windows\System32\kernel32.dll>+0x0
(Full path)
On Windows, Addresses are relative to a loaded DLL's (Dynamic-Link Library) name or EXE (Executable) name of the attached app (basically, any loaded module, including itself).
For example, to get the start of code, after the PE header, you can enter something like:
<app.exe>+0x1000
.
It also supports dereferencing (denoted by ->
), like so: <app.exe>+0x5000->0x24
. This will add 0x5000
to app.exe
's base address, dereference the result and then read out bytes starting from 0x24
after the resulting dereference.
Example genny files
Example scripts
- https://github.com/cursey/regenny/blob/master/examples/test.lua
- https://github.com/cursey/regenny/pull/10
-- An overlay is a special Lua type that allows access to the memory of a structure.
-- It has index and newindex metamethods that access to the members of the structure
-- seamlessly, with no magic numbers/offsets, completely externally.
local foo = regenny:overlay()
if not foo or foo:type():name() ~= "Foo" then
print("Overlay is not a Foo, cannot use this script.")
return
end
-- Dynamically read the structure memory from the target process.
print(foo.a) -- prints the value of foo.a
print(foo.b) -- prints the value of foo.b, will be a string
-- Setter demonstrations
-- These actually set the values within the target process's structure behind the scenes.
foo.c = 0x12345 -- sets foo.c to 0x12345
foo.e = 3.14159 -- sets foo.e to 3.14159
local bar = foo.bar -- gets the value of foo.bar
-- Because pointers can be null.
if bar ~= nil then
bar.b = 0x1337 -- sets bar.b to 0x1337
bar.c = bar.b + 0x100 -- sets bar.c to bar.b + 0x100
print(bar.parent:ptr() == foo:address()) -- prints true if bar.parent is equal to foo
if bar.parent:ptr() ~= foo:address() then
-- sets bar.parent to foo
-- pointers can be explicitly written to with a raw integer value
bar.parent = foo:address()
end
end
Globals
These can be accessed from anywhere in a script.
regenny
This is the global API for ReGenny.
sdkgenny
This is the global API for luagenny.
sdkgenny.Sdk
The Sdk
is the top level class required to generate the SDK and/or perform reflection.
sdkgenny.StructOverlay
An StructOverlay is a special Lua type that allows access to the memory of a structure. It has index and newindex metamethods that access to the members of the structure seamlessly, with no magic numbers/offsets, completely externally*.
* This is not true if using luagenny outside of regenny. By default it operates on memory as if it was in the same context as the current process. regenny overrides luagenny's read and write functions to read and write from the target process.
Example assuming Foo is:
struct Foo {
int a;
int b;
};
local sdk = regenny:sdk() -- in another application, this would be different.
local overlay = sdkgenny.StructOverlay(0xdeadbeef, sdk:global_ns():find_type("Foo"))
overlay.a = 123
overlay.b = 456
Static methods
sdkgenny.StructOverlay(address: number, struct: sdkgenny.Struct)
Creates and returns a new StructOverlay.
Methods
self.index(key: string or number)
Or in other words, self.foo
or self[1]
.
When key
is a string, it returns the value of the member at the given key. Structs and Pointers are automatically returned as StructOverlay or PointerOverlay respectively.
When key
is a number, it treats the structure as an inlined array and returns a new StructOverlay
at the given index. Essentially, it returns self:address() + (key * self:type():size())
.
self.newindex(key: string, value: any)
Or in other words, self.foo = 123
When key
is a string, it sets the value of the member at the given key.
self:type()
Returns the sdkgenny.Struct
that this overlay is for.
sdkgenny.PointerOverlay
A PointerOverlay is a special Lua type for interacting with pointers. When the pointed to structure is an sdkgenny.Struct
, it is not much different from an sdkgenny.StructOverlay
, but when the pointed to type is a primitive type or a pointer, it has different behavior.
Methods
self.index(key: string or number)
Or in other words, self.foo
or self[1]
.
When key
is a string
It returns the value of the member at the given key. This will only work if the pointed to type is an sdkgenny.Struct
.
When key
is a number
It treats the pointer as an array.
If the pointed to type is a primitive type, it returns the value at the given index.
Essentially, it returns self:ptr() + (key * self:type():to():size())
, and dereferences the pointer.
If the pointed to type is a pointer, it returns a new sdkgenny.PointerOverlay
at the given index. Essentially, it returns self:ptr() + (key * platform_pointer_size)
, and dereferences the pointer.
self.newindex(key: string, value: any)
Or in other words, self.foo = 123
When key
is a string, it sets the value of the member at the given key.
self:address()
Returns the address of the pointer itself.
self:ptr()
Returns the pointed to address.
self:type()
Returns the sdkgenny.Pointer that this overlay is for.
Types
sdkgenny.Object
All types inherit from this.
Most methods with an asterisk * take many different strings, e.g.
object:is_struct() -- returns true if the object is a struct
object:is_variable() -- returns true if the object is a variable
object:is_function() -- returns true if the object is a function
Methods
self:is_*()
Returns true if the object is of the given type.
self:as_*()
Returns the object as the given type, or nil if it is not of the given type.
self:find_*()
Searches the children of the object for the given type. Returns nil
if not found.
Typename
Namespace
Type
sdkgenny.Struct
Methods
Class
sdkgenny.Pointer
Reference
Enum
EnumClass
Constant
Function
VirtualFunction
Parameter
Variable
Types
ReGenny
This is the class used to access the ReGenny API. It can be accessed through the global regenny
variable.
Methods
self:process()
Returns the current Process. Gets upcasted to a WindowsProcess if the process is a Windows process.
self:address()
Returns the current address set in the GUI.
self:type()
Returns the current sdkgenny.Type
set in the GUI.
Can usually be casted to an sdkgenny.Struct
with self:type():as_struct()
.
self:sdk()
Returns the current sdkgenny.Sdk
for the current project (generated from the editor).
self:overlay()
Constructs and returns an sdkgenny.StructOverlay
using the address and current structure set in the GUI.
Any member reads or writes will be done from/to the process memory dynamically on each access.
Example:
local baz = regenny:overlay()
if baz == nil then
print("Cannot continue test, overlay does not exist.")
return false
end
if baz:type():name() ~= "Baz" then
print("Cannot continue test, selected type is not Baz")
return false
end
print(baz)
print(baz:type())
print(string.format("%x", baz:address()))
print(baz.hello) -- Prints "Hello, world!", which is the value pointed to by baz.hello
baz.foo.a = baz.foo.a + 1 -- Increments baz.foo.a by 1
Process
Class referring to the currently attached process in regenny.
Methods
self:protect(address: number, size: number, flags: number)
Modifies the protection of a region of memory in the process.
On Windows, the flags
are the same as the flNewProtect
flags in VirtualProtectEx.
Returns the old protection flags of the region.
self:allocate(address: number, size: number, flags: number)
Allocates a region of memory in the process.
address
is optional, set it to 0 to let the OS choose the address.
On Windows, the flags
are the same as the flProtect
flags in VirtualAllocEx.
self:get_module_within(address: number)
Returns the ProcessModule that contains the given address.
self:get_module(name: string)
Returns the ProcessModule with the given name.
self:modules()
Returns a list of ProcessModules in the process.
self:allocations()
Returns a list of ProcessAllocations in the process.
self:read_int8(address: number)
Reads a int8_t
from the process memory at the given address.
self:read_int16(address: number)
Reads a int16_t
from the process memory at the given address.
self:read_int32(address: number)
Reads a int32_t
from the process memory at the given address.
self:read_int64(address: number)
Reads a int64_t
from the process memory at the given address.
self:read_uint8(address: number)
Reads a uint8_t
from the process memory at the given address.
self:read_uint16(address: number)
Reads a uint16_t
from the process memory at the given address.
self:read_uint32(address: number)
Reads a uint32_t
from the process memory at the given address.
self:read_uint64(address: number)
Reads a uint64_t
from the process memory at the given address.
self:read_float(address: number)
Reads a float
from the process memory at the given address.
self:read_double(address: number)
Reads a double
from the process memory at the given address.
self:read_string(address: number)
Reads a char*
from the process memory at the given address.
Size is automatically deduced using a strlen
-like algorithm.
self:write_int8(address: number, value: number)
Writes a int8_t
to the process memory at the given address.
self:write_int16(address: number, value: number)
Writes a int16_t
to the process memory at the given address.
self:write_int32(address: number, value: number)
Writes a int32_t
to the process memory at the given address.
self:write_int64(address: number, value: number)
Writes a int64_t
to the process memory at the given address.
self:write_uint8(address: number, value: number)
Writes a uint8_t
to the process memory at the given address.
self:write_uint16(address: number, value: number)
Writes a uint16_t
to the process memory at the given address.
self:write_uint32(address: number, value: number)
Writes a uint32_t
to the process memory at the given address.
self:write_uint64(address: number, value: number)
Writes a uint64_t
to the process memory at the given address.
self:write_float(address: number, value: number)
Writes a float
to the process memory at the given address.
self:write_double(address: number, value: number)
Writes a double
to the process memory at the given address.
WindowsProcess
Windows version of the Process class. Inherits from Process.
Methods
self:get_typename(obj: number)
Returns the RTTI name of the given object at the given address.
self:allocate_rwx(address: number, size: number)
Wrapper over Process.allocate that allocates a region of memory with the PAGE_EXECUTE_READWRITE
protection.
self:protect_rwx(address: number, size: number)
Wrapper over Process.protect that changes the protection of a region of memory to PAGE_EXECUTE_READWRITE
.
self:create_remote_thread(address: number, param: number)
ProcessModule
Fields
self.name
The name of the module.
self.start
The base address of the module.
self.end
The end address of the module.
self.size
The size of the module.
ProcessAllocation
Fields
self.start
The base address of the allocation.
self.end
The end address of the allocation.
self.size
The size of the allocation.
self.read
Whether the allocation is readable.
self.write
Whether the allocation is writable.
self.execute
Whether the allocation is executable.