初始化仓库

main
zhangxinyu 1 year ago
parent b8a765ff45
commit b9e67ab6a6

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/memflow_scan.iml" filepath="$PROJECT_DIR$/.idea/memflow_scan.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,8 @@
[package]
name = "memflow_scan"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,33 @@
# Release notes
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 0.1.5
- Added memflow::prelude::v1 and memflow_win32::prelude::v1 modules
- Added new fields to FFI
- Improved consistency of these function names in C FFI: `phys_read_raw` -> `phys_read_raw_into`, `page_size` -> `arch_page_size`.
- Added C++ bindings for the FFI
- Fixed core errors not displaying the full error message when wrapped in a win32 error
- Changed windows inventory search path from [user]/.local/lib/memflow to [user]/Documents/memflow
- Added {PWD} to inventory search path
Transitioning from C FFI to C++ FFI:
- `memflow.h`, and `memflow_win32.h` become `memflow_cpp.h`, and `memflow_win32_cpp.h`.
- The headers still depend on `memflow.h`, and `memflow_win32.h`. They are just wrappers for safety, and ergonomics.
- Types transition from `Type *` to `CType`. Every `CType` include automatic object destruction, so there is no need for the `type_free` methods.
- `CType` contains a `Type *` inside. The pointer can still be `null`. Checking whether object is valid is still the same: `if (CType != NULL)`
- Methods are implemented as class members. Most methods loose their prefix. The change looks like this: `process_module_info(Win32Process *process, const char *name)` becomes `CWin32Process::module_info(this, const char *name)`.
- Calling methods changes into calling a function on the object, instead of with the object. Example: `process_module_info(proc, "ntdll.dll")` becomes `proc.module_info("ntdll.dll")`.
- Exception to this are `virt`, and `phys` read/write functions. They do not loose their prefix, because they do have the prefix in the Rust library. So, `virt_read_u64(mem, addr)` becomes `mem.virt_read_u64(addr)`.
- There are extra convenience functions that utilize STL's `string`, and `vector` containers. Getting process/module names, and lists becomes much simpler.
## 0.1.4
- Removed namespaces in FFI headers and unused dependencies
- Fixed connector errors not being shown properly
- Added `main_module_info()` helper function which retrieves the main module of a process
- Added the DLL path to the Win32ModuleInfo structure
- Fixed duplicated connectors being added to the inventory multiple times
- Renamed and deprecated the `ConnectorInventory::try_new()` and `ConnectorInventory::with_path()` functions. The new function names are `ConnectorInventory::scan()` and `ConnectorInventory::scan_path()`
- Added a `available_connectors()` function to the ConnectorInventory which returns all connectors that have been found on the system.
- Added a fallback signature for windows 10 for the win32 keyboard implementation in case the PE Header of the win32kbase.sys is paged out
- Added a `MemoryMap::open()` function to load a memory map in TOML format

@ -0,0 +1,28 @@
# Contributing
There is a feature missing? A bug you have noticed? Some inconsistencies? **Contributions are welcome, and are encouraged!**
## Guidelines
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.
- 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`
## Review
Once you submit a pull request, one of the maintainers will have a look at it, and give you some feedback. If everything looks alright, we will be almost ready to merge it in! If not, the maintainer will point you to the right direction where things may need changing in the code.
## Merging
Once the code is ready, the last step is merging. There are only 2 important things that you need to confirm:
- That the code is yours
- And that you agree with the project's license terms to be applied to the entire pull request.
By default, we will go by the assumption that those 2 points are true, but it would still be nice that you confirmed those. And sometimes, we may ask you to do so, just to be sure.
Ultimately, unless you state otherwise, the merged code will be licensed under the current license of the project.
Anyways, thanks for giving this a read, and happy hacking!

@ -0,0 +1,24 @@
[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"
]

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
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.

@ -0,0 +1,146 @@
# memflow
[![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)
## physical memory introspection framework
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.
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.
In the repository, you can find various examples available (which use the memflow-win32 layer)
## Building from source
To build all projects in the memflow workspace:
`cargo build --release --workspace`
To build all examples:
`cargo build --release --workspace --examples`
Run all tests:
`cargo test --workspace`
Execute the benchmarks:
`cargo bench`
## Documentation
Extensive code documentation can be found at [docs.rs](https://docs.rs/memflow/0.1/).
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).
If you decide to build the latest documentation you can do it by issuing:
`cargo doc --workspace --no-deps --open`
## Basic usage
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.
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.
- `RUST_SETPTRACE` will enable PTRACE permissions on the resulting binary before executing it.
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 inventory system.
You will have to install at least one `connector` to use the examples.
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.
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
```
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.
Now you can just run the examples by providing the appropriate connector name:
Run memflow\_win32/read\_keys example with a procfs connector:
`RUST_SETPTRACE=1 cargo run --example read_keys -- -vv -c qemu_procfs -a [vmname]`
Run memflow\_win32/read\_bench example with a coredump connector:
`cargo run --example read_bench --release -- -vv -c coredump -a coredump_win10_64bit.raw`
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.
## Compilation support
| 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: |
| 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.
## Connectors
All examples provided in this repository are using the 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)
- [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.
## Road map / Future Development
- Provide a rust native connector for PCILeech based hardware
- Provide a UEFI Demo
- Linux target support
## Acknowledgements
- [CasualX](https://github.com/casualx/) for his wonderful pelite crate
- [ufrisk](https://github.com/ufrisk/) for his prior work on the subject and many inspirations
## Contributing
Please check [CONTRIBUTE.md](CONTRIBUTE.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1,14 @@
load kernel syms:
.sympath srv*https://msdl.microsoft.com/download/symbols
.reload /f
get eprocess of a proc:
!process 0 0
or
!process calc.exe
then
dt nt!_EPROCESS <address>
vtop:
!vtop PFN VirtualAddress
!vtop 0 VirtualAddress

@ -0,0 +1,42 @@
[package]
name = "memflow-bench"
version = "0.1.5"
authors = ["Aurimas Blažulionis <0x60@pm.me>"]
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"
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"
# This branch provides throughput plots
criterion = { git = "https://github.com/h33p/criterion.rs.git", branch = "tput" }
memflow-win32 = { path = "../memflow-win32" }
[dev-dependencies]
memflow = { path = "../memflow", features = ["dummy_mem"] }
memflow-win32 = { path = "../memflow-win32" }
[features]
default = []
[[bench]]
name = "read_dummy"
harness = false
#[[bench]]
#name = "read_win32"
#harness = false
[[bench]]
name = "batcher"
harness = false

@ -0,0 +1,15 @@
# memflow-bench
[![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 bench crate contains benchmarks for the [memflow](https://github.com/memflow/memflow) library by utiziling the [criterion.rs](https://github.com/bheisler/criterion.rs) framework.
You can run the benchmarks by executing `cargo bench` in the memflow workspace root.
Current benchmarks contain:
- physical reads
- virtual address translations
- virtual reads

@ -0,0 +1,168 @@
use criterion::*;
use memflow::prelude::v1::*;
//use memflow::mem::dummy::DummyMemory as Memory;
struct NullMem {}
impl NullMem {
pub fn new(_: usize) -> Self {
Self {}
}
}
impl PhysicalMemory for NullMem {
fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
black_box(data.iter_mut().count());
Ok(())
}
fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
black_box(data.iter().count());
Ok(())
}
fn metadata(&self) -> PhysicalMemoryMetadata {
PhysicalMemoryMetadata {
size: 0,
readonly: true,
}
}
}
use NullMem as Memory;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
static mut TSLICE: [[u8; 16]; 0x10000] = [[0; 16]; 0x10000];
fn read_test_nobatcher<T: PhysicalMemory>(
chunk_size: usize,
mem: &mut T,
mut rng: CurRng,
size: usize,
tbuf: &mut [PhysicalReadData],
) {
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();
}
let _ = black_box(mem.phys_read_raw_list(&mut tbuf[..chunk_size]));
}
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));
let mut batcher = mem.phys_batcher();
batcher.read_prealloc(chunk_size);
for i in unsafe { TSLICE.iter_mut().take(chunk_size) } {
batcher.read_into((base_addr + rng.gen_range(0, 0x2000)).into(), i);
}
let _ = black_box(batcher.commit_rw());
}
fn read_test_with_ctx<T: PhysicalMemory>(
bench: &mut Bencher,
chunk_size: usize,
use_batcher: bool,
mem: &mut T,
) {
let rng = CurRng::from_rng(thread_rng()).unwrap();
let mem_size = size::mb(64);
let mut tbuf = vec![];
tbuf.extend(
unsafe { TSLICE }
.iter_mut()
.map(|arr| {
PhysicalReadData(PhysicalAddress::INVALID, unsafe {
std::mem::transmute(&mut arr[..])
})
})
.take(chunk_size),
);
if !use_batcher {
bench.iter(move || {
read_test_nobatcher(chunk_size, mem, rng.clone(), mem_size, &mut tbuf[..])
});
} else {
bench.iter(|| read_test_batcher(chunk_size, mem, rng.clone(), mem_size));
}
}
fn chunk_read_params<T: PhysicalMemory>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
use_batcher: bool,
initialize_ctx: &dyn Fn() -> T,
) {
for &chunk_size in [1, 4, 16, 64, 256, 1024, 4096, 16384, 65536].iter() {
group.throughput(Throughput::Bytes(chunk_size));
group.bench_with_input(
BenchmarkId::new(func_name.clone(), chunk_size),
&chunk_size,
|b, &chunk_size| {
read_test_with_ctx(
b,
black_box(chunk_size as usize),
use_batcher,
&mut initialize_ctx(),
)
},
);
}
}
fn chunk_read<T: PhysicalMemory>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> T,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_batched_read", backend_name);
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_without", group_name),
false,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_with", group_name),
true,
initialize_ctx,
);
}
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));
targets = dummy_read_group
}
fn dummy_read_group(c: &mut Criterion) {
chunk_read(c, "dummy", &|| Memory::new(size::mb(64)));
}
criterion_main!(dummy_read);

