diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -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
diff --git a/.idea/memflow_scan.iml b/.idea/memflow_scan.iml
new file mode 100644
index 0000000..6102194
--- /dev/null
+++ b/.idea/memflow_scan.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..98c468c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..278d0b5
--- /dev/null
+++ b/Cargo.toml
@@ -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]
\ No newline at end of file
diff --git a/memflow_lib/CHANGES.md b/memflow_lib/CHANGES.md
new file mode 100644
index 0000000..e517465
--- /dev/null
+++ b/memflow_lib/CHANGES.md
@@ -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
diff --git a/memflow_lib/CONTRIBUTE.md b/memflow_lib/CONTRIBUTE.md
new file mode 100644
index 0000000..87730ac
--- /dev/null
+++ b/memflow_lib/CONTRIBUTE.md
@@ -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!
diff --git a/memflow_lib/Cargo.toml b/memflow_lib/Cargo.toml
new file mode 100644
index 0000000..938407c
--- /dev/null
+++ b/memflow_lib/Cargo.toml
@@ -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"
+]
diff --git a/memflow_lib/LICENSE b/memflow_lib/LICENSE
new file mode 100644
index 0000000..dd842a4
--- /dev/null
+++ b/memflow_lib/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2020 ko1N
+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.
\ No newline at end of file
diff --git a/memflow_lib/README.md b/memflow_lib/README.md
new file mode 100644
index 0000000..d17b228
--- /dev/null
+++ b/memflow_lib/README.md
@@ -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)
diff --git a/memflow_lib/docs/logo.png b/memflow_lib/docs/logo.png
new file mode 100644
index 0000000..80f8c1b
Binary files /dev/null and b/memflow_lib/docs/logo.png differ
diff --git a/memflow_lib/docs/windbg_cheat_sheet.txt b/memflow_lib/docs/windbg_cheat_sheet.txt
new file mode 100644
index 0000000..11e1144
--- /dev/null
+++ b/memflow_lib/docs/windbg_cheat_sheet.txt
@@ -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
+
+vtop:
+!vtop PFN VirtualAddress
+!vtop 0 VirtualAddress
diff --git a/memflow_lib/memflow-bench/.gitignore b/memflow_lib/memflow-bench/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/memflow_lib/memflow-bench/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/memflow_lib/memflow-bench/Cargo.toml b/memflow_lib/memflow-bench/Cargo.toml
new file mode 100644
index 0000000..a3c2c9a
--- /dev/null
+++ b/memflow_lib/memflow-bench/Cargo.toml
@@ -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
diff --git a/memflow_lib/memflow-bench/README.md b/memflow_lib/memflow-bench/README.md
new file mode 100644
index 0000000..e801c03
--- /dev/null
+++ b/memflow_lib/memflow-bench/README.md
@@ -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
diff --git a/memflow_lib/memflow-bench/benches/batcher.rs b/memflow_lib/memflow-bench/benches/batcher.rs
new file mode 100644
index 0000000..79046aa
--- /dev/null
+++ b/memflow_lib/memflow-bench/benches/batcher.rs
@@ -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(
+ 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(
+ 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(
+ 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(
+ 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(
+ 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);
diff --git a/memflow_lib/memflow-bench/benches/read_dummy.rs b/memflow_lib/memflow-bench/benches/read_dummy.rs
new file mode 100644
index 0000000..632781a
--- /dev/null
+++ b/memflow_lib/memflow-bench/benches/read_dummy.rs
@@ -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);
diff --git a/memflow_lib/memflow-bench/benches/read_win32.rs b/memflow_lib/memflow-bench/benches/read_win32.rs
new file mode 100644
index 0000000..666946d
--- /dev/null
+++ b/memflow_lib/memflow-bench/benches/read_win32.rs
@@ -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 {
+ 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 = {
+ 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);
diff --git a/memflow_lib/memflow-bench/src/lib.rs b/memflow_lib/memflow-bench/src/lib.rs
new file mode 100644
index 0000000..8c06806
--- /dev/null
+++ b/memflow_lib/memflow-bench/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod phys;
+pub mod vat;
+pub mod virt;
diff --git a/memflow_lib/memflow-bench/src/phys.rs b/memflow_lib/memflow-bench/src/phys.rs
new file mode 100644
index 0000000..5f9ac7d
--- /dev/null
+++ b/memflow_lib/memflow-bench/src/phys.rs
@@ -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(
+ 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(
+ 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(
+ 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(
+ group: &mut BenchmarkGroup<'_, measurement::WallTime>,
+ func_name: String,
+ cache_size: u64,
+ initialize_ctx: &dyn Fn() -> Result,
+) {
+ 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(
+ group: &mut BenchmarkGroup<'_, measurement::WallTime>,
+ func_name: String,
+ cache_size: u64,
+ initialize_ctx: &dyn Fn() -> Result,
+) {
+ 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(
+ c: &mut Criterion,
+ backend_name: &str,
+ initialize_ctx: &dyn Fn() -> Result,
+) {
+ 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(
+ c: &mut Criterion,
+ backend_name: &str,
+ initialize_ctx: &dyn Fn() -> Result,
+) {
+ 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,
+ );
+}
diff --git a/memflow_lib/memflow-bench/src/vat.rs b/memflow_lib/memflow-bench/src/vat.rs
new file mode 100644
index 0000000..72090fb
--- /dev/null
+++ b/memflow_lib/memflow-bench/src/vat.rs
@@ -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,
+ );
+}
diff --git a/memflow_lib/memflow-bench/src/virt.rs b/memflow_lib/memflow-bench/src/virt.rs
new file mode 100644
index 0000000..5c95769
--- /dev/null
+++ b/memflow_lib/memflow-bench/src/virt.rs
@@ -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(
+ 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(
+ 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,
+ );
+}
diff --git a/memflow_lib/memflow-derive/Cargo.toml b/memflow_lib/memflow-derive/Cargo.toml
new file mode 100644
index 0000000..3b3fb67
--- /dev/null
+++ b/memflow_lib/memflow-derive/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "memflow-derive"
+version = "0.1.5"
+authors = ["ko1N ", "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" }
diff --git a/memflow_lib/memflow-derive/README.md b/memflow_lib/memflow-derive/README.md
new file mode 100644
index 0000000..e2ac146
--- /dev/null
+++ b/memflow_lib/memflow-derive/README.md
@@ -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
diff --git a/memflow_lib/memflow-derive/src/lib.rs b/memflow_lib/memflow-derive/src/lib.rs
new file mode 100644
index 0000000..a4d9d0b
--- /dev/null
+++ b/memflow_lib/memflow-derive/src/lib.rs
@@ -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,
+}
+
+// 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 {
+ #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()
+}
diff --git a/memflow_lib/memflow-derive/tests/derive_test.rs b/memflow_lib/memflow-derive/tests/derive_test.rs
new file mode 100644
index 0000000..86165b7
--- /dev/null
+++ b/memflow_lib/memflow-derive/tests/derive_test.rs
@@ -0,0 +1,38 @@
+use memflow::types::byte_swap::ByteSwap;
+use memflow_derive::*;
+
+#[derive(ByteSwap)]
+struct ByteSwapDerive {
+ pub val: u32,
+}
+
+#[derive(ByteSwap)]
+struct ByteSwapDeriveGeneric {
+ pub val: T,
+}
+
+#[derive(ByteSwap)]
+struct ByteSwapDeriveWhere
+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 {
+ pub slice: [ByteSwapDeriveGeneric; 128],
+}
+
+#[test]
+pub fn compiles() {}
diff --git a/memflow_lib/memflow-ffi/.gitignore b/memflow_lib/memflow-ffi/.gitignore
new file mode 100644
index 0000000..50a5bd4
--- /dev/null
+++ b/memflow_lib/memflow-ffi/.gitignore
@@ -0,0 +1,6 @@
+/target
+**/*.rs.bk
+bindings
+**/node_modules
+**/*.out
+**/*.o
diff --git a/memflow_lib/memflow-ffi/Cargo.toml b/memflow_lib/memflow-ffi/Cargo.toml
new file mode 100644
index 0000000..d7863f1
--- /dev/null
+++ b/memflow_lib/memflow-ffi/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "memflow-ffi"
+version = "0.1.5"
+authors = ["ko1N ", "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 = []
diff --git a/memflow_lib/memflow-ffi/README.md b/memflow_lib/memflow-ffi/README.md
new file mode 100644
index 0000000..b91e52e
--- /dev/null
+++ b/memflow_lib/memflow-ffi/README.md
@@ -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
+
+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.
diff --git a/memflow_lib/memflow-ffi/binddestr.h b/memflow_lib/memflow-ffi/binddestr.h
new file mode 100644
index 0000000..3d39fea
--- /dev/null
+++ b/memflow_lib/memflow-ffi/binddestr.h
@@ -0,0 +1,69 @@
+#ifndef BINDDESTR_H
+#define BINDDESTR_H
+
+#include
+
+// Binds a particular destructor function to the type, automatically destroying it
+template
+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 \
+ 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::result_type, CLASS, FNAME)
+
+// Same, but invalidates the pointer
+#define WRAP_FN_TYPE_INVALIDATE(TYPE, CLASS, FNAME) \
+ template \
+ inline TYPE FNAME (Args... args) { \
+ return :: CLASS##_##FNAME (this->invalidate(), args...); \
+ }
+
+#define WRAP_FN_INVALIDATE(CLASS, FNAME) WRAP_FN_TYPE_INVALIDATE(std::function::result_type, CLASS, FNAME)
+
+// Wrap a C function in a raw way with specified return type
+#define WRAP_FN_RAW_TYPE(TYPE, FNAME) \
+ template \
+ 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::result_type, FNAME)
+
+#endif
diff --git a/memflow_lib/memflow-ffi/bindgen.sh b/memflow_lib/memflow-ffi/bindgen.sh
new file mode 100644
index 0000000..ed04e66
--- /dev/null
+++ b/memflow_lib/memflow-ffi/bindgen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+cargo build --release --workspace
+cbindgen --config cbindgen.toml --crate memflow-ffi --output memflow.h
diff --git a/memflow_lib/memflow-ffi/cbindgen.toml b/memflow_lib/memflow-ffi/cbindgen.toml
new file mode 100644
index 0000000..ebc3b22
--- /dev/null
+++ b/memflow_lib/memflow-ffi/cbindgen.toml
@@ -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"
diff --git a/memflow_lib/memflow-ffi/examples/Makefile b/memflow_lib/memflow-ffi/examples/Makefile
new file mode 100644
index 0000000..ec39061
--- /dev/null
+++ b/memflow_lib/memflow-ffi/examples/Makefile
@@ -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
diff --git a/memflow_lib/memflow-ffi/examples/phys_mem.c b/memflow_lib/memflow-ffi/examples/phys_mem.c
new file mode 100644
index 0000000..fb81a8a
--- /dev/null
+++ b/memflow_lib/memflow-ffi/examples/phys_mem.c
@@ -0,0 +1,35 @@
+#include "memflow.h"
+#include
+
+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;
+}
diff --git a/memflow_lib/memflow-ffi/memflow.h b/memflow_lib/memflow-ffi/memflow.h
new file mode 100644
index 0000000..7655946
--- /dev/null
+++ b/memflow_lib/memflow-ffi/memflow.h
@@ -0,0 +1,520 @@
+#ifndef MEMFLOW_H
+#define MEMFLOW_H
+
+#include
+#include
+#include
+#include
+
+/**
+ * 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 */
diff --git a/memflow_lib/memflow-ffi/memflow_cpp.h b/memflow_lib/memflow-ffi/memflow_cpp.h
new file mode 100644
index 0000000..65b5352
--- /dev/null
+++ b/memflow_lib/memflow-ffi/memflow_cpp.h
@@ -0,0 +1,177 @@
+#ifndef MEMFLOW_HLAPI_H
+#define MEMFLOW_HLAPI_H
+
+#include "memflow.h"
+#include "binddestr.h"
+
+#ifndef NO_STL_CONTAINERS
+#include
+#ifndef AUTO_STRING_SIZE
+#define AUTO_STRING_SIZE 128
+#endif
+#endif
+
+struct CConnectorInventory
+ : BindDestr
+{
+ 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
+{
+ 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
+ T phys_read(PhysicalAddress address) {
+ T data;
+ this->phys_read_raw_into(address, (uint8_t *)&data, sizeof(T));
+ return data;
+ }
+
+ template
+ int32_t phys_write(PhysicalAddress address, const T &data) {
+ return this->phys_write_raw(address, (const uint8_t *)&data, sizeof(T));
+ }
+};
+
+struct CCloneablePhysicalMemory
+ : BindDestr
+{
+ CCloneablePhysicalMemory(CloneablePhysicalMemoryObj *mem)
+ : BindDestr(mem) {}
+
+ WRAP_FN(connector, clone);
+ WRAP_FN_RAW_TYPE(CPhysicalMemory, downcast_cloneable);
+};
+
+struct CVirtualMemory
+ : BindDestr
+{
+ 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
+ T virt_read(Address address) {
+ T data;
+ this->virt_read_raw_into(address, (uint8_t *)&data, sizeof(T));
+ return data;
+ }
+
+ template
+ int32_t virt_write(Address address, const T &data) {
+ return this->virt_write_raw(address, (const uint8_t *)&data, sizeof(T));
+ }
+};
+
+struct CArchitecture
+ : BindDestr
+{
+ 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
+{
+ 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
+{
+ 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
diff --git a/memflow_lib/memflow-ffi/src/architecture/mod.rs b/memflow_lib/memflow-ffi/src/architecture/mod.rs
new file mode 100644
index 0000000..b0f2fb1
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/architecture/mod.rs
@@ -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);
+}
diff --git a/memflow_lib/memflow-ffi/src/architecture/x86.rs b/memflow_lib/memflow-ffi/src/architecture/x86.rs
new file mode 100644
index 0000000..5af59c5
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/architecture/x86.rs
@@ -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
diff --git a/memflow_lib/memflow-ffi/src/connectors/mod.rs b/memflow_lib/memflow-ffi/src/connectors/mod.rs
new file mode 100644
index 0000000..5d56888
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/connectors/mod.rs
@@ -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);
+}
diff --git a/memflow_lib/memflow-ffi/src/lib.rs b/memflow_lib/memflow-ffi/src/lib.rs
new file mode 100644
index 0000000..05f4d9d
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/lib.rs
@@ -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;
diff --git a/memflow_lib/memflow-ffi/src/log.rs b/memflow_lib/memflow-ffi/src/log.rs
new file mode 100644
index 0000000..64b5905
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/log.rs
@@ -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();
+}
diff --git a/memflow_lib/memflow-ffi/src/mem/mod.rs b/memflow_lib/memflow-ffi/src/mem/mod.rs
new file mode 100644
index 0000000..90264d3
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/mem/mod.rs
@@ -0,0 +1,2 @@
+pub mod phys_mem;
+pub mod virt_mem;
diff --git a/memflow_lib/memflow-ffi/src/mem/phys_mem.rs b/memflow_lib/memflow-ffi/src/mem/phys_mem.rs
new file mode 100644
index 0000000..f3705ce
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/mem/phys_mem.rs
@@ -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::(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::(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()
+}
diff --git a/memflow_lib/memflow-ffi/src/mem/virt_mem.rs b/memflow_lib/memflow-ffi/src/mem/virt_mem.rs
new file mode 100644
index 0000000..117b991
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/mem/virt_mem.rs
@@ -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::(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::(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()
+}
diff --git a/memflow_lib/memflow-ffi/src/process.rs b/memflow_lib/memflow-ffi/src/process.rs
new file mode 100644
index 0000000..b3228f0
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/process.rs
@@ -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);
+}
diff --git a/memflow_lib/memflow-ffi/src/types/mod.rs b/memflow_lib/memflow-ffi/src/types/mod.rs
new file mode 100644
index 0000000..760b66e
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/types/mod.rs
@@ -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()
+}
diff --git a/memflow_lib/memflow-ffi/src/util.rs b/memflow_lib/memflow-ffi/src/util.rs
new file mode 100644
index 0000000..6070fd6
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/util.rs
@@ -0,0 +1,44 @@
+use log::error;
+
+pub fn inspect_err(e: E) -> E {
+ error!("{}", e);
+ e
+}
+
+pub fn to_heap(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 ToIntResult for Result {
+ 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
+ }
+ }
+}
diff --git a/memflow_lib/memflow-ffi/src/win32.rs b/memflow_lib/memflow-ffi/src/win32.rs
new file mode 100644
index 0000000..06ebbc0
--- /dev/null
+++ b/memflow_lib/memflow-ffi/src/win32.rs
@@ -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> = 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 = 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 = 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 = std::mem::transmute(offsets);
+ // drop _offsets
+ }
+}
+*/
diff --git a/memflow_lib/memflow-qemu-procfs/.gitignore b/memflow_lib/memflow-qemu-procfs/.gitignore
new file mode 100644
index 0000000..3582899
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/.gitignore
@@ -0,0 +1,5 @@
+/target
+**/*.rs.bk
+*.swp
+.vscode
+Cargo.lock
diff --git a/memflow_lib/memflow-qemu-procfs/Cargo.toml b/memflow_lib/memflow-qemu-procfs/Cargo.toml
new file mode 100644
index 0000000..c35f691
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "memflow-qemu-procfs"
+version = "0.1.5"
+authors = ["ko1N ", "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"
diff --git a/memflow_lib/memflow-qemu-procfs/LICENSE b/memflow_lib/memflow-qemu-procfs/LICENSE
new file mode 100644
index 0000000..7c11d2d
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/LICENSE
@@ -0,0 +1,23 @@
+MIT License
+
+Copyright (c) 2020 ko1N
+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.
+
diff --git a/memflow_lib/memflow-qemu-procfs/Makefile b/memflow_lib/memflow-qemu-procfs/Makefile
new file mode 100644
index 0000000..75e3cd5
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/Makefile
@@ -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
+
diff --git a/memflow_lib/memflow-qemu-procfs/README.md b/memflow_lib/memflow-qemu-procfs/README.md
new file mode 100644
index 0000000..58571de
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/README.md
@@ -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.
diff --git a/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs b/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs
new file mode 100644
index 0000000..9e82e03
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs
@@ -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)));
+ }
+ }
+ }
+}
diff --git a/memflow_lib/memflow-qemu-procfs/install.sh b/memflow_lib/memflow-qemu-procfs/install.sh
new file mode 100644
index 0000000..f5a751c
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/install.sh
@@ -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
diff --git a/memflow_lib/memflow-qemu-procfs/src/lib.rs b/memflow_lib/memflow-qemu-procfs/src/lib.rs
new file mode 100644
index 0000000..baa1a0d
--- /dev/null
+++ b/memflow_lib/memflow-qemu-procfs/src/lib.rs
@@ -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 {
+ 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::>();
+ 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 {
+ 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 {
+ 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 {
+ // 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::(),
+ 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 {
+ 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
+ );
+ }
+}
diff --git a/memflow_lib/memflow-win32-ffi/.gitignore b/memflow_lib/memflow-win32-ffi/.gitignore
new file mode 100644
index 0000000..50a5bd4
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/.gitignore
@@ -0,0 +1,6 @@
+/target
+**/*.rs.bk
+bindings
+**/node_modules
+**/*.out
+**/*.o
diff --git a/memflow_lib/memflow-win32-ffi/Cargo.toml b/memflow_lib/memflow-win32-ffi/Cargo.toml
new file mode 100644
index 0000000..68cd1a6
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/Cargo.toml
@@ -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" }
diff --git a/memflow_lib/memflow-win32-ffi/README.md b/memflow_lib/memflow-win32-ffi/README.md
new file mode 100644
index 0000000..13bb90d
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/README.md
@@ -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
+
+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.
diff --git a/memflow_lib/memflow-win32-ffi/bindgen.sh b/memflow_lib/memflow-win32-ffi/bindgen.sh
new file mode 100644
index 0000000..337d60b
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/bindgen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+cargo build --release --workspace
+cbindgen --config cbindgen.toml --crate memflow-win32-ffi --output memflow_win32.h
diff --git a/memflow_lib/memflow-win32-ffi/cbindgen.toml b/memflow_lib/memflow-win32-ffi/cbindgen.toml
new file mode 100644
index 0000000..5a8b6d5
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/cbindgen.toml
@@ -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"]
diff --git a/memflow_lib/memflow-win32-ffi/examples/Makefile b/memflow_lib/memflow-win32-ffi/examples/Makefile
new file mode 100644
index 0000000..8b76722
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/examples/Makefile
@@ -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
diff --git a/memflow_lib/memflow-win32-ffi/examples/dump_header.c b/memflow_lib/memflow-win32-ffi/examples/dump_header.c
new file mode 100644
index 0000000..f8fd1ba
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/examples/dump_header.c
@@ -0,0 +1,61 @@
+#include "memflow_win32.h"
+#include
+
+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;
+}
diff --git a/memflow_lib/memflow-win32-ffi/examples/process_list.c b/memflow_lib/memflow-win32-ffi/examples/process_list.c
new file mode 100644
index 0000000..aa3b4a4
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/examples/process_list.c
@@ -0,0 +1,54 @@
+#include "memflow_win32.h"
+#include
+
+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;
+}
diff --git a/memflow_lib/memflow-win32-ffi/memflow_win32.h b/memflow_lib/memflow-win32-ffi/memflow_win32.h
new file mode 100644
index 0000000..a292d9c
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/memflow_win32.h
@@ -0,0 +1,321 @@
+#ifndef MEMFLOW_WIN32_H
+#define MEMFLOW_WIN32_H
+
+#include
+#include
+#include
+#include
+#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 */
diff --git a/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h b/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h
new file mode 100644
index 0000000..961f4ef
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.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
+// 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
+{
+ CWin32ModuleInfo(Win32ModuleInfo *modinfo)
+ : BindDestr(modinfo) {}
+
+ WRAP_FN_TYPE(COsProcessModuleInfo, module, info_trait);
+};
+
+struct CWin32Process
+ : BindDestr
+{
+ 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
+{
+ 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
+{
+ 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 eprocess_vec(size_t max_size) {
+ Address *buf = (Address *)malloc(sizeof(Address *) * max_size);
+ std::vector 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 eprocess_vec() {
+ return this->eprocess_vec(AUTO_VEC_SIZE);
+ }
+
+ // Manual process_info_list impl
+ std::vector process_info_vec(size_t max_size) {
+ Win32ProcessInfo **buf = (Win32ProcessInfo **)malloc(sizeof(Win32ProcessInfo *) * max_size);
+ std::vector 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 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
diff --git a/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs b/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs
new file mode 100644
index 0000000..0ef541c
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs
@@ -0,0 +1 @@
+pub mod start_block;
diff --git a/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs b/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs
new file mode 100644
index 0000000..aa20c3b
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs
@@ -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 for StartBlock {
+ fn from(o: kernel::StartBlock) -> StartBlock {
+ StartBlock {
+ kernel_hint: o.kernel_hint,
+ dtb: o.dtb,
+ }
+ }
+}
diff --git a/memflow_lib/memflow-win32-ffi/src/lib.rs b/memflow_lib/memflow-win32-ffi/src/lib.rs
new file mode 100644
index 0000000..de4ae2e
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod kernel;
+pub mod win32;
diff --git a/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs b/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs
new file mode 100644
index 0000000..cdd3ad1
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs
@@ -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, TimedCacheValidator>;
+pub(crate) type FFIVirtualTranslate = CachedVirtualTranslate;
+
+pub(crate) type FFIVirtualMemory =
+ VirtualDMA;
+
+pub type Kernel = kernel::Kernel;
+
+/// 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 = 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 = 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)
+}
diff --git a/memflow_lib/memflow-win32-ffi/src/win32/mod.rs b/memflow_lib/memflow-win32-ffi/src/win32/mod.rs
new file mode 100644
index 0000000..8157daf
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/win32/mod.rs
@@ -0,0 +1,4 @@
+pub mod kernel;
+pub mod module;
+pub mod process;
+pub mod process_info;
diff --git a/memflow_lib/memflow-win32-ffi/src/win32/module.rs b/memflow_lib/memflow-win32-ffi/src/win32/module.rs
new file mode 100644
index 0000000..6badc87
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/win32/module.rs
@@ -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);
+}
diff --git a/memflow_lib/memflow-win32-ffi/src/win32/process.rs b/memflow_lib/memflow-win32-ffi/src/win32/process.rs
new file mode 100644
index 0000000..80b3436
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/win32/process.rs
@@ -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;
+
+/// 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()
+}
diff --git a/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs b/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs
new file mode 100644
index 0000000..f1e3a7e
--- /dev/null
+++ b/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs
@@ -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);
+}
diff --git a/memflow_lib/memflow-win32/Cargo.toml b/memflow_lib/memflow-win32/Cargo.toml
new file mode 100644
index 0000000..399c3a3
--- /dev/null
+++ b/memflow_lib/memflow-win32/Cargo.toml
@@ -0,0 +1,80 @@
+[package]
+name = "memflow-win32"
+version = "0.1.5"
+authors = ["ko1N ", "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"
diff --git a/memflow_lib/memflow-win32/README.md b/memflow_lib/memflow-win32/README.md
new file mode 100644
index 0000000..ef7bf99
--- /dev/null
+++ b/memflow_lib/memflow-win32/README.md
@@ -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.
diff --git a/memflow_lib/memflow-win32/build.rs b/memflow_lib/memflow-win32/build.rs
new file mode 100644
index 0000000..580ee8a
--- /dev/null
+++ b/memflow_lib/memflow-win32/build.rs
@@ -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> {
+ 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> {
+ Ok(())
+}
+
+fn main() -> Result<(), Box> {
+ embed_offsets()?;
+ Ok(())
+}
diff --git a/memflow_lib/memflow-win32/examples/dump_offsets.rs b/memflow_lib/memflow-win32/examples/dump_offsets.rs
new file mode 100644
index 0000000..8409ab5
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/dump_offsets.rs
@@ -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");
+ }
+}
diff --git a/memflow_lib/memflow-win32/examples/generate_offsets.rs b/memflow_lib/memflow-win32/examples/generate_offsets.rs
new file mode 100644
index 0000000..42ffbb4
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/generate_offsets.rs
@@ -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::().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
+ )
+ }
+ }
+}
diff --git a/memflow_lib/memflow-win32/examples/integration.rs b/memflow_lib/memflow-win32/examples/integration.rs
new file mode 100644
index 0000000..eb3099c
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/integration.rs
@@ -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(r: &Option) -> ColoredString {
+ bool_str(r.is_some())
+}
+
+fn ok_str(r: &Result) -> 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(process: &mut Win32Process) -> Result> {
+ let modules = process.module_list();
+ println!("modules ... {}", ok_str(&modules));
+ modules
+}
+
+fn build_kernel(
+ mem: T,
+) -> Result> {
+ 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(),
+ )
+}
diff --git a/memflow_lib/memflow-win32/examples/multithreading.rs b/memflow_lib/memflow-win32/examples/multithreading.rs
new file mode 100644
index 0000000..afce665
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/multithreading.rs
@@ -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(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(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(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(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);
+}
diff --git a/memflow_lib/memflow-win32/examples/read_bench.rs b/memflow_lib/memflow-win32/examples/read_bench.rs
new file mode 100644
index 0000000..3260c67
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/read_bench.rs
@@ -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(
+ proc: &mut Win32Process,
+ 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(
+ 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 = 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(())
+}
diff --git a/memflow_lib/memflow-win32/examples/read_keys.rs b/memflow_lib/memflow-win32/examples/read_keys.rs
new file mode 100644
index 0000000..1821088
--- /dev/null
+++ b/memflow_lib/memflow-win32/examples/read_keys.rs
@@ -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));
+ }
+}
diff --git a/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml b/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml
new file mode 100644
index 0000000..3fe28d5
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml
new file mode 100644
index 0000000..ef02c48
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml
new file mode 100644
index 0000000..e66d218
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml
new file mode 100644
index 0000000..6bec991
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml b/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml
new file mode 100644
index 0000000..3c98e57
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml
@@ -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 #?
+
diff --git a/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml b/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml
new file mode 100644
index 0000000..52e6ab9
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml b/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml
new file mode 100644
index 0000000..322aa81
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml b/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml
new file mode 100644
index 0000000..8260bd7
--- /dev/null
+++ b/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml
@@ -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
diff --git a/memflow_lib/memflow-win32/src/error.rs b/memflow_lib/memflow-win32/src/error.rs
new file mode 100644
index 0000000..311eff1
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/error.rs
@@ -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 for Error {
+ fn from(error: memflow::error::Error) -> Error {
+ Error::Core(error)
+ }
+}
+
+/// Convert from flow_core::PartialError
+impl From> for Error {
+ fn from(_error: memflow::error::PartialError) -> Error {
+ Error::Core(memflow::error::Error::Partial)
+ }
+}
+
+/// Convert from pelite::Error
+impl From for Error {
+ fn from(error: pelite::Error) -> Error {
+ Error::PE(error)
+ }
+}
+
+/// Convert from str::Utf8Error
+impl From 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 = result::Result;
diff --git a/memflow_lib/memflow-win32/src/kernel/mod.rs b/memflow_lib/memflow-win32/src/kernel/mod.rs
new file mode 100644
index 0000000..30f37f0
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/mod.rs
@@ -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 {
+ 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())
+ }
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/ntos.rs b/memflow_lib/memflow-win32/src/kernel/ntos.rs
new file mode 100644
index 0000000..5c2996e
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/ntos.rs
@@ -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(
+ 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(virt_mem: &mut T, kernel_base: Address) -> Result {
+ 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 {
+ 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(
+ virt_mem: &mut T,
+ kernel_base: Address,
+) -> Result {
+ 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)
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs b/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs
new file mode 100644
index 0000000..9394959
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs
@@ -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(virt_mem: &mut T, probe_addr: Address) -> Result {
+ 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(
+ virt_mem: &mut T,
+ probe_addr: Address,
+) -> Result> {
+ 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(virt_mem: &mut T, probe_addr: Address) -> Result {
+ 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())
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs b/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs
new file mode 100644
index 0000000..b7935cf
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs
@@ -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(
+ 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(virt_mem: &mut T, va_base: u64) -> Result {
+ 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::(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(
+ 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",
+ )),
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs b/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs
new file mode 100644
index 0000000..efec044
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs
@@ -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(
+ 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::(0).e_magic != 0x5a4d {
+ continue;
+ }
+
+ if view.read::(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",
+ ))
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/start_block.rs b/memflow_lib/memflow-win32/src/kernel/start_block.rs
new file mode 100644
index 0000000..5838ae9
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/start_block.rs
@@ -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(mem: &mut T, arch: ArchitectureObj) -> Result {
+ 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(mem: &mut T, arch: Option) -> Result {
+ 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"))
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs
new file mode 100644
index 0000000..1df1e2a
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs
@@ -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 {
+ 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 {
+ // 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 {
+ 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"))
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs
new file mode 100644
index 0000000..30f0c25
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs
@@ -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 {
+ 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"))
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs
new file mode 100644
index 0000000..0657768
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs
@@ -0,0 +1,31 @@
+use crate::error::{Error, Result};
+use crate::kernel::StartBlock;
+
+use std::convert::TryInto;
+
+use memflow::architecture::x86::x32_pae;
+use memflow::iter::PageChunks;
+use memflow::types::Address;
+
+fn check_page(addr: Address, mem: &[u8]) -> bool {
+ for (i, chunk) in mem.to_vec().chunks_exact(8).enumerate() {
+ let qword = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
+ if (i < 4 && qword != addr.as_u64() + ((i as u64 * 8) << 9) + 0x1001)
+ || (i >= 4 && qword != 0)
+ {
+ return false;
+ }
+ }
+ true
+}
+
+pub fn find(mem: &[u8]) -> Result {
+ mem.page_chunks(Address::from(0), x32_pae::ARCH.page_size())
+ .find(|(a, c)| check_page(*a, c))
+ .map(|(a, _)| StartBlock {
+ arch: x32_pae::ARCH,
+ kernel_hint: 0.into(),
+ dtb: a,
+ })
+ .ok_or_else(|| Error::Initialization("unable to find x86_pae dtb in lowstub < 16M"))
+}
diff --git a/memflow_lib/memflow-win32/src/kernel/sysproc.rs b/memflow_lib/memflow-win32/src/kernel/sysproc.rs
new file mode 100644
index 0000000..ef997f0
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/kernel/sysproc.rs
@@ -0,0 +1,108 @@
+use std::prelude::v1::*;
+
+use super::ntos::pehelper;
+use super::StartBlock;
+use crate::error::{Error, Result};
+
+use std::convert::TryInto;
+
+use log::{debug, info, warn};
+
+use memflow::mem::VirtualMemory;
+use memflow::types::{size, Address};
+
+use pelite::{self, pe64::exports::Export, PeView};
+
+pub fn find(
+ virt_mem: &mut T,
+ start_block: &StartBlock,
+ ntos: Address,
+) -> Result {
+ debug!("trying to find system eprocess");
+
+ match find_exported(virt_mem, start_block, ntos) {
+ Ok(e) => return Ok(e),
+ Err(e) => warn!("{}", e),
+ }
+
+ match find_in_section(virt_mem, start_block, ntos) {
+ Ok(e) => return Ok(e),
+ Err(e) => warn!("{}", e),
+ }
+
+ Err(Error::Initialization("unable to find system eprocess"))
+}
+
+// find from exported symbol
+pub fn find_exported(
+ virt_mem: &mut T,
+ start_block: &StartBlock,
+ kernel_base: Address,
+) -> Result {
+ // PsInitialSystemProcess -> PsActiveProcessHead
+ let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?;
+ let pe = PeView::from_bytes(&image).map_err(Error::PE)?;
+
+ let sys_proc = match pe
+ .get_export_by_name("PsInitialSystemProcess")
+ .map_err(Error::PE)?
+ {
+ Export::Symbol(s) => kernel_base + *s as usize,
+ Export::Forward(_) => {
+ return Err(Error::Other(
+ "PsInitialSystemProcess found but it was a forwarded export",
+ ))
+ }
+ };
+ info!("PsInitialSystemProcess found at 0x{:x}", sys_proc);
+
+ // read containing value
+ let mut buf = vec![0u8; start_block.arch.size_addr()];
+ let sys_proc_addr: Address = match start_block.arch.bits() {
+ 64 => {
+ // TODO: replace by virt_read_into with ByteSwap
+ virt_mem.virt_read_raw_into(sys_proc, &mut buf)?;
+ u64::from_le_bytes(buf[0..8].try_into().unwrap()).into()
+ }
+ 32 => {
+ // TODO: replace by virt_read_into with ByteSwap
+ virt_mem.virt_read_raw_into(sys_proc, &mut buf)?;
+ u32::from_le_bytes(buf[0..4].try_into().unwrap()).into()
+ }
+ _ => return Err(Error::InvalidArchitecture),
+ };
+ Ok(sys_proc_addr)
+}
+
+// scan in pdb
+
+// scan in section
+pub fn find_in_section(
+ virt_mem: &mut T,
+ _start_block: &StartBlock,
+ ntos: Address,
+) -> Result {
+ // find section ALMOSTRO
+ // scan for va of system process (dtb.va)
+ // ... check if its 32 or 64bit
+
+ let mut header_buf = vec![0; size::mb(32)];
+ virt_mem.virt_read_raw_into(ntos, &mut header_buf)?;
+
+ /*
+ let mut pe_opts = ParseOptions::default();
+ pe_opts.resolve_rva = false;
+
+ let header = PE::parse_with_opts(&header_buf, &pe_opts).unwrap(); // TODO: error
+ let _sect = header
+ .sections
+ .iter()
+ .filter(|s| String::from_utf8(s.name.to_vec()).unwrap_or_default() == "ALMOSTRO")
+ .nth(0)
+ .ok_or_else(|| Error::new("unable to find section ALMOSTRO"))?;
+ */
+
+ Err(Error::Other(
+ "sysproc::find_in_section(): not implemented yet",
+ ))
+}
diff --git a/memflow_lib/memflow-win32/src/lib.rs b/memflow_lib/memflow-win32/src/lib.rs
new file mode 100644
index 0000000..389d797
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/lib.rs
@@ -0,0 +1,28 @@
+/*!
+This crate contains memflow's win32 implementation.
+It is used to interface with windows targets.
+*/
+
+#![cfg_attr(not(feature = "std"), no_std)]
+extern crate no_std_compat as std;
+
+pub mod error;
+
+pub mod kernel;
+
+pub mod offsets;
+
+pub mod win32;
+
+pub mod prelude {
+ pub mod v1 {
+ pub use crate::error::*;
+ pub use crate::kernel::*;
+ pub use crate::offsets::*;
+ pub use crate::win32::*;
+ }
+ pub use v1::*;
+}
+
+#[deprecated]
+pub use prelude::v1::*;
diff --git a/memflow_lib/memflow-win32/src/offsets/builder.rs b/memflow_lib/memflow-win32/src/offsets/builder.rs
new file mode 100644
index 0000000..853243f
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/builder.rs
@@ -0,0 +1,193 @@
+use std::convert::TryFrom;
+
+#[cfg(feature = "symstore")]
+use super::symstore::SymbolStore;
+
+use super::offset_table::Win32OffsetFile;
+use super::{Win32Offsets, Win32OffsetsArchitecture};
+
+use crate::error::{Error, Result};
+use crate::kernel::{Win32GUID, Win32Version};
+use crate::win32::KernelInfo;
+
+#[repr(align(16))]
+struct Align16(pub T);
+
+#[cfg(feature = "embed_offsets")]
+const WIN32_OFFSETS: Align16<
+ [u8; include_bytes!(concat!(env!("OUT_DIR"), "/win32_offsets.bin")).len()],
+> = Align16(*include_bytes!(concat!(
+ env!("OUT_DIR"),
+ "/win32_offsets.bin"
+)));
+
+pub struct Win32OffsetBuilder {
+ #[cfg(feature = "symstore")]
+ symbol_store: Option,
+
+ guid: Option,
+ winver: Option,
+ arch: Option,
+}
+
+impl Default for Win32OffsetBuilder {
+ fn default() -> Self {
+ Self {
+ #[cfg(feature = "symstore")]
+ symbol_store: Some(SymbolStore::default()),
+
+ guid: None,
+ winver: None,
+ arch: None,
+ }
+ }
+}
+
+impl Win32OffsetBuilder {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn build(self) -> Result {
+ if self.guid.is_none() && self.winver.is_none() {
+ return Err(Error::Other(
+ "building win32 offsets requires either a guid or winver",
+ ));
+ }
+
+ // try to build via symbol store
+ if let Ok(offs) = self.build_with_symbol_store() {
+ return Ok(offs);
+ }
+
+ // use static offset list
+ if let Ok(offs) = self.build_with_offset_list() {
+ return Ok(offs);
+ }
+
+ Err(Error::Other("not found"))
+ }
+
+ #[cfg(feature = "embed_offsets")]
+ fn build_with_offset_list(&self) -> Result {
+ // # Safety
+ // Struct padding and alignment is compile-time guaranteed by the struct (see mod offset_table).
+ let offsets: [Win32OffsetFile;
+ WIN32_OFFSETS.0.len() / std::mem::size_of::()] =
+ unsafe { std::mem::transmute(WIN32_OFFSETS.0) };
+
+ // Try matching exact guid
+ if let Some(target_guid) = &self.guid {
+ for offset in offsets.iter() {
+ if let (Ok(file), Ok(guid)) = (
+ <&str>::try_from(&offset.pdb_file_name),
+ <&str>::try_from(&offset.pdb_guid),
+ ) {
+ if target_guid.file_name == file && target_guid.guid == guid {
+ return Ok(Win32Offsets {
+ 0: offset.offsets.clone(),
+ });
+ }
+ }
+ }
+ }
+
+ let mut closest_match = None;
+ let mut prev_build_number = 0;
+
+ // Try matching the newest build from that version that is not actually newer
+ if let (Some(winver), Some(arch)) = (&self.winver, self.arch) {
+ for offset in offsets.iter() {
+ if winver.major_version() == offset.nt_major_version
+ && winver.minor_version() == offset.nt_minor_version
+ && winver.build_number() >= offset.nt_build_number
+ && prev_build_number <= offset.nt_build_number
+ && arch == offset.arch
+ {
+ prev_build_number = offset.nt_build_number;
+ closest_match = Some(Win32Offsets {
+ 0: offset.offsets.clone(),
+ });
+ }
+ }
+
+ if prev_build_number != winver.build_number() {
+ log::warn!(
+ "no exact build number ({}) found! Closest match: {}",
+ winver.build_number(),
+ prev_build_number
+ );
+ }
+ }
+
+ closest_match.ok_or(Error::Other("not found"))
+ }
+
+ #[cfg(not(feature = "embed_offsets"))]
+ fn build_with_offset_list(&self) -> Result {
+ Err(Error::Other(
+ "embed offsets feature is deactivated on compilation",
+ ))
+ }
+
+ #[cfg(feature = "symstore")]
+ fn build_with_symbol_store(&self) -> Result {
+ if let Some(store) = &self.symbol_store {
+ if self.guid.is_some() {
+ let pdb = store.load(self.guid.as_ref().unwrap())?;
+ Win32Offsets::from_pdb_slice(&pdb[..])
+ } else {
+ Err(Error::Other("symbol store can only be used with a guid"))
+ }
+ } else {
+ Err(Error::Other("symbol store is disabled"))
+ }
+ }
+
+ #[cfg(not(feature = "symstore"))]
+ fn build_with_symbol_store(&self) -> Result {
+ Err(Error::Other(
+ "symbol store is deactivated via a compilation feature",
+ ))
+ }
+
+ #[cfg(feature = "symstore")]
+ pub fn symbol_store(mut self, symbol_store: SymbolStore) -> Self {
+ self.symbol_store = Some(symbol_store);
+ self
+ }
+
+ #[cfg(feature = "symstore")]
+ pub fn no_symbol_store(mut self) -> Self {
+ self.symbol_store = None;
+ self
+ }
+
+ pub fn guid(mut self, guid: Win32GUID) -> Self {
+ self.guid = Some(guid);
+ self
+ }
+
+ pub fn winver(mut self, winver: Win32Version) -> Self {
+ self.winver = Some(winver);
+ self
+ }
+
+ pub fn arch(mut self, arch: Win32OffsetsArchitecture) -> Self {
+ self.arch = Some(arch);
+ self
+ }
+
+ pub fn kernel_info(mut self, kernel_info: &KernelInfo) -> Self {
+ if self.guid.is_none() {
+ self.guid = kernel_info.kernel_guid.clone();
+ }
+ if self.winver.is_none() {
+ self.winver = Some(kernel_info.kernel_winver);
+ }
+ if self.arch.is_none() {
+ self.arch = Some(kernel_info.start_block.arch.into());
+ }
+ self
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/offsets/mod.rs b/memflow_lib/memflow-win32/src/offsets/mod.rs
new file mode 100644
index 0000000..2918310
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/mod.rs
@@ -0,0 +1,330 @@
+pub mod builder;
+pub use builder::Win32OffsetBuilder;
+
+#[cfg(feature = "symstore")]
+pub mod pdb_struct;
+#[cfg(feature = "symstore")]
+pub mod symstore;
+
+pub mod offset_table;
+#[doc(hidden)]
+pub use offset_table::{Win32OffsetFile, Win32OffsetTable, Win32OffsetsArchitecture};
+
+#[cfg(feature = "symstore")]
+pub use {pdb_struct::PdbStruct, symstore::*};
+
+use std::prelude::v1::*;
+
+#[cfg(feature = "std")]
+use std::{fs::File, io::Read, path::Path};
+
+use crate::error::{Error, Result};
+use crate::kernel::Win32GUID;
+use memflow::architecture::{self, ArchitectureObj};
+
+#[derive(Debug, Copy, Clone)]
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
+pub struct Win32ArchOffsets {
+ pub peb_ldr: usize, // _PEB::Ldr
+ pub ldr_list: usize, // _PEB_LDR_DATA::InLoadOrderModuleList
+ pub ldr_data_base: usize, // _LDR_DATA_TABLE_ENTRY::DllBase
+ pub ldr_data_size: usize, // _LDR_DATA_TABLE_ENTRY::SizeOfImage
+ pub ldr_data_full_name: usize, // _LDR_DATA_TABLE_ENTRY::FullDllName
+ pub ldr_data_base_name: usize, // _LDR_DATA_TABLE_ENTRY::BaseDllName
+}
+
+pub const X86: Win32ArchOffsets = Win32ArchOffsets {
+ peb_ldr: 0xc,
+ ldr_list: 0xc,
+ ldr_data_base: 0x18,
+ ldr_data_size: 0x20,
+ ldr_data_full_name: 0x24,
+ ldr_data_base_name: 0x2c,
+};
+
+pub const X64: Win32ArchOffsets = Win32ArchOffsets {
+ peb_ldr: 0x18,
+ ldr_list: 0x10,
+ ldr_data_base: 0x30,
+ ldr_data_size: 0x40,
+ ldr_data_full_name: 0x48,
+ ldr_data_base_name: 0x58,
+};
+
+impl Win32OffsetsArchitecture {
+ #[inline]
+ fn offsets(&self) -> &'static Win32ArchOffsets {
+ match self {
+ Win32OffsetsArchitecture::X64 => &X64,
+ Win32OffsetsArchitecture::X86 => &X86,
+ Win32OffsetsArchitecture::AArch64 => panic!("Not implemented"),
+ }
+ }
+}
+
+impl From for Win32ArchOffsets {
+ fn from(arch: ArchitectureObj) -> Win32ArchOffsets {
+ *Win32OffsetsArchitecture::from(arch).offsets()
+ }
+}
+
+#[repr(transparent)]
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
+pub struct Win32Offsets(pub Win32OffsetTable);
+
+impl From for Win32Offsets {
+ fn from(other: Win32OffsetTable) -> Self {
+ Self { 0: other }
+ }
+}
+
+impl From for Win32OffsetTable {
+ fn from(other: Win32Offsets) -> Self {
+ other.0
+ }
+}
+
+impl From for Win32OffsetsArchitecture {
+ fn from(arch: ArchitectureObj) -> Win32OffsetsArchitecture {
+ if arch == architecture::x86::x32::ARCH || arch == architecture::x86::x32_pae::ARCH {
+ Self::X86
+ } else if arch == architecture::x86::x64::ARCH {
+ Self::X64
+ } else {
+ // We do not have AArch64, but that is in the plans...
+ panic!("Invalid architecture specified")
+ }
+ }
+}
+
+impl Win32Offsets {
+ #[cfg(feature = "symstore")]
+ pub fn from_pdb>(pdb_path: P) -> Result {
+ let mut file = File::open(pdb_path)
+ .map_err(|_| Error::PDB("unable to open user-supplied pdb file"))?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer)
+ .map_err(|_| Error::PDB("unable to read user-supplied pdb file"))?;
+ Self::from_pdb_slice(&buffer[..])
+ }
+
+ #[cfg(feature = "symstore")]
+ pub fn from_pdb_slice(pdb_slice: &[u8]) -> Result {
+ let list = PdbStruct::with(pdb_slice, "_LIST_ENTRY")
+ .map_err(|_| Error::PDB("_LIST_ENTRY not found"))?;
+ let kproc = PdbStruct::with(pdb_slice, "_KPROCESS")
+ .map_err(|_| Error::PDB("_KPROCESS not found"))?;
+ let eproc = PdbStruct::with(pdb_slice, "_EPROCESS")
+ .map_err(|_| Error::PDB("_EPROCESS not found"))?;
+ let ethread =
+ PdbStruct::with(pdb_slice, "_ETHREAD").map_err(|_| Error::PDB("_ETHREAD not found"))?;
+ let kthread =
+ PdbStruct::with(pdb_slice, "_KTHREAD").map_err(|_| Error::PDB("_KTHREAD not found"))?;
+ let teb = PdbStruct::with(pdb_slice, "_TEB").map_err(|_| Error::PDB("_TEB not found"))?;
+
+ let list_blink = list
+ .find_field("Blink")
+ .ok_or_else(|| Error::PDB("_LIST_ENTRY::Blink not found"))?
+ .offset as _;
+
+ let eproc_link = eproc
+ .find_field("ActiveProcessLinks")
+ .ok_or_else(|| Error::PDB("_EPROCESS::ActiveProcessLinks not found"))?
+ .offset as _;
+
+ let kproc_dtb = kproc
+ .find_field("DirectoryTableBase")
+ .ok_or_else(|| Error::PDB("_KPROCESS::DirectoryTableBase not found"))?
+ .offset as _;
+ let eproc_pid = eproc
+ .find_field("UniqueProcessId")
+ .ok_or_else(|| Error::PDB("_EPROCESS::UniqueProcessId not found"))?
+ .offset as _;
+ let eproc_name = eproc
+ .find_field("ImageFileName")
+ .ok_or_else(|| Error::PDB("_EPROCESS::ImageFileName not found"))?
+ .offset as _;
+ let eproc_peb = eproc
+ .find_field("Peb")
+ .ok_or_else(|| Error::PDB("_EPROCESS::Peb not found"))?
+ .offset as _;
+ let eproc_section_base = eproc
+ .find_field("SectionBaseAddress")
+ .ok_or_else(|| Error::PDB("_EPROCESS::SectionBaseAddress not found"))?
+ .offset as _;
+ let eproc_exit_status = eproc
+ .find_field("ExitStatus")
+ .ok_or_else(|| Error::PDB("_EPROCESS::ExitStatus not found"))?
+ .offset as _;
+ let eproc_thread_list = eproc
+ .find_field("ThreadListHead")
+ .ok_or_else(|| Error::PDB("_EPROCESS::ThreadListHead not found"))?
+ .offset as _;
+
+ // windows 10 uses an uppercase W whereas older windows versions (windows 7) uses a lowercase w
+ let eproc_wow64 = match eproc
+ .find_field("WoW64Process")
+ .or_else(|| eproc.find_field("Wow64Process"))
+ {
+ Some(f) => f.offset as _,
+ None => 0,
+ };
+
+ // threads
+ let kthread_teb = kthread
+ .find_field("Teb")
+ .ok_or_else(|| Error::PDB("_KTHREAD::Teb not found"))?
+ .offset as _;
+ let ethread_list_entry = ethread
+ .find_field("ThreadListEntry")
+ .ok_or_else(|| Error::PDB("_ETHREAD::ThreadListEntry not found"))?
+ .offset as _;
+ let teb_peb = teb
+ .find_field("ProcessEnvironmentBlock")
+ .ok_or_else(|| Error::PDB("_TEB::ProcessEnvironmentBlock not found"))?
+ .offset as _;
+ let teb_peb_x86 = if let Ok(teb32) =
+ PdbStruct::with(pdb_slice, "_TEB32").map_err(|_| Error::PDB("_TEB32 not found"))
+ {
+ teb32
+ .find_field("ProcessEnvironmentBlock")
+ .ok_or_else(|| Error::PDB("_TEB32::ProcessEnvironmentBlock not found"))?
+ .offset as _
+ } else {
+ 0
+ };
+
+ Ok(Self {
+ 0: Win32OffsetTable {
+ list_blink,
+ eproc_link,
+
+ kproc_dtb,
+
+ eproc_pid,
+ eproc_name,
+ eproc_peb,
+ eproc_section_base,
+ eproc_exit_status,
+ eproc_thread_list,
+ eproc_wow64,
+
+ kthread_teb,
+ ethread_list_entry,
+ teb_peb,
+ teb_peb_x86,
+ },
+ })
+ }
+
+ /// _LIST_ENTRY::Blink offset
+ pub fn list_blink(&self) -> usize {
+ self.0.list_blink as usize
+ }
+ /// _LIST_ENTRY::Flink offset
+ pub fn eproc_link(&self) -> usize {
+ self.0.eproc_link as usize
+ }
+
+ /// _KPROCESS::DirectoryTableBase offset
+ /// Exists since version 3.10
+ pub fn kproc_dtb(&self) -> usize {
+ self.0.kproc_dtb as usize
+ }
+ /// _EPROCESS::UniqueProcessId offset
+ /// Exists since version 3.10
+ pub fn eproc_pid(&self) -> usize {
+ self.0.eproc_pid as usize
+ }
+ /// _EPROCESS::ImageFileName offset
+ /// Exists since version 3.10
+ pub fn eproc_name(&self) -> usize {
+ self.0.eproc_name as usize
+ }
+ /// _EPROCESS::Peb offset
+ /// Exists since version 5.10
+ pub fn eproc_peb(&self) -> usize {
+ self.0.eproc_peb as usize
+ }
+ /// _EPROCESS::SectionBaseAddress offset
+ /// Exists since version 3.10
+ pub fn eproc_section_base(&self) -> usize {
+ self.0.eproc_section_base as usize
+ }
+ /// _EPROCESS::ExitStatus offset
+ /// Exists since version 3.10
+ pub fn eproc_exit_status(&self) -> usize {
+ self.0.eproc_exit_status as usize
+ }
+ /// _EPROCESS::ThreadListHead offset
+ /// Exists since version 5.10
+ pub fn eproc_thread_list(&self) -> usize {
+ self.0.eproc_thread_list as usize
+ }
+ /// _EPROCESS::WoW64Process offset
+ /// Exists since version 5.0
+ pub fn eproc_wow64(&self) -> usize {
+ self.0.eproc_wow64 as usize
+ }
+
+ /// _KTHREAD::Teb offset
+ /// Exists since version 6.2
+ pub fn kthread_teb(&self) -> usize {
+ self.0.kthread_teb as usize
+ }
+ /// _ETHREAD::ThreadListEntry offset
+ /// Exists since version 6.2
+ pub fn ethread_list_entry(&self) -> usize {
+ self.0.ethread_list_entry as usize
+ }
+ /// _TEB::ProcessEnvironmentBlock offset
+ /// Exists since version x.x
+ pub fn teb_peb(&self) -> usize {
+ self.0.teb_peb as usize
+ }
+ /// _TEB32::ProcessEnvironmentBlock offset
+ /// Exists since version x.x
+ pub fn teb_peb_x86(&self) -> usize {
+ self.0.teb_peb_x86 as usize
+ }
+
+ pub fn builder() -> Win32OffsetBuilder {
+ Win32OffsetBuilder::default()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn download_pdb() {
+ let guid = Win32GUID {
+ file_name: "ntkrnlmp.pdb".to_string(),
+ guid: "3844DBB920174967BE7AA4A2C20430FA2".to_string(),
+ };
+ let offsets = Win32Offsets::builder()
+ .symbol_store(SymbolStore::new().no_cache())
+ .guid(guid)
+ .build()
+ .unwrap();
+
+ assert_eq!(offsets.0.list_blink, 8);
+ assert_eq!(offsets.0.eproc_link, 392);
+
+ assert_eq!(offsets.0.kproc_dtb, 40);
+
+ assert_eq!(offsets.0.eproc_pid, 384);
+ assert_eq!(offsets.0.eproc_name, 736);
+ assert_eq!(offsets.0.eproc_peb, 824);
+ assert_eq!(offsets.0.eproc_thread_list, 776);
+ assert_eq!(offsets.0.eproc_wow64, 800);
+
+ assert_eq!(offsets.0.kthread_teb, 184);
+ assert_eq!(offsets.0.ethread_list_entry, 1056);
+ assert_eq!(offsets.0.teb_peb, 96);
+ assert_eq!(offsets.0.teb_peb_x86, 48);
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/offsets/offset_table.rs b/memflow_lib/memflow-win32/src/offsets/offset_table.rs
new file mode 100644
index 0000000..f79dfa5
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/offset_table.rs
@@ -0,0 +1,192 @@
+use std::prelude::v1::*;
+
+use dataview::Pod;
+use std::convert::TryFrom;
+use std::str;
+
+/// Describes an offset file.
+/// At compile time this crate will create a binary blob of all
+/// TOML files contained in the memflow-win32/offsets/ folder
+/// and merge the byte buffer directly into the build.
+///
+/// This byte buffer is then transmuted back into a slice of
+/// Win32OffsetFile structs and parsed as a backup in case
+/// no symbol store is available.
+///
+/// To get loaded properly this struct guarantees a certain alignment and no padding.
+/// This is enforced due to a compile time assert as well as the Pod derive itself.
+/// Especially in the case of cross-compilation where the target architecture
+/// is different from the architecture memflow is built with this could give potential issues.
+///
+// # Safety
+// This struct guarantees that it does not contain any padding.
+#[repr(C, align(4))]
+#[derive(Clone, Pod)]
+#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
+pub struct Win32OffsetFile {
+ // Win32GUID
+ #[cfg_attr(feature = "serde", serde(default))]
+ pub pdb_file_name: BinaryString,
+ #[cfg_attr(feature = "serde", serde(default))]
+ pub pdb_guid: BinaryString,
+
+ // Win32Version
+ pub nt_major_version: u32,
+ pub nt_minor_version: u32,
+ pub nt_build_number: u32,
+
+ // Architecture
+ pub arch: Win32OffsetsArchitecture,
+
+ pub offsets: Win32OffsetTable,
+}
+
+const _: [(); std::mem::size_of::<[Win32OffsetFile; 16]>()] =
+ [(); 16 * std::mem::size_of::()];
+
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
+pub enum Win32OffsetsArchitecture {
+ X86 = 0,
+ X64 = 1,
+ AArch64 = 2,
+}
+
+impl std::fmt::Display for Win32OffsetsArchitecture {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+unsafe impl Pod for Win32OffsetsArchitecture {}
+
+// TODO: use const-generics here once they are fully stabilized
+#[derive(Clone)]
+pub struct BinaryString(pub [u8; 128]);
+
+impl Default for BinaryString {
+ fn default() -> Self {
+ (&[][..]).into()
+ }
+}
+
+impl<'a> From<&'a [u8]> for BinaryString {
+ fn from(other: &'a [u8]) -> Self {
+ let mut arr = [0; 128];
+
+ arr[..other.len()].copy_from_slice(other);
+
+ Self { 0: arr }
+ }
+}
+
+impl<'a> TryFrom<&'a BinaryString> for &'a str {
+ type Error = std::str::Utf8Error;
+ fn try_from(other: &'a BinaryString) -> Result {
+ Ok(str::from_utf8(&other.0)?
+ .split_terminator('\0')
+ .next()
+ .unwrap())
+ }
+}
+
+impl<'a> From<&'a str> for BinaryString {
+ fn from(other: &'a str) -> Self {
+ let mut arr = [0; 128];
+
+ arr[..other.len()].copy_from_slice(other.as_bytes());
+
+ Self { 0: arr }
+ }
+}
+
+impl From for BinaryString {
+ fn from(other: String) -> Self {
+ Self::from(other.as_str())
+ }
+}
+
+unsafe impl Pod for BinaryString {}
+
+#[cfg(feature = "serde")]
+impl ::serde::Serialize for BinaryString {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: ::serde::Serializer,
+ {
+ serializer.serialize_str(
+ <&str>::try_from(self)
+ .map_err(|_| ::serde::ser::Error::custom("invalid UTF-8 characters"))?,
+ )
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> ::serde::de::Deserialize<'de> for BinaryString {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: ::serde::de::Deserializer<'de>,
+ {
+ struct BinaryStringVisitor;
+
+ impl<'de> ::serde::de::Visitor<'de> for BinaryStringVisitor {
+ type Value = [u8; 128];
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a string containing json data")
+ }
+
+ fn visit_str(self, v: &str) -> Result
+ where
+ E: ::serde::de::Error,
+ {
+ // unfortunately we lose some typed information
+ // from errors deserializing the json string
+ let mut result = [0u8; 128];
+
+ result[..v.len()].copy_from_slice(v.as_bytes());
+
+ Ok(result)
+ }
+ }
+
+ // use our visitor to deserialize an `ActualValue`
+ let inner: [u8; 128] = deserializer.deserialize_any(BinaryStringVisitor)?;
+ Ok(Self { 0: inner })
+ }
+}
+
+#[repr(C, align(4))]
+#[derive(Debug, Clone, Pod)]
+#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
+pub struct Win32OffsetTable {
+ pub list_blink: u32,
+ pub eproc_link: u32,
+
+ /// Since version 3.10
+ pub kproc_dtb: u32,
+ /// Since version 3.10
+ pub eproc_pid: u32,
+ /// Since version 3.10
+ pub eproc_name: u32,
+ /// Since version 5.10
+ pub eproc_peb: u32,
+ /// Since version 3.10
+ pub eproc_section_base: u32,
+ /// Since version 3.10
+ pub eproc_exit_status: u32,
+ /// Since version 5.10
+ pub eproc_thread_list: u32,
+ /// Since version 5.0
+ pub eproc_wow64: u32,
+
+ /// Since version 6.2
+ pub kthread_teb: u32,
+ /// Since version 6.2
+ pub ethread_list_entry: u32,
+ /// Since version x.x
+ pub teb_peb: u32,
+ /// Since version x.x
+ pub teb_peb_x86: u32,
+}
diff --git a/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs b/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs
new file mode 100644
index 0000000..aa3bf49
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs
@@ -0,0 +1,150 @@
+mod data;
+
+use std::prelude::v1::*;
+
+use data::TypeSet;
+use std::collections::HashMap;
+use std::{fmt, io, result};
+
+use pdb::{FallibleIterator, Result, Source, SourceSlice, SourceView, TypeData, PDB};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct PdbField {
+ pub type_name: String,
+ pub offset: usize,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct PdbStruct {
+ field_map: HashMap,
+}
+
+impl PdbStruct {
+ pub fn with(pdb_slice: &[u8], class_name: &str) -> Result {
+ let pdb_buffer = PdbSourceBuffer::new(pdb_slice);
+ let mut pdb = PDB::open(pdb_buffer)?;
+
+ let type_information = pdb.type_information()?;
+ let mut type_finder = type_information.finder();
+
+ let mut needed_types = TypeSet::new();
+ let mut data = data::Data::new();
+
+ let mut type_iter = type_information.iter();
+ while let Some(typ) = type_iter.next()? {
+ // keep building the index
+ type_finder.update(&type_iter);
+
+ if let Ok(TypeData::Class(class)) = typ.parse() {
+ if class.name.as_bytes() == class_name.as_bytes()
+ && !class.properties.forward_reference()
+ {
+ data.add(&type_finder, typ.index(), &mut needed_types)?;
+ break;
+ }
+ }
+ }
+
+ // add all the needed types iteratively until we're done
+ loop {
+ // get the last element in needed_types without holding an immutable borrow
+ let last = match needed_types.iter().next_back() {
+ Some(n) => Some(*n),
+ None => None,
+ };
+
+ if let Some(type_index) = last {
+ // remove it
+ needed_types.remove(&type_index);
+
+ // add the type
+ data.add(&type_finder, type_index, &mut needed_types)?;
+ } else {
+ break;
+ }
+ }
+
+ let mut field_map = HashMap::new();
+ for class in &data.classes {
+ class.fields.iter().for_each(|f| {
+ field_map.insert(
+ f.name.to_string().into_owned(),
+ PdbField {
+ type_name: f.type_name.clone(),
+ offset: f.offset as usize,
+ },
+ );
+ });
+ }
+
+ Ok(Self { field_map })
+ }
+
+ pub fn find_field(&self, name: &str) -> Option<&PdbField> {
+ self.field_map.get(name)
+ }
+}
+
+pub struct PdbSourceBuffer<'a> {
+ bytes: &'a [u8],
+}
+
+impl<'a> PdbSourceBuffer<'a> {
+ pub fn new(bytes: &'a [u8]) -> Self {
+ Self { bytes }
+ }
+}
+
+impl<'a> fmt::Debug for PdbSourceBuffer<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "PdbSourceBuffer({} bytes)", self.bytes.len())
+ }
+}
+
+impl<'a, 's> Source<'s> for PdbSourceBuffer<'a> {
+ fn view(
+ &mut self,
+ slices: &[SourceSlice],
+ ) -> result::Result>, io::Error> {
+ let len = slices.iter().fold(0 as usize, |acc, s| acc + s.size);
+
+ let mut v = PdbSourceBufferView {
+ bytes: Vec::with_capacity(len),
+ };
+ v.bytes.resize(len, 0);
+
+ let bytes = v.bytes.as_mut_slice();
+ let mut output_offset: usize = 0;
+ for slice in slices {
+ bytes[output_offset..(output_offset + slice.size)].copy_from_slice(
+ &self.bytes[slice.offset as usize..(slice.offset as usize + slice.size)],
+ );
+ output_offset += slice.size;
+ }
+
+ Ok(Box::new(v))
+ }
+}
+
+#[derive(Clone)]
+struct PdbSourceBufferView {
+ bytes: Vec,
+}
+
+impl fmt::Debug for PdbSourceBufferView {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "PdbSourceBufferView({} bytes)", self.bytes.len())
+ }
+}
+
+impl SourceView<'_> for PdbSourceBufferView {
+ fn as_slice(&self) -> &[u8] {
+ self.bytes.as_slice()
+ }
+}
+
+impl Drop for PdbSourceBufferView {
+ fn drop(&mut self) {
+ // no-op
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs b/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs
new file mode 100644
index 0000000..98b7827
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs
@@ -0,0 +1,445 @@
+// https://github.com/willglynn/pdb/blob/master/examples/pdb2hpp.rs
+
+use std::prelude::v1::*;
+
+use log::{info, trace};
+use std::collections::BTreeSet;
+
+pub type TypeSet = BTreeSet;
+
+pub fn type_name<'p>(
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+) -> pdb::Result {
+ let mut name = match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::Primitive(data) => {
+ let mut name = match data.kind {
+ pdb::PrimitiveKind::Void => "void".to_string(),
+ pdb::PrimitiveKind::Char => "char".to_string(),
+ pdb::PrimitiveKind::UChar => "unsigned char".to_string(),
+
+ pdb::PrimitiveKind::I8 => "int8_t".to_string(),
+ pdb::PrimitiveKind::U8 => "uint8_t".to_string(),
+ pdb::PrimitiveKind::I16 => "int16_t".to_string(),
+ pdb::PrimitiveKind::U16 => "uint16_t".to_string(),
+ pdb::PrimitiveKind::I32 => "int32_t".to_string(),
+ pdb::PrimitiveKind::U32 => "uint32_t".to_string(),
+ pdb::PrimitiveKind::I64 => "int64_t".to_string(),
+ pdb::PrimitiveKind::U64 => "uint64_t".to_string(),
+
+ pdb::PrimitiveKind::F32 => "float".to_string(),
+ pdb::PrimitiveKind::F64 => "double".to_string(),
+
+ pdb::PrimitiveKind::Bool8 => "bool".to_string(),
+
+ _ => format!("unhandled_primitive.kind /* {:?} */", data.kind),
+ };
+
+ if data.indirection.is_some() {
+ name.push_str(" *");
+ }
+
+ name
+ }
+
+ pdb::TypeData::Class(data) => {
+ needed_types.insert(type_index);
+ data.name.to_string().into_owned()
+ }
+
+ pdb::TypeData::Enumeration(data) => {
+ needed_types.insert(type_index);
+ data.name.to_string().into_owned()
+ }
+
+ pdb::TypeData::Union(data) => {
+ needed_types.insert(type_index);
+ data.name.to_string().into_owned()
+ }
+
+ pdb::TypeData::Pointer(data) => format!(
+ "{}*",
+ type_name(type_finder, data.underlying_type, needed_types)?
+ ),
+
+ pdb::TypeData::Modifier(data) => {
+ if data.constant {
+ format!(
+ "const {}",
+ type_name(type_finder, data.underlying_type, needed_types)?
+ )
+ } else if data.volatile {
+ format!(
+ "volatile {}",
+ type_name(type_finder, data.underlying_type, needed_types)?
+ )
+ } else {
+ // ?
+ type_name(type_finder, data.underlying_type, needed_types)?
+ }
+ }
+
+ pdb::TypeData::Array(data) => {
+ let mut name = type_name(type_finder, data.element_type, needed_types)?;
+ for size in data.dimensions {
+ name = format!("{}[{}]", name, size);
+ }
+ name
+ }
+
+ _ => format!("Type{} /* TODO: figure out how to name it */", type_index),
+ };
+
+ // TODO: search and replace std:: patterns
+ if name == "std::basic_string,std::allocator >" {
+ name = "std::string".to_string();
+ }
+
+ Ok(name)
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Class<'p> {
+ pub kind: pdb::ClassKind,
+ pub name: pdb::RawString<'p>,
+ pub base_classes: Vec,
+ pub fields: Vec>,
+ pub instance_methods: Vec>,
+ pub static_methods: Vec>,
+}
+
+impl<'p> Class<'p> {
+ fn add_derived_from(
+ &mut self,
+ _: &pdb::TypeFinder<'p>,
+ _: pdb::TypeIndex,
+ _: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ // TODO
+ Ok(())
+ }
+
+ fn add_fields(
+ &mut self,
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::FieldList(data) => {
+ for field in &data.fields {
+ self.add_field(type_finder, field, needed_types)?;
+ }
+
+ if let Some(continuation) = data.continuation {
+ // recurse
+ self.add_fields(type_finder, continuation, needed_types)?;
+ }
+ }
+ other => {
+ info!(
+ "trying to Class::add_fields() got {} -> {:?}",
+ type_index, other
+ );
+ panic!("unexpected type in Class::add_fields()");
+ }
+ }
+
+ Ok(())
+ }
+
+ fn add_field(
+ &mut self,
+ type_finder: &pdb::TypeFinder<'p>,
+ field: &pdb::TypeData<'p>,
+ needed_types: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ match *field {
+ pdb::TypeData::Member(ref data) => {
+ // TODO: attributes (static, virtual, etc.)
+ self.fields.push(Field {
+ type_name: type_name(type_finder, data.field_type, needed_types)?,
+ name: data.name,
+ offset: data.offset,
+ });
+ }
+
+ pdb::TypeData::Method(ref data) => {
+ let method = Method::find(
+ data.name,
+ data.attributes,
+ type_finder,
+ data.method_type,
+ needed_types,
+ )?;
+ if data.attributes.is_static() {
+ self.static_methods.push(method);
+ } else {
+ self.instance_methods.push(method);
+ }
+ }
+
+ pdb::TypeData::OverloadedMethod(ref data) => {
+ // this just means we have more than one method with the same name
+ // find the method list
+ match type_finder.find(data.method_list)?.parse()? {
+ pdb::TypeData::MethodList(method_list) => {
+ for pdb::MethodListEntry {
+ attributes,
+ method_type,
+ ..
+ } in method_list.methods
+ {
+ // hooray
+ let method = Method::find(
+ data.name,
+ attributes,
+ type_finder,
+ method_type,
+ needed_types,
+ )?;
+
+ if attributes.is_static() {
+ self.static_methods.push(method);
+ } else {
+ self.instance_methods.push(method);
+ }
+ }
+ }
+ other => {
+ info!(
+ "processing OverloadedMethod, expected MethodList, got {} -> {:?}",
+ data.method_list, other
+ );
+ panic!("unexpected type in Class::add_field()");
+ }
+ }
+ }
+
+ pdb::TypeData::BaseClass(ref data) => self.base_classes.push(BaseClass {
+ type_name: type_name(type_finder, data.base_class, needed_types)?,
+ offset: data.offset,
+ }),
+
+ pdb::TypeData::VirtualBaseClass(ref data) => self.base_classes.push(BaseClass {
+ type_name: type_name(type_finder, data.base_class, needed_types)?,
+ offset: data.base_pointer_offset,
+ }),
+
+ _ => {
+ // ignore everything else even though that's sad
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BaseClass {
+ pub type_name: String,
+ pub offset: u32,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Field<'p> {
+ pub type_name: String,
+ pub name: pdb::RawString<'p>,
+ pub offset: u16,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Method<'p> {
+ pub name: pdb::RawString<'p>,
+ pub return_type_name: String,
+ pub arguments: Vec,
+ pub is_virtual: bool,
+}
+
+impl<'p> Method<'p> {
+ fn find(
+ name: pdb::RawString<'p>,
+ attributes: pdb::FieldAttributes,
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+ ) -> pdb::Result> {
+ match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::MemberFunction(data) => Ok(Method {
+ name,
+ return_type_name: type_name(type_finder, data.return_type, needed_types)?,
+ arguments: argument_list(type_finder, data.argument_list, needed_types)?,
+ is_virtual: attributes.is_virtual(),
+ }),
+
+ other => {
+ info!("other: {:?}", other);
+ Err(pdb::Error::UnimplementedFeature("that"))
+ }
+ }
+ }
+}
+
+fn argument_list<'p>(
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+) -> pdb::Result> {
+ match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::ArgumentList(data) => {
+ let mut args: Vec = Vec::new();
+ for arg_type in data.arguments {
+ args.push(type_name(type_finder, arg_type, needed_types)?);
+ }
+ Ok(args)
+ }
+ _ => Err(pdb::Error::UnimplementedFeature(
+ "argument list of non-argument-list type",
+ )),
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Enum<'p> {
+ name: pdb::RawString<'p>,
+ underlying_type_name: String,
+ values: Vec>,
+}
+
+impl<'p> Enum<'p> {
+ fn add_fields(
+ &mut self,
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::FieldList(data) => {
+ for field in &data.fields {
+ self.add_field(type_finder, field, needed_types)?;
+ }
+
+ if let Some(continuation) = data.continuation {
+ // recurse
+ self.add_fields(type_finder, continuation, needed_types)?;
+ }
+ }
+ other => {
+ info!(
+ "trying to Enum::add_fields() got {} -> {:?}",
+ type_index, other
+ );
+ panic!("unexpected type in Enum::add_fields()");
+ }
+ }
+
+ Ok(())
+ }
+
+ fn add_field(
+ &mut self,
+ _: &pdb::TypeFinder<'p>,
+ field: &pdb::TypeData<'p>,
+ _: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ // ignore everything else even though that's sad
+ if let pdb::TypeData::Enumerate(ref data) = field {
+ self.values.push(EnumValue {
+ name: data.name,
+ value: data.value,
+ });
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct EnumValue<'p> {
+ name: pdb::RawString<'p>,
+ value: pdb::Variant,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ForwardReference<'p> {
+ kind: pdb::ClassKind,
+ name: pdb::RawString<'p>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Data<'p> {
+ pub forward_references: Vec>,
+ pub classes: Vec>,
+ pub enums: Vec>,
+}
+
+impl<'p> Data<'p> {
+ pub fn new() -> Data<'p> {
+ Data {
+ forward_references: Vec::new(),
+ classes: Vec::new(),
+ enums: Vec::new(),
+ }
+ }
+
+ pub fn add(
+ &mut self,
+ type_finder: &pdb::TypeFinder<'p>,
+ type_index: pdb::TypeIndex,
+ needed_types: &mut TypeSet,
+ ) -> pdb::Result<()> {
+ match type_finder.find(type_index)?.parse()? {
+ pdb::TypeData::Class(data) => {
+ if data.properties.forward_reference() {
+ self.forward_references.push(ForwardReference {
+ kind: data.kind,
+ name: data.name,
+ });
+
+ return Ok(());
+ }
+
+ let mut class = Class {
+ kind: data.kind,
+ name: data.name,
+ fields: Vec::new(),
+ base_classes: Vec::new(),
+ instance_methods: Vec::new(),
+ static_methods: Vec::new(),
+ };
+
+ if let Some(derived_from) = data.derived_from {
+ class.add_derived_from(type_finder, derived_from, needed_types)?;
+ }
+
+ if let Some(fields) = data.fields {
+ class.add_fields(type_finder, fields, needed_types)?;
+ }
+
+ self.classes.insert(0, class);
+ }
+
+ pdb::TypeData::Enumeration(data) => {
+ let mut e = Enum {
+ name: data.name,
+ underlying_type_name: type_name(
+ type_finder,
+ data.underlying_type,
+ needed_types,
+ )?,
+ values: Vec::new(),
+ };
+
+ e.add_fields(type_finder, data.fields, needed_types)?;
+
+ self.enums.insert(0, e);
+ }
+
+ // ignore
+ other => trace!("don't know how to add {:?}", other),
+ }
+
+ Ok(())
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/offsets/symstore.rs b/memflow_lib/memflow-win32/src/offsets/symstore.rs
new file mode 100644
index 0000000..d305a12
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/offsets/symstore.rs
@@ -0,0 +1,168 @@
+use std::prelude::v1::*;
+
+use crate::error::{Error, Result};
+use crate::offsets::Win32GUID;
+
+use std::fs::{self, File};
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+
+use dirs::home_dir;
+use log::info;
+
+#[cfg(feature = "download_progress")]
+use {
+ pbr::ProgressBar,
+ progress_streams::ProgressReader,
+ std::sync::atomic::{AtomicBool, AtomicUsize, Ordering},
+ std::sync::Arc,
+};
+
+#[cfg(feature = "download_progress")]
+fn read_to_end(reader: &mut T, len: usize) -> Result> {
+ let mut buffer = vec![];
+
+ let total = Arc::new(AtomicUsize::new(0));
+ let mut reader = ProgressReader::new(reader, |progress: usize| {
+ total.fetch_add(progress, Ordering::SeqCst);
+ });
+ let mut pb = ProgressBar::new(len as u64);
+
+ let finished = Arc::new(AtomicBool::new(false));
+ let thread = {
+ let finished_thread = finished.clone();
+ let total_thread = total.clone();
+
+ std::thread::spawn(move || {
+ while !finished_thread.load(Ordering::Relaxed) {
+ pb.set(total_thread.load(Ordering::SeqCst) as u64);
+ std::thread::sleep(std::time::Duration::from_millis(10));
+ }
+ pb.finish();
+ })
+ };
+
+ reader
+ .read_to_end(&mut buffer)
+ .map_err(|_| Error::SymbolStore("unable to read from http request"))?;
+ finished.store(true, Ordering::Relaxed);
+ thread.join().unwrap();
+
+ Ok(buffer)
+}
+
+#[cfg(not(feature = "download_progress"))]
+fn read_to_end(reader: &mut T, _len: usize) -> Result> {
+ let mut buffer = vec![];
+ reader.read_to_end(&mut buffer)?;
+ Ok(buffer)
+}
+
+#[derive(Debug, Clone)]
+pub struct SymbolStore {
+ base_url: String,
+ cache_path: Option,
+}
+
+impl Default for SymbolStore {
+ fn default() -> Self {
+ let home_dir = home_dir().expect("unable to get home directory");
+ Self {
+ base_url: "https://msdl.microsoft.com/download/symbols".to_string(),
+ cache_path: Some(home_dir.join(".memflow").join("cache")),
+ }
+ }
+}
+
+impl SymbolStore {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn load(&self, guid: &Win32GUID) -> Result> {
+ if let Some(cache_path) = &self.cache_path {
+ let cache_dir = cache_path.join(guid.file_name.clone());
+ let cache_file = cache_dir.join(guid.guid.clone());
+
+ let buffer = if cache_file.exists() {
+ info!(
+ "reading pdb from local cache: {}",
+ cache_file.to_string_lossy()
+ );
+ let mut file = File::open(cache_file)
+ .map_err(|_| Error::SymbolStore("unable to open pdb in local cache"))?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer)
+ .map_err(|_| Error::SymbolStore("unable to read pdb from local cache"))?;
+ buffer
+ } else {
+ let buffer = self.download(guid)?;
+
+ if !cache_dir.exists() {
+ info!("creating cache directory {:?}", cache_dir.to_str());
+ fs::create_dir_all(&cache_dir).map_err(|_| {
+ Error::SymbolStore("unable to create folder in local pdb cache")
+ })?;
+ }
+
+ info!(
+ "writing pdb to local cache: {}",
+ cache_file.to_string_lossy()
+ );
+ let mut file = File::create(cache_file)
+ .map_err(|_| Error::SymbolStore("unable to create file in local pdb cache"))?;
+ file.write_all(&buffer[..])
+ .map_err(|_| Error::SymbolStore("unable to write pdb to local cache"))?;
+
+ buffer
+ };
+
+ Ok(buffer)
+ } else {
+ self.download(guid)
+ }
+ }
+
+ fn download(&self, guid: &Win32GUID) -> Result> {
+ let pdb_url = format!("{}/{}/{}", self.base_url, guid.file_name, guid.guid);
+
+ self.download_file(&format!("{}/{}", pdb_url, guid.file_name))
+ .or_else(|_| self.download_file(&format!("{}/{}", pdb_url, "file.ptr")))
+ }
+
+ fn download_file(&self, url: &str) -> Result> {
+ info!("downloading pdb from {}", url);
+ let resp = ureq::get(url).call();
+ if !resp.ok() {
+ return Err(Error::SymbolStore("unable to download pdb"));
+ }
+
+ assert!(resp.has("Content-Length"));
+ let len = resp
+ .header("Content-Length")
+ .and_then(|s| s.parse::().ok())
+ .unwrap();
+
+ let mut reader = resp.into_reader();
+ let buffer = read_to_end(&mut reader, len)?;
+
+ assert_eq!(buffer.len(), len);
+ Ok(buffer)
+ }
+
+ // symbol store configurations
+ pub fn base_url(mut self, base_url: &str) -> Self {
+ self.base_url = base_url.to_string();
+ self
+ }
+
+ pub fn no_cache(mut self) -> Self {
+ self.cache_path = None;
+ self
+ }
+
+ pub fn cache_path>(mut self, cache_path: P) -> Self {
+ self.cache_path = Some(cache_path.as_ref().to_path_buf());
+ self
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/win32.rs b/memflow_lib/memflow-win32/src/win32.rs
new file mode 100644
index 0000000..51dc6bb
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/win32.rs
@@ -0,0 +1,19 @@
+pub mod kernel;
+pub mod kernel_builder;
+pub mod kernel_info;
+
+pub use kernel::Kernel;
+pub use kernel_builder::KernelBuilder;
+pub use kernel_info::KernelInfo;
+
+pub mod keyboard;
+pub mod module;
+pub mod process;
+pub mod unicode_string;
+pub mod vat;
+
+pub use keyboard::*;
+pub use module::*;
+pub use process::*;
+pub use unicode_string::*;
+pub use vat::*;
diff --git a/memflow_lib/memflow-win32/src/win32/kernel.rs b/memflow_lib/memflow-win32/src/win32/kernel.rs
new file mode 100644
index 0000000..0bf8a03
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/win32/kernel.rs
@@ -0,0 +1,530 @@
+use std::prelude::v1::*;
+
+use super::{
+ process::EXIT_STATUS_STILL_ACTIVE, process::IMAGE_FILE_NAME_LENGTH, KernelBuilder, KernelInfo,
+ Win32ExitStatus, Win32ModuleListInfo, Win32Process, Win32ProcessInfo, Win32VirtualTranslate,
+};
+
+use crate::error::{Error, Result};
+use crate::offsets::Win32Offsets;
+
+use log::{info, trace};
+use std::fmt;
+
+use memflow::architecture::x86;
+use memflow::mem::{DirectTranslate, PhysicalMemory, VirtualDMA, VirtualMemory, VirtualTranslate};
+use memflow::process::{OperatingSystem, OsProcessInfo, OsProcessModuleInfo, PID};
+use memflow::types::Address;
+
+use pelite::{self, pe64::exports::Export, PeView};
+
+const MAX_ITER_COUNT: usize = 65536;
+
+#[derive(Clone)]
+pub struct Kernel {
+ pub phys_mem: T,
+ pub vat: V,
+ pub offsets: Win32Offsets,
+
+ pub kernel_info: KernelInfo,
+ pub sysproc_dtb: Address,
+}
+
+impl OperatingSystem for Kernel {}
+
+impl Kernel {
+ pub fn new(
+ mut phys_mem: T,
+ mut vat: V,
+ offsets: Win32Offsets,
+ kernel_info: KernelInfo,
+ ) -> Self {
+ // start_block only contains the winload's dtb which might
+ // be different to the one used in the actual kernel.
+ // In case of a failure this will fall back to the winload dtb.
+ let sysproc_dtb = {
+ let mut reader = VirtualDMA::with_vat(
+ &mut phys_mem,
+ kernel_info.start_block.arch,
+ Win32VirtualTranslate::new(
+ kernel_info.start_block.arch,
+ kernel_info.start_block.dtb,
+ ),
+ &mut vat,
+ );
+
+ if let Ok(dtb) = reader.virt_read_addr_arch(
+ kernel_info.start_block.arch,
+ kernel_info.eprocess_base + offsets.kproc_dtb(),
+ ) {
+ dtb
+ } else {
+ kernel_info.start_block.dtb
+ }
+ };
+ info!("sysproc_dtb={:x}", sysproc_dtb);
+
+ Self {
+ phys_mem,
+ vat,
+ offsets,
+
+ kernel_info,
+ sysproc_dtb,
+ }
+ }
+
+ /// Consume the self object and return the containing memory connection
+ pub fn destroy(self) -> T {
+ self.phys_mem
+ }
+
+ pub fn eprocess_list(&mut self) -> Result> {
+ let mut eprocs = Vec::new();
+ self.eprocess_list_extend(&mut eprocs)?;
+ trace!("found {} eprocesses", eprocs.len());
+ Ok(eprocs)
+ }
+
+ pub fn eprocess_list_extend>(&mut self, eprocs: &mut E) -> Result<()> {
+ // TODO: create a VirtualDMA constructor for kernel_info
+ let mut reader = VirtualDMA::with_vat(
+ &mut self.phys_mem,
+ self.kernel_info.start_block.arch,
+ Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb),
+ &mut self.vat,
+ );
+
+ let list_start = self.kernel_info.eprocess_base + self.offsets.eproc_link();
+ let mut list_entry = list_start;
+
+ for _ in 0..MAX_ITER_COUNT {
+ let eprocess = list_entry - self.offsets.eproc_link();
+ trace!("eprocess={}", eprocess);
+
+ // test flink + blink before adding the process
+ let flink_entry =
+ reader.virt_read_addr_arch(self.kernel_info.start_block.arch, list_entry)?;
+ trace!("flink_entry={}", flink_entry);
+ let blink_entry = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ list_entry + self.offsets.list_blink(),
+ )?;
+ trace!("blink_entry={}", blink_entry);
+
+ if flink_entry.is_null()
+ || blink_entry.is_null()
+ || flink_entry == list_start
+ || flink_entry == list_entry
+ {
+ break;
+ }
+
+ trace!("found eprocess {:x}", eprocess);
+ eprocs.extend(Some(eprocess).into_iter());
+
+ // continue
+ list_entry = flink_entry;
+ }
+
+ Ok(())
+ }
+
+ pub fn kernel_process_info(&mut self) -> Result {
+ // TODO: create a VirtualDMA constructor for kernel_info
+ let mut reader = VirtualDMA::with_vat(
+ &mut self.phys_mem,
+ self.kernel_info.start_block.arch,
+ Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb),
+ &mut self.vat,
+ );
+
+ // TODO: cache pe globally
+ // find PsLoadedModuleList
+ let loaded_module_list = {
+ let image =
+ reader.virt_read_raw(self.kernel_info.kernel_base, self.kernel_info.kernel_size)?;
+ let pe = PeView::from_bytes(&image).map_err(Error::PE)?;
+ match pe
+ .get_export_by_name("PsLoadedModuleList")
+ .map_err(Error::PE)?
+ {
+ Export::Symbol(s) => self.kernel_info.kernel_base + *s as usize,
+ Export::Forward(_) => {
+ return Err(Error::Other(
+ "PsLoadedModuleList found but it was a forwarded export",
+ ))
+ }
+ }
+ };
+
+ let kernel_modules =
+ reader.virt_read_addr_arch(self.kernel_info.start_block.arch, loaded_module_list)?;
+
+ Ok(Win32ProcessInfo {
+ address: self.kernel_info.kernel_base,
+
+ pid: 0,
+ name: "ntoskrnl.exe".to_string(),
+ dtb: self.sysproc_dtb,
+ section_base: Address::NULL, // TODO: see below
+ exit_status: EXIT_STATUS_STILL_ACTIVE,
+ ethread: Address::NULL, // TODO: see below
+ wow64: Address::NULL,
+
+ teb: None,
+ teb_wow64: None,
+
+ peb_native: Address::NULL,
+ peb_wow64: None,
+
+ module_info_native: Win32ModuleListInfo::with_base(
+ kernel_modules,
+ self.kernel_info.start_block.arch,
+ )?,
+ module_info_wow64: None,
+
+ sys_arch: self.kernel_info.start_block.arch,
+ proc_arch: self.kernel_info.start_block.arch,
+ })
+ }
+
+ pub fn process_info_from_eprocess(&mut self, eprocess: Address) -> Result {
+ // TODO: create a VirtualDMA constructor for kernel_info
+ let mut reader = VirtualDMA::with_vat(
+ &mut self.phys_mem,
+ self.kernel_info.start_block.arch,
+ Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb),
+ &mut self.vat,
+ );
+
+ let pid: PID = reader.virt_read(eprocess + self.offsets.eproc_pid())?;
+ trace!("pid={}", pid);
+
+ let name =
+ reader.virt_read_cstr(eprocess + self.offsets.eproc_name(), IMAGE_FILE_NAME_LENGTH)?;
+ trace!("name={}", name);
+
+ let dtb = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.kproc_dtb(),
+ )?;
+ trace!("dtb={:x}", dtb);
+
+ let wow64 = if self.offsets.eproc_wow64() == 0 {
+ trace!("eproc_wow64=null; skipping wow64 detection");
+ Address::null()
+ } else {
+ trace!(
+ "eproc_wow64={:x}; trying to read wow64 pointer",
+ self.offsets.eproc_wow64()
+ );
+ reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.eproc_wow64(),
+ )?
+ };
+ trace!("wow64={:x}", wow64);
+
+ // determine process architecture
+ let sys_arch = self.kernel_info.start_block.arch;
+ trace!("sys_arch={:?}", sys_arch);
+ let proc_arch = match sys_arch.bits() {
+ 64 => {
+ if wow64.is_null() {
+ x86::x64::ARCH
+ } else {
+ x86::x32::ARCH
+ }
+ }
+ 32 => x86::x32::ARCH,
+ _ => return Err(Error::InvalidArchitecture),
+ };
+ trace!("proc_arch={:?}", proc_arch);
+
+ // read native_peb (either the process peb or the peb containing the wow64 helpers)
+ let native_peb = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.eproc_peb(),
+ )?;
+ trace!("native_peb={:x}", native_peb);
+
+ let section_base = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.eproc_section_base(),
+ )?;
+ trace!("section_base={:x}", section_base);
+
+ let exit_status: Win32ExitStatus =
+ reader.virt_read(eprocess + self.offsets.eproc_exit_status())?;
+ trace!("exit_status={}", exit_status);
+
+ // find first ethread
+ let ethread = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.eproc_thread_list(),
+ )? - self.offsets.ethread_list_entry();
+ trace!("ethread={:x}", ethread);
+
+ let peb_native = reader
+ .virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ eprocess + self.offsets.eproc_peb(),
+ )?
+ .non_null()
+ .ok_or(Error::Other("Could not retrieve peb_native"))?;
+
+ let mut peb_wow64 = None;
+
+ // TODO: does this need to be read with the process ctx?
+ let (teb, teb_wow64) = if self.kernel_info.kernel_winver >= (6, 2).into() {
+ let teb = reader.virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ ethread + self.offsets.kthread_teb(),
+ )?;
+
+ trace!("teb={:x}", teb);
+
+ if !teb.is_null() {
+ (
+ Some(teb),
+ if wow64.is_null() {
+ None
+ } else {
+ Some(teb + 0x2000)
+ },
+ )
+ } else {
+ (None, None)
+ }
+ } else {
+ (None, None)
+ };
+
+ std::mem::drop(reader);
+
+ // construct reader with process dtb
+ // TODO: can tlb be used here already?
+ let mut proc_reader = VirtualDMA::with_vat(
+ &mut self.phys_mem,
+ proc_arch,
+ Win32VirtualTranslate::new(self.kernel_info.start_block.arch, dtb),
+ DirectTranslate::new(),
+ );
+
+ if let Some(teb) = teb_wow64 {
+ // from here on out we are in the process context
+ // we will be using the process type architecture now
+ peb_wow64 = proc_reader
+ .virt_read_addr_arch(
+ self.kernel_info.start_block.arch,
+ teb + self.offsets.teb_peb_x86(),
+ )?
+ .non_null();
+
+ trace!("peb_wow64={:?}", peb_wow64);
+ }
+
+ trace!("peb_native={:?}", peb_native);
+
+ let module_info_native =
+ Win32ModuleListInfo::with_peb(&mut proc_reader, peb_native, sys_arch)?;
+
+ let module_info_wow64 = peb_wow64
+ .map(|peb| Win32ModuleListInfo::with_peb(&mut proc_reader, peb, proc_arch))
+ .transpose()?;
+
+ Ok(Win32ProcessInfo {
+ address: eprocess,
+
+ pid,
+ name,
+ dtb,
+ section_base,
+ exit_status,
+ ethread,
+ wow64,
+
+ teb,
+ teb_wow64,
+
+ peb_native,
+ peb_wow64,
+
+ module_info_native,
+ module_info_wow64,
+
+ sys_arch,
+ proc_arch,
+ })
+ }
+
+ pub fn process_info_list_extend>(
+ &mut self,
+ list: &mut E,
+ ) -> Result<()> {
+ let mut vec = Vec::new();
+ self.eprocess_list_extend(&mut vec)?;
+ for eprocess in vec.into_iter() {
+ if let Ok(prc) = self.process_info_from_eprocess(eprocess) {
+ list.extend(Some(prc).into_iter());
+ }
+ }
+ Ok(())
+ }
+
+ /// Retrieves a list of `Win32ProcessInfo` structs for all processes
+ /// that can be found on the target system.
+ pub fn process_info_list(&mut self) -> Result> {
+ let mut list = Vec::new();
+ self.process_info_list_extend(&mut list)?;
+ Ok(list)
+ }
+
+ /// Finds a process by it's name and returns the `Win32ProcessInfo` struct.
+ /// If no process with the specified name can be found this function will return an Error.
+ pub fn process_info(&mut self, name: &str) -> Result {
+ let name16 = name[..name.len().min(IMAGE_FILE_NAME_LENGTH - 1)].to_lowercase();
+
+ let process_info_list = self.process_info_list()?;
+ let candidates = process_info_list
+ .iter()
+ .inspect(|process| trace!("{} {}", process.pid(), process.name()))
+ .filter(|process| {
+ // strip process name to IMAGE_FILE_NAME_LENGTH without trailing \0
+ process.name().to_lowercase() == name16
+ })
+ .collect::>();
+
+ for &candidate in candidates.iter() {
+ // TODO: properly probe pe header here and check ImageBase
+ // TODO: this wont work with tlb
+ trace!("inspecting candidate process: {:?}", candidate);
+ let mut process = Win32Process::with_kernel_ref(self, candidate.clone());
+ if process
+ .module_list()?
+ .iter()
+ .inspect(|&module| trace!("{:x} {}", module.base(), module.name()))
+ .find(|&module| module.name().to_lowercase() == name.to_lowercase())
+ .ok_or_else(|| Error::ModuleInfo)
+ .is_ok()
+ {
+ return Ok(candidate.clone());
+ }
+ }
+
+ Err(Error::ProcessInfo)
+ }
+
+ /// Finds a process by it's process id and returns the `Win32ProcessInfo` struct.
+ /// If no process with the specified PID can be found this function will return an Error.
+ ///
+ /// If the specified PID is 0 the kernel process is returned.
+ pub fn process_info_pid(&mut self, pid: PID) -> Result {
+ if pid > 0 {
+ // regular pid
+ let process_info_list = self.process_info_list()?;
+ process_info_list
+ .into_iter()
+ .inspect(|process| trace!("{} {}", process.pid(), process.name()))
+ .find(|process| process.pid == pid)
+ .ok_or_else(|| Error::Other("pid not found"))
+ } else {
+ // kernel pid
+ self.kernel_process_info()
+ }
+ }
+
+ /// Constructs a `Win32Process` struct for the targets kernel by borrowing this kernel instance.
+ ///
+ /// This function can be useful for quickly accessing the kernel process.
+ pub fn kernel_process(
+ &mut self,
+ ) -> Result>> {
+ let proc_info = self.kernel_process_info()?;
+ Ok(Win32Process::with_kernel_ref(self, proc_info))
+ }
+
+ /// Finds a process by its name and constructs a `Win32Process` struct
+ /// by borrowing this kernel instance.
+ /// If no process with the specified name can be found this function will return an Error.
+ ///
+ /// This function can be useful for quickly accessing a process.
+ pub fn process(
+ &mut self,
+ name: &str,
+ ) -> Result>> {
+ let proc_info = self.process_info(name)?;
+ Ok(Win32Process::with_kernel_ref(self, proc_info))
+ }
+
+ /// Finds a process by its process id and constructs a `Win32Process` struct
+ /// by borrowing this kernel instance.
+ /// If no process with the specified name can be found this function will return an Error.
+ ///
+ /// This function can be useful for quickly accessing a process.
+ pub fn process_pid(
+ &mut self,
+ pid: PID,
+ ) -> Result>> {
+ let proc_info = self.process_info_pid(pid)?;
+ Ok(Win32Process::with_kernel_ref(self, proc_info))
+ }
+
+ /// Constructs a `Win32Process` struct by consuming this kernel struct
+ /// and moving it into the resulting process.
+ ///
+ /// If necessary the kernel can be retrieved back by calling `destroy()` on the process after use.
+ ///
+ /// This function can be useful for quickly accessing a process.
+ pub fn into_kernel_process(
+ mut self,
+ ) -> Result>> {
+ let proc_info = self.kernel_process_info()?;
+ Ok(Win32Process::with_kernel(self, proc_info))
+ }
+
+ /// Finds a process by its name and constructs a `Win32Process` struct
+ /// by consuming the kernel struct and moving it into the process.
+ ///
+ /// If necessary the kernel can be retrieved back by calling `destroy()` on the process after use.
+ ///
+ /// If no process with the specified name can be found this function will return an Error.
+ ///
+ /// This function can be useful for quickly accessing a process.
+ pub fn into_process(
+ mut self,
+ name: &str,
+ ) -> Result>> {
+ let proc_info = self.process_info(name)?;
+ Ok(Win32Process::with_kernel(self, proc_info))
+ }
+
+ /// Finds a process by its process id and constructs a `Win32Process` struct
+ /// by consuming the kernel struct and moving it into the process.
+ ///
+ /// If necessary the kernel can be retrieved back by calling `destroy()` on the process again.
+ ///
+ /// If no process with the specified name can be found this function will return an Error.
+ ///
+ /// This function can be useful for quickly accessing a process.
+ pub fn into_process_pid(
+ mut self,
+ pid: PID,
+ ) -> Result>> {
+ let proc_info = self.process_info_pid(pid)?;
+ Ok(Win32Process::with_kernel(self, proc_info))
+ }
+}
+
+impl Kernel {
+ pub fn builder(connector: T) -> KernelBuilder {
+ KernelBuilder::::new(connector)
+ }
+}
+
+impl fmt::Debug for Kernel {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self.kernel_info)
+ }
+}
diff --git a/memflow_lib/memflow-win32/src/win32/kernel_builder.rs b/memflow_lib/memflow-win32/src/win32/kernel_builder.rs
new file mode 100644
index 0000000..5ffd99b
--- /dev/null
+++ b/memflow_lib/memflow-win32/src/win32/kernel_builder.rs
@@ -0,0 +1,409 @@
+use std::prelude::v1::*;
+
+use super::{Kernel, KernelInfo};
+use crate::error::Result;
+use crate::offsets::Win32Offsets;
+
+#[cfg(feature = "symstore")]
+use crate::offsets::SymbolStore;
+
+use memflow::architecture::ArchitectureObj;
+use memflow::mem::{
+ CachedMemoryAccess, CachedVirtualTranslate, DefaultCacheValidator, DirectTranslate,
+ PhysicalMemory, VirtualTranslate,
+};
+use memflow::types::Address;
+
+/// Builder for a Windows Kernel structure.
+///
+/// This function encapsulates the entire setup process for a Windows target
+/// and will make sure the user gets a properly initialized object at the end.
+///
+/// This function is a high level abstraction over the individual parts of initialization a Windows target:
+/// - Scanning for the ntoskrnl and retrieving the `KernelInfo` struct.
+/// - Retrieving the Offsets for the target Windows version.
+/// - Creating a struct which implements `VirtualTranslate` for virtual to physical address translations.
+/// - Optionally wrapping the Connector or the `VirtualTranslate` object into a cached object.
+/// - Initialization of the Kernel structure itself.
+///
+/// # Examples
+///
+/// Using the builder with default values:
+/// ```
+/// use memflow::mem::PhysicalMemory;
+/// use memflow_win32::win32::Kernel;
+///
+/// fn test(connector: T) {
+/// let _kernel = Kernel::builder(connector)
+/// .build()
+/// .unwrap();
+/// }
+/// ```
+///
+/// Using the builder with default cache configurations:
+/// ```
+/// use memflow::mem::PhysicalMemory;
+/// use memflow_win32::win32::Kernel;
+///
+/// fn test