WIP: dtb fix

Updated memflow and memory class in order to fix the dtb value of protected processes.
pull/38/head
MisterY52 8 months ago
parent 04b858895a
commit c79f9f2e32

@ -1,12 +1,11 @@
CXX=g++
CXXFLAGS=-I./memflow_lib/memflow-win32-ffi/ -I./memflow_lib/memflow-ffi/ -L./memflow_lib/target/release -Wno-multichar
LIBS=-lm -Wl,--no-as-needed -ldl -lpthread -l:libmemflow_win32_ffi.a
CXXFLAGS=-I./memflow_lib/memflow-ffi/ -L./memflow_lib/target/release -Wno-multichar
LIBS=-lm -ldl -lpthread -l:libmemflow_ffi.a
OUTDIR=./build
OBJDIR=$(OUTDIR)/obj
$(shell mkdir -p $(OBJDIR))
$(shell cp memflow_lib/memflow-qemu-procfs/target/release/libmemflow_qemu_procfs.so $(OUTDIR))
%.o: %.cpp
$(CXX) -c -o $(OBJDIR)/$@ $< $(CXXFLAGS)

@ -705,8 +705,7 @@ int main(int argc, char *argv[])
{
if(geteuid() != 0)
{
printf("Error: %s is not running as root\n", argv[0]);
return 0;
printf("Warning: %s is not running as root\n", argv[0]);
}
const char* cl_proc = "client_ap.exe";

@ -1,16 +1,9 @@
#!/bin/bash
cd memflow_lib/memflow-win32-ffi/
cd memflow_lib/memflow-ffi/
if cargo build --release ; then
cd ../memflow-qemu-procfs
if cargo build --release --all-features ; then
cd ../../
make
else
echo "Error while building memflow-qemu-procfs"
fi
cd ../../
make
else
echo "Error while building memflow-win32-ffi"
echo "Error while building memflow-ffi"
fi

@ -0,0 +1,2 @@
[target.'cfg(unix)']
runner = "./runner.sh"

@ -0,0 +1,176 @@
name: Build and test
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
flags: [--all-features, --no-default-features]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
- name: Build
run: cargo build --workspace ${{ matrix.flags }} --verbose
- name: Build examples
run: cargo build --workspace ${{ matrix.flags }} --examples --verbose
build-cross-targets:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [aarch64-unknown-linux-gnu, armv7-unknown-linux-gnueabihf, i686-unknown-linux-gnu]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
target: ${{ matrix.target }}
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --target ${{ matrix.target }} --workspace --all-features --verbose
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
- name: Pre-build binaries (for inventory integration tests)
run: cargo build --workspace --all-features --verbose
- name: Run all tests
run: cargo test --workspace --all-features --verbose
if: runner.os == 'Linux'
- name: Run all tests
run: cargo test --workspace --exclude memflow-derive --all-features --verbose
if: runner.os != 'Linux'
test-cross:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly # currently required due to -Zdoctest-xcompile
target: ${{ matrix.target }}
override: true
- name: Pre-build binaries (for inventory integration tests)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --target ${{ matrix.target }} --workspace --all-features --verbose --release
- name: Run all tests
uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: -Zdoctest-xcompile --target ${{ matrix.target }} --workspace --all-features --verbose --release
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
components: rustfmt, clippy
- name: Check formatting
run: cargo fmt -- --check
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-targets --all-features --workspace -- -D clippy::all
build-ffi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly # required by cglue codegen
override: true
- name: Validate c and c++ headers
run: cd memflow-ffi; ./verify_headers.sh
- name: Build memflow
run: cargo build --workspace --release --verbose
- name: Build c examples
run: cd memflow-ffi/examples/c; make all
- name: Build c++ examples
run: cd memflow-ffi/examples/cpp; make all
build-nostd:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Rust nightly
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2023-12-15
override: true
- run: rustup toolchain install nightly-2023-12-15
- run: rustup +nightly-2023-12-15 component add rust-src
- name: Build no_std crate
run: cd nostd-test; cargo +nightly-2023-12-15 build --all-features --verbose
build-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
path: 'memflow-repo'
- name: Set up Rust nightly
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- run: cargo install grcov
- name: Run tests with coverage
run: |
cd memflow-repo
export CARGO_INCREMENTAL=0
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
export RUSTDOCFLAGS="-Cpanic=abort"
cargo build --workspace --exclude memflow-derive --all-features
cargo test --workspace --exclude memflow-derive --all-features
grcov ./target/debug/ -s . -t lcov --llvm --branch --ignore-not-existing -o ./target/debug/coverage
bash <(curl -s https://codecov.io/bash) -f ./target/debug/coverage -t ${{ secrets.CODECOV_TOKEN }};

@ -0,0 +1,13 @@
/target
**/*.rs.bk
*.swp
*.so
*.dll
*.dylib
.vscode
nostd-test/target
nostd-test/Cargo.lock
*.so
.vagrant
TODO.md
TODO.txt

@ -2,6 +2,48 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 0.2.0-beta11
- Added dtb1 and dtb2 fields to ProcessInfo structure
- Added a function to the process trait which allows overriding dtb1/dtb2 with a custom value
## 0.2.0-beta10
- Removed all 'Inner' types and replaced them with GAT - this also shifts memflow to a minimum rust version of 1.70.0
- Improved cache configuration when using plugins (usage: --connector kvm:::cache=true,cache_size=1kb,cache_time=10,cache_page_size=1000 where size and page_size is specified in hex)
- Added DelayedPhysicalMemory middleware (usage: --connector kvm:::delay=200 where delay is specified in microseconds)
- Added PhysicalMemoryMetrics middleware (usage: --connector kvm:::metrics=true)
- Updated FileIoMemory constructor with a default identity mapped memory mapping.
- Rewrote argument parser to properly handle quotes in complex arguments.
## 0.2.0-beta9
## 0.2.0-beta8
- Hotfix for new bumpalo release
## 0.2.0-beta7
- Unified and simplified plugin proc macros and updated their documentation
## 0.2.0-beta6
- Added additional export/import/section helpers
- Dependency updates
## 0.2.0-beta5
- Cleaned up plugin search paths and matched them with memflowup
- Improved error messages
- Plugins are resolved to their canonical path before adding
- Added VirtualTranslate as optional trait on Os
- Updated to latest cglue
## 0.2.0-beta4
- Added missing functions to retrieve exports/imports/sections from kernel modules
- Added functions to retrieve primary kernel module
## 0.2.0-beta3
- Allow for PhysicalMemoryView to fill in gaps with zeros
## 0.2.0-beta2
- Memory API and Address rework
## 0.2.0-beta1
- Entirely new cglue based plugin architecture and various other major improvements
## 0.1.5
- Added memflow::prelude::v1 and memflow_win32::prelude::v1 modules
- Added new fields to FFI

@ -6,7 +6,7 @@ There is a feature missing? A bug you have noticed? Some inconsistencies? **Cont
We welcome your contributions, and we love to keep our code standards high. So, there are a few key guidelines that you should follow for smooth sailing:
- All our code is formatted using rustfmt. Please, run `cargo fmt` before committing your changes.
- All our code is formatted using rustfmt. Please, run `cargo fmt --all` before committing your changes.
- Make sure all of the tests pass with `cargo test`, as this would prevent us from merging your changes.
- Make sure that clippy does not complain with `cargo clippy --all-targets --all-features --workspace -- -D warnings -D clippy::all`

@ -1,24 +1,21 @@
[profile.bench]
debug = true
[workspace]
members = [
"memflow",
"memflow-win32",
"memflow-ffi",
"memflow-win32-ffi",
"memflow-bench",
]
default-members = [
"memflow",
"memflow-win32",
"memflow-ffi",
"memflow-win32-ffi",
"memflow-bench",
]
exclude = [
"nostd-test",
"memflow-qemu-procfs"
"nostd-test"
]
[patch.crates-io]
goblin = { git = "https://github.com/h33p/goblin", branch = "lossy-macho" }

@ -1,7 +1,7 @@
MIT License
Copyright (c) 2020 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020 Aurimas Blažulionis <0x60@pm.me>
Copyright (c) 2020-2022 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020-2022 Aurimas Blažulionis <0x60@pm.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -5,58 +5,81 @@
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR)
## physical memory introspection framework
## machine introspection made easy
memflow is a library that allows live memory introspection of running systems and their snapshots. Due to its modular approach it trivial to support almost any scenario where Direct Memory Access is available.
memflow is a library that enables introspection of various machines (hardware, virtual machines, memory dumps) in a generic fashion. There are 2 primary types of objects in memflow - _Connectors_ and _OS layers_. Connector provides raw access to physical memory of a machine. Meanwhile, OS layer builds a higher level abstraction over running operating system, providing access to running processes, input events, etc. These objects are incredibly flexible as they can be chained together to gain access to a process running multiple levels of virtualization deep (see figure below).
The very core of the library is a [PhysicalMemory](https://docs.rs/memflow/latest/memflow/mem/phys_mem/trait.PhysicalMemory.html) that provides direct memory access in an abstract environment. This object can be defined both statically, and dynamically with the use of the `inventory` feature. If `inventory` is enabled, it is possible to dynamically load libraries that provide Direct Memory Access.
Through the use of OS abstraction layers, like [memflow-win32](https://github.com/memflow/memflow/tree/master/memflow-win32), users can gain access to virtual memory of individual processes by creating objects that implement [VirtualMemory](https://docs.rs/memflow/latest/memflow/mem/virt_mem/trait.VirtualMemory.html).
Bridging the two is done by a highly throughput optimized virtual address translation function, which allows for crazy fast memory transfers at scale.
The core is architecture-independent (as long as addresses fit in 64-bits), and currently, both 32, and 64-bit versions of the x86 family are available to be used.
For non-rust libraries, it is possible to use the [FFI](https://github.com/memflow/memflow/tree/master/memflow-ffi) to interface with the library.
```
+-----------+ +-----------+
| native OS | | leechcore |
+-+---------+ +-+---------+
| |
| +-----------+ | +----------+
+->| QEMU VM | +->| Win32 OS |
+-+---------+ +-+--------+
| |
| +----------+ | +-----------+
+->| Win32 OS | +->| lsass.exe |
+-+--------+ +-----------+
|
| +-----------+
+->| Hyper-V |
+-+---------+
|
| +----------+
+->| Linux OS |
+-+--------+
|
| +-----------+
+->| SSHD Proc |
+-----------+
(Example chains of access. For illustrative purposes only - Hyper-V Connector and Linux OS are not yet available)
```
In the repository, you can find various examples available (which use the memflow-win32 layer)
As a library user, you do not have to worry about delicacies of chaining - everything is provided, batteries included. See one of our [examples](memflow/examples/process_list.rs) on how simple it is to build a chain (excluding parsing). All Connectors and OS layers are dynamically loadable with common interface binding them.
## Building from source
All of this flexibility is provided with very robust and efficient backend - memory interface is batchable and divisible, which gets taken advantage of by our throughput optimized virtual address translation pipeline that is able to walk the entire process virtual address space in under a second. Connectors and OS layers can be composed with the vast library of generic caching mechanisms, utility functions and data structures.
To build all projects in the memflow workspace:
The memflow ecosystem is not bound to just Rust - Connector and OS layer functions are linked together using C ABI, thus users can write code that interfaces with them in other languages, such as C, C++, Zig, etc. In addition, these plugins can too be implemented in foreign languages - everything is open.
`cargo build --release --workspace`
Overall, memflow is the most robust, efficient and flexible solution out there for machine introspection.
To build all examples:
## Getting started
`cargo build --release --workspace --examples`
Make sure that your rustc version is at least `1.70.0` or newer.
Run all tests:
memflow uses a plugin based approach and is capable of loading different physical memory backends (so-called [`connectors`](#connectors)) at runtime. On top of the physical memory backends memflow is also capable of loading plugins for interfacing with a specific target OS at runtime.
`cargo test --workspace`
To get started, you want to at least install one connector. For that, use the [memflowup](https://github.com/memflow/memflowup) utility (use dev channel).
Execute the benchmarks:
### Manual installation
`cargo bench`
The recommended installation locations for connectors on Linux are:
```
/usr/lib/memflow/libmemflow_xxx.so
$HOME/.local/lib/memflow/libmemflow_xxx.so
```
## Documentation
The recommended installation locations for connectors on Windows are:
```
[Username]/Documents/memflow/libmemflow_xxx.dll
```
Extensive code documentation can be found at [docs.rs](https://docs.rs/memflow/0.1/).
Additionally, connectors can be placed in any directory of the environment PATH or the working directory of the program as well.
An additional getting started guide as well as a higher level
explanation of the inner workings of memflow can be found at [memflow.github.io](https://memflow.github.io).
For more information about how to get started with memflow please head over to the YouTube series produced by [h33p](https://github.com/h33p/):
If you decide to build the latest documentation you can do it by issuing:
- [memflow basics](https://www.youtube.com/playlist?list=PLrC4R7zDrxB3RSJQk9ahmXNCw8m3pdP6z)
- [memflow applied](https://www.youtube.com/watch?v=xJXkRMy71dc&list=PLrC4R7zDrxB17iWCy9eEdCaluCR3Bkn8q)
`cargo doc --workspace --no-deps --open`
## Basic usage
## Running Examples
You can either run one of the examples with `cargo run --release --example`. Pass nothing to get a list of examples.
Some connectors like `qemu_procfs` will require elevated privileges. See the Connectors section of this Readme for more information.
Some connectors like `qemu` will require elevated privileges. Refer to the readme of the connector for additional information on their required access rights.
To simplify running examples, tests, and benchmarks through different connectors we added a simple cargo runner script for Linux to this repository.
To simplify running examples, tests, and benchmarks through different connectors, we added a simple cargo runner script for Linux to this repository.
Simply set any of the following environment variables when running the `cargo` command to elevate privileges:
- `RUST_SUDO` will start the resulting binary via sudo.
@ -64,78 +87,75 @@ Simply set any of the following environment variables when running the `cargo` c
Alternatively, you can run the benchmarks via `cargo bench` (can pass regex filters). Win32 benchmarks currently work only on Linux.
## Running Examples
All examples support the memflow connector `plugins` inventory system.
You will have to install at least one `connector` to use the examples. Refer to the [getting started](#getting-started) section for more details.
All examples support the memflow connector inventory system.
You will have to install at least one `connector` to use the examples.
Run memflow/read\_keys example with a qemu connector:
To install a connector just use the [memflowup](https://github.com/memflow/memflowup) utility,
or, head over to the corresponding repository and install them via the `install.sh` script.
`RUST_SETPTRACE=1 cargo run --example read_keys -- -vv -c qemu -a [vmname] -o win32`
You will find a folder called `memflow` in any of the following locations:
```
/opt
/lib
/usr/lib/
/usr/local/lib
/lib32
/lib64
/usr/lib32
/usr/lib64
/usr/local/lib32
/usr/local/lib64
```
Run memflow/read\_bench example with a coredump connector:
On Windows, you can put the connector DLL in a folder named `memflow`
that is either in your current PATH or put it in `C:\Users\{Username}\.local\lib\memflow`.
Additionally connectors can be placed in the working directory of the process as well.
`cargo run --example read_bench --release -- -vv -c coredump -a coredump_win10_64bit.raw -o win32`
Now you can just run the examples by providing the appropriate connector name:
Note: In the examples above the `qemu` connector requires `'CAP_SYS_PTRACE=ep'` permissions. The runner script in this repository will set the appropriate flags when the `RUST_SETPTRACE` environment variable is passed to it.
Run memflow\_win32/read\_keys example with a procfs connector:
## Documentation
`RUST_SETPTRACE=1 cargo run --example read_keys -- -vv -c qemu_procfs -a [vmname]`
Extensive code documentation can be found at [docs.rs](https://docs.rs/memflow/0.2.0-beta/)
(it currently is relatively out of date).
Run memflow\_win32/read\_bench example with a coredump connector:
An additional getting started guide as well as a higher level
explanation of the inner workings of memflow can be found at [memflow.github.io](https://memflow.github.io).
`cargo run --example read_bench --release -- -vv -c coredump -a coredump_win10_64bit.raw`
If you decide to build the latest documentation you can do it by issuing:
Note: In the examples above the `qemu_procfs` connector requires `'CAP_SYS_PTRACE=ep'` permissions. The runner script in this repository will set the appropriate flags when the `RUST_SETPTRACE` environment variable is passed to it.
`cargo doc --workspace --no-deps --open`
## Compilation support
memflow currently requires at least rustc version `1.70.0` or newer.
| target | build | tests | benches | compiles on stable |
|---------------|--------------------|--------------------|--------------------|--------------------|
| linux x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| mac x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| win x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux aarch64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux i686 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux armv7 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| no-std | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
## Target support
memflow-win32 is tested on the latest Windows 10 versions all the way down to Windows NT 4.0. If you found a version that does not work please submit an issue with the major/minor version as well as the build number.
By default, memflow supports analyzing 64-bit machines on any machine - be it 32 or 64 bit. Using memflow without `default_features` can disable 64-bit support on 32-bit machines for an efficiency gain, while enabling `128_bit_mem` feature can be done for theoretical future 128-bit machine analysis. Note that all connectors and OS layers must be compiled with the same memory features enabled, and memflowup currently only compiles the default set of features.
memflow-win32 is tested on the latest Windows 11 and Windows 10 versions all the way down to Windows NT 4.0. If you found a version that does not work please submit an issue with the major/minor version as well as the build number.
## Connectors
All examples provided in this repository are using the inventory to
All examples provided in this repository are using the `plugins` inventory to
dynamically load a connector at runtime. When using the library programmatically it is possible to just statically link a connector into the code.
Some connectors also require different permissions. Please refer to the individual connector repositories for more information.
These are the currently officially existing connectors:
- [qemu_procfs](https://github.com/memflow/memflow-qemu-procfs)
- [qemu](https://github.com/memflow/memflow-qemu-procfs)
- [kvm](https://github.com/memflow/memflow-kvm)
- [pcileech](https://github.com/memflow/memflow-pcileech)
- [coredump](https://github.com/memflow/memflow-coredump)
In case you write your own connector please hit us up with a merge request so we can maintain a list of third-party connectors as well.
In case you write your own connector please hit us up with a pull request so we can maintain a list of third-party connectors as well.
## Build on memflow
Officialy supported projects:
- [memflow-py](https://github.com/memflow/memflow-py) Python Wrapper for memflow (thanks to [emesare](https://github.com/emesare))
## Road map / Future Development
- Provide a rust native connector for PCILeech based hardware
- Provide a UEFI Demo
- Linux target support
Additional projects from the community:
- [.NET wrapper for memflow-ffi](https://github.com/uberhalit/memflow.NET) by [uberhalit](https://github.com/uberhalit)
- [rhai integration](https://github.com/dankope/rhai-memflow) by [emesare](https://github.com/emesare)
## Acknowledgements
- [CasualX](https://github.com/casualx/) for his wonderful pelite crate

@ -0,0 +1,21 @@
# Checklist:
- memflow
- memflow-ffi
# Plugins:
- memflow-win32
- memflow-pcileech
- memflow-reclass-plugin
- memflow-native
- memflow-qemu
- memflow-kcore
- memflow-cmake-example
- memflow-kvm
- memflow-coredump
- memflow-linux
- memflow-microvmi
# Tools:
- cloudflow
- scanflow
- reflow

@ -1,30 +1,29 @@
[package]
name = "memflow-bench"
version = "0.1.5"
authors = ["Aurimas Blažulionis <0x60@pm.me>"]
version = "0.2.0"
authors = ["Aurimas Blažulionis <0x60@pm.me>", "ko1N <ko1N1337@gmail.com>"]
edition = "2018"
description = "benchmarks for the memflow physical memory introspection framework"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow"
license-file = "../LICENSE"
license = "MIT"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "memory-management", "os" ]
publish = false
[dependencies]
memflow = { path = "../memflow", features = ["dummy_mem"] }
rand = "0.7"
rand_xorshift = "0.2"
memflow = { version = "0.2", path = "../memflow", features = ["dummy_mem"] }
log = "^0.4.14"
rand = "^0.8.4"
rand_xorshift = "^0.3"
# This branch provides throughput plots
criterion = { git = "https://github.com/h33p/criterion.rs.git", branch = "tput" }
memflow-win32 = { path = "../memflow-win32" }
criterion = { git = "https://github.com/h33p/criterion.rs.git", branch = "tput2" }
[dev-dependencies]
memflow = { path = "../memflow", features = ["dummy_mem"] }
memflow-win32 = { path = "../memflow-win32" }
memflow = { version = "0.2", path = "../memflow", features = ["dummy_mem", "plugins"] }
simplelog = "^0.12.0"
[features]
default = []
@ -33,9 +32,9 @@ default = []
name = "read_dummy"
harness = false
#[[bench]]
#name = "read_win32"
#harness = false
[[bench]]
name = "read_win32"
harness = false
[[bench]]
name = "batcher"

@ -2,31 +2,35 @@ use criterion::*;
use memflow::prelude::v1::*;
//use memflow::mem::dummy::DummyMemory as Memory;
use std::convert::TryInto;
//use memflow::dummy::DummyMemory as Memory;
struct NullMem {}
impl NullMem {
pub fn new(_: usize) -> Self {
pub fn new(_: umem) -> Self {
Self {}
}
}
impl PhysicalMemory for NullMem {
fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
black_box(data.iter_mut().count());
fn phys_read_raw_iter(&mut self, data: PhysicalReadMemOps) -> Result<()> {
black_box(data);
Ok(())
}
fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
black_box(data.iter().count());
fn phys_write_raw_iter(&mut self, data: PhysicalWriteMemOps) -> Result<()> {
black_box(data);
Ok(())
}
fn metadata(&self) -> PhysicalMemoryMetadata {
PhysicalMemoryMetadata {
size: 0,
max_address: Address::NULL,
real_size: 0,
readonly: true,
ideal_batch_size: u32::MAX,
}
}
}
@ -39,41 +43,42 @@ use rand_xorshift::XorShiftRng as CurRng;
static mut TSLICE: [[u8; 16]; 0x10000] = [[0; 16]; 0x10000];
fn read_test_nobatcher<T: PhysicalMemory>(
fn read_test_nobatcher<T: MemoryView>(
chunk_size: usize,
mem: &mut T,
mut rng: CurRng,
size: usize,
tbuf: &mut [PhysicalReadData],
size: umem,
tbuf: &mut [ReadDataRaw],
) {
let base_addr = Address::from(rng.gen_range(0, size));
let base_addr = Address::from(rng.gen_range(0..size));
for PhysicalReadData(addr, _) in tbuf.iter_mut().take(chunk_size) {
*addr = (base_addr + rng.gen_range(0, 0x2000)).into();
for CTup3(addr, _, _) in tbuf.iter_mut().take(chunk_size) {
*addr = base_addr + rng.gen_range(0usize..0x2000);
}
let _ = black_box(mem.phys_read_raw_list(&mut tbuf[..chunk_size]));
let iter = tbuf[..chunk_size]
.iter_mut()
.map(|CTup3(a, b, c): &mut ReadDataRaw| CTup3(*a, *b, c.into()));
let _ = black_box(MemOps::with_raw(iter, None, None, |data| {
mem.read_raw_iter(data)
}));
}
fn read_test_batcher<T: PhysicalMemory>(
chunk_size: usize,
mem: &mut T,
mut rng: CurRng,
size: usize,
) {
let base_addr = Address::from(rng.gen_range(0, size));
fn read_test_batcher<T: MemoryView>(chunk_size: usize, mem: &mut T, mut rng: CurRng, size: umem) {
let base_addr = Address::from(rng.gen_range(0..size));
let mut batcher = mem.phys_batcher();
batcher.read_prealloc(chunk_size);
let mut batcher = mem.batcher();
batcher.reserve(chunk_size);
for i in unsafe { TSLICE.iter_mut().take(chunk_size) } {
batcher.read_into((base_addr + rng.gen_range(0, 0x2000)).into(), i);
batcher.read_into(base_addr + rng.gen_range(0usize..0x2000), i);
}
let _ = black_box(batcher.commit_rw());
}
fn read_test_with_ctx<T: PhysicalMemory>(
fn read_test_with_ctx<T: MemoryView>(
bench: &mut Bencher,
chunk_size: usize,
use_batcher: bool,
@ -81,7 +86,7 @@ fn read_test_with_ctx<T: PhysicalMemory>(
) {
let rng = CurRng::from_rng(thread_rng()).unwrap();
let mem_size = size::mb(64);
let mem_size = mem::mb(64);
let mut tbuf = vec![];
@ -89,7 +94,7 @@ fn read_test_with_ctx<T: PhysicalMemory>(
unsafe { TSLICE }
.iter_mut()
.map(|arr| {
PhysicalReadData(PhysicalAddress::INVALID, unsafe {
CTup3(Address::INVALID, Address::INVALID, unsafe {
std::mem::transmute(&mut arr[..])
})
})
@ -119,9 +124,9 @@ fn chunk_read_params<T: PhysicalMemory>(
|b, &chunk_size| {
read_test_with_ctx(
b,
black_box(chunk_size as usize),
black_box(chunk_size.try_into().unwrap()),
use_batcher,
&mut initialize_ctx(),
&mut initialize_ctx().into_phys_view(),
)
},
);
@ -135,20 +140,20 @@ fn chunk_read<T: PhysicalMemory>(
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_batched_read", backend_name);
let group_name = format!("{backend_name}_batched_read");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_without", group_name),
format!("{group_name}_without"),
false,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_with", group_name),
format!("{group_name}_with"),
true,
initialize_ctx,
);
@ -156,13 +161,13 @@ fn chunk_read<T: PhysicalMemory>(
criterion_group! {
name = dummy_read;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_millis(300))
.measurement_time(std::time::Duration::from_millis(2700));
.warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_millis(5000));
targets = dummy_read_group
}
fn dummy_read_group(c: &mut Criterion) {
chunk_read(c, "dummy", &|| Memory::new(size::mb(64)));
chunk_read(c, "dummy", &|| Memory::new(mem::mb(64)));
}
criterion_main!(dummy_read);

@ -3,39 +3,26 @@ use memflow_bench::*;
use criterion::*;
use memflow::mem::dummy::{DummyMemory as Memory, DummyModule, DummyProcess};
use memflow::dummy::DummyMemory as Memory;
use memflow::prelude::v1::*;
fn initialize_virt_ctx() -> Result<(
Memory,
DirectTranslate,
DummyProcess,
impl ScopedVirtualTranslate,
DummyModule,
)> {
let mut mem = Memory::new(size::mb(64));
let vat = DirectTranslate::new();
let proc = mem.alloc_process(size::mb(60), &[]);
let module = proc.get_module(size::mb(4));
let translator = proc.translator();
Ok((mem, vat, proc, translator, module))
fn initialize_virt_ctx(cache_size: usize, use_tlb: bool) -> Result<OsInstanceArcBox<'static>> {
util::build_os("", cache_size, "dummy", use_tlb)
}
fn dummy_read_group(c: &mut Criterion) {
virt::seq_read(c, "dummy", &initialize_virt_ctx);
virt::chunk_read(c, "dummy", &initialize_virt_ctx);
virt::seq_read(c, "dummy", &initialize_virt_ctx, false);
virt::chunk_read(c, "dummy", &initialize_virt_ctx, false);
phys::seq_read(c, "dummy", &|| Ok(Memory::new(size::mb(64))));
phys::chunk_read(c, "dummy", &|| Ok(Memory::new(size::mb(64))));
vat::chunk_vat(c, "dummy", &initialize_virt_ctx);
vat::chunk_vat(c, "dummy", &initialize_virt_ctx, false);
}
criterion_group! {
name = dummy_read;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_millis(300))
.measurement_time(std::time::Duration::from_millis(2700));
.warm_up_time(std::time::Duration::from_millis(1000))
.measurement_time(std::time::Duration::from_millis(10000));
targets = dummy_read_group
}

@ -1,85 +1,39 @@
extern crate memflow_bench;
use memflow_bench::{phys, vat, virt};
use memflow_bench::{phys, util, vat, virt};
use criterion::*;
use memflow::error::{Error, Result};
use memflow::prelude::v1::*;
use memflow_win32::prelude::v1::*;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn create_connector(args: Option<&ConnectorArgs>) -> Result<impl PhysicalMemory + Clone> {
// this workaround is to prevent loaded libraries
// from spitting out to much log information and skewing benchmarks
let filter = log::max_level();
log::set_max_level(log::Level::Error.to_level_filter());
fn create_connector(args: &ConnectorArgs) -> Result<impl PhysicalMemory> {
unsafe { memflow::connector::ConnectorInventory::scan().create_connector("qemu_procfs", args) }
}
fn initialize_virt_ctx() -> Result<(
impl PhysicalMemory,
DirectTranslate,
Win32ProcessInfo,
impl ScopedVirtualTranslate,
Win32ModuleInfo,
)> {
let mut phys_mem = create_connector(&ConnectorArgs::new())?;
let kernel_info = KernelInfo::scanner(&mut phys_mem)
.scan()
.map_err(|_| Error::Other("unable to find kernel"))?;
let mut vat = DirectTranslate::new();
let offsets = Win32Offsets::builder()
.kernel_info(&kernel_info)
.build()
.map_err(|_| Error::Other("unable to initialize win32 offsets with guid"))?;
let mut kernel = Kernel::new(&mut phys_mem, &mut vat, offsets, kernel_info);
let result = Inventory::scan().create_connector("qemu", None, args)?;
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let proc_list = kernel
.process_info_list()
.map_err(|_| Error::Other("unable to read process list"))?;
for i in -100..(proc_list.len() as isize) {
let idx = if i >= 0 {
i as usize
} else {
rng.gen_range(0, proc_list.len())
};
let mod_list: Vec<Win32ModuleInfo> = {
let mut prc = Win32Process::with_kernel_ref(&mut kernel, proc_list[idx].clone());
prc.module_list()
.unwrap_or_default()
.into_iter()
.filter(|module| module.size > 0x1000)
.collect()
};
if !mod_list.is_empty() {
let tmod = &mod_list[rng.gen_range(0, mod_list.len())];
let proc = proc_list[idx].clone();
let translator = proc.translator();
return Ok((phys_mem, vat, proc, translator, tmod.clone())); // TODO: remove clone of mem + vat
}
}
log::set_max_level(filter);
Ok(result)
}
Err("No module found!".into())
fn initialize_virt_ctx(cache_size: usize, use_tlb: bool) -> Result<OsInstanceArcBox<'static>> {
util::build_os("qemu", cache_size, "win32", use_tlb)
}
fn win32_read_group(c: &mut Criterion) {
virt::seq_read(c, "win32", &initialize_virt_ctx);
virt::chunk_read(c, "win32", &initialize_virt_ctx);
phys::seq_read(c, "win32", &|| create_connector(&ConnectorArgs::new()));
phys::chunk_read(c, "win32", &|| create_connector(&ConnectorArgs::new()));
vat::chunk_vat(c, "win32", &initialize_virt_ctx);
virt::seq_read(c, "win32", &initialize_virt_ctx, true);
virt::chunk_read(c, "win32", &initialize_virt_ctx, true);
phys::seq_read(c, "win32", &|| create_connector(None));
phys::chunk_read(c, "win32", &|| create_connector(None));
vat::chunk_vat(c, "win32", &initialize_virt_ctx, true);
}
criterion_group! {
name = win32_read;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_millis(300))
.measurement_time(std::time::Duration::from_millis(2700));
.warm_up_time(std::time::Duration::from_millis(1000))
.measurement_time(std::time::Duration::from_millis(10000));
targets = win32_read_group
}

@ -1,3 +1,4 @@
pub mod phys;
pub mod util;
pub mod vat;
pub mod virt;

@ -1,8 +1,9 @@
use criterion::*;
use memflow::mem::{CachedMemoryAccess, PhysicalMemory};
use memflow::mem::{CachedPhysicalMemory, MemOps, PhysicalMemory};
use memflow::architecture;
use memflow::cglue::*;
use memflow::error::Result;
use memflow::mem::PhysicalReadData;
use memflow::types::*;
@ -11,9 +12,11 @@ use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn rwtest<T: PhysicalMemory>(
use std::convert::TryInto;
fn rwtest(
bench: &mut Bencher,
mem: &mut T,
mut mem: impl PhysicalMemory,
(start, end): (Address, Address),
chunk_sizes: &[usize],
chunk_counts: &[usize],
@ -25,29 +28,35 @@ fn rwtest<T: PhysicalMemory>(
for i in chunk_sizes {
for o in chunk_counts {
let mut vbufs = vec![vec![0 as u8; *i]; *o];
let mut vbufs = vec![vec![0_u8; *i]; *o];
let mut done_size = 0;
while done_size < read_size {
let base_addr = rng.gen_range(start.as_u64(), end.as_u64());
let base_addr = rng.gen_range(start.to_umem()..end.to_umem());
let mut bufs = Vec::with_capacity(*o);
bufs.extend(vbufs.iter_mut().map(|vec| {
let addr = (base_addr + rng.gen_range(0, 0x2000)).into();
let addr = (base_addr + rng.gen_range(0..0x2000)).into();
PhysicalReadData(
CTup3(
PhysicalAddress::with_page(
addr,
PageType::default().write(true),
size::kb(4),
mem::kb(4),
),
vec.as_mut_slice(),
Address::NULL,
vec.as_mut_slice().into(),
)
}));
bench.iter(|| {
let _ = black_box(mem.phys_read_raw_list(&mut bufs));
let iter = bufs
.iter_mut()
.map(|CTup3(a, b, d): &mut PhysicalReadData| CTup3(*a, *b, d.into()));
let _ = black_box(MemOps::with_raw(iter, None, None, |data| {
mem.phys_read_raw_iter(data)
}));
});
done_size += *i * *o;
@ -60,9 +69,9 @@ fn rwtest<T: PhysicalMemory>(
total_size
}
fn read_test_with_mem<T: PhysicalMemory>(
fn read_test_with_mem(
bench: &mut Bencher,
mem: &mut T,
mem: impl PhysicalMemory,
chunk_size: usize,
chunks: usize,
start_end: (Address, Address),
@ -77,29 +86,35 @@ fn read_test_with_mem<T: PhysicalMemory>(
));
}
fn read_test_with_ctx<T: PhysicalMemory>(
fn read_test_with_ctx(
bench: &mut Bencher,
cache_size: u64,
chunk_size: usize,
chunks: usize,
mut mem: T,
mem: impl PhysicalMemory,
) {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let start = Address::from(rng.gen_range(0, size::mb(50)));
let start = Address::from(rng.gen_range(0..size::mb(50)));
let end = start + size::mb(1);
if cache_size > 0 {
let mut mem = CachedMemoryAccess::builder(&mut mem)
let mut cached_mem = CachedPhysicalMemory::builder(mem)
.arch(architecture::x86::x64::ARCH)
.cache_size(size::mb(cache_size as usize))
.page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE)
.build()
.unwrap();
read_test_with_mem(bench, &mut mem, chunk_size, chunks, (start, end));
read_test_with_mem(
bench,
cached_mem.forward_mut(),
chunk_size,
chunks,
(start, end),
);
} else {
read_test_with_mem(bench, &mut mem, chunk_size, chunks, (start, end));
read_test_with_mem(bench, mem, chunk_size, chunks, (start, end));
}
}
@ -118,9 +133,9 @@ fn seq_read_params<T: PhysicalMemory>(
read_test_with_ctx(
b,
black_box(cache_size),
black_box(size as usize),
black_box(size.try_into().unwrap()),
black_box(1),
initialize_ctx().unwrap(),
initialize_ctx().unwrap().forward_mut(),
)
},
);
@ -137,15 +152,15 @@ fn chunk_read_params<T: PhysicalMemory>(
for &chunk_size in [1, 4, 16, 64].iter() {
group.throughput(Throughput::Bytes(size * chunk_size));
group.bench_with_input(
BenchmarkId::new(format!("{}_s{:x}", func_name, size), size * chunk_size),
BenchmarkId::new(format!("{func_name}_s{size:x}"), size * chunk_size),
&size,
|b, &size| {
read_test_with_ctx(
b,
black_box(cache_size),
black_box(size as usize),
black_box(chunk_size as usize),
initialize_ctx().unwrap(),
black_box(size.try_into().unwrap()),
black_box(chunk_size.try_into().unwrap()),
initialize_ctx().unwrap().forward_mut(),
)
},
);
@ -160,23 +175,18 @@ pub fn seq_read<T: PhysicalMemory>(
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_phys_seq_read", backend_name);
let group_name = format!("{backend_name}_phys_seq_read");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
seq_read_params(
&mut group,
format!("{}_nocache", group_name),
format!("{group_name}_nocache"),
0,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{}_cache", group_name),
2,
initialize_ctx,
);
seq_read_params(&mut group, format!("{group_name}_cache"), 2, initialize_ctx);
}
pub fn chunk_read<T: PhysicalMemory>(
@ -186,21 +196,16 @@ pub fn chunk_read<T: PhysicalMemory>(
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_phys_chunk_read", backend_name);
let group_name = format!("{backend_name}_phys_chunk_read");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_nocache", group_name),
format!("{group_name}_nocache"),
0,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_cache", group_name),
2,
initialize_ctx,
);
chunk_read_params(&mut group, format!("{group_name}_cache"), 2, initialize_ctx);
}

@ -0,0 +1,79 @@
use memflow::prelude::v1::*;
pub fn build_os(
conn_name: &str,
cache_size: usize,
os_name: &str,
use_tlb: bool,
) -> Result<OsInstanceArcBox<'static>> {
// this workaround is to prevent loaded libraries
// from spitting out to much log information and skewing benchmarks
let filter = log::max_level();
log::set_max_level(log::Level::Debug.to_level_filter());
let inventory = Inventory::scan();
log::set_max_level(log::Level::Error.to_level_filter());
let mut args = Args::new();
if !use_tlb {
args = args.insert("vatcache", "none");
}
let page_cache_params = if cache_size > 0 {
Some(ConnectorMiddlewareArgs::new().cache_size(cache_size))
} else {
None
};
let conn_args = ConnectorArgs::new(None, Default::default(), page_cache_params);
let args = OsArgs::new(None, args);
let ret = if conn_name.is_empty() {
inventory.builder().os(os_name).args(args).build()
} else {
inventory
.builder()
.connector(conn_name)
.args(conn_args)
.os(os_name)
.args(args)
.build()
}?;
log::set_max_level(filter);
Ok(ret)
}
pub fn find_proc<T: Os>(os: &mut T) -> Result<(<T as Os>::ProcessType<'_>, ModuleInfo)> {
let infos = os.process_info_list()?;
let mut data = None;
for info in infos {
if let Ok(mut proc) = os.process_by_info(info.clone()) {
let mut module = None;
proc.module_list_callback(
None,
(&mut |info: ModuleInfo| {
if info.size > 0x1000 {
module = Some(info);
}
module.is_none()
})
.into(),
)?;
if let Some(module) = module {
data = Some((info, module));
break;
}
}
}
data.and_then(move |(info, module)| Some((os.process_by_info(info).ok()?, module)))
.ok_or_else(|| ErrorKind::NotFound.into())
}

@ -1,218 +1,129 @@
use criterion::*;
use memflow::mem::{CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualTranslate};
use memflow::architecture::ScopedVirtualTranslate;
use memflow::error::Result;
use memflow::iter::FnExtend;
use memflow::process::*;
use memflow::types::*;
use memflow::cglue::as_mut;
use memflow::mem::virt_translate::*;
use memflow::prelude::v1::*;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn vat_test_with_mem<
T: PhysicalMemory,
V: VirtualTranslate,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
use std::convert::TryInto;
fn vat_test_with_mem(
bench: &mut Bencher,
phys_mem: &mut T,
vat: &mut V,
vat: &mut impl VirtualTranslate,
chunk_count: usize,
translations: usize,
translator: S,
module: M,
) -> usize {
module: ModuleInfo,
) {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let mut bufs = vec![Address::null(); chunk_count];
let mut done_size = 0;
let mut bufs = vec![CTup2(Address::null(), 1); translations];
let mut out = Vec::new();
let base_addr = rng.gen_range(module.base.to_umem()..(module.base.to_umem() + module.size));
while done_size < translations {
let base_addr = rng.gen_range(
module.base().as_u64(),
module.base().as_u64() + module.size() as u64,
);
for CTup2(address, _) in bufs.iter_mut() {
*address = (base_addr + rng.gen_range(0..0x2000)).into();
}
for addr in bufs.iter_mut() {
*addr = (base_addr + rng.gen_range(0, 0x2000)).into();
}
let mut out = vec![];
bench.iter(|| {
bench.iter(|| {
for chunk in bufs.chunks_mut(chunk_count) {
out.clear();
vat.virt_to_phys_iter(
phys_mem,
&translator,
bufs.iter_mut().map(|x| (*x, 1)),
&mut out,
&mut FnExtend::new(|_| {}),
);
vat.virt_to_phys_list(chunk, (&mut out).into(), (&mut |_| true).into());
black_box(&out);
});
done_size += chunk_count;
}
done_size
}
});
}
fn vat_test_with_ctx<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
fn vat_test_with_os(
bench: &mut Bencher,
cache_size: u64,
chunks: usize,
translations: usize,
use_tlb: bool,
(mut mem, mut vat, prc, translator, tmod): (T, V, P, S, M),
os: &mut OsInstanceArcBox<'static>,
) {
if cache_size > 0 {
let cache = CachedMemoryAccess::builder(&mut mem)
.arch(prc.sys_arch())
.cache_size(size::mb(cache_size as usize))
.page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE);
if use_tlb {
let mut mem = cache.build().unwrap();
let mut vat = CachedVirtualTranslate::builder(vat)
.arch(prc.sys_arch())
.build()
.unwrap();
vat_test_with_mem(
bench,
&mut mem,
&mut vat,
chunks,
translations,
translator,
tmod,
);
} else {
let mut mem = cache.build().unwrap();
vat_test_with_mem(
bench,
&mut mem,
&mut vat,
chunks,
translations,
translator,
tmod,
);
}
} else if use_tlb {
let mut vat = CachedVirtualTranslate::builder(vat)
.arch(prc.sys_arch())
.build()
.unwrap();
vat_test_with_mem(
bench,
&mut mem,
&mut vat,
chunks,
translations,
translator,
tmod,
);
} else {
vat_test_with_mem(
bench,
&mut mem,
&mut vat,
chunks,
translations,
translator,
tmod,
);
}
let (mut process, module) = crate::util::find_proc(os).unwrap();
vat_test_with_mem(
bench,
as_mut!(process impl VirtualTranslate).unwrap(),
chunks,
translations,
module,
);
}
fn chunk_vat_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
fn chunk_vat_params(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
cache_size: usize,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
) {
let size = 0x10;
let mut os = initialize_ctx(cache_size, use_tlb).unwrap();
for &chunk_size in [1, 4, 16, 64].iter() {
group.throughput(Throughput::Elements(chunk_size * size));
group.bench_with_input(
BenchmarkId::new(func_name.clone(), chunk_size),
&size,
|b, &size| {
vat_test_with_ctx(
vat_test_with_os(
b,
black_box(cache_size),
black_box(chunk_size as usize),
black_box((size * chunk_size) as usize),
black_box(use_tlb),
initialize_ctx().unwrap(),
black_box(chunk_size.try_into().unwrap()),
black_box((size * chunk_size).try_into().unwrap()),
&mut os,
)
},
);
}
}
pub fn chunk_vat<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
pub fn chunk_vat(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
use_caches: bool,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_chunk_vat", backend_name);
let group_name = format!("{backend_name}_chunk_vat");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_vat_params(
&mut group,
format!("{}_nocache", group_name),
format!("{group_name}_nocache"),
0,
false,
initialize_ctx,
);
chunk_vat_params(
&mut group,
format!("{}_tlb_nocache", group_name),
0,
true,
initialize_ctx,
);
chunk_vat_params(
&mut group,
format!("{}_cache", group_name),
2,
false,
initialize_ctx,
);
chunk_vat_params(
&mut group,
format!("{}_tlb_cache", group_name),
2,
true,
initialize_ctx,
);
if use_caches {
chunk_vat_params(
&mut group,
format!("{group_name}_tlb_nocache"),
0,
true,
initialize_ctx,
);
chunk_vat_params(
&mut group,
format!("{group_name}_cache"),
2,
false,
initialize_ctx,
);
chunk_vat_params(
&mut group,
format!("{group_name}_tlb_cache"),
2,
true,
initialize_ctx,
);
}
}

@ -1,23 +1,22 @@
use criterion::*;
use memflow::mem::{
CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualDMA, VirtualMemory,
VirtualReadData, VirtualTranslate,
};
use memflow::mem::MemoryView;
use memflow::architecture::ScopedVirtualTranslate;
use memflow::cglue::*;
use memflow::error::Result;
use memflow::process::*;
use memflow::types::*;
use memflow::os::*;
use memflow::plugins::*;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn rwtest<T: VirtualMemory, M: OsProcessModuleInfo>(
use std::convert::TryInto;
fn rwtest<T: MemoryView>(
bench: &mut Bencher,
virt_mem: &mut T,
module: &M,
module: &ModuleInfo,
chunk_sizes: &[usize],
chunk_counts: &[usize],
read_size: usize,
@ -28,30 +27,28 @@ fn rwtest<T: VirtualMemory, M: OsProcessModuleInfo>(
for i in chunk_sizes {
for o in chunk_counts {
let mut vbufs = vec![vec![0 as u8; *i]; *o];
let mut vbufs = vec![vec![0_u8; *i]; *o];
let mut done_size = 0;
while done_size < read_size {
let base_addr = rng.gen_range(
module.base().as_u64(),
module.base().as_u64() + module.size() as u64,
);
let base_addr =
rng.gen_range(module.base.to_umem()..(module.base.to_umem() + module.size));
let mut bufs = Vec::with_capacity(*o);
for VirtualReadData(addr, _) in bufs.iter_mut() {
*addr = (base_addr + rng.gen_range(0, 0x2000)).into();
for CTup2(addr, _) in bufs.iter_mut() {
*addr = (base_addr + rng.gen_range(0..0x2000)).into();
}
bufs.extend(vbufs.iter_mut().map(|vec| {
VirtualReadData(
(base_addr + rng.gen_range(0, 0x2000)).into(),
vec.as_mut_slice(),
CTup2(
(base_addr + rng.gen_range(0..0x2000)).into(),
vec.as_mut_slice().into(),
)
}));
bench.iter(|| {
let _ = black_box(virt_mem.virt_read_raw_list(bufs.as_mut_slice()));
let _ = black_box(virt_mem.read_raw_list(bufs.as_mut_slice()));
});
done_size += *i * *o;
}
@ -63,12 +60,12 @@ fn rwtest<T: VirtualMemory, M: OsProcessModuleInfo>(
total_size
}
pub fn read_test_with_mem<T: VirtualMemory, M: OsProcessModuleInfo>(
pub fn read_test_with_mem<T: MemoryView>(
bench: &mut Bencher,
virt_mem: &mut T,
chunk_size: usize,
chunks: usize,
tmod: M,
tmod: ModuleInfo,
) {
black_box(rwtest(
bench,
@ -80,111 +77,63 @@ pub fn read_test_with_mem<T: VirtualMemory, M: OsProcessModuleInfo>(
));
}
fn read_test_with_ctx<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
fn read_test_with_os(
bench: &mut Bencher,
cache_size: u64,
chunk_size: usize,
chunks: usize,
use_tlb: bool,
(mut mem, vat, proc, translator, tmod): (T, V, P, S, M),
os: &mut OsInstanceArcBox<'static>,
) {
if cache_size > 0 {
let cache = CachedMemoryAccess::builder(&mut mem)
.arch(proc.sys_arch())
.cache_size(size::mb(cache_size as usize))
.page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE);
if use_tlb {
let mem = cache.build().unwrap();
let vat = CachedVirtualTranslate::builder(vat)
.arch(proc.sys_arch())
.build()
.unwrap();
let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat);
read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod);
} else {
let mem = cache.build().unwrap();
let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat);
read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod);
}
} else if use_tlb {
let vat = CachedVirtualTranslate::builder(vat)
.arch(proc.sys_arch())
.build()
.unwrap();
let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat);
read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod);
} else {
let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat);
read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod);
}
let (mut proc, module) = crate::util::find_proc(os).unwrap();
read_test_with_mem(bench, &mut proc, chunk_size, chunks, module);
}
fn seq_read_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
fn seq_read_params(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
cache_size: usize,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
) {
let mut os = initialize_ctx(cache_size, use_tlb).unwrap();
for &size in [0x8, 0x10, 0x100, 0x1000, 0x10000].iter() {
group.throughput(Throughput::Bytes(size));
group.bench_with_input(
BenchmarkId::new(func_name.clone(), size),
&size,
|b, &size| {
read_test_with_ctx(
read_test_with_os(
b,
black_box(cache_size),
black_box(size as usize),
black_box(size.try_into().unwrap()),
black_box(1),
black_box(use_tlb),
initialize_ctx().unwrap(),
&mut os,
)
},
);
}
}
fn chunk_read_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
fn chunk_read_params(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
cache_size: usize,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
) {
let mut os = initialize_ctx(cache_size, use_tlb).unwrap();
for &size in [0x8, 0x10, 0x100, 0x1000].iter() {
for &chunk_size in [1, 4, 16, 64].iter() {
group.throughput(Throughput::Bytes(size * chunk_size));
group.bench_with_input(
BenchmarkId::new(format!("{}_s{:x}", func_name, size), size * chunk_size),
BenchmarkId::new(format!("{func_name}_s{size:x}"), size * chunk_size),
&size,
|b, &size| {
read_test_with_ctx(
read_test_with_os(
b,
black_box(cache_size),
black_box(size as usize),
black_box(chunk_size as usize),
black_box(use_tlb),
initialize_ctx().unwrap(),
black_box(size.try_into().unwrap()),
black_box(chunk_size.try_into().unwrap()),
&mut os,
)
},
);
@ -192,98 +141,93 @@ fn chunk_read_params<
}
}
pub fn seq_read<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
pub fn seq_read(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
use_caches: bool,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_virt_seq_read", backend_name);
let group_name = format!("{backend_name}_virt_seq_read");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
seq_read_params(
&mut group,
format!("{}_nocache", group_name),
0,
false,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{}_tlb_nocache", group_name),
format!("{group_name}_nocache"),
0,
true,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{}_cache", group_name),
2,
false,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{}_tlb_cache", group_name),
2,
true,
initialize_ctx,
);
if use_caches {
seq_read_params(
&mut group,
format!("{group_name}_tlb_nocache"),
0,
true,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{group_name}_cache"),
2,
false,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{group_name}_tlb_cache"),
2,
true,
initialize_ctx,
);
}
}
pub fn chunk_read<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
pub fn chunk_read(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
use_caches: bool,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_virt_chunk_read", backend_name);
let group_name = format!("{backend_name}_virt_chunk_read");
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_nocache", group_name),
format!("{group_name}_nocache"),
0,
false,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_tlb_nocache", group_name),
0,
true,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_cache", group_name),
2,
false,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_tlb_cache", group_name),
2,
true,
initialize_ctx,
);
if use_caches {
chunk_read_params(
&mut group,
format!("{group_name}_tlb_nocache"),
0,
true,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{group_name}_cache"),
2,
false,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{group_name}_tlb_cache"),
2,
true,
initialize_ctx,
);
}
}

@ -0,0 +1,39 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 2048
v.cpus = 4
config.vm.synced_folder "../..", "/memflow", create: true, disabled: false
end
config.vm.provider :libvirt do |v|
v.memory = 2048
v.cpus = 4
v.qemu_use_session = false
config.vm.synced_folder "../..", "/memflow", type: 'nfs', nfs_udp: false, create: true, disabled: false
end
config.vm.box = "generic/ubuntu2004"
config.vm.provision :shell, privileged: true, inline: $install_updates
config.vm.provision :shell, privileged: false, inline: $install_memflow
end
$install_updates = <<-SCRIPT
echo "installing updates"
export DEBIAN_FRONTEND=noninteractive
apt-get -qq update
apt-get -qq install git build-essential
SCRIPT
$install_memflow = <<-SCRIPT
echo "installing rust"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
echo "building memflow"
cd /memflow
cargo build --release --workspace --all-features
# TODO: install memflow-win32, memflow-coredump, etc
SCRIPT

@ -1,6 +1,6 @@
[package]
name = "memflow-derive"
version = "0.1.5"
version = "0.2.0"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "derive macros for the memflow physical memory introspection framework"
@ -8,7 +8,7 @@ documentation = "https://docs.rs/memflow-derive"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow"
license-file = "../LICENSE"
license = "MIT"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "memory-management", "os" ]
@ -19,10 +19,9 @@ maintenance = { status = "actively-developed" }
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"
darling = "0.10"
proc-macro2 = "^1.0"
syn = "^2.0"
quote = "^1.0"
darling = "^0.20"
proc-macro-crate = "^2.0"
[dev-dependencies]
memflow = { version = "0.1", path = "../memflow" }

@ -1,69 +1,603 @@
use darling::FromMeta;
use darling::{ast::NestedMeta, FromMeta};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, ItemFn};
use proc_macro_crate::*;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, ItemFn};
#[derive(Debug, FromMeta)]
struct ConnectorFactoryArgs {
name: String,
#[darling(default)]
version: Option<String>,
#[darling(default)]
description: Option<String>,
#[darling(default)]
help_fn: Option<String>,
#[darling(default)]
target_list_fn: Option<String>,
#[darling(default)]
accept_input: bool,
#[darling(default)]
return_wrapped: bool,
#[darling(default)]
no_default_cache: bool,
}
#[derive(Debug, FromMeta)]
struct OsFactoryArgs {
name: String,
#[darling(default)]
version: Option<String>,
#[darling(default)]
description: Option<String>,
#[darling(default)]
help_fn: Option<String>,
#[darling(default)]
accept_input: bool,
#[darling(default)]
return_wrapped: bool,
}
fn validate_plugin_name(name: &str) {
if !name
.chars()
.all(|c| char::is_alphanumeric(c) || c == '-' || c == '_')
{
panic!("plugin name must only contain alphanumeric characters");
}
}
// We should add conditional compilation for the crate-type here
// so our rust libraries who use a connector wont export those functions
// again by themselves (e.g. the ffi).
//
// This would also lead to possible duplicated symbols if
// multiple connectors are imported.
//
// See https://github.com/rust-lang/rust/issues/20267 for the tracking issue.
//
// #[cfg(crate_type = "cdylib")]
/// Creates a memflow connector plugin.
/// This function takes care of supplying all necessary underlying structures
/// for exposing a memflow connector plugin in the form of a dylib.
///
/// Remarks:
///
/// We should add conditional compilation for the crate-type here
/// so our rust libraries who use a connector wont export those functions
/// again by themselves (e.g. the ffi).
///
/// This would also lead to possible duplicated symbols if
/// multiple connectors are imported.
///
/// See <https://github.com/rust-lang/rust/issues/20267> for the tracking issue.
///
/// #[cfg(crate_type = "cdylib")]
///
/// Macro Parameters:
///
/// `name` - The name of the plugin
/// `version` - The version of the plugin
/// `description` - Short description of the plugin
/// `help_fn` - Name of the function that provides a help text to the user
/// `target_list_fn` - Name of the function that provides a list of all targets to the user
/// `accept_input` - Wether or not this Connector is able to accept an Os-Plugin as an input
/// `return_wrapped` - Wether or not the return value is an already wrapped cglue object or if the macro needs to construct it
/// `no_default_cache` - Disables the default caching behavior if no cache configuration is supplied by the user.
///
/// Caching:
///
/// By default the proc macro will call `memflow::plugins::connector::create_instance` internally which will handle the caching functionality.
/// Either the user did not specify any caching, which results in the default caching configuration being used, or the user
/// did choose a custom caching configuration which will override the default caching configuration.
///
/// In case `no_default_cache` is used the default behavior will be to use no caching. If the user supplies a cache configuration even
/// if `no_default_cache` is set the `memflow::plugins::connector::create_instance` function will still instantiate the requested configuration.
///
/// In case `return_wrapped` is set to true the caching behavior has to be handled by the end user simply by
/// calling `memflow::plugins::connector::create_instance` with the appropiate arguments.
///
/// Examples:
///
/// Simple usage:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", version = "1.0.0", description = "Dummy Plugin for Testing purposes")]
/// pub fn create_connector(_args: &ConnectorArgs) -> Result<DummyMemory> {
/// Ok(DummyMemory::new(size::mb(16)))
/// }
/// ```
///
/// Disable default caching:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", no_default_cache = true)]
/// pub fn create_connector(_args: &ConnectorArgs) -> Result<DummyMemory> {
/// Ok(DummyMemory::new(size::mb(16)))
/// }
/// ```
///
/// Custom help function:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", help_fn = "help")]
/// pub fn create_connector(_args: &ConnectorArgs) -> Result<DummyMemory> {
/// Ok(DummyMemory::new(size::mb(16)))
/// }
///
/// pub fn help() -> String {
/// "Dummy Plugin for Testing purposes".to_string()
/// }
/// ```
///
/// Custom target list function:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// # use std::vec::Vec;
/// #[connector(name = "dummy_conn", target_list_fn = "target_list")]
/// pub fn create_connector(_args: &ConnectorArgs) -> Result<DummyMemory> {
/// Ok(DummyMemory::new(size::mb(16)))
/// }
///
/// pub fn target_list() -> Result<Vec<TargetInfo>> {
/// Ok(Vec::new())
/// }
/// ```
///
/// Wrapped return with manually created connector instance:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", return_wrapped = true)]
/// pub fn create_connector(
/// args: &ConnectorArgs,
/// lib: LibArc,
/// ) -> Result<ConnectorInstanceArcBox<'static>> {
/// let connector = DummyMemory::new(size::mb(16));
/// Ok(memflow::plugins::connector::create_instance(connector, lib, args, false))
/// }
/// ```
///
/// Connector with input parameter:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", accept_input = true)]
/// pub fn create_connector(
/// _args: &ConnectorArgs,
/// _os: Option<OsInstanceArcBox<'static>>,
/// ) -> Result<DummyMemory> {
/// Ok(DummyMemory::new(size::mb(16)))
/// }
/// ```
///
/// Connector with input parameter and manually created connector instance:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[connector(name = "dummy_conn", accept_input = true, return_wrapped = true)]
/// pub fn create_connector<'a>(
/// args: &ConnectorArgs,
/// _os: Option<OsInstanceArcBox<'static>>,
/// lib: LibArc,
/// ) -> Result<ConnectorInstanceArcBox<'static>> {
/// let connector = DummyMemory::new(size::mb(16));
/// Ok(memflow::plugins::connector::create_instance(connector, lib, args, false))
/// }
/// ```
#[proc_macro_attribute]
pub fn connector(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(args as AttributeArgs);
let crate_path = crate_path();
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => return TokenStream::from(darling::Error::from(e).write_errors()),
};
let args = match ConnectorFactoryArgs::from_list(&attr_args) {
Ok(v) => v,
Err(e) => return TokenStream::from(e.write_errors()),
};
let connector_name = args.name;
validate_plugin_name(&connector_name);
let version_gen = args
.version
.map_or_else(|| quote! { env!("CARGO_PKG_VERSION") }, |v| quote! { #v });
let description_gen = args.description.map_or_else(
|| quote! { env!("CARGO_PKG_DESCRIPTION") },
|d| quote! { #d },
);
let help_gen = if args.help_fn.is_some() {
quote! { Some(mf_help_callback) }
} else {
quote! { None }
};
let target_list_gen = if args.target_list_fn.is_some() {
quote! { Some(mf_target_list_callback) }
} else {
quote! { None }
};
let connector_descriptor: proc_macro2::TokenStream =
["MEMFLOW_CONNECTOR_", &connector_name.to_uppercase()]
.concat()
.parse()
.unwrap();
let func = parse_macro_input!(input as ItemFn);
let func_name = &func.sig.ident;
let gen = quote! {
#[cfg(feature = "inventory")]
#[doc(hidden)]
pub static CONNECTOR_NAME: &str = #connector_name;
let func_accept_input = args.accept_input;
let func_return_wrapped = args.return_wrapped;
let no_default_cache = args.no_default_cache;
#[cfg(feature = "inventory")]
// create wrapping function according to input/output configuration
#[allow(clippy::collapsible_else_if)]
let create_fn_gen_inner = if func_accept_input {
if !func_return_wrapped {
// args + os
quote! {
#crate_path::plugins::wrap_with_input(args, os.into(), lib, logger, out, |a, os, lib| {
Ok(#crate_path::plugins::connector::create_instance(#func_name(a, os)?, lib, a, #no_default_cache))
})
}
} else {
// args + os + lib
quote! {
#crate_path::plugins::wrap_with_input(args, os.into(), lib, logger, out, #func_name)
}
}
} else {
if !func_return_wrapped {
// args
quote! {
#crate_path::plugins::wrap(args, lib, logger, out, |a, lib| {
Ok(#crate_path::plugins::connector::create_instance(#func_name(a)?, lib, a, #no_default_cache))
})
}
} else {
// args + lib
quote! {
#crate_path::plugins::wrap(args, lib, logger, out, #func_name)
}
}
};
let create_fn_gen = quote! {
#[doc(hidden)]
extern "C" fn mf_create(
args: Option<&#crate_path::plugins::connector::ConnectorArgs>,
os: #crate_path::cglue::option::COption<#crate_path::plugins::os::OsInstanceArcBox<'static>>,
lib: #crate_path::plugins::LibArc,
logger: Option<&'static #crate_path::plugins::PluginLogger>,
out: &mut #crate_path::plugins::connector::MuConnectorInstanceArcBox<'static>
) -> i32 {
#create_fn_gen_inner
}
};
let help_fn_gen = args.help_fn.map(|v| v.parse().unwrap()).map_or_else(
proc_macro2::TokenStream::new,
|func_name: proc_macro2::TokenStream| {
quote! {
#[doc(hidden)]
extern "C" fn mf_help_callback(
mut callback: #crate_path::plugins::HelpCallback,
) {
let helpstr = #func_name();
let _ = callback.call(helpstr.into());
}
}
},
);
let target_list_fn_gen = args.target_list_fn.map(|v| v.parse().unwrap()).map_or_else(
proc_macro2::TokenStream::new,
|func_name: proc_macro2::TokenStream| {
quote! {
#[doc(hidden)]
extern "C" fn mf_target_list_callback(
mut callback: #crate_path::plugins::TargetCallback,
) -> i32 {
#func_name()
.map(|mut targets| {
targets
.into_iter()
.take_while(|t| callback.call(t.clone()))
.for_each(|_| ());
})
.into_int_result()
}
}
},
);
let gen = quote! {
#[doc(hidden)]
#[no_mangle]
pub static MEMFLOW_CONNECTOR: ::memflow::connector::ConnectorDescriptor = ::memflow::connector::ConnectorDescriptor {
connector_version: ::memflow::connector::MEMFLOW_CONNECTOR_VERSION,
name: CONNECTOR_NAME,
factory: connector_factory,
pub static #connector_descriptor: #crate_path::plugins::ConnectorDescriptor = #crate_path::plugins::ConnectorDescriptor {
plugin_version: #crate_path::plugins::MEMFLOW_PLUGIN_VERSION,
accept_input: #func_accept_input,
input_layout: <<#crate_path::plugins::LoadableConnector as #crate_path::plugins::Loadable>::CInputArg as #crate_path::abi_stable::StableAbi>::LAYOUT,
output_layout: <<#crate_path::plugins::LoadableConnector as #crate_path::plugins::Loadable>::Instance as #crate_path::abi_stable::StableAbi>::LAYOUT,
name: #crate_path::cglue::CSliceRef::from_str(#connector_name),
version: #crate_path::cglue::CSliceRef::from_str(#version_gen),
description: #crate_path::cglue::CSliceRef::from_str(#description_gen),
help_callback: #help_gen,
target_list_callback: #target_list_gen,
create: mf_create,
};
#[cfg(feature = "inventory")]
pub extern "C" fn connector_factory(args: &::memflow::connector::ConnectorArgs) -> ::memflow::error::Result<::memflow::connector::ConnectorType> {
let connector = #func_name(args)?;
Ok(Box::new(connector))
#create_fn_gen
#help_fn_gen
#target_list_fn_gen
#func
};
gen.into()
}
/// Creates a memflow os plugin.
/// This function takes care of supplying all necessary underlying structures
/// for exposing a memflow os plugin in the form of a dylib.
///
/// Macro Parameters:
///
/// `name` - The name of the plugin
/// `version` - The version of the plugin
/// `description` - Short description of the plugin
/// `help_fn` - Name of the function that provides a help text to the user
/// `accept_input` - Wether or not this Os-Plugin is able to accept a connector as an input
/// `return_wrapped` - Wether or not the return value is an already wrapped cglue object or if the macro needs to construct it
///
/// Examples:
///
/// Simple usage:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[os(name = "dummy_os", version = "1.0.0", description = "Dummy Plugin for Testing purposes")]
/// pub fn create_os(
/// _args: &OsArgs,
/// ) -> Result<DummyOs> {
/// let phys_mem = DummyMemory::new(size::mb(16));
/// Ok(DummyOs::new(phys_mem))
/// }
///
/// ```
/// Custom help function:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[os(name = "dummy_os", help_fn = "help")]
/// pub fn create_os(
/// _args: &OsArgs,
/// ) -> Result<DummyOs> {
/// let phys_mem = DummyMemory::new(size::mb(16));
/// Ok(DummyOs::new(phys_mem))
/// }
///
/// pub fn help() -> String {
/// "Dummy Plugin for Testing purposes".to_string()
/// }
/// ```
///
/// Wrapped return with manually created os instance:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[os(name = "dummy_os", return_wrapped = true)]
/// pub fn create_os(
/// args: &OsArgs,
/// lib: LibArc,
/// ) -> Result<OsInstanceArcBox<'static>> {
/// let phys_mem = DummyMemory::new(size::mb(16));
/// let os = DummyOs::new(phys_mem);
/// Ok(memflow::plugins::os::create_instance(os, lib, args))
/// }
/// ```
///
/// Os with input parameter:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[os(name = "dummy_os", accept_input = true)]
/// pub fn create_os(
/// args: &OsArgs,
/// _connector: Option<ConnectorInstanceArcBox<'static>>,
/// ) -> Result<DummyOs> {
/// let phys_mem = DummyMemory::new(size::mb(16));
/// Ok(DummyOs::new(phys_mem))
/// }
/// ```
///
/// Os with input parameter and manually created os instance:
/// ```rust,ignore
/// # use ::memflow::prelude::v1::*;
/// # use ::memflow::dummy::*;
/// #[os(name = "dummy_os", accept_input = true, return_wrapped = true)]
/// pub fn create_os(
/// args: &OsArgs,
/// _connector: Option<ConnectorInstanceArcBox<'static>>,
/// lib: LibArc,
/// ) -> Result<OsInstanceArcBox<'static>> {
/// let phys_mem = DummyMemory::new(size::mb(16));
/// let os = DummyOs::new(phys_mem);
/// Ok(memflow::plugins::os::create_instance(os, lib, args))
/// }
/// ```
#[proc_macro_attribute]
pub fn os(args: TokenStream, input: TokenStream) -> TokenStream {
let crate_path = crate_path();
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => return TokenStream::from(darling::Error::from(e).write_errors()),
};
let args = match OsFactoryArgs::from_list(&attr_args) {
Ok(v) => v,
Err(e) => return TokenStream::from(e.write_errors()),
};
let os_name = args.name;
validate_plugin_name(&os_name);
let version_gen = args
.version
.map_or_else(|| quote! { env!("CARGO_PKG_VERSION") }, |v| quote! { #v });
let description_gen = args.description.map_or_else(
|| quote! { env!("CARGO_PKG_DESCRIPTION") },
|d| quote! { #d },
);
let help_gen = if args.help_fn.is_some() {
quote! { Some(mf_help_callback) }
} else {
quote! { None }
};
let os_descriptor: proc_macro2::TokenStream = ["MEMFLOW_OS_", &os_name.to_uppercase()]
.concat()
.parse()
.unwrap();
let func = parse_macro_input!(input as ItemFn);
let func_name = &func.sig.ident;
let func_accept_input = args.accept_input;
let func_return_wrapped = args.return_wrapped;
// create wrapping function according to input/output configuration
#[allow(clippy::collapsible_else_if)]
let create_fn_gen_inner = if func_accept_input {
if !func_return_wrapped {
// inputs: args + connector
quote! {
#crate_path::plugins::wrap_with_input(args, connector.into(), lib, logger, out, |a, os, lib| {
Ok(#crate_path::plugins::os::create_instance(#func_name(a, os)?, lib, a))
})
}
} else {
// inputs: args + connector + lib
quote! {
#crate_path::plugins::wrap_with_input(args, connector.into(), lib, logger, out, #func_name)
}
}
} else {
if !func_return_wrapped {
// inputs: args
quote! {
#crate_path::plugins::wrap(args, lib, logger, out, |a, lib| {
Ok(#crate_path::plugins::os::create_instance(#func_name(a)?, lib, a))
})
}
} else {
// inputs: args + lib
quote! {
#crate_path::plugins::wrap(args, lib, logger, out, #func_name)
}
}
};
pub fn static_connector_factory(args: &::memflow::connector::ConnectorArgs) -> ::memflow::error::Result<impl ::memflow::mem::PhysicalMemory> {
#func_name(args)
let create_fn_gen = quote! {
#[doc(hidden)]
extern "C" fn mf_create(
args: Option<&#crate_path::plugins::os::OsArgs>,
connector: #crate_path::cglue::COption<#crate_path::plugins::connector::ConnectorInstanceArcBox<'static>>,
lib: #crate_path::plugins::LibArc,
logger: Option<&'static #crate_path::plugins::PluginLogger>,
out: &mut #crate_path::plugins::os::MuOsInstanceArcBox<'static>
) -> i32 {
#create_fn_gen_inner
}
};
let help_fn_gen = args.help_fn.map(|v| v.parse().unwrap()).map_or_else(
proc_macro2::TokenStream::new,
|func_name: proc_macro2::TokenStream| {
quote! {
#[doc(hidden)]
extern "C" fn mf_help_callback(
mut callback: #crate_path::plugins::HelpCallback,
) {
let helpstr = #func_name();
let _ = callback.call(helpstr.into());
}
}
},
);
let gen = quote! {
#[doc(hidden)]
#[no_mangle]
pub static #os_descriptor: #crate_path::plugins::os::OsDescriptor = #crate_path::plugins::os::OsDescriptor {
plugin_version: #crate_path::plugins::MEMFLOW_PLUGIN_VERSION,
accept_input: #func_accept_input,
input_layout: <<#crate_path::plugins::os::LoadableOs as #crate_path::plugins::Loadable>::CInputArg as #crate_path::abi_stable::StableAbi>::LAYOUT,
output_layout: <<#crate_path::plugins::os::LoadableOs as #crate_path::plugins::Loadable>::Instance as #crate_path::abi_stable::StableAbi>::LAYOUT,
name: #crate_path::cglue::CSliceRef::from_str(#os_name),
version: #crate_path::cglue::CSliceRef::from_str(#version_gen),
description: #crate_path::cglue::CSliceRef::from_str(#description_gen),
help_callback: #help_gen,
target_list_callback: None, // non existent on Os Plugins
create: mf_create,
};
#create_fn_gen
#help_fn_gen
#func
};
gen.into()
}
/// Auto derive the `Pod` trait for structs.
///
/// The type is checked for requirements of the `Pod` trait:
///
/// * Be annotated with `repr(C)` or `repr(transparent)`.
///
/// * Have every field's type implement `Pod` itself.
///
/// * Not have any padding between its fields.
///
/// # Compile errors
///
/// Error reporting is not very ergonomic due to how errors are detected:
///
/// * `error[E0277]: the trait bound $TYPE: Pod is not satisfied`
///
/// The struct contains a field whose type does not implement `Pod`.
///
/// * `error[E0512]: cannot transmute between types of different sizes, or dependently-sized types`
///
/// This error means your struct has padding as its size is not equal to a byte array of length equal to the sum of the size of its fields.
///
/// * `error: no rules expected the token <`
///
/// The struct contains generic parameters which are not supported. It may still be possible to manually implement `Pod` but extra care should be taken to ensure its invariants are upheld.
///
/// # Remarks:
/// This custom derive macro is required because the dataview proc macro searches for ::dataview::derive_pod!().
/// See <https://github.com/CasualX/dataview/blob/master/derive_pod/lib.rs> for the original implementation.
#[proc_macro_derive(Pod)]
pub fn pod_derive(input: TokenStream) -> TokenStream {
let crate_path = crate_path();
format!("{crate_path}::dataview::derive_pod!{{ {input} }}")
.parse()
.unwrap()
}
#[proc_macro_derive(ByteSwap)]
pub fn byteswap_derive(input: TokenStream) -> TokenStream {
let crate_path = crate_path();
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@ -85,7 +619,7 @@ pub fn byteswap_derive(input: TokenStream) -> TokenStream {
};
let gen = quote!(
impl #impl_generics ::memflow::types::byte_swap::ByteSwap for #name #ty_generics #where_clause {
impl #impl_generics #crate_path::types::byte_swap::ByteSwap for #name #ty_generics #where_clause {
fn byte_swap(&mut self) {
#gen_inner
}
@ -94,3 +628,34 @@ pub fn byteswap_derive(input: TokenStream) -> TokenStream {
gen.into()
}
fn crate_path() -> proc_macro2::TokenStream {
let (col, ident) = crate_path_ident();
quote!(#col #ident)
}
fn crate_path_ident() -> (Option<syn::token::PathSep>, proc_macro2::Ident) {
match crate_path_fixed() {
FoundCrate::Itself => (None, format_ident!("crate")),
FoundCrate::Name(name) => (Some(Default::default()), format_ident!("{}", name)),
}
}
fn crate_path_fixed() -> FoundCrate {
let found_crate = crate_name("memflow").expect("memflow found in `Cargo.toml`");
match found_crate {
FoundCrate::Itself => {
let has_doc_env = std::env::vars().any(|(k, _)| {
k == "UNSTABLE_RUSTDOC_TEST_LINE" || k == "UNSTABLE_RUSTDOC_TEST_PATH"
});
if has_doc_env {
FoundCrate::Name("memflow".to_string())
} else {
FoundCrate::Itself
}
}
x => x,
}
}

@ -1,38 +0,0 @@
use memflow::types::byte_swap::ByteSwap;
use memflow_derive::*;
#[derive(ByteSwap)]
struct ByteSwapDerive {
pub val: u32,
}
#[derive(ByteSwap)]
struct ByteSwapDeriveGeneric<T: ByteSwap> {
pub val: T,
}
#[derive(ByteSwap)]
struct ByteSwapDeriveWhere<T>
where
T: ByteSwap,
{
pub val: T,
}
#[derive(ByteSwap)]
struct ByteSwapDeriveSlice {
pub slice: [u8; 32],
}
#[derive(ByteSwap)]
struct ByteSwapDeriveStructSlice {
pub slice: [ByteSwapDeriveSlice; 128],
}
#[derive(ByteSwap)]
struct ByteSwapDeriveStructGenericSlice<T: ByteSwap> {
pub slice: [ByteSwapDeriveGeneric<T>; 128],
}
#[test]
pub fn compiles() {}

@ -1,6 +1,6 @@
[package]
name = "memflow-ffi"
version = "0.1.5"
version = "0.2.0"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "C bindings for the memflow physical memory introspection framework"
@ -8,7 +8,7 @@ documentation = "https://docs.rs/memflow-ffi"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow"
license-file = "../LICENSE"
license = "MIT"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "api-bindings", "memory-management", "os" ]
@ -21,9 +21,9 @@ name = "memflow_ffi"
crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
memflow = { version = "0.1", path = "../memflow" }
log = "0.4"
simple_logger = "1.9"
memflow = { version = "0.2", path = "../memflow" }
log = "^0.4.14"
simplelog = "^0.12.0"
[features]
default = []

@ -46,4 +46,4 @@ int main(int argc, char *argv[]) {
}
```
Additional examples can be found in the `examples` folder as well as in the [memflow-win32-ffi](https://github.com/memflow/memflow/memflow-win32-ffi) crate.
Additional examples can be found in the `examples` folder.

@ -1,3 +1,23 @@
#!/bin/bash
cargo build --release --workspace
cbindgen --config cbindgen.toml --crate memflow-ffi --output memflow.h
#!/usr/bin/env bash
run_twice() {
echo $@
v=$(exec $@)
if [ $? -ne 0 ]; then
echo "Error occured! Maybe a fluke. Retrying..."
v=$(exec $@)
fi
}
# remove any RUSTC_WRAPPER like sccache which might cause issues with cglue-bindgen
export RUSTC_WRAPPER=""
# update cglue-bindgen
cargo +nightly install cbindgen
cargo +nightly install cglue-bindgen
# generate c and cpp bindings
run_twice rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi --output memflow.h -l C
run_twice rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi --output memflow.hpp -l C++

@ -7,13 +7,26 @@ style = "both"
#no_includes = true
cpp_compat = true
after_includes = "typedef void *Library;"
[parse]
parse_deps = true
include = ["memflow"]
[parse.expand]
crates = ["cglue", "memflow", "memflow-ffi", "log"]
[macro_expansion]
bitflags = true
[fn]
sort_by = "None"
[export]
include = ["OsInstanceArcBox", "ProcessInstanceArcBox", "IntoProcessInstanceArcBox", "MemoryViewArcBox"]
[export.rename]
"OptionMut_c_void" = "pvoid"
[enum]
prefix_with_name = true

@ -0,0 +1,3 @@
default_container = "Box"
default_context = "Arc"
function_prefix = "mf"

@ -1,19 +0,0 @@
CC =g++
CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release
LIBS=-lm -Wl,--no-as-needed -ldl -lpthread -l:libmemflow_win32_ffi.a
ODIR=./
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
phys_mem.out: phys_mem.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: all
all: phys_mem.out
.DEFAULT_GOAL := all
clean:
rm -f $(ODIR)/*.o

@ -0,0 +1,31 @@
CC = clang
CFLAGS = -g -O0 -I../../../memflow-ffi/ -L../../../target/release
LIBS=-lm -ldl -lpthread -l:libmemflow_ffi.a
ODIR=./
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
phys_mem.out: phys_mem.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
process_list.out: process_list.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
module_list.out: module_list.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
module_dump.out: module_dump.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
find_process.out: find_process.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: all
all: phys_mem.out process_list.out module_list.out module_dump.out find_process.out
.DEFAULT_GOAL := all
clean:
rm -f $(ODIR)/*.o

@ -0,0 +1,210 @@
#include "memflow.h"
#include <pthread.h>
#include <stdio.h>
#include <string.h>
ProcessInstance target_process;
ProcessInstance target_process2;
struct FindProcessContext {
OsInstance *os;
const char *name;
ProcessInstance *target_process;
bool found;
};
bool find_process(struct FindProcessContext *find_context, Address addr) {
if (find_context->found) {
return false;
}
if (mf_osinstance_process_by_address(find_context->os, addr,
find_context->target_process)) {
return true;
}
const struct ProcessInfo *info =
mf_processinstance_info(find_context->target_process);
if (!strcmp(info->name, find_context->name)) {
// abort iteration
find_context->found = true;
return false;
}
mf_processinstance_drop(*find_context->target_process);
// continue iteration
return true;
}
pthread_mutex_t lock;
void *readmem(void *vargp) {
// Store the value argument passed to this thread
int *myid = (int *)vargp;
uint8_t buffer[0x8];
while (true) {
pthread_mutex_lock(&lock);
mf_processinstance_read_raw_into(&target_process, 0x7FF72AF30000,
MUT_SLICE(u8, buffer, sizeof(buffer)));
printf("TID: %d, Read: %lx\n", *myid, *(uint64_t *)buffer);
pthread_mutex_unlock(&lock);
}
return 0;
}
int main(int argc, char *argv[]) {
int ret = 0;
// enable info level logging
log_init(3);
// load all available plugins
Inventory *inventory = inventory_scan();
printf("inventory initialized: %p\n", inventory);
const char *conn_name = argc > 1 ? argv[1] : "kvm";
const char *conn_arg = argc > 2 ? argv[2] : "";
const char *os_name = argc > 3 ? argv[3] : "win32";
const char *os_arg = argc > 4 ? argv[4] : "";
const char *target_proc = argc > 5 ? argv[5] : "notepad.exe";
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
// initialize the connector plugin
if (conn) {
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
log_error("unable to initialize connector");
inventory_free(inventory);
return 1;
}
printf("connector initialized: %p\n",
connector.container.instance.instance);
}
// initialize the OS plugin
OsInstance os;
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
log_error("unable to initialize os plugin");
inventory_free(inventory);
return 1;
}
printf("os plugin initialized: %p\n", os.container.instance.instance);
// find a specific process based on it's name.
// this can easily be replaced by process_by_name but
// is being used here as a demonstration.
/* struct FindProcessContext find_context = {
&os,
target_proc,
&target_process,
false,
};
mf_osinstance_process_address_list_callback(
&os, CALLBACK(Address, &find_context, find_process));
if (find_context.found) {
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
info->pid, info->name, info->path);
mf_processinstance_drop(target_process);
} else {
printf("Unable to find %s\n", target_proc);
}*/
// find a specific process based on its name
// via process_by_name
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc),
&target_process))) {
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
info->pid, info->name, info->path);
ModuleInfo module_info;
mf_processinstance_module_by_name(&target_process, STR(target_proc),
&module_info);
printf("0x%lx\n", module_info.base);
/*ConnectorInstance conn2;
if (inventory_create_connector(inventory, conn_name, conn_arg, &conn2)) {
log_error("unable to initialize connector");
inventory_free(inventory);
return 1;
}
ConnectorInstance cloned = mf_connectorinstance_clone(&conn2);
printf("ok1\n");
mf_connectorinstance_drop(cloned);
printf("ok2\n");*/
// MemoryView phys_view = mf_connectorinstance_phys_view(&conn2);
/*if (pthread_mutex_init(&lock, NULL) != 0) {
printf("\n mutex init has failed\n");
return 1;
}
pthread_t tid;
pthread_t tid2;
pthread_create(&tid, NULL, readmem, (void *)&tid);
pthread_create(&tid2, NULL, readmem, (void *)&tid2);
pthread_join(tid, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&lock);*/
if (!(ret = mf_osinstance_process_by_name(&os, STR("notepad++.exe"),
&target_process2))) {
const struct ProcessInfo *info2 =
mf_processinstance_info(&target_process2);
printf("%s process found: 0x%lx] %d %s %s\n", "notepad++.exe",
info2->address, info2->pid, info2->name, info2->path);
uint8_t buffer[0x8];
mf_processinstance_read_raw_into(&target_process, info->address + 0x520,
MUT_SLICE(u8, buffer, sizeof(buffer)));
printf("Read: %lx\n", *(uint64_t *)buffer);
mf_processinstance_read_raw_into(&target_process2, info2->address + 0x520,
MUT_SLICE(u8, buffer, sizeof(buffer)));
printf("Read2: %lx\n", *(uint64_t *)buffer);
} else {
printf("error notepad++");
}
mf_processinstance_drop(target_process);
} else {
printf("Unable to find %s\n", target_proc);
log_debug_errorcode(ret);
}
// This will also free the connector here
// as it was _moved_ into the os by `inventory_create_os`
mf_osinstance_drop(os);
log_info("os plugin/connector freed");
inventory_free(inventory);
log_info("inventory freed");
return 0;
}