@ -0,0 +1,42 @@
extern crate memflow_bench;
use memflow_bench::*;
use criterion::*;
use memflow::mem::dummy::{DummyMemory as Memory, DummyModule, DummyProcess};
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 dummy_read_group(c: &mut Criterion) {
virt::seq_read(c, "dummy", &initialize_virt_ctx);
virt::chunk_read(c, "dummy", &initialize_virt_ctx);
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);
}
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));
targets = dummy_read_group
}
criterion_main!(dummy_read);

@ -0,0 +1,86 @@
extern crate memflow_bench;
use memflow_bench::{phys, 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: &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 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
}
}
Err("No module found!".into())
}
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);
}
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));
targets = win32_read_group
}
criterion_main!(win32_read);

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

@ -0,0 +1,206 @@
use criterion::*;
use memflow::mem::{CachedMemoryAccess, PhysicalMemory};
use memflow::architecture;
use memflow::error::Result;
use memflow::mem::PhysicalReadData;
use memflow::types::*;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn rwtest<T: PhysicalMemory>(
bench: &mut Bencher,
mem: &mut T,
(start, end): (Address, Address),
chunk_sizes: &[usize],
chunk_counts: &[usize],
read_size: usize,
) -> usize {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let mut total_size = 0;
for i in chunk_sizes {
for o in chunk_counts {
let mut vbufs = vec![vec![0 as 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 mut bufs = Vec::with_capacity(*o);
bufs.extend(vbufs.iter_mut().map(|vec| {
let addr = (base_addr + rng.gen_range(0, 0x2000)).into();
PhysicalReadData(
PhysicalAddress::with_page(
addr,
PageType::default().write(true),
size::kb(4),
),
vec.as_mut_slice(),
)
}));
bench.iter(|| {
let _ = black_box(mem.phys_read_raw_list(&mut bufs));
});
done_size += *i * *o;
}
total_size += done_size
}
}
total_size
}
fn read_test_with_mem<T: PhysicalMemory>(
bench: &mut Bencher,
mem: &mut T,
chunk_size: usize,
chunks: usize,
start_end: (Address, Address),
) {
black_box(rwtest(
bench,
mem,
start_end,
&[chunk_size],
&[chunks],
chunk_size,
));
}
fn read_test_with_ctx<T: PhysicalMemory>(
bench: &mut Bencher,
cache_size: u64,
chunk_size: usize,
chunks: usize,
mut mem: T,
) {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
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)
.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));
} else {
read_test_with_mem(bench, &mut mem, chunk_size, chunks, (start, end));
}
}
fn seq_read_params<T: PhysicalMemory>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
initialize_ctx: &dyn Fn() -> Result<T>,
) {
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(
b,
black_box(cache_size),
black_box(size as usize),
black_box(1),
initialize_ctx().unwrap(),
)
},
);
}
}
fn chunk_read_params<T: PhysicalMemory>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
initialize_ctx: &dyn Fn() -> Result<T>,
) {
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),
&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(),
)
},
);
}
}
}
pub fn seq_read<T: PhysicalMemory>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<T>,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_phys_seq_read", backend_name);
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
seq_read_params(
&mut group,
format!("{}_nocache", group_name),
0,
initialize_ctx,
);
seq_read_params(
&mut group,
format!("{}_cache", group_name),
2,
initialize_ctx,
);
}
pub fn chunk_read<T: PhysicalMemory>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<T>,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_phys_chunk_read", backend_name);
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_nocache", group_name),
0,
initialize_ctx,
);
chunk_read_params(
&mut group,
format!("{}_cache", group_name),
2,
initialize_ctx,
);
}

@ -0,0 +1,218 @@
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 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,
>(
bench: &mut Bencher,
phys_mem: &mut T,
vat: &mut V,
chunk_count: usize,
translations: usize,
translator: S,
module: M,
) -> usize {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let mut bufs = vec![Address::null(); chunk_count];
let mut done_size = 0;
let mut out = Vec::new();
while done_size < translations {
let base_addr = rng.gen_range(
module.base().as_u64(),
module.base().as_u64() + module.size() as u64,
);
for addr in bufs.iter_mut() {
*addr = (base_addr + rng.gen_range(0, 0x2000)).into();
}
bench.iter(|| {
out.clear();
vat.virt_to_phys_iter(
phys_mem,
&translator,
bufs.iter_mut().map(|x| (*x, 1)),
&mut out,
&mut FnExtend::new(|_| {}),
);
black_box(&out);
});
done_size += chunk_count;
}
done_size
}
fn vat_test_with_ctx<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
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),
) {
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,
);
}
}
fn chunk_vat_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
let size = 0x10;
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(
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(),
)
},
);
}
}
pub fn chunk_vat<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_chunk_vat", backend_name);
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_vat_params(
&mut group,
format!("{}_nocache", group_name),
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,
);
}

@ -0,0 +1,289 @@
use criterion::*;
use memflow::mem::{
CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualDMA, VirtualMemory,
VirtualReadData, VirtualTranslate,
};
use memflow::architecture::ScopedVirtualTranslate;
use memflow::error::Result;
use memflow::process::*;
use memflow::types::*;
use rand::prelude::*;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng as CurRng;
fn rwtest<T: VirtualMemory, M: OsProcessModuleInfo>(
bench: &mut Bencher,
virt_mem: &mut T,
module: &M,
chunk_sizes: &[usize],
chunk_counts: &[usize],
read_size: usize,
) -> usize {
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
let mut total_size = 0;
for i in chunk_sizes {
for o in chunk_counts {
let mut vbufs = vec![vec![0 as 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 mut bufs = Vec::with_capacity(*o);
for VirtualReadData(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(),
)
}));
bench.iter(|| {
let _ = black_box(virt_mem.virt_read_raw_list(bufs.as_mut_slice()));
});
done_size += *i * *o;
}
total_size += done_size
}
}
total_size
}
pub fn read_test_with_mem<T: VirtualMemory, M: OsProcessModuleInfo>(
bench: &mut Bencher,
virt_mem: &mut T,
chunk_size: usize,
chunks: usize,
tmod: M,
) {
black_box(rwtest(
bench,
virt_mem,
&tmod,
&[chunk_size],
&[chunks],
chunk_size,
));
}
fn read_test_with_ctx<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
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),
) {
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);
}
}
fn seq_read_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
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(
b,
black_box(cache_size),
black_box(size as usize),
black_box(1),
black_box(use_tlb),
initialize_ctx().unwrap(),
)
},
);
}
}
fn chunk_read_params<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
func_name: String,
cache_size: u64,
use_tlb: bool,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
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),
&size,
|b, &size| {
read_test_with_ctx(
b,
black_box(cache_size),
black_box(size as usize),
black_box(chunk_size as usize),
black_box(use_tlb),
initialize_ctx().unwrap(),
)
},
);
}
}
}
pub fn seq_read<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_virt_seq_read", backend_name);
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),
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,
);
}
pub fn chunk_read<
T: PhysicalMemory,
V: VirtualTranslate,
P: OsProcessInfo,
S: ScopedVirtualTranslate,
M: OsProcessModuleInfo,
>(
c: &mut Criterion,
backend_name: &str,
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
) {
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
let group_name = format!("{}_virt_chunk_read", backend_name);
let mut group = c.benchmark_group(group_name.clone());
group.plot_config(plot_config);
chunk_read_params(
&mut group,
format!("{}_nocache", group_name),
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,
);
}

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

