Skip to content

Repository files navigation

worm

worm is a simple cross-platform C++20 library that provides bindings to Windows and POSIX API with intent to simplify interactions with external processes' virtual memory.

It is highly encouraged that you use this library in conjunction with C++20 STL features.

Features

  • Simplicity - minimal interface for reading from and writing to external processes' virtual memory
  • Compile-time safety - privilege errors are caught compile-time
  • Portability - the same feature set is available both for Windows and POSIX-compliant system
  • Compliance with STL - the library is built with C++20 features in mind

Exceptions

While using the library, keep in mind that almost all functions in it are potentially throwing (including constructors).

In most of the examples below, however, there are no try-catch blocks in order to reduce visual noise.

Examples

Let pid be the process id of an arbitrary running process.

Prerequisite for each of the examples:

#include<worm/worm.hpp>

Creating a handle

Constructing a handle will call OpenProcess on Windows, or call nothing on POSIX-compliant systems.

For reading only

worm::ihandle handle(pid);

For writing only

worm::ohandle handle(pid);

For reading and writing

worm::iohandle handle(pid);

Obtaining memory regions

Let handle be an instance of worm::ihandle, or worm::ohandle, or worm::iohandle.

static_assert(decltype(handle)::readable);
std::vector<worm::memory_region> regions = handle.regions();

Interacting with virtual memory

Let addr be the address of an arbitrary virtual memory location of the aforementioned process.

Note that handles cannot be copied and can only be moved.

Readable handle

static_assert(decltype(handle)::readable);
autoconst value = handle.read<int>(addr);
// Reading into a byte bufferunsignedchar buffer[42];
std::size_tconst bytes_read = handle.read_bytes(addr, buffer, sizeof(buffer));
// Reading a valueunsignedlongconst value = handle.read<unsignedlong>(addr);

Writable handle

static_assert(decltype(handle)::writable);
// Writing from a byte bufferunsignedchar buffer[42]{};
std::size_tconst bytes_written_via_buffer = handle.write_bytes(addr, buffer, sizeof(buffer));
// Writing a value
std::size_tconst bytes_written_via_value = handle.write<unsignedlong>(addr, 0xdeadbeef);

Bound values

A bound value can be one of the following types:

  • worm::ihandle::bound<T> - a readable bound of type T
  • worm::ohandle::bound<T> - a writable bound of type T
  • worm::iohandle::bound<T> - a readable and writable bound of type T

Readable bound value

static_assert(decltype(handle)::readable);
autoconst readable_bound = handle.bind<int>(addr);
intconst value = readable_bound.read();

Writable bound value

static_assert(decltype(handle)::writable);
autoconst writable_bound = handle.bind<int>(addr);
std::size_tconst bytes_written = writable_bound.write(42);

Scanning virtual memory

Say we want to find first four addresses that hold (int) 213456 in the first memory region.

static_assert(decltype(handle)::readable);
staticconstexprint sought_value = 213456;
autoconst available_range = handle.regions().front().range;
// worm::memory_region::range is a contiguous range of addresses// belonging to the given range, so you have to be careful with// the bounds in order to read only what you really need. Here,// we substract the size of read value in order to not go out// of bounds of the address space.decltype(available_range) range{available_range.front(), available_range.back() - sizeof(sought_value)};
// Capture handles by reference, as they cannot be copied.autoconst pred = [&](worm::address_tconst& addr)
{
try
{
return handle.read<int>(addr) == sought_value;
}
catch (std::system_error const& e)
{
// Handle errors
}
returnfalse;
};
for (autoconst& address : range | std::views::filter(pred) | std::views::take(4))
{
std::cout << std::hex << address << '\n';
}

If we were to scan the entire available memory, we would define available_range as follows:

autoconst regions = handle.regions();
decltype(worm::memory_region::range) available_range{regions.front().range.front(), regions.back().range.back()};

Requirements

The following requirements must be met to be able to build the library:

  • C++20 support
  • CMake 3.26 or newer

Contributing

See CONTRIBUTING.md for the contribution guidelines.

License

The library is licensed under the MIT License. A copy of the license is available in the LICENSE file.

Authors

A.A.A. (contact at aeverless dot dev)

About

A library to simplify basic I/O operations with external processes' memory

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Contributors

Languages