@ -0,0 +1,126 @@
/**
This example demonstrates how to read the contents of a module from a process.
To read from a specific module the following steps have to be done:
- Create an inventory and let it search for plugins in the system
- Load the plugins to access physical memory and the operating system
(by default the `qemu` plugin and `win32` plugin are being used)
- Find the process by the specified name
- Find the module_info for the given module in the process
- Allocate a buffer which will fit the entire module
- Read the entire module into the buffer and ignore partial read errors
- Write the contents of the retrieved buffer to the specified output location
Usage:
./module_dump.out kvm :: win32 :: notepad.exe notepad.exe notepad.exe.bin
*/
#include "memflow.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[]) {
int ret = 0;
// enable info level logging
log_init(4);
// load all available plugins
Inventory *inventory = inventory_scan();
printf("inventory initialized: %p\n", inventory);
const char *conn_name = argc > 1 ? argv[1] : "qemu";
const char *conn_arg = argc > 2 ? argv[2] : "";
const char *os_name = argc > 3 ? argv[3]: "win32";
const char *os_arg = argc > 4? argv[4]: "";
const char *target_proc = argc > 5? argv[5]: "notepad.exe";
const char *target_module = argc > 6? argv[6]: "notepad.exe";
const char *output_file = argc > 7? argv[7]: "notepad.exe.bin";
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
// initialize the connector plugin
if (conn) {
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
log_error("unable to initialize connector");
inventory_free(inventory);
return 1;
}
printf("connector initialized: %p\n", connector.container.instance.instance);
}
// initialize the OS plugin
OsInstance os;
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
log_error("unable to initialize os plugin");
inventory_free(inventory);
return 1;
}
printf("os plugin initialized: %p\n", os.container.instance.instance);
// find a specific process based on its name via process_by_name
ProcessInstance target_process;
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc), &target_process))) {
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
info->pid, info->name, info->path);
// find the module by its name
ModuleInfo module_info;
if (!(ret = mf_processinstance_module_by_name(&target_process, STR(target_module), &module_info))) {
printf("%s module found: 0x%lx] 0x%lx %s %s\n", target_proc, module_info.address,
module_info.base, module_info.name, module_info.path);
// read module into buffer, in this case -2 / -3 are partial read/write errors
void *module_buffer = malloc(module_info.size);
ret = mf_processinstance_read_raw_into(&target_process, module_info.base, MUT_SLICE(u8, module_buffer, module_info.size));
if (ret == -2) {
printf("%s warning: %s] module only read partially\n", target_proc, target_module);
}
// module has been read
printf("%s read module: %s] read 0x%lx bytes\n", target_proc, target_module, module_info.size);
// write the buffer to the specified location
FILE *file = fopen(output_file, "wb");
if (file) {
fwrite(module_buffer, module_info.size, 1, file);
fclose(file);
printf("dumped 0x%lx bytes to %s\n", module_info.size, output_file);
} else {
printf("unable to open output file %s: %s\n", output_file, strerror(errno));
}
free(module_buffer);
} else {
printf("unable to find module: %s\n", target_module);
log_debug_errorcode(ret);
}
// cleanup the processinstance
mf_processinstance_drop(target_process);
} else {
printf("unable to find process: %s\n", target_proc);
log_debug_errorcode(ret);
}
// This will also free the connector here
// as it was _moved_ into the os by `inventory_create_os`
mf_osinstance_drop(os);
log_info("os plugin/connector freed");
inventory_free(inventory);
log_info("inventory freed");
return 0;
}