@ -0,0 +1,12 @@
# memflow-derive
[![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 derive crate contains proc macros that can be used with the [memflow](https://github.com/memflow/memflow) library.
Currently it features the following proc macros:
- A `connector` macro for creating the boilerplate connector plugin code
- A `ByteSwap` derive proc macro

@ -0,0 +1,96 @@
use darling::FromMeta;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, ItemFn};
#[derive(Debug, FromMeta)]
struct ConnectorFactoryArgs {
name: String,
#[darling(default)]
version: Option<String>,
}
// 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")]
#[proc_macro_attribute]
pub fn connector(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(args as AttributeArgs);
let args = match ConnectorFactoryArgs::from_list(&attr_args) {
Ok(v) => v,
Err(e) => return TokenStream::from(e.write_errors()),
};
let connector_name = args.name;
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;
#[cfg(feature = "inventory")]
#[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,
};
#[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))
}
pub fn static_connector_factory(args: &::memflow::connector::ConnectorArgs) -> ::memflow::error::Result<impl ::memflow::mem::PhysicalMemory> {
#func_name(args)
}
#func
};
gen.into()
}
#[proc_macro_derive(ByteSwap)]
pub fn byteswap_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut gen_inner = quote!();
match input.data {
Data::Struct(data) => match data.fields {
Fields::Named(named) => {
for field in named.named.iter() {
let name = field.ident.as_ref().unwrap();
gen_inner.extend(quote!(
self.#name.byte_swap();
));
}
}
_ => unimplemented!(),
},
_ => unimplemented!(),
};
let gen = quote!(
impl #impl_generics ::memflow::types::byte_swap::ByteSwap for #name #ty_generics #where_clause {
fn byte_swap(&mut self) {
#gen_inner
}
}
);
gen.into()
}

@ -0,0 +1,38 @@
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() {}

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

@ -0,0 +1,29 @@
[package]
name = "memflow-ffi"
version = "0.1.5"
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
edition = "2018"
description = "C bindings for the memflow physical memory introspection framework"
documentation = "https://docs.rs/memflow-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_ffi"
crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
memflow = { version = "0.1", path = "../memflow" }
log = "0.4"
simple_logger = "1.9"
[features]
default = []

@ -0,0 +1,49 @@
# memflow-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) FFI crate provides an interface to the memflow API for C/C++. Currently a single `memflow.h` file is generated aside from the dynamic library that can be used to interact with memflow.
A simple example that initializes the library:
```cpp
#include "memflow.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
log_init(4);
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) {
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;
}
```
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.

@ -0,0 +1,69 @@
#ifndef BINDDESTR_H
#define BINDDESTR_H
#include <functional>
// Binds a particular destructor function to the type, automatically destroying it
template<typename T, void (*D)(T *)>
struct BindDestr
{
T *inner;
BindDestr(BindDestr &other) = delete;
BindDestr(BindDestr &&other) {
this->inner = other.inner;
other.inner = NULL;
}
BindDestr(T *inner2)
: inner(inner2) {}
~BindDestr() {
if (this->inner) {
D(this->inner);
}
}
inline operator const T *() const {
return this->inner;
}
inline T *invalidate() {
T *ret = this->inner;
this->inner = NULL;
return ret;
}
};
// Wrap a C function with a particular class prefix (removes it in the class function)
// and specified return type
#define WRAP_FN_TYPE(TYPE, CLASS, FNAME) \
template<typename... Args> \
inline TYPE FNAME (Args... args) { \
return :: CLASS##_##FNAME (this->inner, args...); \
}
// Wrap a C function with a particular class prefix (removes it in the class function)
#define WRAP_FN(CLASS, FNAME) WRAP_FN_TYPE(std::function<decltype( :: CLASS##_##FNAME )>::result_type, CLASS, FNAME)
// Same, but invalidates the pointer
#define WRAP_FN_TYPE_INVALIDATE(TYPE, CLASS, FNAME) \
template<typename... Args> \
inline TYPE FNAME (Args... args) { \
return :: CLASS##_##FNAME (this->invalidate(), args...); \
}
#define WRAP_FN_INVALIDATE(CLASS, FNAME) WRAP_FN_TYPE_INVALIDATE(std::function<decltype( :: CLASS##_##FNAME )>::result_type, CLASS, FNAME)
// Wrap a C function in a raw way with specified return type
#define WRAP_FN_RAW_TYPE(TYPE, FNAME) \
template<typename... Args> \
inline TYPE FNAME (Args... args) { \
return :: FNAME (this->inner, args...); \
}
// Wrap a C function in a raw way
#define WRAP_FN_RAW(FNAME) WRAP_FN_RAW_TYPE(std::function<decltype( :: FNAME )>::result_type, FNAME)
#endif

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

@ -0,0 +1,19 @@
language = "C"
include_guard = "MEMFLOW_H"
tab_width = 4
documentation_style = "doxy"
style = "both"
#no_includes = true
cpp_compat = true
[parse]
parse_deps = true
include = ["memflow"]
[macro_expansion]
bitflags = true
[fn]
sort_by = "None"

@ -0,0 +1,19 @@
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,35 @@
#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;
}