@ -0,0 +1,80 @@
#include "memflow.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int ret = 0;
// enable info level logging
log_init(3);
// load all available plugins
Inventory *inventory = inventory_scan();
printf("inventory initialized: %p\n", inventory);
const char *conn_name = argc > 1 ? argv[1] : "kvm";
const char *conn_arg = argc > 2 ? argv[2] : "";
const char *os_name = argc > 3 ? argv[3]: "win32";
const char *os_arg = argc > 4? argv[4]: "";
const char *target_proc = argc > 5? argv[5]: "notepad.exe";
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
// initialize the connector plugin
if (conn) {
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
log_error("unable to initialize connector");
inventory_free(inventory);
return 1;
}
printf("connector initialized: %p\n", connector.container.instance.instance);
}
// initialize the OS plugin
OsInstance os;
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
log_error("unable to initialize os plugin");
inventory_free(inventory);
return 1;
}
printf("os plugin initialized: %p\n", os.container.instance.instance);
// find a specific process based on its name via process_by_name
ProcessInstance target_process;
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc), &target_process))) {
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
info->pid, info->name, info->path);
// iterate over all module info structs and collect them in a buffer
COLLECT_CB(ModuleInfo, module_info);
mf_processinstance_module_list_callback(&target_process, NULL, module_info);
for (size_t i = 0; i < module_info_base.size; i++) {
ModuleInfo *module_info = &((ModuleInfo *)module_info_base.buf)[i];
printf("%s module found: 0x%lx] 0x%lx %s %s\n", target_proc, module_info->address,
module_info->base, module_info->name, module_info->path);
}
free(module_info_base.buf);
// cleanup the processinstance
mf_processinstance_drop(target_process);
} else {
printf("Unable to find %s\n", target_proc);
log_debug_errorcode(ret);
}
// This will also free the connector here
// as it was _moved_ into the os by `inventory_create_os`
mf_osinstance_drop(os);
log_info("os plugin/connector freed");
inventory_free(inventory);
log_info("inventory freed");
return 0;
}