@ -0,0 +1,520 @@
#ifndef MEMFLOW_H
#define MEMFLOW_H
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* Identifies the byte order of a architecture
*
* This enum is used when reading/writing to/from the memory of a target system.
* The memory will be automatically converted to the endianess memflow is currently running on.
*
* See the [wikipedia article](https://en.wikipedia.org/wiki/Endianness) for more information on the subject.
*/
enum Endianess
#ifdef __cplusplus
: uint8_t
#endif // __cplusplus
{
/**
* Little Endianess
*/
LittleEndian,
/**
* Big Endianess
*/
BigEndian,
};
#ifndef __cplusplus
typedef uint8_t Endianess;
#endif // __cplusplus
typedef struct ArchitectureObj ArchitectureObj;
typedef struct CloneablePhysicalMemoryObj CloneablePhysicalMemoryObj;
/**
* Holds an inventory of available connectors.
*/
typedef struct ConnectorInventory ConnectorInventory;
typedef struct OsProcessInfoObj OsProcessInfoObj;
typedef struct OsProcessModuleInfoObj OsProcessModuleInfoObj;
typedef struct PhysicalMemoryObj PhysicalMemoryObj;
typedef struct PhysicalReadData PhysicalReadData;
typedef struct PhysicalWriteData PhysicalWriteData;
typedef struct VirtualMemoryObj VirtualMemoryObj;
typedef struct VirtualReadData VirtualReadData;
typedef struct VirtualWriteData VirtualWriteData;
/**
* This type represents a address on the target system.
* It internally holds a `u64` value but can also be used
* when working in 32-bit environments.
*
* This type will not handle overflow for 32-bit or 64-bit addresses / lengths.
*/
typedef uint64_t Address;
/**
* A address with the value of zero.
*
* # Examples
*
* ```
* use memflow::types::Address;
*
* println!("address: {}", Address::NULL);
* ```
*/
#define Address_NULL 0
/**
* Describes the type of a page using a bitflag.
*/
typedef uint8_t PageType;
/**
* The page explicitly has no flags.
*/
#define PageType_NONE (uint8_t)0
/**
* The page type is not known.
*/
#define PageType_UNKNOWN (uint8_t)1
/**
* The page contains page table entries.
*/
#define PageType_PAGE_TABLE (uint8_t)2
/**
* The page is a writeable page.
*/
#define PageType_WRITEABLE (uint8_t)4
/**
* The page is read only.
*/
#define PageType_READ_ONLY (uint8_t)8
/**
* The page is not executable.
*/
#define PageType_NOEXEC (uint8_t)16
/**
* This type represents a wrapper over a [address](address/index.html)
* with additional information about the containing page in the physical memory domain.
*
* This type will mostly be used by the [virtual to physical address translation](todo.html).
* When a physical address is translated from a virtual address the additional information
* about the allocated page the virtual address points to can be obtained from this structure.
*
* Most architectures have support multiple page sizes (see [huge pages](todo.html))
* which will be represented by the containing `page` of the `PhysicalAddress` struct.
*/
typedef struct PhysicalAddress {
Address address;
PageType page_type;
uint8_t page_size_log2;
} PhysicalAddress;
typedef struct PhysicalMemoryMetadata {
uintptr_t size;
bool readonly;
} PhysicalMemoryMetadata;
/**
* Type alias for a PID.
*/
typedef uint32_t PID;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
extern const struct ArchitectureObj *X86_32;
extern const struct ArchitectureObj *X86_32_PAE;
extern const struct ArchitectureObj *X86_64;
void log_init(int32_t level_num);
/**
* Helper to convert `Address` to a `PhysicalAddress`
*
* This will create a `PhysicalAddress` with `UNKNOWN` PageType.
*/
struct PhysicalAddress addr_to_paddr(Address address);
/**
* 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.
*/
struct ConnectorInventory *inventory_scan(void);
/**
* Create a new inventory with custom path string
*
* # Safety
*
* `path` must be a valid null terminated string
*/
struct ConnectorInventory *inventory_scan_path(const char *path);
/**
* Add a directory to an existing inventory
*
* # Safety
*
* `dir` must be a valid null terminated string
*/
int32_t inventory_add_dir(struct ConnectorInventory *inv, const char *dir);
/**
* 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.
*/
struct CloneablePhysicalMemoryObj *inventory_create_connector(struct ConnectorInventory *inv,
const char *name,
const char *args);
/**
* 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.
*/
struct CloneablePhysicalMemoryObj *connector_clone(const struct CloneablePhysicalMemoryObj *conn);
/**
* 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.
*/
void connector_free(struct CloneablePhysicalMemoryObj *conn);
/**
* Free a connector inventory
*
* # Safety
*
* `inv` must point to a valid `ConnectorInventory` that was created using one of the provided
* functions.
*/
void inventory_free(struct ConnectorInventory *inv);
/**
* 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.
*/
struct PhysicalMemoryObj *downcast_cloneable(struct CloneablePhysicalMemoryObj *cloneable);
/**
* 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.
*/
void phys_free(struct PhysicalMemoryObj *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`
*/
int32_t phys_read_raw_list(struct PhysicalMemoryObj *mem,
struct PhysicalReadData *data,
uintptr_t len);
/**
* 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`
*/
int32_t phys_write_raw_list(struct PhysicalMemoryObj *mem,
const struct PhysicalWriteData *data,
uintptr_t len);
/**
* Retrieve metadata about the physical memory object
*/
struct PhysicalMemoryMetadata phys_metadata(const struct PhysicalMemoryObj *mem);
/**
* 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.
*/
int32_t phys_read_raw_into(struct PhysicalMemoryObj *mem,
struct PhysicalAddress addr,
uint8_t *out,
uintptr_t len);
/**
* Read a single 32-bit value from a provided `PhysicalAddress`
*/
uint32_t phys_read_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr);
/**
* Read a single 64-bit value from a provided `PhysicalAddress`
*/
uint64_t phys_read_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr);
/**
* 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.
*/
int32_t phys_write_raw(struct PhysicalMemoryObj *mem,
struct PhysicalAddress addr,
const uint8_t *input,
uintptr_t len);
/**
* Write a single 32-bit value into a provided `PhysicalAddress`
*/
int32_t phys_write_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint32_t val);
/**
* Write a single 64-bit value into a provided `PhysicalAddress`
*/
int32_t phys_write_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint64_t val);
/**
* 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.
*/
void virt_free(struct VirtualMemoryObj *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`
*/
int32_t virt_read_raw_list(struct VirtualMemoryObj *mem,
struct VirtualReadData *data,
uintptr_t len);
/**
* 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`
*/
int32_t virt_write_raw_list(struct VirtualMemoryObj *mem,
const struct VirtualWriteData *data,
uintptr_t len);
/**
* 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.
*/
int32_t virt_read_raw_into(struct VirtualMemoryObj *mem, Address addr, uint8_t *out, uintptr_t len);
/**
* Read a single 32-bit value from a provided `Address`
*/
uint32_t virt_read_u32(struct VirtualMemoryObj *mem, Address addr);
/**
* Read a single 64-bit value from a provided `Address`
*/
uint64_t virt_read_u64(struct VirtualMemoryObj *mem, Address addr);
/**
* 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.
*/
int32_t virt_write_raw(struct VirtualMemoryObj *mem,
Address addr,
const uint8_t *input,
uintptr_t len);
/**
* Write a single 32-bit value into a provided `Address`
*/
int32_t virt_write_u32(struct VirtualMemoryObj *mem, Address addr, uint32_t val);
/**
* Write a single 64-bit value into a provided `Address`
*/
int32_t virt_write_u64(struct VirtualMemoryObj *mem, Address addr, uint64_t val);
uint8_t arch_bits(const struct ArchitectureObj *arch);
Endianess arch_endianess(const struct ArchitectureObj *arch);
uintptr_t arch_page_size(const struct ArchitectureObj *arch);
uintptr_t arch_size_addr(const struct ArchitectureObj *arch);
uint8_t arch_address_space_bits(const struct ArchitectureObj *arch);
/**
* Free an architecture reference
*
* # Safety
*
* `arch` must be a valid heap allocated reference created by one of the API's functions.
*/
void arch_free(struct ArchitectureObj *arch);
bool is_x86_arch(const struct ArchitectureObj *arch);
Address os_process_info_address(const struct OsProcessInfoObj *obj);
PID os_process_info_pid(const struct OsProcessInfoObj *obj);
/**
* 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
*/
uintptr_t os_process_info_name(const struct OsProcessInfoObj *obj, char *out, uintptr_t max_len);
const struct ArchitectureObj *os_process_info_sys_arch(const struct OsProcessInfoObj *obj);
const struct ArchitectureObj *os_process_info_proc_arch(const struct OsProcessInfoObj *obj);
/**
* Free a OsProcessInfoObj reference
*
* # Safety
*
* `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's
* functions.
*/
void os_process_info_free(struct OsProcessInfoObj *obj);
Address os_process_module_address(const struct OsProcessModuleInfoObj *obj);
Address os_process_module_parent_process(const struct OsProcessModuleInfoObj *obj);
Address os_process_module_base(const struct OsProcessModuleInfoObj *obj);
uintptr_t os_process_module_size(const struct OsProcessModuleInfoObj *obj);
/**
* 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
*/
uintptr_t os_process_module_name(const struct OsProcessModuleInfoObj *obj,
char *out,
uintptr_t max_len);
/**
* Free a OsProcessModuleInfoObj reference
*
* # Safety
*
* `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's
* functions.
*/
void os_process_module_free(struct OsProcessModuleInfoObj *obj);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* MEMFLOW_H */

@ -0,0 +1,177 @@
#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,38 @@
use memflow::architecture::{ArchitectureObj, Endianess};
pub mod x86;
#[no_mangle]
pub extern "C" fn arch_bits(arch: &ArchitectureObj) -> u8 {
arch.bits()
}
#[no_mangle]
pub extern "C" fn arch_endianess(arch: &ArchitectureObj) -> Endianess {
arch.endianess()
}
#[no_mangle]
pub extern "C" fn arch_page_size(arch: &ArchitectureObj) -> usize {
arch.page_size()
}
#[no_mangle]
pub extern "C" fn arch_size_addr(arch: &ArchitectureObj) -> usize {
arch.size_addr()
}
#[no_mangle]
pub extern "C" fn arch_address_space_bits(arch: &ArchitectureObj) -> u8 {
arch.address_space_bits()
}
/// Free an architecture reference
///
/// # Safety
///
/// `arch` must be a valid heap allocated reference created by one of the API's functions.
#[no_mangle]
pub unsafe extern "C" fn arch_free(arch: &'static mut ArchitectureObj) {
let _ = Box::from_raw(arch);
}

@ -0,0 +1,17 @@
use memflow::architecture::{x86, ArchitectureObj};
#[no_mangle]
pub static X86_32: &ArchitectureObj = &x86::x32::ARCH;
#[no_mangle]
pub static X86_32_PAE: &ArchitectureObj = &x86::x32_pae::ARCH;
#[no_mangle]
pub static X86_64: &ArchitectureObj = &x86::x64::ARCH;
#[no_mangle]
pub extern "C" fn is_x86_arch(arch: &ArchitectureObj) -> bool {
x86::is_x86_arch(*arch)
}
// TODO: new_translator, if it is feasible

@ -0,0 +1,150 @@
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);
}

@ -0,0 +1,13 @@
pub mod log;
pub mod types;
pub mod connectors;
pub mod mem;
pub mod architecture;
pub mod process;
pub mod util;

@ -0,0 +1,17 @@
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();
}

@ -0,0 +1,2 @@
pub mod phys_mem;
pub mod virt_mem;

@ -0,0 +1,146 @@
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()
}

@ -0,0 +1,117 @@
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,119 @@
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);
}

@ -0,0 +1,9 @@
use memflow::types::{Address, PhysicalAddress};
/// Helper to convert `Address` to a `PhysicalAddress`
///
/// This will create a `PhysicalAddress` with `UNKNOWN` PageType.
#[no_mangle]
pub extern "C" fn addr_to_paddr(address: Address) -> PhysicalAddress {
address.into()
}

@ -0,0 +1,44 @@
use log::error;
pub fn inspect_err<E: std::fmt::Display>(e: E) -> E {
error!("{}", 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
}
}
}

@ -0,0 +1,65 @@
/*
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,5 @@
/target
**/*.rs.bk
*.swp
.vscode
Cargo.lock