@ -0,0 +1,46 @@
#include "memflow.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
// enable debug level logging
log_init(3);
Inventory *inv = inventory_scan();
printf("inv: %p\n", inv);
const char *conn_name = argc > 1 ? argv[1] : "kvm";
const char *conn_arg = argc > 2 ? argv[2] : "";
ConnectorInstance conn;
if (!inventory_create_connector(inv, conn_name, conn_arg, &conn)) {
for (int i = 0; i < 1000 * 1000; i++) {
uint8_t buffer[0x1000];
ConnectorInstance cloned = mf_connectorinstance_clone(&conn);
mf_connectorinstance_drop(cloned);
MemoryView phys_view = mf_connectorinstance_phys_view(&conn);
// regular read_into
mf_read_raw_into(&phys_view, 0x1000 + i, MUT_SLICE(u8, buffer, sizeof(buffer)));
// read multiple
ReadData read_data = {0x1000 + i, {buffer, sizeof(buffer)}};
mf_read_raw_list(&phys_view, MUT_SLICE(ReadData, &read_data, 1));
printf("Read: %lx\n", *(uint64_t *)buffer);
mf_memoryview_drop(phys_view);
}
mf_connectorinstance_drop(conn);
printf("conn dropped!\n");
}
inventory_free(inv);
printf("inv freed!\n");
return 0;
}

@ -0,0 +1,125 @@
#include "memflow.h"
#include <stdio.h>
bool list_processes(OsInstance *os, Address addr) {
int ret;
ProcessInstance process;
if ((ret = mf_osinstance_process_by_address(os, addr, &process))) {
log_debug_errorcode(ret);
return true;
}
const struct ProcessInfo *info = mf_processinstance_info(&process);
ModuleInfo primary_module;
if ((ret = mf_processinstance_primary_module(&process, &primary_module))) {
// no primary module found, continue iteration - this should _never_ happen
printf("%d\t%s\t0x%lx\tN/A\n", info->pid, info->name, info->address);
log_debug_errorcode(ret);
return true;
}
printf("%d\t%s\t0x%lx\t0x%lx\n", info->pid, info->name, info->address,
primary_module.address);
// iterate over all module addresses and collect them in an array
struct ModuleAddressInfo module_addresses[256];
COLLECT_CB_INTO_ARR(ModuleAddressInfo, module_address, module_addresses);
mf_processinstance_module_address_list_callback(&process, NULL, module_address);
printf("Read %zu modules\n", module_address_base.size);
// iterate over all module info structs and collect them in a buffer
COLLECT_CB(ModuleInfo, module_info);
mf_processinstance_module_list_callback(&process, NULL, module_info);
printf("Read %zu modules\n", module_info_base.size);
free(module_info_base.buf);
// iterate over all imports and collect them in a buffer
COLLECT_CB(ImportInfo, import_info);
mf_processinstance_module_import_list_callback(&process, &primary_module, import_info);
printf("Read %zu imports\n", import_info_base.size);
free(import_info_base.buf);
// iterate over all exports and collect them in a buffer
COLLECT_CB(ExportInfo, exports);
mf_processinstance_module_export_list_callback(&process, &primary_module, exports);
printf("Read %zu exports\n", exports_base.size);
free(exports_base.buf);
// iterate over all sections and collect them in a buffer
COLLECT_CB(SectionInfo, sections);
mf_processinstance_module_section_list_callback(&process, &primary_module, sections);
printf("Read %zu sections\n", sections_base.size);
free(sections_base.buf);
mf_processinstance_drop(process);
return true;
}
int main(int argc, char *argv[]) {
// enable debug level logging
log_init(2);
// load all available plugins
Inventory *inventory = inventory_scan();
printf("inventory initialized: %p\n", inventory);
const char *conn_name = argc > 1 ? argv[1] : "kvm";
const char *conn_arg = argc > 2 ? argv[2] : "";
const char *os_name = argc > 3 ? argv[3]: "win32";
const char *os_arg = argc > 4? argv[4]: "";
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
// initialize the connector plugin
if (conn) {
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
printf("unable to initialize connector\n");
inventory_free(inventory);
return 1;
}
printf("connector initialized: %p\n", connector.container.instance.instance);
}
// initialize the OS plugin
OsInstance os;
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
printf("unable to initialize os plugin\n");
inventory_free(inventory);
return 1;
}
printf("os plugin initialized: %p\n", os.container.instance.instance);
// iterate over all processes and print them manually
printf("Pid\tNAME\tADDRESS\tMAIN_MODULE\n");
mf_osinstance_process_address_list_callback(&os, CALLBACK(Address, &os, list_processes));
// count all processes
COUNT_CB(Address, process_address);
mf_osinstance_process_address_list_callback(&os, process_address);
printf("Counted %zu processes\n", process_address_count);
// iterate over all process info structs and collect them in an array
struct ProcessInfo process_info[256];
COLLECT_CB_INTO_ARR(ProcessInfo, process_info_cb, process_info);
mf_osinstance_process_info_list_callback(&os, process_info_cb);
printf("Read %zu process infos\n", process_info_cb_base.size);
// This will also free the connector here
// as it was _moved_ into the os by `inventory_create_os`
mf_osinstance_drop(os);
printf("os plugin/connector freed\n");
inventory_free(inventory);
printf("inventory freed\n");
return 0;
}

@ -0,0 +1,19 @@
CC = g++
CFLAGS = -g -O0 -pedantic -std=c++14 -I../../../memflow-ffi/ -L../../../target/release
LIBS=-lm -ldl -lpthread -l:libmemflow_ffi.a
ODIR=./
%.o: %.cpp $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
plist.out: plist.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: all
all: plist.out
.DEFAULT_GOAL := all
clean:
rm -f $(ODIR)/*.o

@ -0,0 +1,104 @@
#include "memflow.hpp"
#include <stdio.h>
#include <vector>
#include <memory>
#include <unistd.h>
void fmt_arch(char *arch, int n, ArchitectureIdent ident);
int main(int argc, char *argv[], const int t[])
{
log_init(LevelFilter::LevelFilter_Info);
int arr[] = {1, 2, 3, 4, 5};
int b=3;
//CSliceMut<unsigned char>((char*)arr);
CSliceRef<uint8_t>((char*)t, sizeof(int));
/*Inventory *inventory = inventory_scan();
if (!inventory)
{
log_error("unable to create inventory");
return 1;
}
printf("inventory initialized: %p\n", inventory);
const char *conn_name = argc > 1 ? argv[1] : "kvm";
const char *conn_arg = argc > 2 ? argv[2] : "";
const char *os_name = argc > 3 ? argv[3] : "win32";
const char *os_arg = argc > 4 ? argv[4] : "";
// ConnectorInstance<> connector, *conn = conn_name[0] ? &connector : nullptr;
std::unique_ptr<ConnectorInstance<>> connector = nullptr;
if (!connector)
{
connector = std::make_unique<ConnectorInstance<>>();
if (inventory_create_connector(inventory, conn_name, conn_arg, connector.get()))
{
printf("unable to initialize connector\n");
inventory_free(inventory);
return 1;
}
printf("connector initialized: %p\n", connector->container.instance.instance);
}
OsInstance<> os;
if (inventory_create_os(inventory, os_name, os_arg, connector.get(), &os))
{
printf("unable to initialize OS\n");
inventory_free(inventory);
return 1;
}
inventory_free(inventory);
printf("os initialized: %p\n", os.container.instance.instance);
auto info = os.info();
char arch[11];
fmt_arch(arch, sizeof(arch), info->arch);
printf("Kernel base: %llx\nKernel size: %llx\nArchitecture: %s\n", info->base, info->size, arch);
printf("Process List:\n");
printf("%-4s | %-8s | %-10s | %-10s | %s\n", "Seq", "Pid", "Sys Arch", "Proc Arch", "Name");
int i = 0;
os.process_info_list_callback([&i](ProcessInfo info)
{
char sys_arch[11];
char proc_arch[11];
fmt_arch(sys_arch, sizeof(sys_arch), info.sys_arch);
fmt_arch(proc_arch, sizeof(proc_arch), info.proc_arch);
printf("%-4d | %-8d | %-10s | %-10s | %s\n", i++, info.pid, sys_arch, proc_arch, info.name);
return true; });
*/
return 0;
}
void fmt_arch(char *arch, int n, ArchitectureIdent ident)
{
switch (ident.tag)
{
case ArchitectureIdent::Tag::ArchitectureIdent_X86:
snprintf(arch, n, "X86_%d", ident.x86._0);
break;
case ArchitectureIdent::Tag::ArchitectureIdent_AArch64:
snprintf(arch, n, "AArch64");
break;
default:
snprintf(arch, n, "Unknown");
}
}

@ -1,35 +0,0 @@
#include "memflow.h"
#include <stdio.h>
int main(int argc, char *argv[])
{
log_init(4);
ConnectorInventory *inv = inventory_scan();
printf("inv: %p\n", inv);
const char *conn_name = argc > 1? argv[1]: "qemu_procfs";
const char *conn_arg = argc > 2? argv[2]: "";
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
printf("conn: %p\n", conn);
if (conn) {
PhysicalMemoryObj *phys_mem = downcast_cloneable(conn);
printf("phys_mem: %p\n", phys_mem);
uint64_t read = phys_read_u64(phys_mem, addr_to_paddr(0x30000));
printf("Read: %lx\n", read);
phys_free(phys_mem);
connector_free(conn);
printf("conn freed!\n");
}
inventory_free(inv);
printf("inv freed!\n");
return 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,177 +0,0 @@
#ifndef MEMFLOW_HLAPI_H
#define MEMFLOW_HLAPI_H
#include "memflow.h"
#include "binddestr.h"
#ifndef NO_STL_CONTAINERS
#include <string>
#ifndef AUTO_STRING_SIZE
#define AUTO_STRING_SIZE 128
#endif
#endif
struct CConnectorInventory
: BindDestr<ConnectorInventory, inventory_free>
{
CConnectorInventory(ConnectorInventory *inv)
: BindDestr(inv) {}
CConnectorInventory()
: CConnectorInventory(::inventory_scan()) {}
CConnectorInventory(const char *path)
: CConnectorInventory(::inventory_scan_path(path)) {}
WRAP_FN(inventory, add_dir);
WRAP_FN(inventory, create_connector);
};
struct CPhysicalMemory
: BindDestr<PhysicalMemoryObj, phys_free>
{
CPhysicalMemory(PhysicalMemoryObj *mem)
: BindDestr(mem) {}
WRAP_FN_RAW(phys_read_raw_list);
WRAP_FN_RAW(phys_write_raw_list);
WRAP_FN_RAW(phys_metadata);
WRAP_FN_RAW(phys_read_raw_into);
WRAP_FN_RAW(phys_read_u32);
WRAP_FN_RAW(phys_read_u64);
WRAP_FN_RAW(phys_write_raw);
WRAP_FN_RAW(phys_write_u32);
WRAP_FN_RAW(phys_write_u64);
template<typename T>
T phys_read(PhysicalAddress address) {
T data;
this->phys_read_raw_into(address, (uint8_t *)&data, sizeof(T));
return data;
}
template<typename T>
int32_t phys_write(PhysicalAddress address, const T &data) {
return this->phys_write_raw(address, (const uint8_t *)&data, sizeof(T));
}
};
struct CCloneablePhysicalMemory
: BindDestr<CloneablePhysicalMemoryObj, connector_free>
{
CCloneablePhysicalMemory(CloneablePhysicalMemoryObj *mem)
: BindDestr(mem) {}
WRAP_FN(connector, clone);
WRAP_FN_RAW_TYPE(CPhysicalMemory, downcast_cloneable);
};
struct CVirtualMemory
: BindDestr<VirtualMemoryObj, virt_free>
{
CVirtualMemory(VirtualMemoryObj *virt_mem)
: BindDestr(virt_mem) {}
WRAP_FN_RAW(virt_read_raw_list);
WRAP_FN_RAW(virt_write_raw_list);
WRAP_FN_RAW(virt_read_raw_into);
WRAP_FN_RAW(virt_read_u32);
WRAP_FN_RAW(virt_read_u64);
WRAP_FN_RAW(virt_write_raw);
WRAP_FN_RAW(virt_write_u32);
WRAP_FN_RAW(virt_write_u64);
template<typename T>
T virt_read(Address address) {
T data;
this->virt_read_raw_into(address, (uint8_t *)&data, sizeof(T));
return data;
}
template<typename T>
int32_t virt_write(Address address, const T &data) {
return this->virt_write_raw(address, (const uint8_t *)&data, sizeof(T));
}
};
struct CArchitecture
: BindDestr<ArchitectureObj, arch_free>
{
CArchitecture(ArchitectureObj *arch)
: BindDestr(arch) {}
WRAP_FN(arch, bits);
WRAP_FN(arch, endianess);
WRAP_FN(arch, page_size);
WRAP_FN(arch, size_addr);
WRAP_FN(arch, address_space_bits);
WRAP_FN_RAW(is_x86_arch);
};
struct COsProcessInfo
: BindDestr<OsProcessInfoObj, os_process_info_free>
{
COsProcessInfo(OsProcessInfoObj *info)
: BindDestr(info) {}
WRAP_FN(os_process_info, address);
WRAP_FN(os_process_info, pid);
WRAP_FN(os_process_info, name);
WRAP_FN_TYPE(CArchitecture, os_process_info, sys_arch);
WRAP_FN_TYPE(CArchitecture, os_process_info, proc_arch);
#ifndef NO_STL_CONTAINERS
std::string name_string(size_t max_size) {
char *buf = (char *)malloc(max_size);
if (buf) {
this->name(buf, max_size);
std::string ret = std::string(buf);
free(buf);
return ret;
} else {
return std::string();
}
}
std::string name_string() {
char buf[AUTO_STRING_SIZE];
size_t ret = this->name(buf, AUTO_STRING_SIZE);
return std::string(buf);
}
#endif
};
struct COsProcessModuleInfo
: BindDestr<OsProcessModuleInfoObj, os_process_module_free>
{
COsProcessModuleInfo(OsProcessModuleInfoObj *modinfo)
: BindDestr(modinfo) {}
WRAP_FN(os_process_module, address);
WRAP_FN(os_process_module, parent_process);
WRAP_FN(os_process_module, base);
WRAP_FN(os_process_module, size);
WRAP_FN(os_process_module, name);
#ifndef NO_STL_CONTAINERS
std::string name_string(size_t max_size) {
char *buf = (char *)malloc(max_size);
if (buf) {
this->name(buf, max_size);
std::string ret = std::string(buf);
free(buf);
return ret;
} else {
return std::string();
}
}
std::string name_string() {
char buf[AUTO_STRING_SIZE];
this->name(buf, AUTO_STRING_SIZE);
return std::string(buf);
}
#endif
};
#endif

@ -0,0 +1,43 @@
---
GENERATOR:
PackageName: memflow_go
PackageDescription: "core components of the memflow physical memory introspection framework"
PackageLicense: "MIT"
Includes: ["memflow.h"]
FlagGroups:
- { name: "CFLAGS", traits: ["linux"], flags: [ -I../../memflow-ffi/ ] }
- { name: "LDFLAGS", traits: ["linux"], flags: [ -L../../target/release -lm -ldl -lpthread -l:libmemflow_ffi.a ] }
PARSER:
IncludePaths: ["./"]
SourcesPaths: ["memflow.h"]
TRANSLATOR:
ConstRules:
defines: expand
Rules:
global:
- { transform: lower }
- { action: accept, from: "^log_" }
- { action: accept, from: "^inventory_" }
- { action: accept, from: "^connector_" }
- { action: accept, from: "^phys_" }
- { action: accept, from: "^virt_" }
- { action: replace, from: "_", to: _ }
- { transform: export }
const:
- { action: replace, from: "_", to: _ }
type:
- { action: accept, from: "^MU" }
- { action: accept, from: "Inventory" }
- { action: accept, from: "^Connector" }
- { action: accept, from: "^Physical" }
- { action: accept, from: "^Virtual" }
- { action: accept, from: "^Translation" }
- { action: accept, from: "^Page" }
- { action: replace, from: "_t$" }
private:
- { transform: unexport }
post-global:
- { action: replace, from: _$ }
- { load: snakecase }

@ -1,150 +0,0 @@
use std::ffi::CStr;
use std::os::raw::c_char;
use std::path::PathBuf;
use memflow::connector::{ConnectorArgs, ConnectorInventory};
use crate::util::*;
use crate::mem::phys_mem::CloneablePhysicalMemoryObj;
use log::trace;
/// Create a new connector inventory
///
/// This function will try to find connectors using PATH environment variable
///
/// Note that all functions go through each directories, and look for a `memflow` directory,
/// and search for libraries in those.
///
/// # Safety
///
/// ConnectorInventory is inherently unsafe, because it loads shared libraries which can not be
/// guaranteed to be safe.
#[no_mangle]
pub unsafe extern "C" fn inventory_scan() -> &'static mut ConnectorInventory {
to_heap(ConnectorInventory::scan())
}
/// Create a new inventory with custom path string
///
/// # Safety
///
/// `path` must be a valid null terminated string
#[no_mangle]
pub unsafe extern "C" fn inventory_scan_path(
path: *const c_char,
) -> Option<&'static mut ConnectorInventory> {
let rpath = CStr::from_ptr(path).to_string_lossy();
ConnectorInventory::scan_path(rpath.to_string())
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Add a directory to an existing inventory
///
/// # Safety
///
/// `dir` must be a valid null terminated string
#[no_mangle]
pub unsafe extern "C" fn inventory_add_dir(
inv: &mut ConnectorInventory,
dir: *const c_char,
) -> i32 {
let rdir = CStr::from_ptr(dir).to_string_lossy();
inv.add_dir(PathBuf::from(rdir.to_string()))
.int_result_logged()
}
/// Create a connector with given arguments
///
/// This creates an instance of a `CloneablePhysicalMemory`. To use it for physical memory
/// operations, please call `downcast_cloneable` to create a instance of `PhysicalMemory`.
///
/// Regardless, this instance needs to be freed using `connector_free`.
///
/// # Arguments
///
/// * `name` - name of the connector to use
/// * `args` - arguments to be passed to the connector upon its creation
///
/// # Safety
///
/// Both `name`, and `args` must be valid null terminated strings.
///
/// Any error strings returned by the connector must not be outputed after the connector gets
/// freed, because that operation could cause the underlying shared library to get unloaded.
#[no_mangle]
pub unsafe extern "C" fn inventory_create_connector(
inv: &mut ConnectorInventory,
name: *const c_char,
args: *const c_char,
) -> Option<&'static mut CloneablePhysicalMemoryObj> {
let rname = CStr::from_ptr(name).to_string_lossy();
if args.is_null() {
inv.create_connector_default(&rname)
.map_err(inspect_err)
.ok()
.map(to_heap)
.map(|c| c as CloneablePhysicalMemoryObj)
.map(to_heap)
} else {
let rargs = CStr::from_ptr(args).to_string_lossy();
let conn_args = ConnectorArgs::parse(&rargs).map_err(inspect_err).ok()?;
inv.create_connector(&rname, &conn_args)
.map_err(inspect_err)
.ok()
.map(to_heap)
.map(|c| c as CloneablePhysicalMemoryObj)
.map(to_heap)
}
}
/// Clone a connector
///
/// This method is useful when needing to perform multithreaded operations, as a connector is not
/// guaranteed to be thread safe. Every single cloned instance also needs to be freed using
/// `connector_free`.
///
/// # Safety
///
/// `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn connector_clone(
conn: &CloneablePhysicalMemoryObj,
) -> &'static mut CloneablePhysicalMemoryObj {
trace!("connector_clone: {:?}", conn as *const _);
Box::leak(Box::new(Box::leak(conn.clone_box())))
}
/// Free a connector instance
///
/// # Safety
///
/// `conn` has to point to a valid `CloneablePhysicalMemoryObj` created by one of the provided
/// functions.
///
/// There has to be no instance of `PhysicalMemory` created from the input `conn`, because they
/// will become invalid.
#[no_mangle]
pub unsafe extern "C" fn connector_free(conn: &'static mut CloneablePhysicalMemoryObj) {
trace!("connector_free: {:?}", conn as *mut _);
let _ = Box::from_raw(*Box::from_raw(conn));
}
/// Free a connector inventory
///
/// # Safety
///
/// `inv` must point to a valid `ConnectorInventory` that was created using one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn inventory_free(inv: &'static mut ConnectorInventory) {
trace!("inventory_free: {:?}", inv as *mut _);
let _ = Box::from_raw(inv);
}

@ -2,12 +2,12 @@ pub mod log;
pub mod types;
pub mod connectors;
pub mod plugins;
pub mod mem;
pub mod architecture;
pub mod os;
pub mod process;
pub mod architecture;
pub mod util;

@ -1,17 +1,125 @@
use log::Level;
#[no_mangle]
pub extern "C" fn log_init(level_num: i32) {
let level = match level_num {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
use log::{Level, LevelFilter};
use memflow::cglue::IntError;
use memflow::error::Error;
use memflow::plugins::Inventory;
use std::num::NonZeroI32;
use std::ffi::CStr;
use std::os::raw::c_char;
/// Initialize logging with selected logging level.
#[no_mangle]
pub extern "C" fn log_init(level_filter: LevelFilter) {
simplelog::TermLogger::init(
level_filter,
simplelog::Config::default(),
simplelog::TerminalMode::Stdout,
simplelog::ColorChoice::Auto,
)
.unwrap();
}
// TODO: add variadic functions when this is being stabilized, see https://github.com/rust-lang/rust/issues/44930
/// Logs a error message via log::error!
///
/// # Safety
///
/// The provided string must be a valid null-terminated char array.
#[no_mangle]
pub unsafe extern "C" fn log_error(s: *const c_char) {
if !s.is_null() {
let c_str = CStr::from_ptr(s);
if let Ok(r_str) = c_str.to_str() {
log::error!("{}", r_str);
}
}
}
/// Logs a warning message via log::warn!
///
/// # Safety
///
/// The provided string must be a valid null-terminated char array.
#[no_mangle]
pub unsafe extern "C" fn log_warn(s: *const c_char) {
if !s.is_null() {
let c_str = CStr::from_ptr(s);
if let Ok(r_str) = c_str.to_str() {
log::warn!("{}", r_str);
}
}
}
/// Logs a info message via log::info!
///
/// # Safety
///
/// The provided string must be a valid null-terminated char array.
#[no_mangle]
pub unsafe extern "C" fn log_info(s: *const c_char) {
if !s.is_null() {
let c_str = CStr::from_ptr(s);
if let Ok(r_str) = c_str.to_str() {
log::info!("{}", r_str);
}
}
}
/// Logs a debug message via log::debug!
///
/// # Safety
///
/// The provided string must be a valid null-terminated char array.
#[no_mangle]
pub unsafe extern "C" fn log_debug(s: *const c_char) {
if !s.is_null() {
let c_str = CStr::from_ptr(s);
if let Ok(r_str) = c_str.to_str() {
log::debug!("{}", r_str);
}
}
}
/// Logs a trace message via log::trace!
///
/// # Safety
///
/// The provided string must be a valid null-terminated char array.
#[no_mangle]
pub unsafe extern "C" fn log_trace(s: *const c_char) {
if !s.is_null() {
let c_str = CStr::from_ptr(s);
if let Ok(r_str) = c_str.to_str() {
log::trace!("{}", r_str);
}
}
}
/// Logs an error code with custom log level.
#[no_mangle]
pub extern "C" fn log_errorcode(level: Level, error: i32) {
if let Some(error) = NonZeroI32::new(error) {
log::log!(level, "{}", <Error as IntError>::from_int_err(error));
}
}
/// Logs an error with debug log level.
#[no_mangle]
pub extern "C" fn log_debug_errorcode(error: i32) {
log_errorcode(Level::Debug, error)
}
/// Sets new maximum log level.
///
/// If `inventory` is supplied, the log level is also updated within all plugin instances. However,
/// if it is not supplied, plugins will not have their log levels updated, potentially leading to
/// lower performance, or less logging than expected.
#[no_mangle]
pub extern "C" fn log_set_max_level(level_filter: LevelFilter, inventory: Option<&Inventory>) {
if let Some(inventory) = inventory {
inventory.set_max_log_level(level_filter);
} else {
log::set_max_level(level_filter);
}
}

@ -1,2 +1,4 @@
pub mod phys_mem;
pub mod virt_mem;
#[allow(unused)]
pub use memflow::mem::phys_mem::*;
#[allow(unused)]
pub use memflow::mem::virt_mem::*;

@ -1,146 +0,0 @@
use memflow::mem::phys_mem::*;
use memflow::types::PhysicalAddress;
use crate::util::*;
use std::slice::{from_raw_parts, from_raw_parts_mut};
use log::trace;
pub type CloneablePhysicalMemoryObj = &'static mut dyn CloneablePhysicalMemory;
pub type PhysicalMemoryObj = &'static mut dyn PhysicalMemory;
/// Downcast a cloneable physical memory into a physical memory object.
///
/// This function will take a `cloneable` and turn it into a `PhysicalMemoryObj`, which then can be
/// used by physical memory functions.
///
/// Please note that this does not free `cloneable`, and the reference is still valid for further
/// operations.
#[no_mangle]
pub extern "C" fn downcast_cloneable(
cloneable: &'static mut CloneablePhysicalMemoryObj,
) -> &'static mut PhysicalMemoryObj {
Box::leak(Box::new((*cloneable).downcast()))
}
/// Free a `PhysicalMemoryObj`
///
/// This will free a reference to a `PhysicalMemoryObj`. If the physical memory object was created
/// using `downcast_cloneable`, this will NOT free the cloneable reference.
///
/// # Safety
///
/// `mem` must point to a valid `PhysicalMemoryObj` that was created using one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn phys_free(mem: &'static mut PhysicalMemoryObj) {
trace!("phys_free: {:?}", mem as *mut _);
let _ = Box::from_raw(mem);
}
/// Read a list of values
///
/// This will perform `len` physical memory reads on the provided `data`. Using lists is preferable
/// for performance, because then the underlying connectors can batch those operations.
///
/// # Safety
///
/// `data` must be a valid array of `PhysicalReadData` with the length of at least `len`
#[no_mangle]
pub unsafe extern "C" fn phys_read_raw_list(
mem: &mut PhysicalMemoryObj,
data: *mut PhysicalReadData,
len: usize,
) -> i32 {
let data = from_raw_parts_mut(data, len);
mem.phys_read_raw_list(data).int_result()
}
/// Write a list of values
///
/// This will perform `len` physical memory writes on the provided `data`. Using lists is preferable
/// for performance, because then the underlying connectors can batch those operations.
///
/// # Safety
///
/// `data` must be a valid array of `PhysicalWriteData` with the length of at least `len`
#[no_mangle]
pub unsafe extern "C" fn phys_write_raw_list(
mem: &mut PhysicalMemoryObj,
data: *const PhysicalWriteData,
len: usize,
) -> i32 {
let data = from_raw_parts(data, len);
mem.phys_write_raw_list(data).int_result()
}
/// Retrieve metadata about the physical memory object
#[no_mangle]
pub extern "C" fn phys_metadata(mem: &PhysicalMemoryObj) -> PhysicalMemoryMetadata {
mem.metadata()
}
/// Read a single value into `out` from a provided `PhysicalAddress`
///
/// # Safety
///
/// `out` must be a valid pointer to a data buffer of at least `len` size.
#[no_mangle]
pub unsafe extern "C" fn phys_read_raw_into(
mem: &mut PhysicalMemoryObj,
addr: PhysicalAddress,
out: *mut u8,
len: usize,
) -> i32 {
mem.phys_read_raw_into(addr, from_raw_parts_mut(out, len))
.int_result()
}
/// Read a single 32-bit value from a provided `PhysicalAddress`
#[no_mangle]
pub extern "C" fn phys_read_u32(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u32 {
mem.phys_read::<u32>(addr).unwrap_or_default()
}
/// Read a single 64-bit value from a provided `PhysicalAddress`
#[no_mangle]
pub extern "C" fn phys_read_u64(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u64 {
mem.phys_read::<u64>(addr).unwrap_or_default()
}
/// Write a single value from `input` into a provided `PhysicalAddress`
///
/// # Safety
///
/// `input` must be a valid pointer to a data buffer of at least `len` size.
#[no_mangle]
pub unsafe extern "C" fn phys_write_raw(
mem: &mut PhysicalMemoryObj,
addr: PhysicalAddress,
input: *const u8,
len: usize,
) -> i32 {
mem.phys_write_raw(addr, from_raw_parts(input, len))
.int_result()
}
/// Write a single 32-bit value into a provided `PhysicalAddress`
#[no_mangle]
pub extern "C" fn phys_write_u32(
mem: &mut PhysicalMemoryObj,
addr: PhysicalAddress,
val: u32,
) -> i32 {
mem.phys_write(addr, &val).int_result()
}
/// Write a single 64-bit value into a provided `PhysicalAddress`
#[no_mangle]
pub extern "C" fn phys_write_u64(
mem: &mut PhysicalMemoryObj,
addr: PhysicalAddress,
val: u64,
) -> i32 {
mem.phys_write(addr, &val).int_result()
}

@ -1,117 +0,0 @@
use memflow::error::PartialResultExt;
use memflow::mem::virt_mem::*;
use memflow::types::Address;
use crate::util::*;
use std::slice::{from_raw_parts, from_raw_parts_mut};
pub type VirtualMemoryObj = &'static mut dyn VirtualMemory;
/// Free a virtual memory object reference
///
/// This function frees the reference to a virtual memory object.
///
/// # Safety
///
/// `mem` must be a valid reference to a virtual memory object.
#[no_mangle]
pub unsafe extern "C" fn virt_free(mem: &'static mut VirtualMemoryObj) {
let _ = Box::from_raw(mem);
}
/// Read a list of values
///
/// This will perform `len` virtual memory reads on the provided `data`. Using lists is preferable
/// for performance, because then the underlying connectors can batch those operations, and virtual
/// translation function can cut down on read operations.
///
/// # Safety
///
/// `data` must be a valid array of `VirtualReadData` with the length of at least `len`
#[no_mangle]
pub unsafe extern "C" fn virt_read_raw_list(
mem: &mut VirtualMemoryObj,
data: *mut VirtualReadData,
len: usize,
) -> i32 {
let data = from_raw_parts_mut(data, len);
mem.virt_read_raw_list(data).data_part().int_result()
}
/// Write a list of values
///
/// This will perform `len` virtual memory writes on the provided `data`. Using lists is preferable
/// for performance, because then the underlying connectors can batch those operations, and virtual
/// translation function can cut down on read operations.
///
/// # Safety
///
/// `data` must be a valid array of `VirtualWriteData` with the length of at least `len`
#[no_mangle]
pub unsafe extern "C" fn virt_write_raw_list(
mem: &mut VirtualMemoryObj,
data: *const VirtualWriteData,
len: usize,
) -> i32 {
let data = from_raw_parts(data, len);
mem.virt_write_raw_list(data).data_part().int_result()
}
/// Read a single value into `out` from a provided `Address`
///
/// # Safety
///
/// `out` must be a valid pointer to a data buffer of at least `len` size.
#[no_mangle]
pub unsafe extern "C" fn virt_read_raw_into(
mem: &mut VirtualMemoryObj,
addr: Address,
out: *mut u8,
len: usize,
) -> i32 {
mem.virt_read_raw_into(addr, from_raw_parts_mut(out, len))
.data_part()
.int_result()
}
/// Read a single 32-bit value from a provided `Address`
#[no_mangle]
pub extern "C" fn virt_read_u32(mem: &mut VirtualMemoryObj, addr: Address) -> u32 {
mem.virt_read::<u32>(addr).unwrap_or_default()
}
/// Read a single 64-bit value from a provided `Address`
#[no_mangle]
pub extern "C" fn virt_read_u64(mem: &mut VirtualMemoryObj, addr: Address) -> u64 {
mem.virt_read::<u64>(addr).unwrap_or_default()
}
/// Write a single value from `input` into a provided `Address`
///
/// # Safety
///
/// `input` must be a valid pointer to a data buffer of at least `len` size.
#[no_mangle]
pub unsafe extern "C" fn virt_write_raw(
mem: &mut VirtualMemoryObj,
addr: Address,
input: *const u8,
len: usize,
) -> i32 {
mem.virt_write_raw(addr, from_raw_parts(input, len))
.data_part()
.int_result()
}
/// Write a single 32-bit value into a provided `Address`
#[no_mangle]
pub extern "C" fn virt_write_u32(mem: &mut VirtualMemoryObj, addr: Address, val: u32) -> i32 {
mem.virt_write(addr, &val).data_part().int_result()
}
/// Write a single 64-bit value into a provided `Address`
#[no_mangle]
pub extern "C" fn virt_write_u64(mem: &mut VirtualMemoryObj, addr: Address, val: u64) -> i32 {
mem.virt_write(addr, &val).data_part().int_result()
}

@ -0,0 +1,4 @@
#[allow(unused)]
pub use memflow::os::*;
#[allow(unused)]
pub use memflow::plugins::*;

@ -0,0 +1,215 @@
use std::ffi::CStr;
use std::os::raw::c_char;
use std::path::PathBuf;
use memflow::plugins::Inventory;
use memflow::plugins::{
connector::{ConnectorInstanceArcBox, MuConnectorInstanceArcBox},
os::{MuOsInstanceArcBox, OsInstanceArcBox},
};
use crate::util::*;
use memflow::cglue::result::IntResult;
use log::trace;
/// Create a new connector inventory
///
/// This function will try to find connectors using PATH environment variable
///
/// Note that all functions go through each directories, and look for a `memflow` directory,
/// and search for libraries in those.
///
/// # Safety
///
/// Inventory is inherently unsafe, because it loads shared libraries which can not be
/// guaranteed to be safe.
#[no_mangle]
pub unsafe extern "C" fn inventory_scan() -> &'static mut Inventory {
to_heap(Inventory::scan())
}
/// Create a new inventory with custom path string
///
/// # Safety
///
/// `path` must be a valid null terminated string
#[no_mangle]
pub unsafe extern "C" fn inventory_scan_path(
path: *const c_char,
) -> Option<&'static mut Inventory> {
let rpath = CStr::from_ptr(path).to_string_lossy();
Inventory::scan_path(rpath.to_string())
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Add a directory to an existing inventory
///
/// # Safety
///
/// `dir` must be a valid null terminated string
#[no_mangle]
pub unsafe extern "C" fn inventory_add_dir(inv: &mut Inventory, dir: *const c_char) -> i32 {
let rdir = CStr::from_ptr(dir).to_string_lossy();
inv.add_dir(PathBuf::from(rdir.to_string()))
.into_int_result()
}
/// Create a connector with given arguments
///
/// This creates an instance of `ConnectorInstance`.
///
/// This instance needs to be dropped using `connector_drop`.
///
/// # Arguments
///
/// * `name` - name of the connector to use
/// * `args` - arguments to be passed to the connector upon its creation
///
/// # Safety
///
/// Both `name`, and `args` must be valid null terminated strings.
///
/// Any error strings returned by the connector must not be outputed after the connector gets
/// freed, because that operation could cause the underlying shared library to get unloaded.
#[no_mangle]
pub unsafe extern "C" fn inventory_create_connector(
inv: &mut Inventory,
name: *const c_char,
args: *const c_char,
out: &mut MuConnectorInstanceArcBox<'static>,
) -> i32 {
let rname = CStr::from_ptr(name).to_string_lossy();
if args.is_null() {
inv.create_connector(&rname, None, None)
.map_err(inspect_err)
.into_int_out_result(out)
} else {
let rargs = CStr::from_ptr(args).to_string_lossy();
str::parse(&rargs)
.map_err(inspect_err)
.and_then(|args| inv.create_connector(&rname, None, Some(&args)))
.map_err(inspect_err)
.into_int_out_result(out)
}
}
/// Create a OS instance with given arguments
///
/// This creates an instance of `KernelInstance`.
///
/// This instance needs to be freed using `os_drop`.
///
/// # Arguments
///
/// * `name` - name of the OS to use
/// * `args` - arguments to be passed to the connector upon its creation
/// * `mem` - a previously initialized connector instance
/// * `out` - a valid memory location that will contain the resulting os-instance
///
/// # Remarks
///
/// The `mem` connector instance is being _moved_ into the os layer.
/// This means upon calling `os_drop` it is not unnecessary to call `connector_drop` anymore.
///
/// # Safety
///
/// Both `name`, and `args` must be valid null terminated strings.
///
/// Any error strings returned by the connector must not be outputed after the connector gets
/// freed, because that operation could cause the underlying shared library to get unloaded.
#[no_mangle]
pub unsafe extern "C" fn inventory_create_os(
inv: &mut Inventory,
name: *const c_char,
args: *const c_char,
mem: *mut ConnectorInstanceArcBox<'static>,
out: &mut MuOsInstanceArcBox<'static>,
) -> i32 {
let rname = CStr::from_ptr(name).to_string_lossy();
let _args = CStr::from_ptr(args).to_string_lossy();
let mem_obj = if mem.is_null() {
None
} else {
let mem_obj = mem.read();
// Zero out the data so that any automatic destructors on the other side do nothing.
std::ptr::write_bytes(mem, 0, 1);
Some(mem_obj)
};
if args.is_null() {
inv.create_os(&rname, mem_obj, None)
.map_err(inspect_err)
.into_int_out_result(out)
} else {
let rargs = CStr::from_ptr(args).to_string_lossy();
str::parse(&rargs)
.map_err(inspect_err)
.and_then(|args| inv.create_os(&rname, mem_obj, Some(&args)))
.map_err(inspect_err)
.into_int_out_result(out)
}
}
/// Free a os plugin
///
/// # Safety
///
/// `os` must point to a valid `OsInstance` that was created using one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn os_drop(os: &mut OsInstanceArcBox<'static>) {
trace!("connector_drop: {:?}", os as *mut _);
std::ptr::drop_in_place(os);
}
/// Clone a connector
///
/// This method is useful when needing to perform multithreaded operations, as a connector is not
/// guaranteed to be thread safe. Every single cloned instance also needs to be dropped using
/// `connector_drop`.
///
/// # Safety
///
/// `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn connector_clone(
conn: &ConnectorInstanceArcBox<'static>,
out: &mut MuConnectorInstanceArcBox<'static>,
) {
trace!("connector_clone: {:?}", conn as *const _);
*out.as_mut_ptr() = conn.clone();
}
/// Free a connector instance
///
/// # Safety
///
/// `conn` has to point to a valid [`ConnectorInstance`](ConnectorInstanceArcBox) created by one of the provided
/// functions.
///
/// There has to be no instance of `PhysicalMemory` created from the input `conn`, because they
/// will become invalid.
#[no_mangle]
pub unsafe extern "C" fn connector_drop(conn: &mut ConnectorInstanceArcBox<'static>) {
trace!("connector_drop: {:?}", conn as *mut _);
std::ptr::drop_in_place(conn)
}
/// Free a connector inventory
///
/// # Safety
///
/// `inv` must point to a valid `Inventory` that was created using one of the provided
/// functions.
#[no_mangle]
pub unsafe extern "C" fn inventory_free(inv: &'static mut Inventory) {
trace!("inventory_free: {:?}", inv as *mut _);
let _ = Box::from_raw(inv);
}

@ -1,119 +0,0 @@
use crate::util::*;
use memflow::process::*;
use std::os::raw::c_char;
use std::slice::from_raw_parts_mut;
use memflow::architecture::ArchitectureObj;
use memflow::types::Address;
pub type OsProcessInfoObj = &'static dyn OsProcessInfo;
#[no_mangle]
pub extern "C" fn os_process_info_address(obj: &OsProcessInfoObj) -> Address {
obj.address()
}
#[no_mangle]
pub extern "C" fn os_process_info_pid(obj: &OsProcessInfoObj) -> PID {
obj.pid()
}
/// Retreive name of the process
///
/// This will copy at most `max_len` characters (including the null terminator) into `out` of the
/// name.
///
/// # Safety
///
/// `out` must be a buffer with at least `max_len` size
#[no_mangle]
pub unsafe extern "C" fn os_process_info_name(
obj: &OsProcessInfoObj,
out: *mut c_char,
max_len: usize,
) -> usize {
let name = obj.name();
let name_bytes = name.as_bytes();
let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1));
let len = out_bytes.len();
out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]);
*out_bytes.iter_mut().last().unwrap() = 0;
len
}
#[no_mangle]
pub extern "C" fn os_process_info_sys_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj {
to_heap(obj.sys_arch())
}
#[no_mangle]
pub extern "C" fn os_process_info_proc_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj {
to_heap(obj.proc_arch())
}
/// Free a OsProcessInfoObj reference
///
/// # Safety
///
/// `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's
/// functions.
#[no_mangle]
pub unsafe extern "C" fn os_process_info_free(obj: &'static mut OsProcessInfoObj) {
let _ = Box::from_raw(obj);
}
pub type OsProcessModuleInfoObj = &'static dyn OsProcessModuleInfo;
#[no_mangle]
pub extern "C" fn os_process_module_address(obj: &OsProcessModuleInfoObj) -> Address {
obj.address()
}
#[no_mangle]
pub extern "C" fn os_process_module_parent_process(obj: &OsProcessModuleInfoObj) -> Address {
obj.parent_process()
}
#[no_mangle]
pub extern "C" fn os_process_module_base(obj: &OsProcessModuleInfoObj) -> Address {
obj.base()
}
#[no_mangle]
pub extern "C" fn os_process_module_size(obj: &OsProcessModuleInfoObj) -> usize {
obj.size()
}
/// Retreive name of the module
///
/// This will copy at most `max_len` characters (including the null terminator) into `out` of the
/// name.
///
/// # Safety
///
/// `out` must be a buffer with at least `max_len` size
#[no_mangle]
pub unsafe extern "C" fn os_process_module_name(
obj: &OsProcessModuleInfoObj,
out: *mut c_char,
max_len: usize,
) -> usize {
let name = obj.name();
let name_bytes = name.as_bytes();
let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1));
let len = out_bytes.len();
out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]);
*out_bytes.iter_mut().last().unwrap() = 0;
len
}
/// Free a OsProcessModuleInfoObj reference
///
/// # Safety
///
/// `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's
/// functions.
#[no_mangle]
pub unsafe extern "C" fn os_process_module_free(obj: &'static mut OsProcessModuleInfoObj) {
let _ = Box::from_raw(obj);
}

@ -8,37 +8,3 @@ pub fn inspect_err<E: std::fmt::Display>(e: E) -> E {
pub fn to_heap<T>(a: T) -> &'static mut T {
Box::leak(Box::new(a))
}
pub trait ToIntResult {
fn int_result(self) -> i32;
fn int_result_logged(self) -> i32
where
Self: Sized,
{
let res = self.int_result();
if res != 0 {
error!("err value: {}", res);
}
res
}
}
impl<T, E: std::fmt::Display> ToIntResult for Result<T, E> {
fn int_result(self) -> i32 {
if self.is_ok() {
0
} else {
-1
}
}
fn int_result_logged(self) -> i32 {
if let Err(e) = self {
error!("{}", e);
-1
} else {
0
}
}
}

@ -1,65 +0,0 @@
/*
use std::ffi::c_void;
use std::ptr;
use memflow_win32::*;
*/
/*
/// # Safety
///
/// this function has to be called with an initialized memory backend
/// this function will return a pointer to a win32 object that has to be freed via win32_free()
#[no_mangle]
pub unsafe extern "C" fn win32_init(mem: *mut c_void) -> *mut Win32 {
if !mem.is_null() {
let mut _mem: Box<Box<dyn MemoryBackend>> = std::mem::transmute(mem as *mut _);
let _os = Win32::try_with(&mut **_mem).unwrap();
Box::leak(_mem);
return std::mem::transmute(Box::new(_os));
}
ptr::null_mut()
}
/// # Safety
///
/// this function has to be called with a pointer that has been initialized from win32_init()
#[no_mangle]
pub unsafe extern "C" fn win32_free(win32: *mut Win32) {
if !win32.is_null() {
let _win32: Box<Win32> = std::mem::transmute(win32);
// drop _win32
}
}
/// # Safety
///
/// this function will return a pointer to a win32_offsets object that has to be freed via win32_offsets_free()
#[no_mangle]
pub unsafe extern "C" fn win32_offsets_init(win32: *mut Win32) -> *mut Win32Offsets {
if !win32.is_null() {
let _win32: Box<Win32> = std::mem::transmute(win32);
let _offsets = Win32Offsets::try_with_guid(&_win32.kernel_guid()).unwrap();
Box::leak(_win32);
return std::mem::transmute(Box::new(_offsets));
}
ptr::null_mut()
}
/// # Safety
///
/// this function has to be called with a pointer that has been initialized from win32_offsets_init()
#[no_mangle]
pub unsafe extern "C" fn win32_offsets_free(offsets: *mut Win32Offsets) {
if !offsets.is_null() {
let _offsets: Box<Win32Offsets> = std::mem::transmute(offsets);
// drop _offsets
}
}
*/

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# update cglue-bindgen
cargo +nightly install cbindgen
cargo +nightly install cglue-bindgen
DIFFC=$(diff memflow.h <(rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi -l C))
DIFFCPP=$(diff memflow.hpp <(rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi -l C++))
if [ "$DIFFC" != "" ] || [ "$DIFFCPP" != "" ]
then
exit 1
fi