@ -0,0 +1,37 @@
[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"

@ -0,0 +1,23 @@
MIT License
Copyright (c) 2020 ko1N <ko1N1337@gmail.com>
Copyright (c) 2020 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
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.

@ -0,0 +1,24 @@
.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

@ -0,0 +1,55 @@
# 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.

@ -0,0 +1,45 @@
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)));
}
}
}
}

@ -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/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

@ -0,0 +1,475 @@
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
);
}
}

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

@ -0,0 +1,26 @@
[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" }

@ -0,0 +1,47 @@
# 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.

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

@ -0,0 +1,22 @@
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"]

@ -0,0 +1,22 @@
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

@ -0,0 +1,61 @@
#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;
}

@ -0,0 +1,54 @@
#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;
}

@ -0,0 +1,321 @@
#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 */

@ -0,0 +1,151 @@
#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

@ -0,0 +1,17 @@
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,
}
}
}

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

@ -0,0 +1,331 @@
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)
}

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

@ -0,0 +1,24 @@
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);
}

@ -0,0 +1,136 @@
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()
}

@ -0,0 +1,81 @@
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,80 @@
[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" ]
[badges]
maintenance = { status = "actively-developed" }
codecov = { repository = "github", branch = "master", service = "github" }
[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"] }
# will be replaced by our own signature scanner
regex = { version = "1", optional = true }
# 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"

@ -0,0 +1,44 @@
# memflow-win32
[![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)
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.

@ -0,0 +1,50 @@
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(())
}

@ -0,0 +1,110 @@
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");
}
}

@ -0,0 +1,121 @@
use clap::*;
use log::{error, Level};
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,
};
pub fn main() {
let matches = App::new("generate offsets example")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("verbose").short("v").multiple(true))
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.takes_value(true)
.required(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();
let win_ids = vec![
/*
(
Win32Version::new(5, 2, 3790),
Win32GUID::new("ntkrnlmp.pdb", "82DCF67A38274C9CA99B60B421D2786D2"),
),
*/
(
Win32Version::new(6, 1, 7601),
Win32OffsetsArchitecture::X86,
Win32GUID::new("ntkrpamp.pdb", "684DA42A30CC450F81C535B4D18944B12"),
),
(
Win32Version::new(6, 1, 7601),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "ECE191A20CFF4465AE46DF96C22638451"),
),
(
Win32Version::new(10, 0, 18362),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "0AFB69F5FD264D54673570E37B38A3181"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "BBED7C2955FBE4522AAA23F4B8677AD91"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X64,
Win32GUID::new("ntkrnlmp.pdb", "1C9875F76C8F0FBF3EB9A9D7C1C274061"),
),
(
Win32Version::new(10, 0, 19041),
Win32OffsetsArchitecture::X86,
Win32GUID::new("ntkrpamp.pdb", "1B1D6AA205E1C87DC63A314ACAA50B491"),
),
];
let out_dir = matches.value_of("output").unwrap();
create_dir_all(out_dir).unwrap();
for win_id in win_ids.into_iter() {
if let Ok(offsets) = Win32Offsets::builder()
.symbol_store(SymbolStore::new())
.guid(win_id.2.clone())
.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(),
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,
offsets: offsets.0,
};
let offsetstr = toml::to_string_pretty(&offset_file).unwrap();
let file_name = format!(
"{}_{}_{}_{}_{}.toml",
win_id.0.major_version(),
win_id.0.minor_version(),
win_id.0.build_number(),
win_id.1.to_string(),
win_id.2.guid,
);
let mut file =
File::create([out_dir, &file_name].iter().collect::<PathBuf>().as_path()).unwrap();
file.write_all(offsetstr.as_bytes()).unwrap();
} else {
error!(
"unable to find offsets for {} {:?} {:?}",
win_id.0, win_id.1, win_id.2
)
}
}
}

@ -0,0 +1,211 @@
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(),
)
}

@ -0,0 +1,136 @@
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);
}

@ -0,0 +1,216 @@
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(())
}

@ -0,0 +1,69 @@
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,22 @@
pdb_file_name = 'ntkrnlmp.pdb'
pdb_guid = '0AFB69F5FD264D54673570E37B38A3181'
nt_major_version = 10
nt_minor_version = 0
nt_build_number = 18362
arch = 'X64'
[offsets]
list_blink = 8
eproc_link = 752
kproc_dtb = 40
eproc_pid = 744
eproc_name = 1104
eproc_peb = 1016
eproc_section_base = 968
eproc_exit_status = 1620
eproc_thread_list = 1160
eproc_wow64 = 1064
kthread_teb = 240
ethread_list_entry = 1720
teb_peb = 96
teb_peb_x86 = 48

@ -0,0 +1,22 @@
pdb_file_name = 'ntkrnlmp.pdb'
pdb_guid = '1C9875F76C8F0FBF3EB9A9D7C1C274061'
nt_major_version = 10
nt_minor_version = 0
nt_build_number = 19041
arch = 'X64'
[offsets]
list_blink = 8
eproc_link = 1096
kproc_dtb = 40
eproc_pid = 1088
eproc_name = 1448
eproc_peb = 1360
eproc_section_base = 1312
eproc_exit_status = 2004
eproc_thread_list = 1504
eproc_wow64 = 1408
kthread_teb = 240
ethread_list_entry = 1256
teb_peb = 96
teb_peb_x86 = 48

@ -0,0 +1,22 @@
pdb_file_name = 'ntkrnlmp.pdb'
pdb_guid = 'BBED7C2955FBE4522AAA23F4B8677AD91'
nt_major_version = 10
nt_minor_version = 0
nt_build_number = 19041
arch = 'X64'
[offsets]
list_blink = 8
eproc_link = 1096
kproc_dtb = 40
eproc_pid = 1088
eproc_name = 1448
eproc_peb = 1360
eproc_section_base = 1312
eproc_exit_status = 2004
eproc_thread_list = 1504
eproc_wow64 = 1408
kthread_teb = 240
ethread_list_entry = 1256
teb_peb = 96
teb_peb_x86 = 48

@ -0,0 +1,22 @@
pdb_file_name = 'ntkrpamp.pdb'
pdb_guid = '1B1D6AA205E1C87DC63A314ACAA50B491'
nt_major_version = 10
nt_minor_version = 0
nt_build_number = 19041
arch = 'X86'
[offsets]
list_blink = 4
eproc_link = 232
kproc_dtb = 24
eproc_pid = 228
eproc_name = 428
eproc_peb = 380
eproc_section_base = 352
eproc_exit_status = 844
eproc_thread_list = 464
eproc_wow64 = 0
kthread_teb = 168
ethread_list_entry = 740
teb_peb = 48
teb_peb_x86 = 48

@ -0,0 +1,23 @@
nt_major_version = 4
nt_minor_version = 0
nt_build_number = 1381
arch = 'X86'
[offsets]
list_blink = 4
eproc_link = 0x98
kproc_dtb = 0x18
eproc_pid = 0x94
eproc_name = 0x1dc
eproc_peb = 0x18c
eproc_section_base = 0x190
eproc_exit_status = 0 #5.1+
eproc_thread_list = 0 #5.1+
eproc_wow64 = 0 #5.0+
kthread_teb = 0 #6.2+
ethread_list_entry = 0x0 #5.0+
teb_peb = 0 #?
teb_peb_x86 = 0 #?

@ -0,0 +1,21 @@
pdb_guid = '82DCF67A38274C9CA99B60B421D2786D2'
nt_major_version = 5
nt_minor_version = 2
nt_build_number = 3790
arch = 'X64'
[offsets]
list_blink = 8
eproc_link = 224
kproc_dtb = 40
eproc_pid = 216
eproc_name = 616
eproc_peb = 704
eproc_section_base = 0x128
eproc_exit_status = 0x024C
eproc_thread_list = 656
eproc_wow64 = 680
kthread_teb = 176
ethread_list_entry = 976
teb_peb = 96
teb_peb_x86 = 48

@ -0,0 +1,22 @@
pdb_file_name = 'ntkrnlmp.pdb'
pdb_guid = 'ECE191A20CFF4465AE46DF96C22638451'
nt_major_version = 6
nt_minor_version = 1
nt_build_number = 7601
arch = 'X64'
[offsets]
list_blink = 8
eproc_link = 392
kproc_dtb = 40
eproc_pid = 384
eproc_name = 736
eproc_peb = 824
eproc_section_base = 624
eproc_exit_status = 1092
eproc_thread_list = 776
eproc_wow64 = 800
kthread_teb = 184
ethread_list_entry = 1064
teb_peb = 96
teb_peb_x86 = 48

@ -0,0 +1,22 @@
pdb_file_name = 'ntkrpamp.pdb'
pdb_guid = '684DA42A30CC450F81C535B4D18944B12'
nt_major_version = 6
nt_minor_version = 1
nt_build_number = 7601
arch = 'X86'
[offsets]
list_blink = 4
eproc_link = 184
kproc_dtb = 24
eproc_pid = 180
eproc_name = 364
eproc_peb = 424
eproc_section_base = 300
eproc_exit_status = 628
eproc_thread_list = 392
eproc_wow64 = 0
kthread_teb = 136
ethread_list_entry = 616
teb_peb = 48
teb_peb_x86 = 48

@ -0,0 +1,126 @@
use std::prelude::v1::*;
use std::{convert, fmt, result, str};
#[cfg(feature = "std")]
use std::error;
// forward declare partial result extension from core for easier access
pub use memflow::error::PartialResultExt;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Error {
/// Generic error type containing a string
Other(&'static str),
/// Out of bounds.
///
/// Catch-all for bounds check errors.
Bounds,
/// Invalid Architecture error.
///
/// The architecture provided is not a valid argument for the given function.
InvalidArchitecture,
Initialization(&'static str),
SymbolStore(&'static str),
ProcessInfo,
ModuleInfo,
/// memflow core error.
///
/// Catch-all for memflow core related errors.
Core(memflow::error::Error),
PDB(&'static str),
/// PE error.
///
/// Catch-all for pe related errors.
PE(pelite::Error),
/// Encoding error.
///
/// Catch-all for string related errors such as lacking a nul terminator.
Encoding,
/// Unicode error when reading a string from windows.
///
/// Encapsulates all unicode related reading errors.
Unicode(&'static str),
}
/// Convert from &str to error
impl convert::From<&'static str> for Error {
fn from(error: &'static str) -> Self {
Error::Other(error)
}
}
/// Convert from flow_core::Error
impl From<memflow::error::Error> for Error {
fn from(error: memflow::error::Error) -> Error {
Error::Core(error)
}
}
/// Convert from flow_core::PartialError
impl<T> From<memflow::error::PartialError<T>> for Error {
fn from(_error: memflow::error::PartialError<T>) -> Error {
Error::Core(memflow::error::Error::Partial)
}
}
/// Convert from pelite::Error
impl From<pelite::Error> for Error {
fn from(error: pelite::Error) -> Error {
Error::PE(error)
}
}
/// Convert from str::Utf8Error
impl From<str::Utf8Error> for Error {
fn from(_err: str::Utf8Error) -> Error {
Error::Encoding
}
}
impl Error {
/// Returns a tuple representing the error description and its string value.
pub fn to_str_pair(self) -> (&'static str, Option<&'static str>) {
match self {
Error::Other(e) => ("other error", Some(e)),
Error::Bounds => ("out of bounds", None),
Error::InvalidArchitecture => ("invalid architecture", None),
Error::Initialization(e) => ("error during initialization", Some(e)),
Error::SymbolStore(e) => ("error in symbol store", Some(e)),
Error::ProcessInfo => ("error retrieving process info", None),
Error::ModuleInfo => ("error retrieving module info", None),
Error::Core(e) => e.to_str_pair(),
Error::PDB(e) => ("error handling pdb", Some(e)),
Error::PE(e) => ("error handling pe", Some(e.to_str())),
Error::Encoding => ("encoding error", None),
Error::Unicode(e) => ("error reading unicode string", Some(e)),
}
}
/// Returns a simple string representation of the error.
pub fn to_str(self) -> &'static str {
self.to_str_pair().0
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (desc, value) = self.to_str_pair();
if let Some(value) = value {
write!(f, "{}: {}", desc, value)
} else {
f.write_str(desc)
}
}
}
#[cfg(feature = "std")]
impl error::Error for Error {
fn description(&self) -> &str {
self.to_str()
}
}
/// Specialized `Result` type for memflow_win32 errors.
pub type Result<T> = result::Result<T, Error>;

@ -0,0 +1,147 @@
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)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
pub struct Win32GUID {
pub file_name: String,
pub guid: String,
}
impl Win32GUID {
pub fn new(file_name: &str, guid: &str) -> Self {
Self {
file_name: file_name.to_string(),
guid: guid.to_string(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
#[repr(C)]
pub struct Win32Version {
nt_major_version: u32,
nt_minor_version: u32,
nt_build_number: u32,
}
impl Win32Version {
pub fn new(nt_major_version: u32, nt_minor_version: u32, nt_build_number: u32) -> Self {
Self {
nt_major_version,
nt_minor_version,
nt_build_number,
}
}
pub fn mask_build_number(mut self) -> Self {
self.nt_build_number &= 0xFFFF;
self
}
pub fn major_version(&self) -> u32 {
self.nt_major_version
}
pub fn minor_version(&self) -> u32 {
self.nt_minor_version
}
pub fn build_number(&self) -> u32 {
self.nt_build_number & 0xFFFF
}
pub fn is_checked_build(&self) -> bool {
(self.nt_build_number & 0xF0000000) == 0xC0000000
}
pub fn as_tuple(&self) -> (u32, u32, u32) {
(
self.major_version(),
self.minor_version(),
self.build_number(),
)
}
}
impl PartialOrd for Win32Version {
fn partial_cmp(&self, other: &Win32Version) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Win32Version {
fn cmp(&self, other: &Win32Version) -> Ordering {
if self.nt_build_number != 0 && other.nt_build_number != 0 {
return self.nt_build_number.cmp(&other.nt_build_number);
}
if self.nt_major_version != other.nt_major_version {
self.nt_major_version.cmp(&other.nt_major_version)
} else if self.nt_minor_version != other.nt_minor_version {
self.nt_minor_version.cmp(&other.nt_minor_version)
} else {
Ordering::Equal
}
}
}
impl PartialEq for Win32Version {
fn eq(&self, other: &Win32Version) -> bool {
if self.nt_build_number != 0 && other.nt_build_number != 0 {
self.nt_build_number.eq(&other.nt_build_number)
} else {
self.nt_major_version == other.nt_major_version
&& self.nt_minor_version == other.nt_minor_version
}
}
}
impl Eq for Win32Version {}
impl From<(u32, u32)> for Win32Version {
fn from((nt_major_version, nt_minor_version): (u32, u32)) -> Win32Version {
Win32Version {
nt_major_version,
nt_minor_version,
nt_build_number: 0,
}
}
}
impl From<(u32, u32, u32)> for Win32Version {
fn from(
(nt_major_version, nt_minor_version, nt_build_number): (u32, u32, u32),
) -> Win32Version {
Win32Version {
nt_major_version,
nt_minor_version,
nt_build_number,
}
}
}
impl fmt::Display for Win32Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.nt_major_version != 0 {
write!(
f,
"{}.{}.{}",
self.major_version(),
self.minor_version(),
self.build_number()
)
} else {
write!(f, "{}", self.build_number())
}
}
}

@ -0,0 +1,160 @@
pub(crate) mod pehelper;
mod x64;
mod x86;
use super::{StartBlock, Win32GUID, Win32Version};
use crate::error::{Error, PartialResultExt, Result};
use std::convert::TryInto;
use std::prelude::v1::*;
use log::{info, warn};
use memflow::mem::VirtualMemory;
use memflow::types::Address;
use pelite::{self, pe64::debug::CodeView, pe64::exports::Export, PeView};
pub fn find<T: VirtualMemory>(
virt_mem: &mut T,
start_block: &StartBlock,
) -> Result<(Address, usize)> {
if start_block.arch.bits() == 64 {
if !start_block.kernel_hint.is_null() {
match x64::find_with_va_hint(virt_mem, start_block) {
Ok(b) => return Ok(b),
Err(e) => warn!("x64::find_with_va_hint() error: {}", e),
}
}
match x64::find(virt_mem, start_block) {
Ok(b) => return Ok(b),
Err(e) => warn!("x64::find() error: {}", e),
}
} else if start_block.arch.bits() == 32 {
match x86::find(virt_mem, start_block) {
Ok(b) => return Ok(b),
Err(e) => warn!("x86::find() error: {}", e),
}
}
Err(Error::Initialization("unable to find ntoskrnl.exe"))
}
// TODO: move to pe::...
pub fn find_guid<T: VirtualMemory>(virt_mem: &mut T, kernel_base: Address) -> Result<Win32GUID> {
let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?;
let pe = PeView::from_bytes(&image).map_err(Error::PE)?;
let debug = match pe.debug() {
Ok(d) => d,
Err(_) => {
return Err(Error::Initialization(
"unable to read debug_data in pe header",
))
}
};
let code_view = debug
.iter()
.map(|e| e.entry())
.filter_map(std::result::Result::ok)
.find(|&e| e.as_code_view().is_some())
.ok_or_else(|| Error::Initialization("unable to find codeview debug_data entry"))?
.as_code_view()
.ok_or_else(|| Error::PE(pelite::Error::Unmapped))?;
let signature = match code_view {
CodeView::Cv70 { image, .. } => image.Signature,
CodeView::Cv20 { .. } => {
return Err(Error::Initialization(
"invalid code_view entry version 2 found, expected 7",
))
}
};
let file_name = code_view.pdb_file_name().to_str()?;
let guid = format!("{:X}{:X}", signature, code_view.age());
Ok(Win32GUID::new(file_name, &guid))
}
fn get_export(pe: &PeView, name: &str) -> Result<usize> {
info!("trying to find {} export", name);
let export = match pe.get_export_by_name(name).map_err(Error::PE)? {
Export::Symbol(s) => *s as usize,
Export::Forward(_) => {
return Err(Error::Other("Export found but it was a forwarded export"))
}
};
info!("{} found at 0x{:x}", name, export);
Ok(export)
}
pub fn find_winver<T: VirtualMemory>(
virt_mem: &mut T,
kernel_base: Address,
) -> Result<Win32Version> {
let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?;
let pe = PeView::from_bytes(&image).map_err(Error::PE)?;
// NtBuildNumber
let nt_build_number_ref = get_export(&pe, "NtBuildNumber")?;
let rtl_get_version_ref = get_export(&pe, "RtlGetVersion");
let nt_build_number: u32 = virt_mem.virt_read(kernel_base + nt_build_number_ref)?;
info!("nt_build_number: {}", nt_build_number);
if nt_build_number == 0 {
return Err(Error::Initialization("unable to fetch nt build number"));
}
// TODO: these reads should be optional
// try to find major/minor version
// read from KUSER_SHARED_DATA. these fields exist since nt 4.0 so they have to exist in case NtBuildNumber exists.
let mut nt_major_version: u32 = virt_mem
.virt_read((0x7ffe0000 + 0x026C).into())
.data_part()?;
let mut nt_minor_version: u32 = virt_mem
.virt_read((0x7ffe0000 + 0x0270).into())
.data_part()?;
// fallback on x64: try to parse RtlGetVersion assembly
if nt_major_version == 0 && rtl_get_version_ref.is_ok() {
let mut buf = [0u8; 0x100];
virt_mem
.virt_read_into(kernel_base + rtl_get_version_ref.unwrap(), &mut buf)
.data_part()?;
nt_major_version = 0;
nt_minor_version = 0;
for i in 0..0xf0 {
if nt_major_version == 0
&& nt_minor_version == 0
&& u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) == 0x441c748
{
nt_major_version =
u16::from_le_bytes(buf[i + 4..i + 4 + 2].try_into().unwrap()) as u32;
nt_minor_version = (buf[i + 5] & 0xF) as u32;
}
if nt_major_version == 0
&& u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) & 0xFFFFF == 0x441c7
{
nt_major_version = buf[i + 3] as u32;
}
if nt_minor_version == 0
&& u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) & 0xFFFFF == 0x841c7
{
nt_major_version = buf[i + 3] as u32;
}
}
}
// construct Win32BuildNumber object (major and minor version might be null but build number should be set)
let version = Win32Version::new(nt_major_version, nt_minor_version, nt_build_number);
info!("kernel version: {}", version);
Ok(version)
}

@ -0,0 +1,57 @@
use std::prelude::v1::*;
use crate::error::{Error, Result};
use log::{debug, info};
use memflow::error::PartialResultExt;
use memflow::mem::VirtualMemory;
use memflow::types::{size, Address};
use pelite::{self, PeView};
pub fn try_get_pe_size<T: VirtualMemory>(virt_mem: &mut T, probe_addr: Address) -> Result<usize> {
let mut probe_buf = vec![0; size::kb(4)];
virt_mem.virt_read_raw_into(probe_addr, &mut probe_buf)?;
let pe_probe = PeView::from_bytes(&probe_buf).map_err(Error::PE)?;
let opt_header = pe_probe.optional_header();
let size_of_image = match opt_header {
pelite::Wrap::T32(opt32) => opt32.SizeOfImage,
pelite::Wrap::T64(opt64) => opt64.SizeOfImage,
};
if size_of_image > 0 {
debug!(
"found pe header for image with a size of {} bytes.",
size_of_image
);
Ok(size_of_image as usize)
} else {
Err(Error::Initialization("pe size_of_image is zero"))
}
}
pub fn try_get_pe_image<T: VirtualMemory>(
virt_mem: &mut T,
probe_addr: Address,
) -> Result<Vec<u8>> {
let size_of_image = try_get_pe_size(virt_mem, probe_addr)?;
virt_mem
.virt_read_raw(probe_addr, size_of_image)
.data_part()
.map_err(Error::Core)
}
pub fn try_get_pe_name<T: VirtualMemory>(virt_mem: &mut T, probe_addr: Address) -> Result<String> {
let image = try_get_pe_image(virt_mem, probe_addr)?;
let pe = PeView::from_bytes(&image).map_err(Error::PE)?;
let name = pe
.exports()
.map_err(|_| Error::Initialization("unable to get exports"))?
.dll_name()
.map_err(|_| Error::Initialization("unable to get dll name"))?
.to_str()?;
info!("try_get_pe_name: found pe header for {}", name);
Ok(name.to_string())
}

@ -0,0 +1,106 @@
use std::prelude::v1::*;
use super::pehelper;
use crate::error::{Error, Result};
use crate::kernel::StartBlock;
use log::{debug, trace};
use memflow::architecture::x86::x64;
use memflow::error::PartialResultExt;
use memflow::iter::PageChunks;
use memflow::mem::VirtualMemory;
use memflow::types::{size, Address};
use dataview::Pod;
use pelite::image::IMAGE_DOS_HEADER;
pub fn find_with_va_hint<T: VirtualMemory>(
virt_mem: &mut T,
start_block: &StartBlock,
) -> Result<(Address, usize)> {
debug!(
"x64::find_with_va_hint: trying to find ntoskrnl.exe with va hint at {:x}",
start_block.kernel_hint.as_u64()
);
// va was found previously
let mut va_base = start_block.kernel_hint.as_u64() & !0x0001_ffff;
while va_base + size::mb(16) as u64 > start_block.kernel_hint.as_u64() {
trace!("x64::find_with_va_hint: probing at {:x}", va_base);
match find_with_va(virt_mem, va_base) {
Ok(a) => {
let addr = Address::from(a);
let size_of_image = pehelper::try_get_pe_size(virt_mem, addr)?;
return Ok((addr, size_of_image));
}
Err(e) => trace!("x64::find_with_va_hint: probe error {:?}", e),
}
va_base -= size::mb(2) as u64;
}
Err(Error::Initialization(
"x64::find_with_va_hint: unable to locate ntoskrnl.exe via va hint",
))
}
fn find_with_va<T: VirtualMemory>(virt_mem: &mut T, va_base: u64) -> Result<u64> {
let mut buf = vec![0; size::mb(2)];
virt_mem
.virt_read_raw_into(Address::from(va_base), &mut buf)
.data_part()?;
buf.chunks_exact(x64::ARCH.page_size())
.enumerate()
.map(|(i, c)| {
let view = Pod::as_data_view(&c[..]);
(i, c, view.copy::<IMAGE_DOS_HEADER>(0)) // TODO: potential endian mismatch
})
.filter(|(_, _, p)| p.e_magic == 0x5a4d) // MZ
.filter(|(_, _, p)| p.e_lfanew <= 0x800)
.inspect(|(i, _, _)| {
trace!(
"x64::find_with_va: found potential header flags at offset {:x}",
i * x64::ARCH.page_size()
)
})
.find(|(i, _, _)| {
let probe_addr = Address::from(va_base + (*i as u64) * x64::ARCH.page_size() as u64);
let name = pehelper::try_get_pe_name(virt_mem, probe_addr).unwrap_or_default();
name == "ntoskrnl.exe"
})
.map(|(i, _, _)| va_base + i as u64 * x64::ARCH.page_size() as u64)
.ok_or_else(|| Error::Initialization("unable to locate ntoskrnl.exe"))
}
pub fn find<T: VirtualMemory>(
virt_mem: &mut T,
start_block: &StartBlock,
) -> Result<(Address, usize)> {
debug!("x64::find: trying to find ntoskrnl.exe with page map",);
let page_map = virt_mem.virt_page_map_range(
size::mb(2),
(!0u64 - (1u64 << (start_block.arch.address_space_bits() - 1))).into(),
(!0u64).into(),
);
match page_map
.into_iter()
.flat_map(|(va, size)| size.page_chunks(va, size::mb(2)))
.filter(|&(_, size)| size == size::mb(2))
.filter_map(|(va, _)| find_with_va(virt_mem, va.as_u64()).ok())
.next()
{
Some(a) => {
let addr = Address::from(a);
let size_of_image = pehelper::try_get_pe_size(virt_mem, addr)?;
Ok((addr, size_of_image))
}
None => Err(Error::Initialization(
"x64::find: unable to locate ntoskrnl.exe with a page map",
)),
}
}

@ -0,0 +1,64 @@
use std::prelude::v1::*;
use super::pehelper;
use crate::error::{Error, Result};
use crate::kernel::StartBlock;
use log::{debug, info};
use memflow::error::PartialResultExt;
use memflow::mem::VirtualMemory;
use memflow::types::{size, Address};
use dataview::Pod;
use pelite::image::IMAGE_DOS_HEADER;
const SIZE_256MB: usize = size::mb(256);
const SIZE_8MB: usize = size::mb(8);
const SIZE_4KB: usize = size::kb(4);
// https://github.com/ufrisk/MemProcFS/blob/f2d15cf4fe4f19cfeea3dad52971fae2e491064b/vmm/vmmwininit.c#L410
pub fn find<T: VirtualMemory>(
virt_mem: &mut T,
_start_block: &StartBlock,
) -> Result<(Address, usize)> {
debug!("x86::find: trying to find ntoskrnl.exe");
for base_addr in (0..SIZE_256MB as u64).step_by(SIZE_8MB) {
let base_addr = size::gb(2) as u64 + base_addr;
// search in each page in the first 8mb chunks in the first 64mb of virtual memory
let mut buf = vec![0; SIZE_8MB];
virt_mem
.virt_read_raw_into(base_addr.into(), &mut buf)
.data_part()?;
for addr in (0..SIZE_8MB as u64).step_by(SIZE_4KB) {
// TODO: potential endian mismatch in pod
let view = Pod::as_data_view(&buf[addr as usize..]);
// check for dos header signature (MZ) // TODO: create global
if view.read::<IMAGE_DOS_HEADER>(0).e_magic != 0x5a4d {
continue;
}
if view.read::<IMAGE_DOS_HEADER>(0).e_lfanew > 0x800 {
continue;
}
let image_base = Address::from(base_addr + addr);
if let Ok(name) = pehelper::try_get_pe_name(virt_mem, image_base) {
if name == "ntoskrnl.exe" {
info!("ntoskrnl found");
// TODO: unify pe name + size
if let Ok(size_of_image) = pehelper::try_get_pe_size(virt_mem, image_base) {
return Ok((image_base, size_of_image));
}
}
}
}
}
Err(Error::Initialization(
"find_x86(): unable to locate ntoskrnl.exe in high mem",
))
}

@ -0,0 +1,75 @@
mod x64;
mod x86;
mod x86pae;
use std::prelude::v1::*;
use crate::error::{Error, Result};
use log::warn;
use memflow::architecture;
use memflow::architecture::ArchitectureObj;
use memflow::mem::PhysicalMemory;
use memflow::types::{size, Address, PhysicalAddress};
// PROCESSOR_START_BLOCK
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
pub struct StartBlock {
pub arch: ArchitectureObj,
pub kernel_hint: Address,
pub dtb: Address,
}
pub fn find_fallback<T: PhysicalMemory>(mem: &mut T, arch: ArchitectureObj) -> Result<StartBlock> {
if arch == architecture::x86::x64::ARCH {
// read low 16mb stub
let mut low16m = vec![0; size::mb(16)];
mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?;
x64::find(&low16m)
} else {
Err(Error::Initialization(
"start_block: fallback not implemented for given arch",
))
}
}
// bcdedit /set firstmegabytepolicyuseall
pub fn find<T: PhysicalMemory>(mem: &mut T, arch: Option<ArchitectureObj>) -> Result<StartBlock> {
if let Some(arch) = arch {
if arch == architecture::x86::x64::ARCH {
// read low 1mb stub
let mut low1m = vec![0; size::mb(1)];
mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low1m)?;
// find x64 dtb in low stub < 1M
match x64::find_lowstub(&low1m) {
Ok(d) => {
if d.dtb.as_u64() != 0 {
return Ok(d);
}
}
Err(e) => warn!("x64::find_lowstub() error: {}", e),
}
find_fallback(mem, arch)
} else if arch == architecture::x86::x32_pae::ARCH {
let mut low16m = vec![0; size::mb(16)];
mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?;
x86pae::find(&low16m)
} else if arch == architecture::x86::x32::ARCH {
let mut low16m = vec![0; size::mb(16)];
mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?;
x86::find(&low16m)
} else {
Err(Error::InvalidArchitecture)
}
} else {
find(mem, Some(architecture::x86::x64::ARCH))
.or_else(|_| find(mem, Some(architecture::x86::x32_pae::ARCH)))
.or_else(|_| find(mem, Some(architecture::x86::x32::ARCH)))
.map_err(|_| Error::Initialization("unable to find dtb"))
}
}

@ -0,0 +1,72 @@
use crate::error::{Error, Result};
use crate::kernel::StartBlock;
use std::convert::TryInto;
use memflow::architecture::x86::x64;
use memflow::types::{size, Address};
// https://github.com/ufrisk/MemProcFS/blob/f2d15cf4fe4f19cfeea3dad52971fae2e491064b/vmm/vmmwininit.c#L560
pub fn find_lowstub(stub: &[u8]) -> Result<StartBlock> {
Ok(stub
.chunks_exact(x64::ARCH.page_size())
.skip(1)
.filter(|c| {
(0xffff_ffff_ffff_00ff & u64::from_le_bytes(c[0..8].try_into().unwrap()))
== 0x0000_0001_0006_00E9
}) // start bytes
.filter(|c| {
(0xffff_f800_0000_0003 & u64::from_le_bytes(c[0x70..0x70 + 8].try_into().unwrap()))
== 0xffff_f800_0000_0000
}) // kernel entry
.find(|c| {
(0xffff_ff00_0000_0fff & u64::from_le_bytes(c[0xa0..0xa0 + 8].try_into().unwrap())) == 0
}) // pml4
.map(|c| StartBlock {
arch: x64::ARCH,
kernel_hint: u64::from_le_bytes(c[0x70..0x70 + 8].try_into().unwrap()).into(),
dtb: u64::from_le_bytes(c[0xa0..0xa0 + 8].try_into().unwrap()).into(),
})
.ok_or_else(|| Error::Initialization("unable to find x64 dtb in lowstub < 1M"))?)
}
fn find_pt(addr: Address, mem: &[u8]) -> Option<Address> {
// TODO: global define / config setting
let max_mem = size::gb(512) as u64;
let pte = u64::from_le_bytes(mem[0..8].try_into().unwrap());
if (pte & 0x0000_0000_0000_0087) != 0x7 || (pte & 0x0000_ffff_ffff_f000) > max_mem {
return None;
}
// Second half must have a self ref entry
// This is usually enough to filter wrong data out
mem[0x800..]
.chunks(8)
.map(|c| u64::from_le_bytes(c.try_into().unwrap()))
.find(|a| (a ^ 0x0000_0000_0000_0063) & !(1u64 << 63) == addr.as_u64())?;
// A page table does need to have some entries, right? Particularly, kernel-side page table
// entries must be marked as such
mem[0x800..]
.chunks(8)
.map(|c| u64::from_le_bytes(c.try_into().unwrap()))
.filter(|a| (a & 0xff) == 0x63)
.nth(5)?;
Some(addr)
}
pub fn find(mem: &[u8]) -> Result<StartBlock> {
mem.chunks_exact(x64::ARCH.page_size())
.enumerate()
.filter_map(|(i, c)| find_pt((i * x64::ARCH.page_size()).into(), c))
.map(|addr| StartBlock {
arch: x64::ARCH,
kernel_hint: 0.into(),
dtb: addr,
})
.next()
.ok_or_else(|| Error::Initialization("unable to find x64 dtb in lowstub < 16M"))
}

@ -0,0 +1,37 @@
use crate::error::{Error, Result};
use crate::kernel::StartBlock;
use std::convert::TryInto;
use memflow::architecture::x86::x32;
use memflow::iter::PageChunks;
use memflow::types::Address;
fn check_page(base: Address, mem: &[u8]) -> bool {
if mem[0] != 0x67 {
return false;
}
let dword = u32::from_le_bytes(mem[0xc00..0xc00 + 4].try_into().unwrap());
if (dword & 0xffff_f003) != (base.as_u32() + 0x3) {
return false;
}
matches!(mem
.iter()
.step_by(4)
.skip(0x200)
.filter(|&&x| x == 0x63 || x == 0xe3)
.count(), x if x > 16)
}
pub fn find(mem: &[u8]) -> Result<StartBlock> {
mem.page_chunks(Address::from(0), x32::ARCH.page_size())
.find(|(a, c)| check_page(*a, c))
.map(|(a, _)| StartBlock {
arch: x32::ARCH,
kernel_hint: 0.into(),
dtb: a,
})
.ok_or_else(|| Error::Initialization("unable to find x86 dtb in lowstub < 16M"))
}

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

Loading…
Cancel
Save