@ -1,5 +0,0 @@
/target
**/*.rs.bk
*.swp
.vscode
Cargo.lock

@ -1,37 +0,0 @@
[package]
name = "memflow-qemu-procfs"
version = "0.1.5"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "qemu procfs connector for the memflow physical memory introspection framework"
documentation = "https://docs.rs/memflow-qemu-procfs"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow-qemu-procfs"
license-file = "LICENSE"
keywords = [ "memflow", "introspection", "memory" ]
categories = [ "api-bindings", "memory-management", "os" ]
[lib]
crate-type = ["lib", "cdylib"]
[dependencies]
memflow = { version = "0.1", features = ["inventory"] }
log = { version = "0.4", default-features = false }
procfs = "0.7"
libc = "0.2"
[dev-dependencies]
clap = "2.33"
simple_logger = "1.0"
[profile.release]
lto = true
[features]
default = []
inventory = []
[[example]]
name = "read_phys"
path = "examples/read_phys.rs"

@ -1,24 +0,0 @@
.PHONY: all release debug test install
all:
make test
make release
release:
cargo build --release --all-features
debug:
cargo build --all-features
clean:
cargo clean
test:
cargo test --all-features
install_user:
./install.sh
install:
./install.sh --system

@ -1,55 +0,0 @@
# memflow-qemu-procfs
This connector implements an interface for Qemu via the Process Filesystem on Linux.
## Compilation
### Installing the library
The `./install.sh` script will just compile and install the plugin.
The connector will be installed to `~/.local/lib/memflow` by default.
Additionally the `--system` flag can be specified which will install the connector in `/usr/lib/memflow` as well.
### Building the stand-alone connector for dynamic loading
The stand-alone connector of this library is feature-gated behind the `inventory` feature.
To compile a dynamic library for use with the connector inventory use the following command:
```
cargo build --release --all-features
```
### Using the crate in a rust project
To use the connector in a rust project just include it in your Cargo.toml
```
memflow-qemu-procfs = "0.1"
```
Make sure to not enable the `inventory` feature when importing multiple
connectors in a rust project without using the memflow connector inventory.
This might cause duplicated exports being generated in your project.
## Arguments
- `name` - the name of the virtual machine (default argument, optional)
## Permissions
The `qemu_procfs` connector requires access to the qemu process via the linux procfs. This means any process which loads this connector requires to have at least ptrace permissions set.
To set ptrace permissions on a binary simply use:
```bash
sudo setcap 'CAP_SYS_PTRACE=ep' [filename]
```
Alternatively you can just run the binary via `sudo`.
## License
Licensed under MIT License, see [LICENSE](LICENSE).
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.

@ -1,45 +0,0 @@
use std::time::Instant;
use log::{info, Level};
use memflow::prelude::v1::*;
fn main() {
simple_logger::SimpleLogger::new()
.with_level(Level::Debug.to_level_filter())
.init()
.unwrap();
let mut conn = match memflow_qemu_procfs::create_connector(&ConnectorArgs::new()) {
Ok(br) => br,
Err(e) => {
info!("couldn't open memory read context: {:?}", e);
return;
}
};
let metadata = conn.metadata();
info!("Received metadata: {:?}", metadata);
let mut mem = vec![0; 8];
conn.phys_read_raw_into(Address::from(0x1000).into(), &mut mem)
.unwrap();
info!("Received memory: {:?}", mem);
let start = Instant::now();
let mut counter = 0;
loop {
let mut buf = vec![0; 0x1000];
conn.phys_read_raw_into(Address::from(0x1000).into(), &mut buf)
.unwrap();
counter += 1;
if (counter % 10000000) == 0 {
let elapsed = start.elapsed().as_millis() as f64;
if elapsed > 0.0 {
info!("{} reads/sec", (f64::from(counter)) / elapsed * 1000.0);
info!("{} ms/read", elapsed / (f64::from(counter)));
}
}
}
}

@ -1,19 +0,0 @@
#!/bin/bash
cargo build --release --all-features
# install connector to system dir
if [ ! -z "$1" ] && [ $1 = "--system" ]; then
echo "installing connector system-wide in /usr/lib/memflow"
if [[ ! -d /usr/lib/memflow ]]; then
sudo mkdir /usr/lib/memflow
fi
sudo cp target/release/libmemflow_qemu_procfs.so /usr/lib/memflow
fi
# install connector in user dir
echo "installing connector for user in ~/.local/lib/memflow"
if [[ ! -d ~/.local/lib/memflow ]]; then
mkdir -p ~/.local/lib/memflow
fi
cp target/release/libmemflow_qemu_procfs.so ~/.local/lib/memflow

@ -1,475 +0,0 @@
use log::info;
use core::ffi::c_void;
use libc::{c_ulong, iovec, pid_t, sysconf, _SC_IOV_MAX};
use memflow::prelude::v1::*;
#[derive(Clone, Copy)]
#[repr(transparent)]
struct IoSendVec(iovec);
unsafe impl Send for IoSendVec {}
fn qemu_arg_opt(args: &[String], argname: &str, argopt: &str) -> Option<String> {
for (idx, arg) in args.iter().enumerate() {
if arg == argname {
let name = args[idx + 1].split(',');
for (i, kv) in name.clone().enumerate() {
let kvsplt = kv.split('=').collect::<Vec<_>>();
if kvsplt.len() == 2 {
if kvsplt[0] == argopt {
return Some(kvsplt[1].to_string());
}
} else if i == 0 {
return Some(kv.to_string());
}
}
}
}
None
}
fn is_qemu(process: &procfs::process::Process) -> bool {
process
.cmdline()
.ok()
.and_then(|cmdline| {
cmdline.iter().nth(0).and_then(|cmd| {
std::path::Path::new(cmd)
.file_name()
.and_then(|exe| exe.to_str())
.map(|v| v.contains("qemu-system-"))
})
})
.unwrap_or(false)
}
#[derive(Clone)]
pub struct QemuProcfs {
pub pid: pid_t,
pub mem_map: MemoryMap<(Address, usize)>,
temp_iov: Box<[IoSendVec]>,
}
impl QemuProcfs {
pub fn new() -> Result<Self> {
let prcs = procfs::process::all_processes()
.map_err(|_| Error::Connector("unable to list procfs processes"))?;
let prc = prcs
.iter()
.find(|p| is_qemu(p))
.ok_or_else(|| Error::Connector("qemu process not found"))?;
info!("qemu process found with pid {:?}", prc.stat.pid);
Self::with_process(prc)
}
pub fn with_guest_name(name: &str) -> Result<Self> {
let prcs = procfs::process::all_processes()
.map_err(|_| Error::Connector("unable to list procefs processes"))?;
let (prc, _) = prcs
.iter()
.filter(|p| is_qemu(p))
.filter_map(|p| {
if let Ok(c) = p.cmdline() {
Some((p, c))
} else {
None
}
})
.find(|(_, c)| qemu_arg_opt(c, "-name", "guest").unwrap_or_default() == name)
.ok_or_else(|| Error::Connector("qemu process not found"))?;
info!(
"qemu process with name {} found with pid {:?}",
name, prc.stat.pid
);
Self::with_process(prc)
}
fn with_process(prc: &procfs::process::Process) -> Result<Self> {
// find biggest memory mapping in qemu process
let mut maps = prc
.maps()
.map_err(|_| Error::Connector("Unable to retrieve Qemu memory maps. Did u run memflow with the correct access rights (SYS_PTRACE or root)?"))?;
maps.sort_by(|b, a| {
(a.address.1 - a.address.0)
.partial_cmp(&(b.address.1 - b.address.0))
.unwrap()
});
let map = maps
.get(0)
.ok_or_else(|| Error::Connector("Qemu memory map could not be read"))?;
info!("qemu memory map found {:?}", map);
let map_base = map.address.0 as usize;
let map_size = (map.address.1 - map.address.0) as usize;
info!("qemu memory map size: {:x}", map_size);
// TODO: instead of hardcoding the memory regions per machine we could just use the hmp to retrieve the proper ranges:
// sudo virsh qemu-monitor-command win10 --hmp 'info mtree -f' | grep pc\.ram
// find machine architecture
let machine = qemu_arg_opt(
&prc.cmdline()
.map_err(|_| Error::Connector("Unable to parse qemu arguments"))?,
"-machine",
"type",
)
.unwrap_or_else(|| "pc".into());
info!("qemu process started with machine: {}", machine);
let mut mem_map = MemoryMap::new();
if machine.contains("q35") {
// q35 -> subtract 2GB
/*
0000000000000000-000000000009ffff (prio 0, ram): pc.ram KVM
00000000000c0000-00000000000c3fff (prio 0, rom): pc.ram @00000000000c0000 KVM
0000000000100000-000000007fffffff (prio 0, ram): pc.ram @0000000000100000 KVM
0000000100000000-000000047fffffff (prio 0, ram): pc.ram @0000000080000000 KVM
*/
// we add all regions additionally shifted to the proper qemu memory map address
mem_map.push_range(Address::NULL, size::kb(640).into(), map_base.into()); // section: [start - 640kb] -> map to start
// If larger than this specific size, second half after 2 gigs gets moved over past 4gb
// TODO: Probably the same happens with i1440-fx
if map_size >= size::mb(2816) {
mem_map.push_range(
size::mb(1).into(),
size::gb(2).into(),
(map_base + size::mb(1)).into(),
); // section: [1mb - 2gb] -> map to 1mb
mem_map.push_range(
size::gb(4).into(),
(map_size + size::gb(2)).into(),
(map_base + size::gb(2)).into(),
); // section: [4gb - max] -> map to 2gb
} else {
mem_map.push_range(
size::mb(1).into(),
map_size.into(),
(map_base + size::mb(1)).into(),
); // section: [1mb - max] -> map to 1mb
}
} else {
// pc-i1440fx
/*
0000000000000000-00000000000bffff (prio 0, ram): pc.ram KVM
00000000000c0000-00000000000cafff (prio 0, rom): pc.ram @00000000000c0000 KVM
00000000000cb000-00000000000cdfff (prio 0, ram): pc.ram @00000000000cb000 KVM
00000000000ce000-00000000000e7fff (prio 0, rom): pc.ram @00000000000ce000 KVM
00000000000e8000-00000000000effff (prio 0, ram): pc.ram @00000000000e8000 KVM
00000000000f0000-00000000000fffff (prio 0, rom): pc.ram @00000000000f0000 KVM
0000000000100000-00000000bfffffff (prio 0, ram): pc.ram @0000000000100000 KVM
0000000100000000-000000023fffffff (prio 0, ram): pc.ram @00000000c0000000 KVM
*/
mem_map.push_range(Address::NULL, size::kb(768).into(), map_base.into()); // section: [start - 768kb] -> map to start
mem_map.push_range(
size::kb(812).into(),
size::kb(824).into(),
(map_base + size::kb(812)).into(),
); // section: [768kb - 812kb] -> map to 768kb
mem_map.push_range(
size::kb(928).into(),
size::kb(960).into(),
(map_base + size::kb(928)).into(),
); // section: [928kb - 960kb] -> map to 928kb
mem_map.push_range(
size::mb(1).into(),
size::gb(3).into(),
(map_base + size::mb(1)).into(),
); // section: [1mb - 3gb] -> map to 1mb
mem_map.push_range(
size::gb(4).into(),
(map_size + size::gb(1)).into(),
(map_base + size::gb(3)).into(),
); // section: [4gb - max] -> map to 3gb
}
info!("qemu machine mem_map: {:?}", mem_map);
let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize;
Ok(Self {
pid: prc.stat.pid,
mem_map,
temp_iov: vec![
IoSendVec {
0: iovec {
iov_base: std::ptr::null_mut::<c_void>(),
iov_len: 0
}
};
iov_max * 2
]
.into_boxed_slice(),
})
}
fn fill_iovec(addr: &Address, data: &[u8], liov: &mut IoSendVec, riov: &mut IoSendVec) {
let iov_len = data.len();
liov.0 = iovec {
iov_base: data.as_ptr() as *mut c_void,
iov_len,
};
riov.0 = iovec {
iov_base: addr.as_u64() as *mut c_void,
iov_len,
};
}
fn vm_error() -> Error {
match unsafe { *libc::__errno_location() } {
libc::EFAULT => Error::Connector("process_vm_readv failed: EFAULT (remote memory address is invalid)"),
libc::ENOMEM => Error::Connector("process_vm_readv failed: ENOMEM (unable to allocate memory for internal copies)"),
libc::EPERM => Error::Connector("process_vm_readv failed: EPERM (insifficient permissions to access the target address space)"),
libc::ESRCH => Error::Connector("process_vm_readv failed: ESRCH (process not found)"),
libc::EINVAL => Error::Connector("process_vm_readv failed: EINVAL (invalid value)"),
_ => Error::Connector("process_vm_readv failed: unknown error")
}
}
}
impl PhysicalMemory for QemuProcfs {
fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
let mem_map = &self.mem_map;
let temp_iov = &mut self.temp_iov;
let mut void = FnExtend::void();
let mut iter = mem_map.map_iter(
data.iter_mut()
.map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)),
&mut void,
);
let max_iov = temp_iov.len() / 2;
let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
let mut elem = iter.next();
let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
let mut iov_next = iov_iter.next();
while let Some(((addr, _), out)) = elem {
let (cnt, (liov, riov)) = iov_next.unwrap();
Self::fill_iovec(&addr, out, liov, riov);
iov_next = iov_iter.next();
elem = iter.next();
if elem.is_none() || iov_next.is_none() {
if unsafe {
libc::process_vm_readv(
self.pid,
iov_local.as_ptr().cast(),
(cnt + 1) as c_ulong,
iov_remote.as_ptr().cast(),
(cnt + 1) as c_ulong,
0,
)
} == -1
{
return Err(Self::vm_error());
}
iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
iov_next = iov_iter.next();
}
}
Ok(())
}
fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
let mem_map = &self.mem_map;
let temp_iov = &mut self.temp_iov;
let mut void = FnExtend::void();
let mut iter = mem_map.map_iter(data.iter().copied().map(<_>::from), &mut void);
//let mut iter = mem_map.map_iter(data.iter(), &mut FnExtend::new(|_|{}));
let max_iov = temp_iov.len() / 2;
let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
let mut elem = iter.next();
let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
let mut iov_next = iov_iter.next();
while let Some(((addr, _), out)) = elem {
let (cnt, (liov, riov)) = iov_next.unwrap();
Self::fill_iovec(&addr, out, liov, riov);
iov_next = iov_iter.next();
elem = iter.next();
if elem.is_none() || iov_next.is_none() {
if unsafe {
libc::process_vm_writev(
self.pid,
iov_local.as_ptr().cast(),
(cnt + 1) as c_ulong,
iov_remote.as_ptr().cast(),
(cnt + 1) as c_ulong,
0,
)
} == -1
{
return Err(Self::vm_error());
}
iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
iov_next = iov_iter.next();
}
}
Ok(())
}
fn metadata(&self) -> PhysicalMemoryMetadata {
PhysicalMemoryMetadata {
size: self
.mem_map
.as_ref()
.iter()
.last()
.map(|map| map.base().as_usize() + map.output().1)
.unwrap(),
readonly: false,
}
}
}
/// Creates a new Qemu Procfs Connector instance.
#[connector(name = "qemu_procfs")]
pub fn create_connector(args: &ConnectorArgs) -> Result<QemuProcfs> {
if let Some(name) = args.get("name").or_else(|| args.get_default()) {
QemuProcfs::with_guest_name(name)
} else {
QemuProcfs::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_name() {
assert_eq!(
qemu_arg_opt(
&["-name".to_string(), "win10-test".to_string()],
"-name",
"guest"
),
Some("win10-test".into())
);
assert_eq!(
qemu_arg_opt(
&[
"-test".to_string(),
"-name".to_string(),
"win10-test".to_string()
],
"-name",
"guest"
),
Some("win10-test".into())
);
assert_eq!(
qemu_arg_opt(
&["-name".to_string(), "win10-test,arg=opt".to_string()],
"-name",
"guest"
),
Some("win10-test".into())
);
assert_eq!(
qemu_arg_opt(
&["-name".to_string(), "guest=win10-test,arg=opt".to_string()],
"-name",
"guest"
),
Some("win10-test".into())
);
assert_eq!(
qemu_arg_opt(
&["-name".to_string(), "arg=opt,guest=win10-test".to_string()],
"-name",
"guest"
),
Some("win10-test".into())
);
assert_eq!(
qemu_arg_opt(
&["-name".to_string(), "arg=opt".to_string()],
"-name",
"guest"
),
None
);
}
#[test]
fn test_machine() {
assert_eq!(
qemu_arg_opt(
&["-machine".to_string(), "q35".to_string()],
"-machine",
"type"
),
Some("q35".into())
);
assert_eq!(
qemu_arg_opt(
&[
"-test".to_string(),
"-machine".to_string(),
"q35".to_string()
],
"-machine",
"type"
),
Some("q35".into())
);
assert_eq!(
qemu_arg_opt(
&["-machine".to_string(), "q35,arg=opt".to_string()],
"-machine",
"type"
),
Some("q35".into())
);
assert_eq!(
qemu_arg_opt(
&["-machine".to_string(), "type=pc,arg=opt".to_string()],
"-machine",
"type"
),
Some("pc".into())
);
assert_eq!(
qemu_arg_opt(
&[
"-machine".to_string(),
"arg=opt,type=pc-i1440fx".to_string()
],
"-machine",
"type"
),
Some("pc-i1440fx".into())
);
assert_eq!(
qemu_arg_opt(
&["-machine".to_string(), "arg=opt".to_string()],
"-machine",
"type"
),
None
);
}
}

@ -1,6 +0,0 @@
/target
**/*.rs.bk
bindings
**/node_modules
**/*.out
**/*.o

@ -1,26 +0,0 @@
[package]
name = "memflow-win32-ffi"
version = "0.1.5"
authors = ["Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "C bindings to memflow-win32"
documentation = "https://docs.rs/memflow-win32-ffi"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow"
license-file = "../LICENSE"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "api-bindings", "memory-management", "os" ]
[badges]
maintenance = { status = "actively-developed" }
codecov = { repository = "github", branch = "master", service = "github" }
[lib]
name = "memflow_win32_ffi"
crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
memflow-win32 = { version = "0.1", path = "../memflow-win32" }
memflow = { version = "0.1", path = "../memflow" }
memflow-ffi = { version = "0.1", path = "../memflow-ffi" }

@ -1,47 +0,0 @@
# memflow-win32-ffi
[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow)
![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev)
[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR)
The [memflow](https://github.com/memflow/memflow) win32 FFI crate provides an interface to the memflow-win32 API for C/C++. Currently a single `memflow_win32.h` file is generated aside from the dynamic library that can be used to interact with memflow.
This FFI library is intended to be used in combination with the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) library.
A simple example that initializes the memflow-ffi and memflow-win32-ffi:
```cpp
#include "memflow_win32.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
log_init(1);
ConnectorInventory *inv = inventory_try_new();
printf("inv: %p\n", inv);
const char *conn_name = argc > 1? argv[1]: "kvm";
const char *conn_arg = argc > 2? argv[2]: "";
CloneablePhysicalMemoryObj *conn =
inventory_create_connector(inv, conn_name, conn_arg);
printf("conn: %p\n", conn);
if (conn) {
Kernel *kernel = kernel_build(conn);
printf("Kernel: %p\n", kernel);
Win32Version ver = kernel_winver(kernel);
printf("major: %d\n", ver.nt_major_version);
printf("minor: %d\n", ver.nt_minor_version);
printf("build: %d\n", ver.nt_build_number);
kernel_free(kernel);
}
inventory_free(inv);
return 0;
}
```
Additional examples can be found in the `examples` folder as well as in the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) crate.

@ -1,3 +0,0 @@
#!/bin/bash
cargo build --release --workspace
cbindgen --config cbindgen.toml --crate memflow-win32-ffi --output memflow_win32.h

@ -1,22 +0,0 @@
language = "C"
include_guard = "MEMFLOW_WIN32_H"
tab_width = 4
documentation_style = "doxy"
style = "both"
cpp_compat = true
includes = ["memflow.h"]
[parse]
parse_deps = true
include = ["memflow-win32", "memflow"]
[macro_expansion]
bitflags = true
[fn]
sort_by = "None"
[export]
exclude = ["PageType", "Address"]

@ -1,22 +0,0 @@
CC =gcc
CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release
LIBS=-lm -ldl -lpthread -l:libmemflow_win32_ffi.a
ODIR=./
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
process_list.out: process_list.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
dump_header.out: dump_header.o
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: all
all: process_list.out dump_header.out
.DEFAULT_GOAL := all
clean:
rm -f $(ODIR)/*.o

@ -1,61 +0,0 @@
#include "memflow_win32.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
log_init(1);
ConnectorInventory *inv = inventory_try_new();
printf("inv: %p\n", inv);
const char *conn_name = argc > 1? argv[1]: "kvm";
const char *conn_arg = argc > 2? argv[2]: "";
const char *proc_name = argc > 3? argv[3]: "lsass.exe";
const char *dll_name = argc > 4? argv[4]: "ntdll.dll";
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
printf("conn: %p\n", conn);
if (conn) {
Kernel *kernel = kernel_build(conn);
printf("Kernel: %p\n", kernel);
Win32Version ver = kernel_winver(kernel);
printf("major: %d\n", ver.nt_major_version);
printf("minor: %d\n", ver.nt_minor_version);
printf("build: %d\n", ver.nt_build_number);
Win32Process *process = kernel_into_process(kernel, proc_name);
if (process) {
Win32ModuleInfo *module = process_module_info(process, dll_name);
if (module) {
OsProcessModuleInfoObj *obj = module_info_trait(module);
Address base = os_process_module_base(obj);
os_process_module_free(obj);
VirtualMemoryObj *virt_mem = process_virt_mem(process);
char header[256];
if (!virt_read_raw_into(virt_mem, base, header, 256)) {
printf("Read successful!\n");
for (int o = 0; o < 8; o++) {
for (int i = 0; i < 32; i++) {
printf("%2hhx ", header[o * 32 + i]);
}
printf("\n");
}
} else {
printf("Failed to read!\n");
}
virt_free(virt_mem);
}
process_free(process);
}
}
inventory_free(inv);
return 0;
}

@ -1,54 +0,0 @@
#include "memflow_win32.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
log_init(1);
ConnectorInventory *inv = inventory_scan();
printf("inv: %p\n", inv);
const char *conn_name = argc > 1? argv[1]: "kvm";
const char *conn_arg = argc > 2? argv[2]: "";
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
printf("conn: %p\n", conn);
if (conn) {
Kernel *kernel = kernel_build(conn);
printf("Kernel: %p\n", kernel);
Win32Version ver = kernel_winver(kernel);
printf("major: %d\n", ver.nt_major_version);
printf("minor: %d\n", ver.nt_minor_version);
printf("build: %d\n", ver.nt_build_number);
Win32ProcessInfo *processes[512];
size_t process_count = kernel_process_info_list(kernel, processes, 512);
printf("Process List:\n");
printf("%-8s | %-16s | %-16s | %-12s | %-5s\n", "PID", "Name", "Base", "DTB", "Wow64");
for (size_t i = 0; i < process_count; i++) {
Win32ProcessInfo *process = processes[i];
OsProcessInfoObj *info = process_info_trait(process);
char name[32];
os_process_info_name(info, name, 32);
printf("%-8d | %-16s | %-16lx | %-12lx | %-5s\n",
os_process_info_pid(info),
name,
process_info_section_base(process),
process_info_dtb(process),
process_info_wow64(process)? "Yes" : "No"
);
os_process_info_free(info);
process_info_free(process);
}
kernel_free(kernel);
}
inventory_free(inv);
return 0;
}

@ -1,321 +0,0 @@
#ifndef MEMFLOW_WIN32_H
#define MEMFLOW_WIN32_H
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "memflow.h"
typedef struct Kernel_FFIMemory__FFIVirtualTranslate Kernel_FFIMemory__FFIVirtualTranslate;
typedef struct Win32ModuleInfo Win32ModuleInfo;
typedef struct Win32ProcessInfo Win32ProcessInfo;
typedef struct Win32Process_FFIVirtualMemory Win32Process_FFIVirtualMemory;
typedef Kernel_FFIMemory__FFIVirtualTranslate Kernel;
typedef struct StartBlock {
Address kernel_hint;
Address dtb;
} StartBlock;
typedef struct Win32Version {
uint32_t nt_major_version;
uint32_t nt_minor_version;
uint32_t nt_build_number;
} Win32Version;
/**
* Type alias for a PID.
*/
typedef uint32_t PID;
typedef Win32Process_FFIVirtualMemory Win32Process;
typedef struct Win32ArchOffsets {
uintptr_t peb_ldr;
uintptr_t ldr_list;
uintptr_t ldr_data_base;
uintptr_t ldr_data_size;
uintptr_t ldr_data_full_name;
uintptr_t ldr_data_base_name;
} Win32ArchOffsets;
typedef struct Win32ModuleListInfo {
Address module_base;
Win32ArchOffsets offsets;
} Win32ModuleListInfo;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Build a cloneable kernel object with default caching parameters
*
* This function will take ownership of the input `mem` object.
*
* # Safety
*
* `mem` must be a heap allocated memory reference, created by one of the API's functions.
* Reference to it becomes invalid.
*/
Kernel *kernel_build(CloneablePhysicalMemoryObj *mem);
/**
* Build a cloneable kernel object with custom caching parameters
*
* This function will take ownership of the input `mem` object.
*
* vat_cache_entries must be positive, or the program will panic upon memory reads or writes.
*
* # Safety
*
* `mem` must be a heap allocated memory reference, created by one of the API's functions.
* Reference to it becomes invalid.
*/
Kernel *kernel_build_custom(CloneablePhysicalMemoryObj *mem,
uint64_t page_cache_time_ms,
PageType page_cache_flags,
uintptr_t page_cache_size_kb,
uint64_t vat_cache_time_ms,
uintptr_t vat_cache_entries);
Kernel *kernel_clone(const Kernel *kernel);
/**
* Free a kernel object
*
* This will free the input `kernel` object (including the underlying memory object)
*
* # Safety
*
* `kernel` must be a valid reference heap allocated by one of the above functions.
*/
void kernel_free(Kernel *kernel);
/**
* Destroy a kernel object and return its underlying memory object
*
* This will free the input `kernel` object, and return the underlying memory object. It will free
* the object from any additional caching that `kernel` had in place.
*
* # Safety
*
* `kernel` must be a valid reference heap allocated by one of the above functions.
*/
CloneablePhysicalMemoryObj *kernel_destroy(Kernel *kernel);
StartBlock kernel_start_block(const Kernel *kernel);
Win32Version kernel_winver(const Kernel *kernel);
Win32Version kernel_winver_unmasked(const Kernel *kernel);
/**
* Retrieve a list of peorcess addresses
*
* # Safety
*
* `buffer` must be a valid buffer of size at least `max_size`
*/
uintptr_t kernel_eprocess_list(Kernel *kernel, Address *buffer, uintptr_t max_size);
/**
* Retrieve a list of processes
*
* This will fill `buffer` with a list of win32 process information. These processes will need to be
* individually freed with `process_info_free`
*
* # Safety
*
* `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`.
*/
uintptr_t kernel_process_info_list(Kernel *kernel, Win32ProcessInfo **buffer, uintptr_t max_size);
Win32ProcessInfo *kernel_kernel_process_info(Kernel *kernel);
Win32ProcessInfo *kernel_process_info_from_eprocess(Kernel *kernel, Address eprocess);
/**
* Retrieve process information by name
*
* # Safety
*
* `name` must be a valid null terminated string
*/
Win32ProcessInfo *kernel_process_info(Kernel *kernel, const char *name);
Win32ProcessInfo *kernel_process_info_pid(Kernel *kernel, PID pid);
/**
* Create a process by looking up its name
*
* This will consume `kernel` and free it later on.
*
* # Safety
*
* `name` must be a valid null terminated string
*
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
* invalid.
*/
Win32Process *kernel_into_process(Kernel *kernel, const char *name);
/**
* Create a process by looking up its PID
*
* This will consume `kernel` and free it later on.
*
* # Safety
*
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
* invalid.
*/
Win32Process *kernel_into_process_pid(Kernel *kernel, PID pid);
/**
* Create a kernel process insatance
*
* This will consume `kernel` and free it later on.
*
* # Safety
*
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
* invalid.
*/
Win32Process *kernel_into_kernel_process(Kernel *kernel);
OsProcessModuleInfoObj *module_info_trait(Win32ModuleInfo *info);
/**
* Free a win32 module info instance.
*
* Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed
* manually.
*
* # Safety
*
* `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the
* reference will become invalid.
*/
void module_info_free(Win32ModuleInfo *info);
/**
* Create a process with kernel and process info
*
* # Safety
*
* `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function
* call, the reference becomes invalid.
*/
Win32Process *process_with_kernel(Kernel *kernel, const Win32ProcessInfo *proc_info);
/**
* Retrieve refernce to the underlying virtual memory object
*
* This will return a static reference to the virtual memory object. It will only be valid as long
* as `process` if valid, and needs to be freed manually using `virt_free` regardless if the
* process if freed or not.
*/
VirtualMemoryObj *process_virt_mem(Win32Process *process);
Win32Process *process_clone(const Win32Process *process);
/**
* Frees the `process`
*
* # Safety
*
* `process` must be a valid heap allocated reference to a `Win32Process` object. After the
* function returns, the reference becomes invalid.
*/
void process_free(Win32Process *process);
/**
* Retrieve a process module list
*
* This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects.
*
* These references then need to be freed with `module_info_free`
*
* # Safety
*
* `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`.
*/
uintptr_t process_module_list(Win32Process *process, Win32ModuleInfo **out, uintptr_t max_len);
/**
* Retrieve the main module of the process
*
* This function searches for a module with a base address
* matching the section_base address from the ProcessInfo structure.
* It then returns a reference to a newly allocated
* `Win32ModuleInfo` object, if a module was found (null otherwise).
*
* The reference later needs to be freed with `module_info_free`
*
* # Safety
*
* `process` must be a valid Win32Process pointer.
*/
Win32ModuleInfo *process_main_module_info(Win32Process *process);
/**
* Lookup a module
*
* This will search for a module called `name`, and return a reference to a newly allocated
* `Win32ModuleInfo` object, if a module was found (null otherwise).
*
* The reference later needs to be freed with `module_info_free`
*
* # Safety
*
* `process` must be a valid Win32Process pointer.
* `name` must be a valid null terminated string.
*/
Win32ModuleInfo *process_module_info(Win32Process *process, const char *name);
OsProcessInfoObj *process_info_trait(Win32ProcessInfo *info);
Address process_info_dtb(const Win32ProcessInfo *info);
Address process_info_section_base(const Win32ProcessInfo *info);
int32_t process_info_exit_status(const Win32ProcessInfo *info);
Address process_info_ethread(const Win32ProcessInfo *info);
Address process_info_wow64(const Win32ProcessInfo *info);
Address process_info_peb(const Win32ProcessInfo *info);
Address process_info_peb_native(const Win32ProcessInfo *info);
Address process_info_peb_wow64(const Win32ProcessInfo *info);
Address process_info_teb(const Win32ProcessInfo *info);
Address process_info_teb_wow64(const Win32ProcessInfo *info);
Win32ModuleListInfo process_info_module_info(const Win32ProcessInfo *info);
Win32ModuleListInfo process_info_module_info_native(const Win32ProcessInfo *info);
/**
* Free a process information reference
*
* # Safety
*
* `info` must be a valid heap allocated reference to a Win32ProcessInfo structure
*/
void process_info_free(Win32ProcessInfo *info);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* MEMFLOW_WIN32_H */

@ -1,151 +0,0 @@
#ifndef MEMFLOW_WIN32_HLAPI_H
#define MEMFLOW_WIN32_HLAPI_H
#include "memflow_cpp.h"
#include "memflow_win32.h"
#include "binddestr.h"
#ifndef NO_STL_CONTAINERS
#include <vector>
// Maximum number of entries allowed in the returned lists
#ifndef AUTO_VEC_SIZE
#define AUTO_VEC_SIZE 2048
#endif
#endif
struct CKernel;
struct CWin32ModuleInfo
: BindDestr<Win32ModuleInfo, module_info_free>
{
CWin32ModuleInfo(Win32ModuleInfo *modinfo)
: BindDestr(modinfo) {}
WRAP_FN_TYPE(COsProcessModuleInfo, module, info_trait);
};
struct CWin32Process
: BindDestr<Win32Process, process_free>
{
CWin32Process(Win32Process *process)
: BindDestr(process) {}
CWin32Process(CKernel &kernel, Win32ProcessInfo *info);
WRAP_FN_TYPE(CWin32ModuleInfo, process, module_info);
WRAP_FN_TYPE(CVirtualMemory, process, virt_mem);
};
struct CWin32ProcessInfo
: BindDestr<Win32ProcessInfo, process_info_free>
{
CWin32ProcessInfo(Win32ProcessInfo *info)
: BindDestr(info) {}
WRAP_FN_TYPE(COsProcessInfo, process_info, trait);
WRAP_FN(process_info, dtb);
WRAP_FN(process_info, section_base);
WRAP_FN(process_info, wow64);
WRAP_FN(process_info, peb);
WRAP_FN(process_info, peb_native);
WRAP_FN(process_info, peb_wow64);
WRAP_FN(process_info, teb);
WRAP_FN(process_info, teb_wow64);
WRAP_FN(process_info, module_info);
WRAP_FN(process_info, module_info_native);
inline operator COsProcessInfo() {
return this->trait();
}
};
struct CKernel
: BindDestr<Kernel, kernel_free>
{
CKernel(Kernel *kernel)
: BindDestr(kernel) {}
CKernel(CCloneablePhysicalMemory &mem)
: BindDestr(kernel_build(mem.invalidate())) {}
CKernel(
CCloneablePhysicalMemory &mem,
uint64_t page_cache_time_ms,
PageType page_cache_flags,
uintptr_t page_cache_size_kb,
uint64_t vat_cache_time_ms,
uintptr_t vat_cache_entries
) : BindDestr(kernel_build_custom(
mem.invalidate(),
page_cache_time_ms,
page_cache_flags,
page_cache_size_kb,
vat_cache_time_ms,
vat_cache_entries
)) {}
WRAP_FN_TYPE(CKernel, kernel, clone);
WRAP_FN_TYPE_INVALIDATE(CCloneablePhysicalMemory, kernel, destroy);
WRAP_FN(kernel, start_block);
WRAP_FN(kernel, winver);
WRAP_FN(kernel, winver_unmasked);
WRAP_FN(kernel, eprocess_list);
WRAP_FN(kernel, process_info_list);
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, kernel_process_info);
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_from_eprocess);
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info);
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_pid);
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process);
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process_pid);
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_kernel_process);
#ifndef NO_STL_CONTAINERS
// Manual eprocess_list impl
std::vector<Address> eprocess_vec(size_t max_size) {
Address *buf = (Address *)malloc(sizeof(Address *) * max_size);
std::vector<Address> ret;
if (buf) {
size_t size = kernel_eprocess_list(this->inner, buf, max_size);
for (size_t i = 0; i < size; i++)
ret.push_back(buf[i]);
free(buf);
}
return ret;
}
std::vector<Address> eprocess_vec() {
return this->eprocess_vec(AUTO_VEC_SIZE);
}
// Manual process_info_list impl
std::vector<CWin32ProcessInfo> process_info_vec(size_t max_size) {
Win32ProcessInfo **buf = (Win32ProcessInfo **)malloc(sizeof(Win32ProcessInfo *) * max_size);
std::vector<CWin32ProcessInfo> ret;
if (buf) {
size_t size = kernel_process_info_list(this->inner, buf, max_size);
for (size_t i = 0; i < size; i++)
ret.push_back(CWin32ProcessInfo(buf[i]));
free(buf);
}
return ret;
}
std::vector<CWin32ProcessInfo> process_info_vec() {
return this->process_info_vec(AUTO_VEC_SIZE);
}
#endif
};
// Extra constructors we couldn't define inside the classes
CWin32Process::CWin32Process(CKernel &kernel, Win32ProcessInfo *info)
: BindDestr(process_with_kernel(kernel.invalidate(), info)) {}
#endif

@ -1,17 +0,0 @@
use memflow::types::Address;
use memflow_win32::kernel;
#[repr(C)]
pub struct StartBlock {
pub kernel_hint: Address,
pub dtb: Address,
}
impl From<kernel::StartBlock> for StartBlock {
fn from(o: kernel::StartBlock) -> StartBlock {
StartBlock {
kernel_hint: o.kernel_hint,
dtb: o.dtb,
}
}
}

@ -1,2 +0,0 @@
pub mod kernel;
pub mod win32;

@ -1,331 +0,0 @@
use memflow_ffi::mem::phys_mem::CloneablePhysicalMemoryObj;
use memflow_ffi::util::*;
use memflow_win32::kernel::Win32Version;
use memflow_win32::win32::{kernel, Win32ProcessInfo, Win32VirtualTranslate};
use memflow::mem::{
cache::{CachedMemoryAccess, CachedVirtualTranslate, TimedCacheValidator},
CloneablePhysicalMemory, DirectTranslate, VirtualDMA,
};
use memflow::iter::FnExtend;
use memflow::process::PID;
use memflow::types::{size, Address, PageType};
use super::process::Win32Process;
use crate::kernel::start_block::StartBlock;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::time::Duration;
pub(crate) type FFIMemory =
CachedMemoryAccess<'static, Box<dyn CloneablePhysicalMemory>, TimedCacheValidator>;
pub(crate) type FFIVirtualTranslate = CachedVirtualTranslate<DirectTranslate, TimedCacheValidator>;
pub(crate) type FFIVirtualMemory =
VirtualDMA<FFIMemory, FFIVirtualTranslate, Win32VirtualTranslate>;
pub type Kernel = kernel::Kernel<FFIMemory, FFIVirtualTranslate>;
/// Build a cloneable kernel object with default caching parameters
///
/// This function will take ownership of the input `mem` object.
///
/// # Safety
///
/// `mem` must be a heap allocated memory reference, created by one of the API's functions.
/// Reference to it becomes invalid.
#[no_mangle]
pub unsafe extern "C" fn kernel_build(
mem: &'static mut CloneablePhysicalMemoryObj,
) -> Option<&'static mut Kernel> {
let mem: Box<dyn CloneablePhysicalMemory> = Box::from_raw(*Box::from_raw(mem));
kernel::Kernel::builder(mem)
.build_default_caches()
.build()
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Build a cloneable kernel object with custom caching parameters
///
/// This function will take ownership of the input `mem` object.
///
/// vat_cache_entries must be positive, or the program will panic upon memory reads or writes.
///
/// # Safety
///
/// `mem` must be a heap allocated memory reference, created by one of the API's functions.
/// Reference to it becomes invalid.
#[no_mangle]
pub unsafe extern "C" fn kernel_build_custom(
mem: &'static mut CloneablePhysicalMemoryObj,
page_cache_time_ms: u64,
page_cache_flags: PageType,
page_cache_size_kb: usize,
vat_cache_time_ms: u64,
vat_cache_entries: usize,
) -> Option<&'static mut Kernel> {
let mem: Box<dyn CloneablePhysicalMemory> = Box::from_raw(*Box::from_raw(mem));
kernel::Kernel::builder(mem)
.build_page_cache(move |connector, arch| {
CachedMemoryAccess::builder(connector)
.arch(arch)
.validator(TimedCacheValidator::new(
Duration::from_millis(page_cache_time_ms).into(),
))
.page_type_mask(page_cache_flags)
.cache_size(size::kb(page_cache_size_kb))
.build()
.unwrap()
})
.build_vat_cache(move |vat, arch| {
CachedVirtualTranslate::builder(vat)
.arch(arch)
.validator(TimedCacheValidator::new(
Duration::from_millis(vat_cache_time_ms).into(),
))
.entries(vat_cache_entries)
.build()
.unwrap()
})
.build()
.map_err(inspect_err)
.ok()
.map(to_heap)
}
#[no_mangle]
pub extern "C" fn kernel_clone(kernel: &Kernel) -> &'static mut Kernel {
Box::leak(Box::new((*kernel).clone()))
}
/// Free a kernel object
///
/// This will free the input `kernel` object (including the underlying memory object)
///
/// # Safety
///
/// `kernel` must be a valid reference heap allocated by one of the above functions.
#[no_mangle]
pub unsafe extern "C" fn kernel_free(kernel: &'static mut Kernel) {
let _ = Box::from_raw(kernel);
}
/// Destroy a kernel object and return its underlying memory object
///
/// This will free the input `kernel` object, and return the underlying memory object. It will free
/// the object from any additional caching that `kernel` had in place.
///
/// # Safety
///
/// `kernel` must be a valid reference heap allocated by one of the above functions.
#[no_mangle]
pub unsafe extern "C" fn kernel_destroy(
kernel: &'static mut Kernel,
) -> &'static mut CloneablePhysicalMemoryObj {
let kernel = Box::from_raw(kernel);
Box::leak(Box::new(Box::leak(kernel.destroy().destroy())))
}
#[no_mangle]
pub extern "C" fn kernel_start_block(kernel: &Kernel) -> StartBlock {
kernel.kernel_info.start_block.into()
}
#[no_mangle]
pub extern "C" fn kernel_winver(kernel: &Kernel) -> Win32Version {
kernel.kernel_info.kernel_winver.mask_build_number()
}
#[no_mangle]
pub extern "C" fn kernel_winver_unmasked(kernel: &Kernel) -> Win32Version {
kernel.kernel_info.kernel_winver
}
/// Retrieve a list of peorcess addresses
///
/// # Safety
///
/// `buffer` must be a valid buffer of size at least `max_size`
#[no_mangle]
pub unsafe extern "C" fn kernel_eprocess_list(
kernel: &'static mut Kernel,
buffer: *mut Address,
max_size: usize,
) -> usize {
let mut ret = 0;
let buffer = std::slice::from_raw_parts_mut(buffer, max_size);
let mut extend_fn = FnExtend::new(|addr| {
if ret < max_size {
buffer[ret] = addr;
ret += 1;
}
});
kernel
.eprocess_list_extend(&mut extend_fn)
.map_err(inspect_err)
.ok()
.map(|_| ret)
.unwrap_or_default()
}
/// Retrieve a list of processes
///
/// This will fill `buffer` with a list of win32 process information. These processes will need to be
/// individually freed with `process_info_free`
///
/// # Safety
///
/// `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`.
#[no_mangle]
pub unsafe extern "C" fn kernel_process_info_list(
kernel: &'static mut Kernel,
buffer: *mut &'static mut Win32ProcessInfo,
max_size: usize,
) -> usize {
let mut ret = 0;
let buffer = std::slice::from_raw_parts_mut(buffer, max_size);
let mut extend_fn = FnExtend::new(|info| {
if ret < max_size {
buffer[ret] = Box::leak(Box::new(info));
ret += 1;
}
});
kernel
.process_info_list_extend(&mut extend_fn)
.map_err(inspect_err)
.ok()
.map(|_| ret)
.unwrap_or_default()
}
// Process info
#[no_mangle]
pub extern "C" fn kernel_kernel_process_info(
kernel: &'static mut Kernel,
) -> Option<&'static mut Win32ProcessInfo> {
kernel
.kernel_process_info()
.map_err(inspect_err)
.ok()
.map(to_heap)
}
#[no_mangle]
pub extern "C" fn kernel_process_info_from_eprocess(
kernel: &'static mut Kernel,
eprocess: Address,
) -> Option<&'static mut Win32ProcessInfo> {
kernel
.process_info_from_eprocess(eprocess)
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Retrieve process information by name
///
/// # Safety
///
/// `name` must be a valid null terminated string
#[no_mangle]
pub unsafe extern "C" fn kernel_process_info(
kernel: &'static mut Kernel,
name: *const c_char,
) -> Option<&'static mut Win32ProcessInfo> {
let name = CStr::from_ptr(name).to_string_lossy();
kernel
.process_info(&name)
.map_err(inspect_err)
.ok()
.map(to_heap)
}
#[no_mangle]
pub extern "C" fn kernel_process_info_pid(
kernel: &'static mut Kernel,
pid: PID,
) -> Option<&'static mut Win32ProcessInfo> {
kernel
.process_info_pid(pid)
.map_err(inspect_err)
.ok()
.map(to_heap)
}
// Process conversion
/// Create a process by looking up its name
///
/// This will consume `kernel` and free it later on.
///
/// # Safety
///
/// `name` must be a valid null terminated string
///
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
/// invalid.
#[no_mangle]
pub unsafe extern "C" fn kernel_into_process(
kernel: &'static mut Kernel,
name: *const c_char,
) -> Option<&'static mut Win32Process> {
let kernel = Box::from_raw(kernel);
let name = CStr::from_ptr(name).to_string_lossy();
kernel
.into_process(&name)
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Create a process by looking up its PID
///
/// This will consume `kernel` and free it later on.
///
/// # Safety
///
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
/// invalid.
#[no_mangle]
pub unsafe extern "C" fn kernel_into_process_pid(
kernel: &'static mut Kernel,
pid: PID,
) -> Option<&'static mut Win32Process> {
let kernel = Box::from_raw(kernel);
kernel
.into_process_pid(pid)
.map_err(inspect_err)
.ok()
.map(to_heap)
}
/// Create a kernel process insatance
///
/// This will consume `kernel` and free it later on.
///
/// # Safety
///
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
/// invalid.
#[no_mangle]
pub unsafe extern "C" fn kernel_into_kernel_process(
kernel: &'static mut Kernel,
) -> Option<&'static mut Win32Process> {
let kernel = Box::from_raw(kernel);
kernel
.into_kernel_process()
.map_err(inspect_err)
.ok()
.map(to_heap)
}

@ -1,4 +0,0 @@
pub mod kernel;
pub mod module;
pub mod process;
pub mod process_info;

@ -1,24 +0,0 @@
use memflow_ffi::process::OsProcessModuleInfoObj;
use memflow_ffi::util::to_heap;
use memflow_win32::win32::Win32ModuleInfo;
#[no_mangle]
pub extern "C" fn module_info_trait(
info: &'static mut Win32ModuleInfo,
) -> &'static mut OsProcessModuleInfoObj {
to_heap(info)
}
/// Free a win32 module info instance.
///
/// Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed
/// manually.
///
/// # Safety
///
/// `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the
/// reference will become invalid.
#[no_mangle]
pub unsafe extern "C" fn module_info_free(info: &'static mut Win32ModuleInfo) {
let _ = Box::from_raw(info);
}

@ -1,136 +0,0 @@
use super::kernel::{FFIVirtualMemory, Kernel};
use memflow::iter::FnExtend;
use memflow_ffi::mem::virt_mem::VirtualMemoryObj;
use memflow_ffi::util::*;
use memflow_win32::win32::{self, Win32ModuleInfo, Win32ProcessInfo};
use std::ffi::CStr;
use std::os::raw::c_char;
pub type Win32Process = win32::Win32Process<FFIVirtualMemory>;
/// Create a process with kernel and process info
///
/// # Safety
///
/// `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function
/// call, the reference becomes invalid.
#[no_mangle]
pub unsafe extern "C" fn process_with_kernel(
kernel: &'static mut Kernel,
proc_info: &Win32ProcessInfo,
) -> &'static mut Win32Process {
let kernel = Box::from_raw(kernel);
to_heap(Win32Process::with_kernel(*kernel, proc_info.clone()))
}
/// Retrieve refernce to the underlying virtual memory object
///
/// This will return a static reference to the virtual memory object. It will only be valid as long
/// as `process` if valid, and needs to be freed manually using `virt_free` regardless if the
/// process if freed or not.
#[no_mangle]
pub extern "C" fn process_virt_mem(
process: &'static mut Win32Process,
) -> &'static mut VirtualMemoryObj {
to_heap(&mut process.virt_mem)
}
#[no_mangle]
pub extern "C" fn process_clone(process: &Win32Process) -> &'static mut Win32Process {
to_heap((*process).clone())
}
/// Frees the `process`
///
/// # Safety
///
/// `process` must be a valid heap allocated reference to a `Win32Process` object. After the
/// function returns, the reference becomes invalid.
#[no_mangle]
pub unsafe extern "C" fn process_free(process: &'static mut Win32Process) {
let _ = Box::from_raw(process);
}
/// Retrieve a process module list
///
/// This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects.
///
/// These references then need to be freed with `module_info_free`
///
/// # Safety
///
/// `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`.
#[no_mangle]
pub unsafe extern "C" fn process_module_list(
process: &mut Win32Process,
out: *mut &'static mut Win32ModuleInfo,
max_len: usize,
) -> usize {
let mut ret = 0;
let buffer = std::slice::from_raw_parts_mut(out, max_len);
let mut extend_fn = FnExtend::new(|info| {
if ret < max_len {
buffer[ret] = to_heap(info);
ret += 1;
}
});
process
.module_list_extend(&mut extend_fn)
.map_err(inspect_err)
.ok()
.map(|_| ret)
.unwrap_or_default()
}
/// Retrieve the main module of the process
///
/// This function searches for a module with a base address
/// matching the section_base address from the ProcessInfo structure.
/// It then returns a reference to a newly allocated
/// `Win32ModuleInfo` object, if a module was found (null otherwise).
///
/// The reference later needs to be freed with `module_info_free`
///
/// # Safety
///
/// `process` must be a valid Win32Process pointer.
#[no_mangle]
pub unsafe extern "C" fn process_main_module_info(
process: &mut Win32Process,
) -> Option<&'static mut Win32ModuleInfo> {
process
.main_module_info()
.map(to_heap)
.map_err(inspect_err)
.ok()
}
/// Lookup a module
///
/// This will search for a module called `name`, and return a reference to a newly allocated
/// `Win32ModuleInfo` object, if a module was found (null otherwise).
///
/// The reference later needs to be freed with `module_info_free`
///
/// # Safety
///
/// `process` must be a valid Win32Process pointer.
/// `name` must be a valid null terminated string.
#[no_mangle]
pub unsafe extern "C" fn process_module_info(
process: &mut Win32Process,
name: *const c_char,
) -> Option<&'static mut Win32ModuleInfo> {
let name = CStr::from_ptr(name).to_string_lossy();
process
.module_info(&name)
.map(to_heap)
.map_err(inspect_err)
.ok()
}

@ -1,81 +0,0 @@
use memflow::types::Address;
use memflow_ffi::process::OsProcessInfoObj;
use memflow_ffi::util::to_heap;
use memflow_win32::win32::{Win32ModuleListInfo, Win32ProcessInfo};
#[no_mangle]
pub extern "C" fn process_info_trait(
info: &'static mut Win32ProcessInfo,
) -> &'static mut OsProcessInfoObj {
to_heap(info)
}
#[no_mangle]
pub extern "C" fn process_info_dtb(info: &Win32ProcessInfo) -> Address {
info.dtb
}
#[no_mangle]
pub extern "C" fn process_info_section_base(info: &Win32ProcessInfo) -> Address {
info.section_base
}
#[no_mangle]
pub extern "C" fn process_info_exit_status(info: &Win32ProcessInfo) -> i32 {
info.exit_status
}
#[no_mangle]
pub extern "C" fn process_info_ethread(info: &Win32ProcessInfo) -> Address {
info.ethread
}
#[no_mangle]
pub extern "C" fn process_info_wow64(info: &Win32ProcessInfo) -> Address {
info.wow64()
}
#[no_mangle]
pub extern "C" fn process_info_peb(info: &Win32ProcessInfo) -> Address {
info.peb()
}
#[no_mangle]
pub extern "C" fn process_info_peb_native(info: &Win32ProcessInfo) -> Address {
info.peb_native()
}
#[no_mangle]
pub extern "C" fn process_info_peb_wow64(info: &Win32ProcessInfo) -> Address {
info.peb_wow64().unwrap_or_default()
}
#[no_mangle]
pub extern "C" fn process_info_teb(info: &Win32ProcessInfo) -> Address {
info.teb.unwrap_or_default()
}
#[no_mangle]
pub extern "C" fn process_info_teb_wow64(info: &Win32ProcessInfo) -> Address {
info.teb_wow64.unwrap_or_default()
}
#[no_mangle]
pub extern "C" fn process_info_module_info(info: &Win32ProcessInfo) -> Win32ModuleListInfo {
info.module_info()
}
#[no_mangle]
pub extern "C" fn process_info_module_info_native(info: &Win32ProcessInfo) -> Win32ModuleListInfo {
info.module_info_native()
}
/// Free a process information reference
///
/// # Safety
///
/// `info` must be a valid heap allocated reference to a Win32ProcessInfo structure
#[no_mangle]
pub unsafe extern "C" fn process_info_free(info: &'static mut Win32ProcessInfo) {
let _ = Box::from_raw(info);
}

@ -0,0 +1,43 @@
name: Binary build
on:
push:
branch:
- 'main'
- 'stable'
env:
CARGO_TERM_COLOR: always
jobs:
cross-build:
name: Publish binary builds
runs-on: ubuntu-latest
strategy:
matrix:
target: ["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf", "x86_64-pc-windows-gnu"]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
- name: Download renamer
run: curl -sSf https://raw.githubusercontent.com/memflow/memflowup/master/target_rename.sh > target_rename.sh
- name: Build artifacts
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --all-features --workspace --verbose --target ${{ matrix.target }}
- name: Rename and collect artifacts
id: artifacts
run: echo "::set-output name=artifact::$(sh ./target_rename.sh "${{ matrix.target }}" | head -n 1)"
- name: Upload build artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: bin-${{ github.ref_name }}
files: |
${{ steps.artifacts.outputs.artifact }}

@ -0,0 +1,115 @@
name: Build and test
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
flags: [--all-features, --no-default-features]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
- name: Build
run: cargo build --workspace ${{ matrix.flags }} --verbose
- name: Build examples
run: cargo build --workspace ${{ matrix.flags }} --examples --verbose
build-cross-targets:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [aarch64-unknown-linux-gnu, armv7-unknown-linux-gnueabihf, i686-unknown-linux-gnu]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
target: ${{ matrix.target }}
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --target ${{ matrix.target }} --workspace --all-features --verbose
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Install rust 1.70.0
uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
- name: Pre-build binaries (for inventory integration tests)
run: cargo build --workspace --all-features --verbose
- name: Run all tests
run: cargo test --workspace --all-features --verbose
if: runner.os == 'Linux'
- name: Run all tests
run: cargo test --workspace --exclude memflow-derive --all-features --verbose
if: runner.os != 'Linux'
test-cross:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly # currently required due to -Zdoctest-xcompile
target: ${{ matrix.target }}
override: true
- name: Pre-build binaries (for inventory integration tests)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --target ${{ matrix.target }} --workspace --all-features --verbose --release
- name: Run all tests
uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: -Zdoctest-xcompile --target ${{ matrix.target }} --workspace --all-features --verbose --release
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.70.0
override: true
components: rustfmt, clippy
- run: rustup component add clippy
- name: Check formatting
run: cargo fmt -- --check
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-targets --all-features --workspace -- -D clippy::all

@ -0,0 +1,10 @@
/target
**/*.rs.bk
*.swp
*.so
*.dll
*.dylib
.vscode
*.so
.vagrant
TODO.md

@ -1,80 +1,18 @@
[package]
name = "memflow-win32"
version = "0.1.5"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "win32 integration of the memflow physical memory introspection framework"
documentation = "https://docs.rs/memflow-win32"
readme = "README.md"
homepage = "https://memflow.github.io"
repository = "https://github.com/memflow/memflow"
license-file = "../LICENSE"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "api-bindings", "memory-management", "os" ]
[profile.bench]
debug = true
[badges]
maintenance = { status = "actively-developed" }
codecov = { repository = "github", branch = "master", service = "github" }
[workspace]
resolver = "1"
[dependencies]
memflow = { version = "0.1", path = "../memflow", default-features = false }
log = { version = "0.4", default-features = false }
dataview = "0.1"
pelite = { version = "0.9", default-features = false }
widestring = { version = "0.4", default-features = false, features = ["alloc"] }
no-std-compat = { version = "0.4", features = ["alloc"] }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
members = [
"memflow-win32",
"memflow-win32-defs",
]
# will be replaced by our own signature scanner
regex = { version = "1", optional = true }
default-members = [
"memflow-win32",
"memflow-win32-defs",
]
# symbolstore
dirs = { version = "2.0", optional = true }
ureq = { version = "1.2", optional = true }
pdb = { version = "0.6", optional = true }
pbr = { version = "1.0", optional = true }
progress-streams = { version = "1.1", optional = true }
[dev_dependencies]
simple_logger = "1.0"
win_key_codes = "0.1"
rand = "0.7"
rand_xorshift = "0.2"
clap = "2.33"
toml = "0.5"
colored = "2.0"
[build_dependencies]
toml = "0.5"
dataview = "0.1"
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
[features]
default = ["std", "serde_derive", "embed_offsets", "symstore", "download_progress", "regex"]
std = ["no-std-compat/std", "memflow/std"]
embed_offsets = ["serde", "memflow/serde_derive"]
collections = []
alloc = []
serde_derive = ["serde", "memflow/serde_derive", "pelite/std", "pelite/serde"]
symstore = ["dirs", "ureq", "pdb"]
download_progress = ["pbr", "progress-streams"]
[[example]]
name = "dump_offsets"
path = "examples/dump_offsets.rs"
[[example]]
name = "generate_offsets"
path = "examples/generate_offsets.rs"
[[example]]
name = "read_keys"
path = "examples/read_keys.rs"
[[example]]
name = "multithreading"
path = "examples/multithreading.rs"
[[example]]
name = "read_bench"
path = "examples/read_bench.rs"
# [patch.crates-io]
# memflow = { path = "../memflow/memflow" }

@ -1,7 +1,7 @@
MIT License
Copyright (c) 2020 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020 Aurimas Blažulionis <0x60@pm.me>
Copyright (c) 2020-2022 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020-2022 Aurimas Blažulionis <0x60@pm.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,5 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

@ -7,38 +7,4 @@
This crate provides integration for win32 targets for [memflow](https://github.com/memflow/memflow). This library can be used in addition to the memflow core itself read processes, modules, drivers, etc.
Example initializing a win32 target:
```rust
use std::fs::File;
use std::io::Write;
use log::{error, Level};
use memflow::connector::*;
use memflow_win32::win32::{Kernel, Win32OffsetFile};
pub fn main() {
let connector_name = std::env::args().nth(1).unwrap();
let connector_args = std::env::args().nth(2).unwrap_or_default();
// create inventory + connector
let inventory = unsafe { ConnectorInventory::try_new() }.unwrap();
let connector = unsafe {
inventory.create_connector(
&connector_name,
&ConnectorArgs::parse(&connector_args).unwrap(),
)
}
.unwrap();
// initialize kernel
let kernel = Kernel::builder(connector)
.build_default_caches()
.build()
.unwrap();
println!("{:?}", kernel);
}
```
Additional examples can be found in the `examples` subdirectory.
Examples can be found in the `memflow-win32/examples` subdirectory.

@ -1,50 +0,0 @@
use dataview::Pod;
use std::{
env,
error::Error,
fs::{self, File},
io::{Read, Write},
path::Path,
};
#[path = "src/offsets/offset_table.rs"]
#[cfg(feature = "embed_offsets")]
mod offset_table;
#[cfg(feature = "embed_offsets")]
use offset_table::Win32OffsetFile;
#[cfg(feature = "embed_offsets")]
fn embed_offsets() -> Result<(), Box<dyn Error>> {
let out_dir = env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("win32_offsets.bin");
let mut all_the_files = File::create(&dest_path)?;
// iterate offsets folder
for f in fs::read_dir("./offsets")? {
let f = f?;
if !f.file_type()?.is_file() {
continue;
}
let mut file = File::open(f.path())?;
let mut tomlstr = String::new();
file.read_to_string(&mut tomlstr)?;
let offsets: Win32OffsetFile = toml::from_str(&tomlstr)?;
all_the_files.write_all(offsets.as_bytes())?;
}
Ok(())
}
#[cfg(not(feature = "embed_offsets"))]
fn embed_offsets() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
embed_offsets()?;
Ok(())
}

@ -1,110 +0,0 @@
use std::fs::File;
use std::io::Write;
use clap::*;
use log::{error, Level};
use memflow::connector::*;
use memflow_win32::prelude::{Kernel, Win32OffsetFile};
pub fn main() {
let matches = App::new("dump offsets example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("connector")
.long("connector")
.short("c")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("args")
.long("args")
.short("a")
.takes_value(true)
.default_value(""),
)
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.takes_value(true),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
// create inventory + connector
let inventory = unsafe { ConnectorInventory::scan() };
let connector = unsafe {
inventory.create_connector(
matches.value_of("connector").unwrap(),
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
)
}
.unwrap();
let kernel = Kernel::builder(connector)
.build_default_caches()
.build()
.unwrap();
let winver = kernel.kernel_info.kernel_winver;
if winver != (0, 0).into() {
let offsets = if let Some(guid) = &kernel.kernel_info.kernel_guid {
Win32OffsetFile {
pdb_file_name: guid.file_name.as_str().into(),
pdb_guid: guid.guid.as_str().into(),
arch: kernel.kernel_info.start_block.arch.into(),
nt_major_version: winver.major_version(),
nt_minor_version: winver.minor_version(),
nt_build_number: winver.build_number(),
offsets: kernel.offsets.into(),
}
} else {
Win32OffsetFile {
pdb_file_name: Default::default(),
pdb_guid: Default::default(),
arch: kernel.kernel_info.start_block.arch.into(),
nt_major_version: winver.major_version(),
nt_minor_version: winver.minor_version(),
nt_build_number: winver.build_number(),
offsets: kernel.offsets.into(),
}
};
// write offsets to file
let offsetstr = toml::to_string_pretty(&offsets).unwrap();
match matches.value_of("output") {
Some(output) => {
let mut file = File::create(output).unwrap();
file.write_all(offsetstr.as_bytes()).unwrap();
}
None => println!("{}", offsetstr),
}
} else {
error!("kernel version has to be valid in order to generate a offsets file");
}
}

@ -1,211 +0,0 @@
use memflow::connector::ConnectorInventory;
use memflow::connector::ConnectorArgs;
use memflow::mem::*;
use memflow_win32::error::{Error, Result};
use memflow_win32::win32::{Kernel, Win32ModuleInfo, Win32Process};
use clap::*;
use log::Level;
use colored::*;
static mut HAD_ERROR: bool = false;
fn main() -> Result<()> {
let (connector, args_str) = parse_args();
let args = ConnectorArgs::parse(&args_str)?;
// create inventory + connector
let inventory = unsafe { ConnectorInventory::scan() };
let connector = unsafe { inventory.create_connector(&connector, &args)? };
let mut kernel = build_kernel(connector)?;
{
println!("Kernel info:");
let info = &kernel.kernel_info;
let start_block = &info.start_block;
println!(
"{:#?} ... {}",
start_block,
some_str(&start_block.dtb.non_null())
);
println!(
"kernel_base: {:x} ... {}",
info.kernel_base,
some_str(&info.kernel_base.non_null())
);
println!(
"kernel_size: {:x} ... {}",
info.kernel_size,
bool_str(info.kernel_size != 0)
);
println!(
"kernel_guid: {:?} ... {}",
info.kernel_guid,
some_str(&info.kernel_guid)
);
println!(
"kernel_winver: {:?} ... {}",
info.kernel_winver.as_tuple(),
bool_str(info.kernel_winver != (0, 0).into())
);
println!(
"eprocess_base: {:x} ... {}",
info.eprocess_base,
some_str(&info.eprocess_base.non_null())
);
println!();
}
{
println!("Kernel Process:");
if let Ok(proc_info) = kernel.kernel_process_info() {
let mut kernel_proc = Win32Process::with_kernel_ref(&mut kernel, proc_info);
let modules = modules(&mut kernel_proc)?;
println!("checking module list:");
println!(
"ntoskrnl.exe ... {}",
some_str(
&modules
.iter()
.find(|e| e.name.to_lowercase() == "ntoskrnl.exe")
)
);
println!(
"hal.dll ... {}",
some_str(&modules.iter().find(|e| e.name.to_lowercase() == "hal.dll"))
);
} else {
println!("{}", bool_str(false));
}
println!();
}
{
println!("Process List:");
let proc_list = kernel.process_info_list()?;
let lsass = proc_list
.iter()
.find(|p| p.name.to_lowercase() == "lsass.exe");
println!("lsass.exe ... {}", some_str(&lsass));
println!();
if let Some(proc) = lsass {
println!("{} info:", proc.name);
println!("pid: {} ... {}", proc.pid, bool_str(proc.pid < 10000));
println!("dtb: {} ... {}", proc.dtb, some_str(&proc.dtb.non_null()));
println!(
"section_base: {} ... {}",
proc.section_base,
some_str(&proc.section_base.non_null())
);
println!(
"ethread: {} ... {}",
proc.ethread,
some_str(&proc.ethread.non_null())
);
println!("teb: {:?} ... {}", proc.teb, bool_str(proc.teb.is_none()));
println!(
"teb_wow64: {:?} ... {}",
proc.teb_wow64,
bool_str(proc.teb_wow64.is_none())
);
println!(
"peb_native: {} ... {}",
proc.peb_native,
some_str(&proc.peb_native.non_null())
);
println!(
"peb_wow64: {:?} ... {}",
proc.teb_wow64,
bool_str(proc.peb_wow64.is_none())
);
}
}
unsafe {
if HAD_ERROR {
Err(Error::Other(
"Some errors encountered, not all functionality may be present!",
))
} else {
Ok(())
}
}
}
fn some_str<T>(r: &Option<T>) -> ColoredString {
bool_str(r.is_some())
}
fn ok_str<T>(r: &Result<T>) -> ColoredString {
bool_str(r.is_ok())
}
fn bool_str(b: bool) -> ColoredString {
if b {
"ok".green()
} else {
unsafe { HAD_ERROR = true };
"error".red()
}
}
fn modules<V: VirtualMemory>(process: &mut Win32Process<V>) -> Result<Vec<Win32ModuleInfo>> {
let modules = process.module_list();
println!("modules ... {}", ok_str(&modules));
modules
}
fn build_kernel<T: PhysicalMemory>(
mem: T,
) -> Result<Kernel<impl PhysicalMemory, impl VirtualTranslate>> {
let kernel = Kernel::builder(mem).build_default_caches().build();
println!("Kernel::build ... {}", ok_str(&kernel));
println!();
kernel
}
fn parse_args() -> (String, String) {
let matches = App::new("read_keys example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("connector")
.long("connector")
.short("c")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("args")
.long("args")
.short("a")
.takes_value(true)
.default_value(""),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
(
matches.value_of("connector").unwrap().into(),
matches.value_of("args").unwrap().into(),
)
}

@ -1,136 +0,0 @@
use std::thread;
use clap::*;
use log::{info, Level};
use memflow::connector::*;
use memflow::mem::*;
use memflow_win32::win32::Kernel;
pub fn parallel_init<T: PhysicalMemory + Clone + 'static>(connector: T) {
(0..8)
.map(|_| connector.clone())
.into_iter()
.map(|c| {
thread::spawn(move || {
Kernel::builder(c)
.no_symbol_store()
.build_default_caches()
.build()
.unwrap();
})
})
.for_each(|t| t.join().unwrap());
}
pub fn parallel_kernels<T: PhysicalMemory + Clone + 'static>(connector: T) {
let kernel = Kernel::builder(connector).build().unwrap();
(0..8)
.map(|_| kernel.clone())
.into_iter()
.map(|mut k| {
thread::spawn(move || {
let _eprocesses = k.eprocess_list().unwrap();
})
})
.for_each(|t| t.join().unwrap());
}
pub fn parallel_kernels_cached<T: PhysicalMemory + Clone + 'static>(connector: T) {
let kernel = Kernel::builder(connector)
.build_default_caches()
.build()
.unwrap();
(0..8)
.map(|_| kernel.clone())
.into_iter()
.map(|mut k| {
thread::spawn(move || {
let eprocesses = k.eprocess_list().unwrap();
info!("eprocesses list fetched: {}", eprocesses.len());
})
})
.for_each(|t| t.join().unwrap());
}
pub fn parallel_processes<T: PhysicalMemory + Clone + 'static>(connector: T) {
let kernel = Kernel::builder(connector)
.build_default_caches()
.build()
.unwrap();
let process = kernel.into_process("wininit.exe").unwrap();
(0..8)
.map(|_| process.clone())
.into_iter()
.map(|mut p| {
thread::spawn(move || {
let module_list = p.module_list().unwrap();
info!("wininit.exe module_list: {}", module_list.len());
})
})
.for_each(|t| t.join().unwrap());
}
pub fn main() {
let matches = App::new("read_keys example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("connector")
.long("connector")
.short("c")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("args")
.long("args")
.short("a")
.takes_value(true)
.default_value(""),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
// create inventory + connector
let inventory = unsafe { ConnectorInventory::scan() };
let connector = unsafe {
inventory.create_connector(
matches.value_of("connector").unwrap(),
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
)
}
.unwrap();
println!("test");
// parallel test functions
// see each function's implementation for further details
parallel_init(connector.clone());
parallel_kernels(connector.clone());
parallel_kernels_cached(connector.clone());
parallel_processes(connector);
}

@ -1,216 +0,0 @@
use std::io::Write;
use std::time::{Duration, Instant};
use clap::*;
use log::Level;
use memflow::connector::*;
use memflow::mem::*;
use memflow::process::*;
use memflow::types::*;
use memflow_win32::error::Result;
use memflow_win32::offsets::Win32Offsets;
use memflow_win32::win32::{Kernel, KernelInfo, Win32ModuleInfo, Win32Process};
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn rwtest<T: VirtualMemory>(
proc: &mut Win32Process<T>,
module: &dyn OsProcessModuleInfo,
chunk_sizes: &[usize],
chunk_counts: &[usize],
read_size: usize,
) {
let mut rng = CurRng::seed_from_u64(0);
println!("Performance bench:");
print!("{:#7}", "SIZE");
for i in chunk_counts {
print!(", x{:02x} mb/s, x{:02x} calls/s", *i, *i);
}
println!();
let start = Instant::now();
let mut ttdur = Duration::new(0, 0);
for i in chunk_sizes {
print!("0x{:05x}", *i);
for o in chunk_counts {
let mut done_size = 0_usize;
let mut total_dur = Duration::new(0, 0);
let mut calls = 0;
let mut bufs = vec![(vec![0 as u8; *i], 0); *o];
let base_addr = rng.gen_range(
module.base().as_u64(),
module.base().as_u64() + module.size() as u64,
);
while done_size < read_size {
for (_, addr) in bufs.iter_mut() {
*addr = base_addr + rng.gen_range(0, 0x2000);
}
let now = Instant::now();
{
let mut batcher = proc.virt_mem.virt_batcher();
for (buf, addr) in bufs.iter_mut() {
batcher.read_raw_into(Address::from(*addr), buf);
}
}
total_dur += now.elapsed();
done_size += *i * *o;
calls += 1;
}
ttdur += total_dur;
let total_time = total_dur.as_secs_f64();
print!(
", {:8.2}, {:11.2}",
(done_size / 0x0010_0000) as f64 / total_time,
calls as f64 / total_time
);
std::io::stdout().flush().expect("");
}
println!();
}
let total_dur = start.elapsed();
println!(
"Total bench time: {:.2} {:.2}",
total_dur.as_secs_f64(),
ttdur.as_secs_f64()
);
}
fn read_bench<T: PhysicalMemory + ?Sized, V: VirtualTranslate>(
phys_mem: &mut T,
vat: &mut V,
kernel_info: KernelInfo,
) -> Result<()> {
let offsets = Win32Offsets::builder().kernel_info(&kernel_info).build()?;
let mut kernel = Kernel::new(phys_mem, vat, offsets, kernel_info);
let proc_list = kernel.process_info_list()?;
let mut rng = CurRng::seed_from_u64(rand::thread_rng().gen_range(0, !0u64));
loop {
let mut prc = Win32Process::with_kernel_ref(
&mut kernel,
proc_list[rng.gen_range(0, proc_list.len())].clone(),
);
let mod_list: Vec<Win32ModuleInfo> = prc
.module_list()?
.into_iter()
.filter(|module| module.size() > 0x1000)
.collect();
if !mod_list.is_empty() {
let tmod = &mod_list[rng.gen_range(0, mod_list.len())];
println!(
"Found test module {} ({:x}) in {}",
tmod.name(),
tmod.size(),
prc.proc_info.name(),
);
let mem_map = prc.virt_mem.virt_page_map(size::gb(1));
println!("Memory map (with up to 1GB gaps):");
for (addr, len) in mem_map {
println!("{:x}-{:x}", addr, addr + len);
}
rwtest(
&mut prc,
tmod,
&[0x10000, 0x1000, 0x100, 0x10, 0x8],
&[32, 8, 1],
0x0010_0000 * 32,
);
break;
}
}
Ok(())
}
fn main() -> Result<()> {
let matches = App::new("read_keys example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("connector")
.long("connector")
.short("c")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("args")
.long("args")
.short("a")
.takes_value(true)
.default_value(""),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
// create inventory + connector
let inventory = unsafe { ConnectorInventory::scan() };
let mut connector = unsafe {
inventory.create_connector(
matches.value_of("connector").unwrap(),
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
)
}
.unwrap();
// scan for win32 kernel
let kernel_info = KernelInfo::scanner(&mut connector).scan()?;
let mut vat = DirectTranslate::new();
println!("Benchmarking uncached reads:");
read_bench(&mut connector, &mut vat, kernel_info.clone()).unwrap();
println!();
println!("Benchmarking cached reads:");
let mut mem_cached = CachedMemoryAccess::builder(&mut connector)
.arch(kernel_info.start_block.arch)
.build()
.unwrap();
let mut vat_cached = CachedVirtualTranslate::builder(vat)
.arch(kernel_info.start_block.arch)
.build()
.unwrap();
read_bench(&mut mem_cached, &mut vat_cached, kernel_info).unwrap();
println!("TLB Hits {}\nTLB Miss {}", vat_cached.hitc, vat_cached.misc);
Ok(())
}

@ -1,69 +0,0 @@
use std::{thread, time};
use clap::*;
use log::Level;
use memflow::connector::*;
use memflow_win32::win32::{Kernel, Keyboard};
pub fn main() {
let matches = App::new("read_keys example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("connector")
.long("connector")
.short("c")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("args")
.long("args")
.short("a")
.takes_value(true)
.default_value(""),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
// create inventory + connector
let inventory = unsafe { ConnectorInventory::scan() };
let connector = unsafe {
inventory.create_connector(
matches.value_of("connector").unwrap(),
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
)
}
.unwrap();
// creating the kernel object
let mut kernel = Kernel::builder(connector)
.build_default_caches()
.build()
.unwrap();
// fetch keyboard state
let kbd = Keyboard::try_with(&mut kernel).unwrap();
loop {
let kbs = kbd.state_with_kernel(&mut kernel).unwrap();
println!("space down: {:?}", kbs.is_down(win_key_codes::VK_SPACE));
thread::sleep(time::Duration::from_millis(1000));
}
}

@ -0,0 +1,19 @@
#!/bin/bash
cargo build --release --all-features
# install connector to system dir
if [ ! -z "$1" ] && [ $1 = "--system" ]; then
echo "installing connector system-wide in /usr/local/lib/memflow"
if [[ ! -d /usr/local/lib/memflow ]]; then
sudo mkdir /usr/local/lib/memflow
fi
sudo cp target/release/libmemflow_win32.so /usr/local/lib/memflow/libmemflow_win32.7.so
fi
# install connector in user dir
echo "installing connector for user in ~/.local/lib/memflow"
if [[ ! -d ~/.local/lib/memflow ]]; then
mkdir -p ~/.local/lib/memflow
fi
cp target/release/libmemflow_win32.so ~/.local/lib/memflow/libmemflow_win32.7.so

@ -0,0 +1,42 @@
[package]
name = "memflow-win32-defs"
version = "0.2.0"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "static offset templates for "
documentation = "https://docs.rs/memflow-win32-defs"
readme = "../README.md"
homepage = "https://memflow.io"
repository = "https://github.com/memflow/memflow-win32"
license = "MIT"
keywords = [ "memflow", "introspection", "memory", "dma" ]
categories = [ "api-bindings", "memory-management", "os" ]
[dependencies]
memflow = { version = "0.2", default-features = false }
log = { version = "^0.4.14", default-features = false }
no-std-compat = { version = "^0.4.1", features = ["alloc"] }
serde = { version = "^1.0.133", default-features = false, optional = true, features = ["derive"] }
# symbolstore
dirs = { version = "^5.0.0", optional = true }
ureq = { version = "^2.4.0", optional = true }
pdb = { version = "^0.8.0", optional = true }
indicatif = { version = "^0.17.2", optional = true }
progress-streams = { version = "^1.1.0", optional = true }
[dev_dependencies]
simplelog = "^0.12.0"
clap = { version = "^4.0.26", features = ["cargo"] }
toml = "^0.8.8"
[features]
default = ["symstore", "download_progress"]
std = ["no-std-compat/std"]
symstore = ["dirs", "ureq", "pdb", "std"]
download_progress = ["indicatif", "progress-streams"]
[[example]]
name = "generate_offsets"
path = "examples/generate_offsets.rs"
required-features = ["symstore", "serde"]

@ -4,26 +4,22 @@ use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use memflow_win32::prelude::{
SymbolStore, Win32GUID, Win32OffsetFile, Win32Offsets, Win32OffsetsArchitecture, Win32Version,
};
use memflow_win32_defs::{kernel::*, offsets::*};
pub fn main() {
let matches = App::new("generate offsets example")
let matches = Command::new("generate offsets example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(Arg::new("verbose").short('v').action(ArgAction::Count))
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.takes_value(true)
Arg::new("output")
.short('o')
.action(ArgAction::Set)
.required(true),
)
.get_matches();
// set log level
let level = match matches.occurrences_of("verbose") {
let log_level = match matches.get_count("verbose") {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
@ -31,51 +27,69 @@ pub fn main() {
4 => Level::Trace,
_ => Level::Trace,
};
simple_logger::SimpleLogger::new()
.with_level(level.to_level_filter())
.init()
.unwrap();
simplelog::TermLogger::init(
log_level.to_level_filter(),
simplelog::Config::default(),
simplelog::TerminalMode::Stdout,
simplelog::ColorChoice::Auto,
)
.unwrap();
let win_ids = vec![
/*
(
Win32Version::new(5, 2, 3790),
Win32GUID::new("ntkrnlmp.pdb", "82DCF67A38274C9CA99B60B421D2786D2"),
Win32Guid::new("ntkrnlmp.pdb", "82DCF67A38274C9CA99B60B421D2786D2"),
),
*/
(
Win32Version::new(6, 1, 7601),
Win32OffsetsArchitecture::X86,
Win32GUID::new("ntkrpamp.pdb", "684DA42A30CC450F81C535B4D18944B12"),
Win32Guid::new("ntkrpamp.pdb", "684DA42A30CC450F81C535B4D18944B12"),
),
(
Win32Version::new(6, 1, 7601),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "ECE191A20CFF4465AE46DF96C22638451"),
Win32Guid::new("ntkrnlmp.pdb", "ECE191A20CFF4465AE46DF96C22638451"),
),
(
Win32Version::new(10, 0, 18362),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "0AFB69F5FD264D54673570E37B38A3181"),
Win32Guid::new("ntkrnlmp.pdb", "0AFB69F5FD264D54673570E37B38A3181"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "BBED7C2955FBE4522AAA23F4B8677AD91"),
Win32Guid::new("ntkrnlmp.pdb", "BBED7C2955FBE4522AAA23F4B8677AD91"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "1C9875F76C8F0FBF3EB9A9D7C1C274061"),
Win32Guid::new("ntkrnlmp.pdb", "1C9875F76C8F0FBF3EB9A9D7C1C274061"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X64,
Win32Guid::new("ntkrnlmp.pdb", "9C00B19DBDE003DBFE4AB4216993C8431"),
),
(
Win32Version::new(10, 0, 19045),
Win32OffsetsArchitecture::X64,
Win32Guid::new("ntkrnlmp.pdb", "5F0CF5D532F385333A9B4ABA25CA65961"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X86,
Win32Guid::new("ntkrpamp.pdb", "1B1D6AA205E1C87DC63A314ACAA50B491"),
),
(
Win32Version::new(10, 0, 4026553840),
Win32OffsetsArchitecture::X86,
Win32GUID::new("ntkrpamp.pdb", "1B1D6AA205E1C87DC63A314ACAA50B491"),
Win32Guid::new("ntkrnlmp.pdb", "55678BC384F099B6ED05E9E39046924A1"),
),
];
let out_dir = matches.value_of("output").unwrap();
let out_dir = matches.get_one::<String>("output").unwrap();
create_dir_all(out_dir).unwrap();
for win_id in win_ids.into_iter() {
@ -85,14 +99,16 @@ pub fn main() {
.build()
{
let offset_file = Win32OffsetFile {
pdb_file_name: win_id.2.file_name.as_str().into(),
pdb_guid: win_id.2.guid.as_str().into(),
header: Win32OffsetHeader {
pdb_file_name: win_id.2.file_name.as_str().into(),
pdb_guid: win_id.2.guid.as_str().into(),
nt_major_version: win_id.0.major_version(),
nt_minor_version: win_id.0.minor_version(),
nt_build_number: win_id.0.build_number(),
nt_major_version: win_id.0.major_version(),
nt_minor_version: win_id.0.minor_version(),
nt_build_number: win_id.0.build_number(),
arch: win_id.1,
arch: win_id.1,
},
offsets: offsets.0,
};
@ -104,7 +120,7 @@ pub fn main() {
win_id.0.major_version(),
win_id.0.minor_version(),
win_id.0.build_number(),
win_id.1.to_string(),
win_id.1,
win_id.2.guid,
);

@ -1,22 +1,14 @@
pub mod ntos;
pub mod start_block;
pub mod sysproc;
use std::prelude::v1::*;
pub use start_block::StartBlock;
use std::cmp::{Ord, Ordering, PartialEq};
use std::fmt;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
pub struct Win32GUID {
pub struct Win32Guid {
pub file_name: String,
pub guid: String,
}
impl Win32GUID {
impl Win32Guid {
pub fn new(file_name: &str, guid: &str) -> Self {
Self {
file_name: file_name.to_string(),

@ -0,0 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate no_std_compat as std;
pub mod kernel;
pub mod offsets;

@ -6,31 +6,22 @@ use super::symstore::SymbolStore;
use super::offset_table::Win32OffsetFile;
use super::{Win32Offsets, Win32OffsetsArchitecture};
use crate::error::{Error, Result};
use crate::kernel::{Win32GUID, Win32Version};
use crate::win32::KernelInfo;
#[repr(align(16))]
struct Align16<T>(pub T);
#[cfg(feature = "embed_offsets")]
const WIN32_OFFSETS: Align16<
[u8; include_bytes!(concat!(env!("OUT_DIR"), "/win32_offsets.bin")).len()],
> = Align16(*include_bytes!(concat!(
env!("OUT_DIR"),
"/win32_offsets.bin"
)));
pub struct Win32OffsetBuilder {
use crate::kernel::{Win32Guid, Win32Version};
use memflow::error::{Error, ErrorKind, ErrorOrigin, Result};
pub struct Win32OffsetBuilder<'a> {
#[cfg(feature = "symstore")]
symbol_store: Option<SymbolStore>,
guid: Option<Win32GUID>,
guid: Option<Win32Guid>,
winver: Option<Win32Version>,
arch: Option<Win32OffsetsArchitecture>,
offset_list: Option<&'a [Win32OffsetFile]>,
}
impl Default for Win32OffsetBuilder {
impl<'a> Default for Win32OffsetBuilder<'a> {
fn default() -> Self {
Self {
#[cfg(feature = "symstore")]
@ -39,20 +30,21 @@ impl Default for Win32OffsetBuilder {
guid: None,
winver: None,
arch: None,
offset_list: None,
}
}
}
impl Win32OffsetBuilder {
impl<'a> Win32OffsetBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn build(self) -> Result<Win32Offsets> {
if self.guid.is_none() && self.winver.is_none() {
return Err(Error::Other(
"building win32 offsets requires either a guid or winver",
));
return Err(Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("building win32 offsets requires either a guid or winver"));
}
// try to build via symbol store
@ -65,28 +57,25 @@ impl Win32OffsetBuilder {
return Ok(offs);
}
Err(Error::Other("not found"))
Err(Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("no valid offset configuration found while building win32"))
}
#[cfg(feature = "embed_offsets")]
fn build_with_offset_list(&self) -> Result<Win32Offsets> {
// # Safety
// Struct padding and alignment is compile-time guaranteed by the struct (see mod offset_table).
let offsets: [Win32OffsetFile;
WIN32_OFFSETS.0.len() / std::mem::size_of::<Win32OffsetFile>()] =
unsafe { std::mem::transmute(WIN32_OFFSETS.0) };
let offsets = self.offset_list.ok_or_else(|| {
Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("no offset list supplied")
})?;
// Try matching exact guid
if let Some(target_guid) = &self.guid {
for offset in offsets.iter() {
if let (Ok(file), Ok(guid)) = (
<&str>::try_from(&offset.pdb_file_name),
<&str>::try_from(&offset.pdb_guid),
<&str>::try_from(&offset.header.pdb_file_name),
<&str>::try_from(&offset.header.pdb_guid),
) {
if target_guid.file_name == file && target_guid.guid == guid {
return Ok(Win32Offsets {
0: offset.offsets.clone(),
});
return Ok(Win32Offsets(offset.offsets));
}
}
}
@ -98,16 +87,14 @@ impl Win32OffsetBuilder {
// Try matching the newest build from that version that is not actually newer
if let (Some(winver), Some(arch)) = (&self.winver, self.arch) {
for offset in offsets.iter() {
if winver.major_version() == offset.nt_major_version
&& winver.minor_version() == offset.nt_minor_version
&& winver.build_number() >= offset.nt_build_number
&& prev_build_number <= offset.nt_build_number
&& arch == offset.arch
if winver.major_version() == offset.header.nt_major_version
&& winver.minor_version() == offset.header.nt_minor_version
&& winver.build_number() >= offset.header.nt_build_number
&& prev_build_number <= offset.header.nt_build_number
&& arch == offset.header.arch
{
prev_build_number = offset.nt_build_number;
closest_match = Some(Win32Offsets {
0: offset.offsets.clone(),
});
prev_build_number = offset.header.nt_build_number;
closest_match = Some(Win32Offsets(offset.offsets));
}
}
@ -120,35 +107,34 @@ impl Win32OffsetBuilder {
}
}
closest_match.ok_or(Error::Other("not found"))
}
#[cfg(not(feature = "embed_offsets"))]
fn build_with_offset_list(&self) -> Result<Win32Offsets> {
Err(Error::Other(
"embed offsets feature is deactivated on compilation",
))
closest_match.ok_or_else(|| {
Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("no valid offset configuration found while building win32")
})
}
#[cfg(feature = "symstore")]
fn build_with_symbol_store(&self) -> Result<Win32Offsets> {
if let Some(store) = &self.symbol_store {
if self.guid.is_some() {
let pdb = store.load(self.guid.as_ref().unwrap())?;
if let Some(guid) = &self.guid {
let pdb = store.load(guid)?;
Win32Offsets::from_pdb_slice(&pdb[..])
} else {
Err(Error::Other("symbol store can only be used with a guid"))
Err(Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("symbol store can only be used with a guid"))
}
} else {
Err(Error::Other("symbol store is disabled"))
Err(Error(ErrorOrigin::OsLayer, ErrorKind::Configuration)
.log_error("symbol store is disabled"))
}
}
#[cfg(not(feature = "symstore"))]
fn build_with_symbol_store(&self) -> Result<Win32Offsets> {
Err(Error::Other(
"symbol store is deactivated via a compilation feature",
))
Err(
Error(ErrorOrigin::OsLayer, ErrorKind::UnsupportedOptionalFeature)
.log_error("symbol store is deactivated via a compilation feature"),
)
}
#[cfg(feature = "symstore")]
@ -163,31 +149,35 @@ impl Win32OffsetBuilder {
self
}
pub fn guid(mut self, guid: Win32GUID) -> Self {
pub fn offset_list(mut self, offset_list: &'a [Win32OffsetFile]) -> Self {
self.offset_list = Some(offset_list);
self
}
pub fn guid(mut self, guid: Win32Guid) -> Self {
self.guid = Some(guid);
self
}
pub fn get_guid(&self) -> &Option<Win32Guid> {
&self.guid
}
pub fn winver(mut self, winver: Win32Version) -> Self {
self.winver = Some(winver);
self
}
pub fn get_winver(&self) -> &Option<Win32Version> {
&self.winver
}
pub fn arch(mut self, arch: Win32OffsetsArchitecture) -> Self {
self.arch = Some(arch);
self
}
pub fn kernel_info(mut self, kernel_info: &KernelInfo) -> Self {
if self.guid.is_none() {
self.guid = kernel_info.kernel_guid.clone();
}
if self.winver.is_none() {
self.winver = Some(kernel_info.kernel_winver);
}
if self.arch.is_none() {
self.arch = Some(kernel_info.start_block.arch.into());
}
self
pub fn get_arch(&self) -> &Option<Win32OffsetsArchitecture> {
&self.arch
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save