commit
185e0b5509
@ -1,16 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd memflow_lib/memflow-win32-ffi/
|
||||
cd memflow_lib/memflow-ffi/
|
||||
if cargo build --release ; then
|
||||
cd ../memflow-qemu-procfs
|
||||
|
||||
if cargo build --release --all-features ; then
|
||||
cd ../../
|
||||
make
|
||||
else
|
||||
echo "Error while building memflow-qemu-procfs"
|
||||
fi
|
||||
|
||||
cd ../../
|
||||
make
|
||||
else
|
||||
echo "Error while building memflow-win32-ffi"
|
||||
echo "Error while building memflow-ffi"
|
||||
fi
|
||||
|
@ -0,0 +1,2 @@
|
||||
[target.'cfg(unix)']
|
||||
runner = "./runner.sh"
|
@ -0,0 +1,176 @@
|
||||
name: Build and test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
flags: [--all-features, --no-default-features]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --workspace ${{ matrix.flags }} --verbose
|
||||
|
||||
- name: Build examples
|
||||
run: cargo build --workspace ${{ matrix.flags }} --examples --verbose
|
||||
|
||||
build-cross-targets:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [aarch64-unknown-linux-gnu, armv7-unknown-linux-gnueabihf, i686-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target ${{ matrix.target }} --workspace --all-features --verbose
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
|
||||
- name: Pre-build binaries (for inventory integration tests)
|
||||
run: cargo build --workspace --all-features --verbose
|
||||
|
||||
- name: Run all tests
|
||||
run: cargo test --workspace --all-features --verbose
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Run all tests
|
||||
run: cargo test --workspace --exclude memflow-derive --all-features --verbose
|
||||
if: runner.os != 'Linux'
|
||||
|
||||
test-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly # currently required due to -Zdoctest-xcompile
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Pre-build binaries (for inventory integration tests)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target ${{ matrix.target }} --workspace --all-features --verbose --release
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: test
|
||||
args: -Zdoctest-xcompile --target ${{ matrix.target }} --workspace --all-features --verbose --release
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Check formatting
|
||||
run: cargo fmt -- --check
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-targets --all-features --workspace -- -D clippy::all
|
||||
|
||||
build-ffi:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly # required by cglue codegen
|
||||
override: true
|
||||
|
||||
- name: Validate c and c++ headers
|
||||
run: cd memflow-ffi; ./verify_headers.sh
|
||||
|
||||
- name: Build memflow
|
||||
run: cargo build --workspace --release --verbose
|
||||
|
||||
- name: Build c examples
|
||||
run: cd memflow-ffi/examples/c; make all
|
||||
- name: Build c++ examples
|
||||
run: cd memflow-ffi/examples/cpp; make all
|
||||
|
||||
build-nostd:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Rust nightly
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2023-12-15
|
||||
override: true
|
||||
- run: rustup toolchain install nightly-2023-12-15
|
||||
- run: rustup +nightly-2023-12-15 component add rust-src
|
||||
- name: Build no_std crate
|
||||
run: cd nostd-test; cargo +nightly-2023-12-15 build --all-features --verbose
|
||||
|
||||
build-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'memflow-repo'
|
||||
- name: Set up Rust nightly
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- run: cargo install grcov
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
cd memflow-repo
|
||||
export CARGO_INCREMENTAL=0
|
||||
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
export RUSTDOCFLAGS="-Cpanic=abort"
|
||||
cargo build --workspace --exclude memflow-derive --all-features
|
||||
cargo test --workspace --exclude memflow-derive --all-features
|
||||
grcov ./target/debug/ -s . -t lcov --llvm --branch --ignore-not-existing -o ./target/debug/coverage
|
||||
bash <(curl -s https://codecov.io/bash) -f ./target/debug/coverage -t ${{ secrets.CODECOV_TOKEN }};
|
@ -0,0 +1,13 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
*.swp
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
.vscode
|
||||
nostd-test/target
|
||||
nostd-test/Cargo.lock
|
||||
*.so
|
||||
.vagrant
|
||||
TODO.md
|
||||
TODO.txt
|
@ -1,24 +1,21 @@
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"memflow",
|
||||
"memflow-win32",
|
||||
"memflow-ffi",
|
||||
"memflow-win32-ffi",
|
||||
"memflow-bench",
|
||||
]
|
||||
default-members = [
|
||||
"memflow",
|
||||
"memflow-win32",
|
||||
"memflow-ffi",
|
||||
"memflow-win32-ffi",
|
||||
"memflow-bench",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"nostd-test",
|
||||
"memflow-qemu-procfs"
|
||||
"nostd-test"
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
goblin = { git = "https://github.com/h33p/goblin", branch = "lossy-macho" }
|
||||
|
@ -0,0 +1,21 @@
|
||||
# Checklist:
|
||||
- memflow
|
||||
- memflow-ffi
|
||||
|
||||
# Plugins:
|
||||
- memflow-win32
|
||||
- memflow-pcileech
|
||||
- memflow-reclass-plugin
|
||||
- memflow-native
|
||||
- memflow-qemu
|
||||
- memflow-kcore
|
||||
- memflow-cmake-example
|
||||
- memflow-kvm
|
||||
- memflow-coredump
|
||||
- memflow-linux
|
||||
- memflow-microvmi
|
||||
|
||||
# Tools:
|
||||
- cloudflow
|
||||
- scanflow
|
||||
- reflow
|
@ -1,3 +1,4 @@
|
||||
pub mod phys;
|
||||
pub mod util;
|
||||
pub mod vat;
|
||||
pub mod virt;
|
||||
|
@ -0,0 +1,79 @@
|
||||
use memflow::prelude::v1::*;
|
||||
|
||||
pub fn build_os(
|
||||
conn_name: &str,
|
||||
cache_size: usize,
|
||||
os_name: &str,
|
||||
use_tlb: bool,
|
||||
) -> Result<OsInstanceArcBox<'static>> {
|
||||
// this workaround is to prevent loaded libraries
|
||||
// from spitting out to much log information and skewing benchmarks
|
||||
let filter = log::max_level();
|
||||
log::set_max_level(log::Level::Debug.to_level_filter());
|
||||
|
||||
let inventory = Inventory::scan();
|
||||
|
||||
log::set_max_level(log::Level::Error.to_level_filter());
|
||||
|
||||
let mut args = Args::new();
|
||||
|
||||
if !use_tlb {
|
||||
args = args.insert("vatcache", "none");
|
||||
}
|
||||
|
||||
let page_cache_params = if cache_size > 0 {
|
||||
Some(ConnectorMiddlewareArgs::new().cache_size(cache_size))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let conn_args = ConnectorArgs::new(None, Default::default(), page_cache_params);
|
||||
let args = OsArgs::new(None, args);
|
||||
|
||||
let ret = if conn_name.is_empty() {
|
||||
inventory.builder().os(os_name).args(args).build()
|
||||
} else {
|
||||
inventory
|
||||
.builder()
|
||||
.connector(conn_name)
|
||||
.args(conn_args)
|
||||
.os(os_name)
|
||||
.args(args)
|
||||
.build()
|
||||
}?;
|
||||
|
||||
log::set_max_level(filter);
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn find_proc<T: Os>(os: &mut T) -> Result<(<T as Os>::ProcessType<'_>, ModuleInfo)> {
|
||||
let infos = os.process_info_list()?;
|
||||
|
||||
let mut data = None;
|
||||
|
||||
for info in infos {
|
||||
if let Ok(mut proc) = os.process_by_info(info.clone()) {
|
||||
let mut module = None;
|
||||
|
||||
proc.module_list_callback(
|
||||
None,
|
||||
(&mut |info: ModuleInfo| {
|
||||
if info.size > 0x1000 {
|
||||
module = Some(info);
|
||||
}
|
||||
module.is_none()
|
||||
})
|
||||
.into(),
|
||||
)?;
|
||||
|
||||
if let Some(module) = module {
|
||||
data = Some((info, module));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.and_then(move |(info, module)| Some((os.process_by_info(info).ok()?, module)))
|
||||
.ok_or_else(|| ErrorKind::NotFound.into())
|
||||
}
|
@ -1,218 +1,129 @@
|
||||
use criterion::*;
|
||||
|
||||
use memflow::mem::{CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualTranslate};
|
||||
|
||||
use memflow::architecture::ScopedVirtualTranslate;
|
||||
|
||||
use memflow::error::Result;
|
||||
use memflow::iter::FnExtend;
|
||||
use memflow::process::*;
|
||||
use memflow::types::*;
|
||||
use memflow::cglue::as_mut;
|
||||
use memflow::mem::virt_translate::*;
|
||||
use memflow::prelude::v1::*;
|
||||
|
||||
use rand::prelude::*;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_xorshift::XorShiftRng as CurRng;
|
||||
|
||||
fn vat_test_with_mem<
|
||||
T: PhysicalMemory,
|
||||
V: VirtualTranslate,
|
||||
S: ScopedVirtualTranslate,
|
||||
M: OsProcessModuleInfo,
|
||||
>(
|
||||
use std::convert::TryInto;
|
||||
|
||||
fn vat_test_with_mem(
|
||||
bench: &mut Bencher,
|
||||
phys_mem: &mut T,
|
||||
vat: &mut V,
|
||||
vat: &mut impl VirtualTranslate,
|
||||
chunk_count: usize,
|
||||
translations: usize,
|
||||
translator: S,
|
||||
module: M,
|
||||
) -> usize {
|
||||
module: ModuleInfo,
|
||||
) {
|
||||
let mut rng = CurRng::from_rng(thread_rng()).unwrap();
|
||||
|
||||
let mut bufs = vec![Address::null(); chunk_count];
|
||||
let mut done_size = 0;
|
||||
let mut bufs = vec![CTup2(Address::null(), 1); translations];
|
||||
|
||||
let mut out = Vec::new();
|
||||
let base_addr = rng.gen_range(module.base.to_umem()..(module.base.to_umem() + module.size));
|
||||
|
||||
while done_size < translations {
|
||||
let base_addr = rng.gen_range(
|
||||
module.base().as_u64(),
|
||||
module.base().as_u64() + module.size() as u64,
|
||||
);
|
||||
for CTup2(address, _) in bufs.iter_mut() {
|
||||
*address = (base_addr + rng.gen_range(0..0x2000)).into();
|
||||
}
|
||||
|
||||
for addr in bufs.iter_mut() {
|
||||
*addr = (base_addr + rng.gen_range(0, 0x2000)).into();
|
||||
}
|
||||
let mut out = vec![];
|
||||
|
||||
bench.iter(|| {
|
||||
bench.iter(|| {
|
||||
for chunk in bufs.chunks_mut(chunk_count) {
|
||||
out.clear();
|
||||
vat.virt_to_phys_iter(
|
||||
phys_mem,
|
||||
&translator,
|
||||
bufs.iter_mut().map(|x| (*x, 1)),
|
||||
&mut out,
|
||||
&mut FnExtend::new(|_| {}),
|
||||
);
|
||||
vat.virt_to_phys_list(chunk, (&mut out).into(), (&mut |_| true).into());
|
||||
black_box(&out);
|
||||
});
|
||||
|
||||
done_size += chunk_count;
|
||||
}
|
||||
|
||||
done_size
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn vat_test_with_ctx<
|
||||
T: PhysicalMemory,
|
||||
V: VirtualTranslate,
|
||||
P: OsProcessInfo,
|
||||
S: ScopedVirtualTranslate,
|
||||
M: OsProcessModuleInfo,
|
||||
>(
|
||||
fn vat_test_with_os(
|
||||
bench: &mut Bencher,
|
||||
cache_size: u64,
|
||||
chunks: usize,
|
||||
translations: usize,
|
||||
use_tlb: bool,
|
||||
(mut mem, mut vat, prc, translator, tmod): (T, V, P, S, M),
|
||||
os: &mut OsInstanceArcBox<'static>,
|
||||
) {
|
||||
if cache_size > 0 {
|
||||
let cache = CachedMemoryAccess::builder(&mut mem)
|
||||
.arch(prc.sys_arch())
|
||||
.cache_size(size::mb(cache_size as usize))
|
||||
.page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE);
|
||||
|
||||
if use_tlb {
|
||||
let mut mem = cache.build().unwrap();
|
||||
let mut vat = CachedVirtualTranslate::builder(vat)
|
||||
.arch(prc.sys_arch())
|
||||
.build()
|
||||
.unwrap();
|
||||
vat_test_with_mem(
|
||||
bench,
|
||||
&mut mem,
|
||||
&mut vat,
|
||||
chunks,
|
||||
translations,
|
||||
translator,
|
||||
tmod,
|
||||
);
|
||||
} else {
|
||||
let mut mem = cache.build().unwrap();
|
||||
vat_test_with_mem(
|
||||
bench,
|
||||
&mut mem,
|
||||
&mut vat,
|
||||
chunks,
|
||||
translations,
|
||||
translator,
|
||||
tmod,
|
||||
);
|
||||
}
|
||||
} else if use_tlb {
|
||||
let mut vat = CachedVirtualTranslate::builder(vat)
|
||||
.arch(prc.sys_arch())
|
||||
.build()
|
||||
.unwrap();
|
||||
vat_test_with_mem(
|
||||
bench,
|
||||
&mut mem,
|
||||
&mut vat,
|
||||
chunks,
|
||||
translations,
|
||||
translator,
|
||||
tmod,
|
||||
);
|
||||
} else {
|
||||
vat_test_with_mem(
|
||||
bench,
|
||||
&mut mem,
|
||||
&mut vat,
|
||||
chunks,
|
||||
translations,
|
||||
translator,
|
||||
tmod,
|
||||
);
|
||||
}
|
||||
let (mut process, module) = crate::util::find_proc(os).unwrap();
|
||||
|
||||
vat_test_with_mem(
|
||||
bench,
|
||||
as_mut!(process impl VirtualTranslate).unwrap(),
|
||||
chunks,
|
||||
translations,
|
||||
module,
|
||||
);
|
||||
}
|
||||
|
||||
fn chunk_vat_params<
|
||||
T: PhysicalMemory,
|
||||
V: VirtualTranslate,
|
||||
P: OsProcessInfo,
|
||||
S: ScopedVirtualTranslate,
|
||||
M: OsProcessModuleInfo,
|
||||
>(
|
||||
fn chunk_vat_params(
|
||||
group: &mut BenchmarkGroup<'_, measurement::WallTime>,
|
||||
func_name: String,
|
||||
cache_size: u64,
|
||||
cache_size: usize,
|
||||
use_tlb: bool,
|
||||
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
|
||||
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
|
||||
) {
|
||||
let size = 0x10;
|
||||
|
||||
let mut os = initialize_ctx(cache_size, use_tlb).unwrap();
|
||||
|
||||
for &chunk_size in [1, 4, 16, 64].iter() {
|
||||
group.throughput(Throughput::Elements(chunk_size * size));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new(func_name.clone(), chunk_size),
|
||||
&size,
|
||||
|b, &size| {
|
||||
vat_test_with_ctx(
|
||||
vat_test_with_os(
|
||||
b,
|
||||
black_box(cache_size),
|
||||
black_box(chunk_size as usize),
|
||||
black_box((size * chunk_size) as usize),
|
||||
black_box(use_tlb),
|
||||
initialize_ctx().unwrap(),
|
||||
black_box(chunk_size.try_into().unwrap()),
|
||||
black_box((size * chunk_size).try_into().unwrap()),
|
||||
&mut os,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_vat<
|
||||
T: PhysicalMemory,
|
||||
V: VirtualTranslate,
|
||||
P: OsProcessInfo,
|
||||
S: ScopedVirtualTranslate,
|
||||
M: OsProcessModuleInfo,
|
||||
>(
|
||||
pub fn chunk_vat(
|
||||
c: &mut Criterion,
|
||||
backend_name: &str,
|
||||
initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>,
|
||||
initialize_ctx: &dyn Fn(usize, bool) -> Result<OsInstanceArcBox<'static>>,
|
||||
use_caches: bool,
|
||||
) {
|
||||
let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
|
||||
|
||||
let group_name = format!("{}_chunk_vat", backend_name);
|
||||
let group_name = format!("{backend_name}_chunk_vat");
|
||||
|
||||
let mut group = c.benchmark_group(group_name.clone());
|
||||
group.plot_config(plot_config);
|
||||
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{}_nocache", group_name),
|
||||
format!("{group_name}_nocache"),
|
||||
0,
|
||||
false,
|
||||
initialize_ctx,
|
||||
);
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{}_tlb_nocache", group_name),
|
||||
0,
|
||||
true,
|
||||
initialize_ctx,
|
||||
);
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{}_cache", group_name),
|
||||
2,
|
||||
false,
|
||||
initialize_ctx,
|
||||
);
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{}_tlb_cache", group_name),
|
||||
2,
|
||||
true,
|
||||
initialize_ctx,
|
||||
);
|
||||
if use_caches {
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{group_name}_tlb_nocache"),
|
||||
0,
|
||||
true,
|
||||
initialize_ctx,
|
||||
);
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{group_name}_cache"),
|
||||
2,
|
||||
false,
|
||||
initialize_ctx,
|
||||
);
|
||||
chunk_vat_params(
|
||||
&mut group,
|
||||
format!("{group_name}_tlb_cache"),
|
||||
2,
|
||||
true,
|
||||
initialize_ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.provider :virtualbox do |v|
|
||||
v.memory = 2048
|
||||
v.cpus = 4
|
||||
config.vm.synced_folder "../..", "/memflow", create: true, disabled: false
|
||||
end
|
||||
config.vm.provider :libvirt do |v|
|
||||
v.memory = 2048
|
||||
v.cpus = 4
|
||||
v.qemu_use_session = false
|
||||
config.vm.synced_folder "../..", "/memflow", type: 'nfs', nfs_udp: false, create: true, disabled: false
|
||||
end
|
||||
|
||||
config.vm.box = "generic/ubuntu2004"
|
||||
config.vm.provision :shell, privileged: true, inline: $install_updates
|
||||
config.vm.provision :shell, privileged: false, inline: $install_memflow
|
||||
end
|
||||
|
||||
$install_updates = <<-SCRIPT
|
||||
echo "installing updates"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -qq update
|
||||
apt-get -qq install git build-essential
|
||||
SCRIPT
|
||||
|
||||
$install_memflow = <<-SCRIPT
|
||||
echo "installing rust"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source $HOME/.cargo/env
|
||||
|
||||
echo "building memflow"
|
||||
cd /memflow
|
||||
cargo build --release --workspace --all-features
|
||||
|
||||
# TODO: install memflow-win32, memflow-coredump, etc
|
||||
SCRIPT
|
@ -1,38 +0,0 @@
|
||||
use memflow::types::byte_swap::ByteSwap;
|
||||
use memflow_derive::*;
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDerive {
|
||||
pub val: u32,
|
||||
}
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDeriveGeneric<T: ByteSwap> {
|
||||
pub val: T,
|
||||
}
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDeriveWhere<T>
|
||||
where
|
||||
T: ByteSwap,
|
||||
{
|
||||
pub val: T,
|
||||
}
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDeriveSlice {
|
||||
pub slice: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDeriveStructSlice {
|
||||
pub slice: [ByteSwapDeriveSlice; 128],
|
||||
}
|
||||
|
||||
#[derive(ByteSwap)]
|
||||
struct ByteSwapDeriveStructGenericSlice<T: ByteSwap> {
|
||||
pub slice: [ByteSwapDeriveGeneric<T>; 128],
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compiles() {}
|
@ -1,3 +1,23 @@
|
||||
#!/bin/bash
|
||||
cargo build --release --workspace
|
||||
cbindgen --config cbindgen.toml --crate memflow-ffi --output memflow.h
|
||||
#!/usr/bin/env bash
|
||||
|
||||
run_twice() {
|
||||
echo $@
|
||||
|
||||
v=$(exec $@)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error occured! Maybe a fluke. Retrying..."
|
||||
v=$(exec $@)
|
||||
fi
|
||||
}
|
||||
|
||||
# remove any RUSTC_WRAPPER like sccache which might cause issues with cglue-bindgen
|
||||
export RUSTC_WRAPPER=""
|
||||
|
||||
# update cglue-bindgen
|
||||
cargo +nightly install cbindgen
|
||||
cargo +nightly install cglue-bindgen
|
||||
|
||||
# generate c and cpp bindings
|
||||
run_twice rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi --output memflow.h -l C
|
||||
run_twice rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi --output memflow.hpp -l C++
|
||||
|
@ -0,0 +1,3 @@
|
||||
default_container = "Box"
|
||||
default_context = "Arc"
|
||||
function_prefix = "mf"
|
@ -1,19 +0,0 @@
|
||||
CC =g++
|
||||
CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release
|
||||
LIBS=-lm -Wl,--no-as-needed -ldl -lpthread -l:libmemflow_win32_ffi.a
|
||||
|
||||
ODIR=./
|
||||
|
||||
%.o: %.c $(DEPS)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
phys_mem.out: phys_mem.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: all
|
||||
all: phys_mem.out
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
clean:
|
||||
rm -f $(ODIR)/*.o
|
@ -0,0 +1,2 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
@ -0,0 +1,31 @@
|
||||
CC = clang
|
||||
CFLAGS = -g -O0 -I../../../memflow-ffi/ -L../../../target/release
|
||||
LIBS=-lm -ldl -lpthread -l:libmemflow_ffi.a
|
||||
|
||||
ODIR=./
|
||||
|
||||
%.o: %.c $(DEPS)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
phys_mem.out: phys_mem.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
process_list.out: process_list.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
module_list.out: module_list.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
module_dump.out: module_dump.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
find_process.out: find_process.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: all
|
||||
all: phys_mem.out process_list.out module_list.out module_dump.out find_process.out
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
clean:
|
||||
rm -f $(ODIR)/*.o
|
@ -0,0 +1,210 @@
|
||||
#include "memflow.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
ProcessInstance target_process;
|
||||
ProcessInstance target_process2;
|
||||
|
||||
struct FindProcessContext {
|
||||
OsInstance *os;
|
||||
const char *name;
|
||||
ProcessInstance *target_process;
|
||||
bool found;
|
||||
};
|
||||
|
||||
bool find_process(struct FindProcessContext *find_context, Address addr) {
|
||||
|
||||
if (find_context->found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mf_osinstance_process_by_address(find_context->os, addr,
|
||||
find_context->target_process)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct ProcessInfo *info =
|
||||
mf_processinstance_info(find_context->target_process);
|
||||
|
||||
if (!strcmp(info->name, find_context->name)) {
|
||||
// abort iteration
|
||||
find_context->found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
mf_processinstance_drop(*find_context->target_process);
|
||||
|
||||
// continue iteration
|
||||
return true;
|
||||
}
|
||||
|
||||
pthread_mutex_t lock;
|
||||
|
||||
void *readmem(void *vargp) {
|
||||
// Store the value argument passed to this thread
|
||||
int *myid = (int *)vargp;
|
||||
|
||||
uint8_t buffer[0x8];
|
||||
|
||||
while (true) {
|
||||
pthread_mutex_lock(&lock);
|
||||
mf_processinstance_read_raw_into(&target_process, 0x7FF72AF30000,
|
||||
MUT_SLICE(u8, buffer, sizeof(buffer)));
|
||||
|
||||
printf("TID: %d, Read: %lx\n", *myid, *(uint64_t *)buffer);
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// enable info level logging
|
||||
log_init(3);
|
||||
|
||||
// load all available plugins
|
||||
Inventory *inventory = inventory_scan();
|
||||
printf("inventory initialized: %p\n", inventory);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "kvm";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
const char *os_name = argc > 3 ? argv[3] : "win32";
|
||||
const char *os_arg = argc > 4 ? argv[4] : "";
|
||||
const char *target_proc = argc > 5 ? argv[5] : "notepad.exe";
|
||||
|
||||
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
|
||||
|
||||
// initialize the connector plugin
|
||||
if (conn) {
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
|
||||
log_error("unable to initialize connector");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("connector initialized: %p\n",
|
||||
connector.container.instance.instance);
|
||||
}
|
||||
|
||||
// initialize the OS plugin
|
||||
OsInstance os;
|
||||
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
|
||||
log_error("unable to initialize os plugin");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("os plugin initialized: %p\n", os.container.instance.instance);
|
||||
|
||||
// find a specific process based on it's name.
|
||||
// this can easily be replaced by process_by_name but
|
||||
// is being used here as a demonstration.
|
||||
|
||||
/* struct FindProcessContext find_context = {
|
||||
&os,
|
||||
target_proc,
|
||||
&target_process,
|
||||
false,
|
||||
};
|
||||
|
||||
mf_osinstance_process_address_list_callback(
|
||||
&os, CALLBACK(Address, &find_context, find_process));
|
||||
|
||||
if (find_context.found) {
|
||||
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
|
||||
|
||||
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
|
||||
info->pid, info->name, info->path);
|
||||
|
||||
mf_processinstance_drop(target_process);
|
||||
} else {
|
||||
printf("Unable to find %s\n", target_proc);
|
||||
}*/
|
||||
|
||||
// find a specific process based on its name
|
||||
// via process_by_name
|
||||
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc),
|
||||
&target_process))) {
|
||||
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
|
||||
|
||||
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
|
||||
info->pid, info->name, info->path);
|
||||
|
||||
ModuleInfo module_info;
|
||||
mf_processinstance_module_by_name(&target_process, STR(target_proc),
|
||||
&module_info);
|
||||
printf("0x%lx\n", module_info.base);
|
||||
|
||||
/*ConnectorInstance conn2;
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, &conn2)) {
|
||||
log_error("unable to initialize connector");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ConnectorInstance cloned = mf_connectorinstance_clone(&conn2);
|
||||
printf("ok1\n");
|
||||
|
||||
mf_connectorinstance_drop(cloned);
|
||||
printf("ok2\n");*/
|
||||
|
||||
// MemoryView phys_view = mf_connectorinstance_phys_view(&conn2);
|
||||
|
||||
/*if (pthread_mutex_init(&lock, NULL) != 0) {
|
||||
printf("\n mutex init has failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
pthread_t tid;
|
||||
pthread_t tid2;
|
||||
pthread_create(&tid, NULL, readmem, (void *)&tid);
|
||||
pthread_create(&tid2, NULL, readmem, (void *)&tid2);
|
||||
|
||||
pthread_join(tid, NULL);
|
||||
pthread_join(tid2, NULL);
|
||||
|
||||
pthread_mutex_destroy(&lock);*/
|
||||
|
||||
if (!(ret = mf_osinstance_process_by_name(&os, STR("notepad++.exe"),
|
||||
&target_process2))) {
|
||||
const struct ProcessInfo *info2 =
|
||||
mf_processinstance_info(&target_process2);
|
||||
|
||||
printf("%s process found: 0x%lx] %d %s %s\n", "notepad++.exe",
|
||||
info2->address, info2->pid, info2->name, info2->path);
|
||||
|
||||
uint8_t buffer[0x8];
|
||||
|
||||
mf_processinstance_read_raw_into(&target_process, info->address + 0x520,
|
||||
MUT_SLICE(u8, buffer, sizeof(buffer)));
|
||||
|
||||
printf("Read: %lx\n", *(uint64_t *)buffer);
|
||||
|
||||
mf_processinstance_read_raw_into(&target_process2, info2->address + 0x520,
|
||||
MUT_SLICE(u8, buffer, sizeof(buffer)));
|
||||
|
||||
printf("Read2: %lx\n", *(uint64_t *)buffer);
|
||||
} else {
|
||||
printf("error notepad++");
|
||||
}
|
||||
|
||||
mf_processinstance_drop(target_process);
|
||||
} else {
|
||||
printf("Unable to find %s\n", target_proc);
|
||||
log_debug_errorcode(ret);
|
||||
}
|
||||
|
||||
// This will also free the connector here
|
||||
// as it was _moved_ into the os by `inventory_create_os`
|
||||
mf_osinstance_drop(os);
|
||||
log_info("os plugin/connector freed");
|
||||
|
||||
inventory_free(inventory);
|
||||
log_info("inventory freed");
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/**
|
||||
|
||||
This example demonstrates how to read the contents of a module from a process.
|
||||
|
||||
To read from a specific module the following steps have to be done:
|
||||
|
||||
- Create an inventory and let it search for plugins in the system
|
||||
- Load the plugins to access physical memory and the operating system
|
||||
(by default the `qemu` plugin and `win32` plugin are being used)
|
||||
- Find the process by the specified name
|
||||
- Find the module_info for the given module in the process
|
||||
- Allocate a buffer which will fit the entire module
|
||||
- Read the entire module into the buffer and ignore partial read errors
|
||||
- Write the contents of the retrieved buffer to the specified output location
|
||||
|
||||
|
||||
Usage:
|
||||
|
||||
./module_dump.out kvm :: win32 :: notepad.exe notepad.exe notepad.exe.bin
|
||||
|
||||
*/
|
||||
#include "memflow.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// enable info level logging
|
||||
log_init(4);
|
||||
|
||||
// load all available plugins
|
||||
Inventory *inventory = inventory_scan();
|
||||
printf("inventory initialized: %p\n", inventory);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "qemu";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
const char *os_name = argc > 3 ? argv[3]: "win32";
|
||||
const char *os_arg = argc > 4? argv[4]: "";
|
||||
const char *target_proc = argc > 5? argv[5]: "notepad.exe";
|
||||
const char *target_module = argc > 6? argv[6]: "notepad.exe";
|
||||
const char *output_file = argc > 7? argv[7]: "notepad.exe.bin";
|
||||
|
||||
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
|
||||
|
||||
// initialize the connector plugin
|
||||
if (conn) {
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
|
||||
log_error("unable to initialize connector");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("connector initialized: %p\n", connector.container.instance.instance);
|
||||
}
|
||||
|
||||
// initialize the OS plugin
|
||||
OsInstance os;
|
||||
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
|
||||
log_error("unable to initialize os plugin");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("os plugin initialized: %p\n", os.container.instance.instance);
|
||||
|
||||
// find a specific process based on its name via process_by_name
|
||||
ProcessInstance target_process;
|
||||
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc), &target_process))) {
|
||||
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
|
||||
|
||||
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
|
||||
info->pid, info->name, info->path);
|
||||
|
||||
// find the module by its name
|
||||
ModuleInfo module_info;
|
||||
if (!(ret = mf_processinstance_module_by_name(&target_process, STR(target_module), &module_info))) {
|
||||
printf("%s module found: 0x%lx] 0x%lx %s %s\n", target_proc, module_info.address,
|
||||
module_info.base, module_info.name, module_info.path);
|
||||
|
||||
// read module into buffer, in this case -2 / -3 are partial read/write errors
|
||||
void *module_buffer = malloc(module_info.size);
|
||||
ret = mf_processinstance_read_raw_into(&target_process, module_info.base, MUT_SLICE(u8, module_buffer, module_info.size));
|
||||
if (ret == -2) {
|
||||
printf("%s warning: %s] module only read partially\n", target_proc, target_module);
|
||||
}
|
||||
|
||||
// module has been read
|
||||
printf("%s read module: %s] read 0x%lx bytes\n", target_proc, target_module, module_info.size);
|
||||
|
||||
// write the buffer to the specified location
|
||||
FILE *file = fopen(output_file, "wb");
|
||||
if (file) {
|
||||
fwrite(module_buffer, module_info.size, 1, file);
|
||||
fclose(file);
|
||||
printf("dumped 0x%lx bytes to %s\n", module_info.size, output_file);
|
||||
} else {
|
||||
printf("unable to open output file %s: %s\n", output_file, strerror(errno));
|
||||
}
|
||||
|
||||
free(module_buffer);
|
||||
} else {
|
||||
printf("unable to find module: %s\n", target_module);
|
||||
log_debug_errorcode(ret);
|
||||
}
|
||||
|
||||
// cleanup the processinstance
|
||||
mf_processinstance_drop(target_process);
|
||||
} else {
|
||||
printf("unable to find process: %s\n", target_proc);
|
||||
log_debug_errorcode(ret);
|
||||
}
|
||||
|
||||
// This will also free the connector here
|
||||
// as it was _moved_ into the os by `inventory_create_os`
|
||||
mf_osinstance_drop(os);
|
||||
log_info("os plugin/connector freed");
|
||||
|
||||
inventory_free(inventory);
|
||||
log_info("inventory freed");
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
#include "memflow.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// enable info level logging
|
||||
log_init(3);
|
||||
|
||||
// load all available plugins
|
||||
Inventory *inventory = inventory_scan();
|
||||
printf("inventory initialized: %p\n", inventory);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "kvm";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
const char *os_name = argc > 3 ? argv[3]: "win32";
|
||||
const char *os_arg = argc > 4? argv[4]: "";
|
||||
const char *target_proc = argc > 5? argv[5]: "notepad.exe";
|
||||
|
||||
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
|
||||
|
||||
// initialize the connector plugin
|
||||
if (conn) {
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
|
||||
log_error("unable to initialize connector");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("connector initialized: %p\n", connector.container.instance.instance);
|
||||
}
|
||||
|
||||
// initialize the OS plugin
|
||||
OsInstance os;
|
||||
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
|
||||
log_error("unable to initialize os plugin");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("os plugin initialized: %p\n", os.container.instance.instance);
|
||||
|
||||
// find a specific process based on its name via process_by_name
|
||||
ProcessInstance target_process;
|
||||
if (!(ret = mf_osinstance_process_by_name(&os, STR(target_proc), &target_process))) {
|
||||
const struct ProcessInfo *info = mf_processinstance_info(&target_process);
|
||||
|
||||
printf("%s process found: 0x%lx] %d %s %s\n", target_proc, info->address,
|
||||
info->pid, info->name, info->path);
|
||||
|
||||
// iterate over all module info structs and collect them in a buffer
|
||||
COLLECT_CB(ModuleInfo, module_info);
|
||||
mf_processinstance_module_list_callback(&target_process, NULL, module_info);
|
||||
for (size_t i = 0; i < module_info_base.size; i++) {
|
||||
ModuleInfo *module_info = &((ModuleInfo *)module_info_base.buf)[i];
|
||||
printf("%s module found: 0x%lx] 0x%lx %s %s\n", target_proc, module_info->address,
|
||||
module_info->base, module_info->name, module_info->path);
|
||||
}
|
||||
free(module_info_base.buf);
|
||||
|
||||
// cleanup the processinstance
|
||||
mf_processinstance_drop(target_process);
|
||||
} else {
|
||||
printf("Unable to find %s\n", target_proc);
|
||||
log_debug_errorcode(ret);
|
||||
}
|
||||
|
||||
// This will also free the connector here
|
||||
// as it was _moved_ into the os by `inventory_create_os`
|
||||
mf_osinstance_drop(os);
|
||||
log_info("os plugin/connector freed");
|
||||
|
||||
inventory_free(inventory);
|
||||
log_info("inventory freed");
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
#include "memflow.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// enable debug level logging
|
||||
log_init(3);
|
||||
|
||||
Inventory *inv = inventory_scan();
|
||||
printf("inv: %p\n", inv);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "kvm";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
|
||||
ConnectorInstance conn;
|
||||
if (!inventory_create_connector(inv, conn_name, conn_arg, &conn)) {
|
||||
for (int i = 0; i < 1000 * 1000; i++) {
|
||||
uint8_t buffer[0x1000];
|
||||
|
||||
ConnectorInstance cloned = mf_connectorinstance_clone(&conn);
|
||||
|
||||
mf_connectorinstance_drop(cloned);
|
||||
|
||||
MemoryView phys_view = mf_connectorinstance_phys_view(&conn);
|
||||
|
||||
// regular read_into
|
||||
mf_read_raw_into(&phys_view, 0x1000 + i, MUT_SLICE(u8, buffer, sizeof(buffer)));
|
||||
|
||||
// read multiple
|
||||
ReadData read_data = {0x1000 + i, {buffer, sizeof(buffer)}};
|
||||
mf_read_raw_list(&phys_view, MUT_SLICE(ReadData, &read_data, 1));
|
||||
|
||||
printf("Read: %lx\n", *(uint64_t *)buffer);
|
||||
|
||||
mf_memoryview_drop(phys_view);
|
||||
}
|
||||
|
||||
mf_connectorinstance_drop(conn);
|
||||
printf("conn dropped!\n");
|
||||
}
|
||||
|
||||
inventory_free(inv);
|
||||
printf("inv freed!\n");
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
#include "memflow.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
bool list_processes(OsInstance *os, Address addr) {
|
||||
|
||||
int ret;
|
||||
|
||||
ProcessInstance process;
|
||||
if ((ret = mf_osinstance_process_by_address(os, addr, &process))) {
|
||||
log_debug_errorcode(ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct ProcessInfo *info = mf_processinstance_info(&process);
|
||||
|
||||
ModuleInfo primary_module;
|
||||
if ((ret = mf_processinstance_primary_module(&process, &primary_module))) {
|
||||
// no primary module found, continue iteration - this should _never_ happen
|
||||
printf("%d\t%s\t0x%lx\tN/A\n", info->pid, info->name, info->address);
|
||||
log_debug_errorcode(ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
printf("%d\t%s\t0x%lx\t0x%lx\n", info->pid, info->name, info->address,
|
||||
primary_module.address);
|
||||
|
||||
// iterate over all module addresses and collect them in an array
|
||||
struct ModuleAddressInfo module_addresses[256];
|
||||
COLLECT_CB_INTO_ARR(ModuleAddressInfo, module_address, module_addresses);
|
||||
mf_processinstance_module_address_list_callback(&process, NULL, module_address);
|
||||
|
||||
printf("Read %zu modules\n", module_address_base.size);
|
||||
|
||||
// iterate over all module info structs and collect them in a buffer
|
||||
COLLECT_CB(ModuleInfo, module_info);
|
||||
mf_processinstance_module_list_callback(&process, NULL, module_info);
|
||||
printf("Read %zu modules\n", module_info_base.size);
|
||||
free(module_info_base.buf);
|
||||
|
||||
// iterate over all imports and collect them in a buffer
|
||||
COLLECT_CB(ImportInfo, import_info);
|
||||
mf_processinstance_module_import_list_callback(&process, &primary_module, import_info);
|
||||
printf("Read %zu imports\n", import_info_base.size);
|
||||
free(import_info_base.buf);
|
||||
|
||||
// iterate over all exports and collect them in a buffer
|
||||
COLLECT_CB(ExportInfo, exports);
|
||||
mf_processinstance_module_export_list_callback(&process, &primary_module, exports);
|
||||
printf("Read %zu exports\n", exports_base.size);
|
||||
free(exports_base.buf);
|
||||
|
||||
// iterate over all sections and collect them in a buffer
|
||||
COLLECT_CB(SectionInfo, sections);
|
||||
mf_processinstance_module_section_list_callback(&process, &primary_module, sections);
|
||||
printf("Read %zu sections\n", sections_base.size);
|
||||
free(sections_base.buf);
|
||||
|
||||
mf_processinstance_drop(process);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// enable debug level logging
|
||||
log_init(2);
|
||||
|
||||
// load all available plugins
|
||||
Inventory *inventory = inventory_scan();
|
||||
|
||||
printf("inventory initialized: %p\n", inventory);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "kvm";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
const char *os_name = argc > 3 ? argv[3]: "win32";
|
||||
const char *os_arg = argc > 4? argv[4]: "";
|
||||
|
||||
ConnectorInstance connector, *conn = conn_name[0] ? &connector : NULL;
|
||||
|
||||
// initialize the connector plugin
|
||||
if (conn) {
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, conn)) {
|
||||
printf("unable to initialize connector\n");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("connector initialized: %p\n", connector.container.instance.instance);
|
||||
}
|
||||
|
||||
// initialize the OS plugin
|
||||
OsInstance os;
|
||||
if (inventory_create_os(inventory, os_name, os_arg, conn, &os)) {
|
||||
printf("unable to initialize os plugin\n");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("os plugin initialized: %p\n", os.container.instance.instance);
|
||||
|
||||
// iterate over all processes and print them manually
|
||||
printf("Pid\tNAME\tADDRESS\tMAIN_MODULE\n");
|
||||
mf_osinstance_process_address_list_callback(&os, CALLBACK(Address, &os, list_processes));
|
||||
|
||||
// count all processes
|
||||
COUNT_CB(Address, process_address);
|
||||
mf_osinstance_process_address_list_callback(&os, process_address);
|
||||
printf("Counted %zu processes\n", process_address_count);
|
||||
|
||||
// iterate over all process info structs and collect them in an array
|
||||
struct ProcessInfo process_info[256];
|
||||
COLLECT_CB_INTO_ARR(ProcessInfo, process_info_cb, process_info);
|
||||
mf_osinstance_process_info_list_callback(&os, process_info_cb);
|
||||
printf("Read %zu process infos\n", process_info_cb_base.size);
|
||||
|
||||
// This will also free the connector here
|
||||
// as it was _moved_ into the os by `inventory_create_os`
|
||||
mf_osinstance_drop(os);
|
||||
printf("os plugin/connector freed\n");
|
||||
|
||||
inventory_free(inventory);
|
||||
printf("inventory freed\n");
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
CC = g++
|
||||
CFLAGS = -g -O0 -pedantic -std=c++14 -I../../../memflow-ffi/ -L../../../target/release
|
||||
LIBS=-lm -ldl -lpthread -l:libmemflow_ffi.a
|
||||
|
||||
ODIR=./
|
||||
|
||||
%.o: %.cpp $(DEPS)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
plist.out: plist.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: all
|
||||
all: plist.out
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
clean:
|
||||
rm -f $(ODIR)/*.o
|
@ -0,0 +1,104 @@
|
||||
#include "memflow.hpp"
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
|
||||
void fmt_arch(char *arch, int n, ArchitectureIdent ident);
|
||||
|
||||
int main(int argc, char *argv[], const int t[])
|
||||
{
|
||||
log_init(LevelFilter::LevelFilter_Info);
|
||||
int arr[] = {1, 2, 3, 4, 5};
|
||||
int b=3;
|
||||
//CSliceMut<unsigned char>((char*)arr);
|
||||
CSliceRef<uint8_t>((char*)t, sizeof(int));
|
||||
|
||||
/*Inventory *inventory = inventory_scan();
|
||||
|
||||
if (!inventory)
|
||||
{
|
||||
log_error("unable to create inventory");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("inventory initialized: %p\n", inventory);
|
||||
|
||||
const char *conn_name = argc > 1 ? argv[1] : "kvm";
|
||||
const char *conn_arg = argc > 2 ? argv[2] : "";
|
||||
const char *os_name = argc > 3 ? argv[3] : "win32";
|
||||
const char *os_arg = argc > 4 ? argv[4] : "";
|
||||
|
||||
// ConnectorInstance<> connector, *conn = conn_name[0] ? &connector : nullptr;
|
||||
|
||||
std::unique_ptr<ConnectorInstance<>> connector = nullptr;
|
||||
|
||||
if (!connector)
|
||||
{
|
||||
connector = std::make_unique<ConnectorInstance<>>();
|
||||
if (inventory_create_connector(inventory, conn_name, conn_arg, connector.get()))
|
||||
{
|
||||
printf("unable to initialize connector\n");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("connector initialized: %p\n", connector->container.instance.instance);
|
||||
}
|
||||
|
||||
OsInstance<> os;
|
||||
|
||||
if (inventory_create_os(inventory, os_name, os_arg, connector.get(), &os))
|
||||
{
|
||||
printf("unable to initialize OS\n");
|
||||
inventory_free(inventory);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inventory_free(inventory);
|
||||
|
||||
printf("os initialized: %p\n", os.container.instance.instance);
|
||||
|
||||
auto info = os.info();
|
||||
char arch[11];
|
||||
fmt_arch(arch, sizeof(arch), info->arch);
|
||||
|
||||
printf("Kernel base: %llx\nKernel size: %llx\nArchitecture: %s\n", info->base, info->size, arch);
|
||||
|
||||
printf("Process List:\n");
|
||||
|
||||
printf("%-4s | %-8s | %-10s | %-10s | %s\n", "Seq", "Pid", "Sys Arch", "Proc Arch", "Name");
|
||||
|
||||
int i = 0;
|
||||
|
||||
os.process_info_list_callback([&i](ProcessInfo info)
|
||||
{
|
||||
char sys_arch[11];
|
||||
char proc_arch[11];
|
||||
|
||||
fmt_arch(sys_arch, sizeof(sys_arch), info.sys_arch);
|
||||
fmt_arch(proc_arch, sizeof(proc_arch), info.proc_arch);
|
||||
|
||||
printf("%-4d | %-8d | %-10s | %-10s | %s\n", i++, info.pid, sys_arch, proc_arch, info.name);
|
||||
|
||||
return true; });
|
||||
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fmt_arch(char *arch, int n, ArchitectureIdent ident)
|
||||
{
|
||||
switch (ident.tag)
|
||||
{
|
||||
case ArchitectureIdent::Tag::ArchitectureIdent_X86:
|
||||
snprintf(arch, n, "X86_%d", ident.x86._0);
|
||||
break;
|
||||
case ArchitectureIdent::Tag::ArchitectureIdent_AArch64:
|
||||
snprintf(arch, n, "AArch64");
|
||||
break;
|
||||
default:
|
||||
snprintf(arch, n, "Unknown");
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#include "memflow.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
log_init(4);
|
||||
|
||||
ConnectorInventory *inv = inventory_scan();
|
||||
printf("inv: %p\n", inv);
|
||||
|
||||
const char *conn_name = argc > 1? argv[1]: "qemu_procfs";
|
||||
const char *conn_arg = argc > 2? argv[2]: "";
|
||||
|
||||
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
|
||||
printf("conn: %p\n", conn);
|
||||
|
||||
if (conn) {
|
||||
PhysicalMemoryObj *phys_mem = downcast_cloneable(conn);
|
||||
printf("phys_mem: %p\n", phys_mem);
|
||||
|
||||
uint64_t read = phys_read_u64(phys_mem, addr_to_paddr(0x30000));
|
||||
|
||||
printf("Read: %lx\n", read);
|
||||
|
||||
phys_free(phys_mem);
|
||||
|
||||
connector_free(conn);
|
||||
printf("conn freed!\n");
|
||||
}
|
||||
|
||||
inventory_free(inv);
|
||||
printf("inv freed!\n");
|
||||
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,177 +0,0 @@
|
||||
#ifndef MEMFLOW_HLAPI_H
|
||||
#define MEMFLOW_HLAPI_H
|
||||
|
||||
#include "memflow.h"
|
||||
#include "binddestr.h"
|
||||
|
||||
#ifndef NO_STL_CONTAINERS
|
||||
#include <string>
|
||||
#ifndef AUTO_STRING_SIZE
|
||||
#define AUTO_STRING_SIZE 128
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct CConnectorInventory
|
||||
: BindDestr<ConnectorInventory, inventory_free>
|
||||
{
|
||||
CConnectorInventory(ConnectorInventory *inv)
|
||||
: BindDestr(inv) {}
|
||||
|
||||
CConnectorInventory()
|
||||
: CConnectorInventory(::inventory_scan()) {}
|
||||
|
||||
CConnectorInventory(const char *path)
|
||||
: CConnectorInventory(::inventory_scan_path(path)) {}
|
||||
|
||||
WRAP_FN(inventory, add_dir);
|
||||
WRAP_FN(inventory, create_connector);
|
||||
};
|
||||
|
||||
struct CPhysicalMemory
|
||||
: BindDestr<PhysicalMemoryObj, phys_free>
|
||||
{
|
||||
CPhysicalMemory(PhysicalMemoryObj *mem)
|
||||
: BindDestr(mem) {}
|
||||
|
||||
WRAP_FN_RAW(phys_read_raw_list);
|
||||
WRAP_FN_RAW(phys_write_raw_list);
|
||||
WRAP_FN_RAW(phys_metadata);
|
||||
WRAP_FN_RAW(phys_read_raw_into);
|
||||
WRAP_FN_RAW(phys_read_u32);
|
||||
WRAP_FN_RAW(phys_read_u64);
|
||||
WRAP_FN_RAW(phys_write_raw);
|
||||
WRAP_FN_RAW(phys_write_u32);
|
||||
WRAP_FN_RAW(phys_write_u64);
|
||||
|
||||
template<typename T>
|
||||
T phys_read(PhysicalAddress address) {
|
||||
T data;
|
||||
this->phys_read_raw_into(address, (uint8_t *)&data, sizeof(T));
|
||||
return data;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int32_t phys_write(PhysicalAddress address, const T &data) {
|
||||
return this->phys_write_raw(address, (const uint8_t *)&data, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
struct CCloneablePhysicalMemory
|
||||
: BindDestr<CloneablePhysicalMemoryObj, connector_free>
|
||||
{
|
||||
CCloneablePhysicalMemory(CloneablePhysicalMemoryObj *mem)
|
||||
: BindDestr(mem) {}
|
||||
|
||||
WRAP_FN(connector, clone);
|
||||
WRAP_FN_RAW_TYPE(CPhysicalMemory, downcast_cloneable);
|
||||
};
|
||||
|
||||
struct CVirtualMemory
|
||||
: BindDestr<VirtualMemoryObj, virt_free>
|
||||
{
|
||||
CVirtualMemory(VirtualMemoryObj *virt_mem)
|
||||
: BindDestr(virt_mem) {}
|
||||
|
||||
WRAP_FN_RAW(virt_read_raw_list);
|
||||
WRAP_FN_RAW(virt_write_raw_list);
|
||||
WRAP_FN_RAW(virt_read_raw_into);
|
||||
WRAP_FN_RAW(virt_read_u32);
|
||||
WRAP_FN_RAW(virt_read_u64);
|
||||
WRAP_FN_RAW(virt_write_raw);
|
||||
WRAP_FN_RAW(virt_write_u32);
|
||||
WRAP_FN_RAW(virt_write_u64);
|
||||
|
||||
template<typename T>
|
||||
T virt_read(Address address) {
|
||||
T data;
|
||||
this->virt_read_raw_into(address, (uint8_t *)&data, sizeof(T));
|
||||
return data;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int32_t virt_write(Address address, const T &data) {
|
||||
return this->virt_write_raw(address, (const uint8_t *)&data, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
struct CArchitecture
|
||||
: BindDestr<ArchitectureObj, arch_free>
|
||||
{
|
||||
CArchitecture(ArchitectureObj *arch)
|
||||
: BindDestr(arch) {}
|
||||
|
||||
WRAP_FN(arch, bits);
|
||||
WRAP_FN(arch, endianess);
|
||||
WRAP_FN(arch, page_size);
|
||||
WRAP_FN(arch, size_addr);
|
||||
WRAP_FN(arch, address_space_bits);
|
||||
WRAP_FN_RAW(is_x86_arch);
|
||||
};
|
||||
|
||||
struct COsProcessInfo
|
||||
: BindDestr<OsProcessInfoObj, os_process_info_free>
|
||||
{
|
||||
COsProcessInfo(OsProcessInfoObj *info)
|
||||
: BindDestr(info) {}
|
||||
|
||||
WRAP_FN(os_process_info, address);
|
||||
WRAP_FN(os_process_info, pid);
|
||||
WRAP_FN(os_process_info, name);
|
||||
WRAP_FN_TYPE(CArchitecture, os_process_info, sys_arch);
|
||||
WRAP_FN_TYPE(CArchitecture, os_process_info, proc_arch);
|
||||
|
||||
#ifndef NO_STL_CONTAINERS
|
||||
std::string name_string(size_t max_size) {
|
||||
char *buf = (char *)malloc(max_size);
|
||||
if (buf) {
|
||||
this->name(buf, max_size);
|
||||
std::string ret = std::string(buf);
|
||||
free(buf);
|
||||
return ret;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::string name_string() {
|
||||
char buf[AUTO_STRING_SIZE];
|
||||
size_t ret = this->name(buf, AUTO_STRING_SIZE);
|
||||
return std::string(buf);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct COsProcessModuleInfo
|
||||
: BindDestr<OsProcessModuleInfoObj, os_process_module_free>
|
||||
{
|
||||
COsProcessModuleInfo(OsProcessModuleInfoObj *modinfo)
|
||||
: BindDestr(modinfo) {}
|
||||
|
||||
WRAP_FN(os_process_module, address);
|
||||
WRAP_FN(os_process_module, parent_process);
|
||||
WRAP_FN(os_process_module, base);
|
||||
WRAP_FN(os_process_module, size);
|
||||
WRAP_FN(os_process_module, name);
|
||||
|
||||
#ifndef NO_STL_CONTAINERS
|
||||
std::string name_string(size_t max_size) {
|
||||
char *buf = (char *)malloc(max_size);
|
||||
if (buf) {
|
||||
this->name(buf, max_size);
|
||||
std::string ret = std::string(buf);
|
||||
free(buf);
|
||||
return ret;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::string name_string() {
|
||||
char buf[AUTO_STRING_SIZE];
|
||||
this->name(buf, AUTO_STRING_SIZE);
|
||||
return std::string(buf);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,43 @@
|
||||
---
|
||||
GENERATOR:
|
||||
PackageName: memflow_go
|
||||
PackageDescription: "core components of the memflow physical memory introspection framework"
|
||||
PackageLicense: "MIT"
|
||||
Includes: ["memflow.h"]
|
||||
FlagGroups:
|
||||
- { name: "CFLAGS", traits: ["linux"], flags: [ -I../../memflow-ffi/ ] }
|
||||
- { name: "LDFLAGS", traits: ["linux"], flags: [ -L../../target/release -lm -ldl -lpthread -l:libmemflow_ffi.a ] }
|
||||
|
||||
PARSER:
|
||||
IncludePaths: ["./"]
|
||||
SourcesPaths: ["memflow.h"]
|
||||
|
||||
TRANSLATOR:
|
||||
ConstRules:
|
||||
defines: expand
|
||||
Rules:
|
||||
global:
|
||||
- { transform: lower }
|
||||
- { action: accept, from: "^log_" }
|
||||
- { action: accept, from: "^inventory_" }
|
||||
- { action: accept, from: "^connector_" }
|
||||
- { action: accept, from: "^phys_" }
|
||||
- { action: accept, from: "^virt_" }
|
||||
- { action: replace, from: "_", to: _ }
|
||||
- { transform: export }
|
||||
const:
|
||||
- { action: replace, from: "_", to: _ }
|
||||
type:
|
||||
- { action: accept, from: "^MU" }
|
||||
- { action: accept, from: "Inventory" }
|
||||
- { action: accept, from: "^Connector" }
|
||||
- { action: accept, from: "^Physical" }
|
||||
- { action: accept, from: "^Virtual" }
|
||||
- { action: accept, from: "^Translation" }
|
||||
- { action: accept, from: "^Page" }
|
||||
- { action: replace, from: "_t$" }
|
||||
private:
|
||||
- { transform: unexport }
|
||||
post-global:
|
||||
- { action: replace, from: _$ }
|
||||
- { load: snakecase }
|
@ -1,150 +0,0 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use memflow::connector::{ConnectorArgs, ConnectorInventory};
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
use crate::mem::phys_mem::CloneablePhysicalMemoryObj;
|
||||
|
||||
use log::trace;
|
||||
|
||||
/// Create a new connector inventory
|
||||
///
|
||||
/// This function will try to find connectors using PATH environment variable
|
||||
///
|
||||
/// Note that all functions go through each directories, and look for a `memflow` directory,
|
||||
/// and search for libraries in those.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// ConnectorInventory is inherently unsafe, because it loads shared libraries which can not be
|
||||
/// guaranteed to be safe.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_scan() -> &'static mut ConnectorInventory {
|
||||
to_heap(ConnectorInventory::scan())
|
||||
}
|
||||
|
||||
/// Create a new inventory with custom path string
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `path` must be a valid null terminated string
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_scan_path(
|
||||
path: *const c_char,
|
||||
) -> Option<&'static mut ConnectorInventory> {
|
||||
let rpath = CStr::from_ptr(path).to_string_lossy();
|
||||
ConnectorInventory::scan_path(rpath.to_string())
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Add a directory to an existing inventory
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `dir` must be a valid null terminated string
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_add_dir(
|
||||
inv: &mut ConnectorInventory,
|
||||
dir: *const c_char,
|
||||
) -> i32 {
|
||||
let rdir = CStr::from_ptr(dir).to_string_lossy();
|
||||
|
||||
inv.add_dir(PathBuf::from(rdir.to_string()))
|
||||
.int_result_logged()
|
||||
}
|
||||
|
||||
/// Create a connector with given arguments
|
||||
///
|
||||
/// This creates an instance of a `CloneablePhysicalMemory`. To use it for physical memory
|
||||
/// operations, please call `downcast_cloneable` to create a instance of `PhysicalMemory`.
|
||||
///
|
||||
/// Regardless, this instance needs to be freed using `connector_free`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the connector to use
|
||||
/// * `args` - arguments to be passed to the connector upon its creation
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Both `name`, and `args` must be valid null terminated strings.
|
||||
///
|
||||
/// Any error strings returned by the connector must not be outputed after the connector gets
|
||||
/// freed, because that operation could cause the underlying shared library to get unloaded.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_create_connector(
|
||||
inv: &mut ConnectorInventory,
|
||||
name: *const c_char,
|
||||
args: *const c_char,
|
||||
) -> Option<&'static mut CloneablePhysicalMemoryObj> {
|
||||
let rname = CStr::from_ptr(name).to_string_lossy();
|
||||
|
||||
if args.is_null() {
|
||||
inv.create_connector_default(&rname)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
.map(|c| c as CloneablePhysicalMemoryObj)
|
||||
.map(to_heap)
|
||||
} else {
|
||||
let rargs = CStr::from_ptr(args).to_string_lossy();
|
||||
let conn_args = ConnectorArgs::parse(&rargs).map_err(inspect_err).ok()?;
|
||||
|
||||
inv.create_connector(&rname, &conn_args)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
.map(|c| c as CloneablePhysicalMemoryObj)
|
||||
.map(to_heap)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone a connector
|
||||
///
|
||||
/// This method is useful when needing to perform multithreaded operations, as a connector is not
|
||||
/// guaranteed to be thread safe. Every single cloned instance also needs to be freed using
|
||||
/// `connector_free`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn connector_clone(
|
||||
conn: &CloneablePhysicalMemoryObj,
|
||||
) -> &'static mut CloneablePhysicalMemoryObj {
|
||||
trace!("connector_clone: {:?}", conn as *const _);
|
||||
Box::leak(Box::new(Box::leak(conn.clone_box())))
|
||||
}
|
||||
|
||||
/// Free a connector instance
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `conn` has to point to a valid `CloneablePhysicalMemoryObj` created by one of the provided
|
||||
/// functions.
|
||||
///
|
||||
/// There has to be no instance of `PhysicalMemory` created from the input `conn`, because they
|
||||
/// will become invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn connector_free(conn: &'static mut CloneablePhysicalMemoryObj) {
|
||||
trace!("connector_free: {:?}", conn as *mut _);
|
||||
let _ = Box::from_raw(*Box::from_raw(conn));
|
||||
}
|
||||
|
||||
/// Free a connector inventory
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `inv` must point to a valid `ConnectorInventory` that was created using one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_free(inv: &'static mut ConnectorInventory) {
|
||||
trace!("inventory_free: {:?}", inv as *mut _);
|
||||
let _ = Box::from_raw(inv);
|
||||
}
|
@ -1,17 +1,125 @@
|
||||
use log::Level;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn log_init(level_num: i32) {
|
||||
let level = match level_num {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
use log::{Level, LevelFilter};
|
||||
use memflow::cglue::IntError;
|
||||
use memflow::error::Error;
|
||||
use memflow::plugins::Inventory;
|
||||
use std::num::NonZeroI32;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
/// Initialize logging with selected logging level.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn log_init(level_filter: LevelFilter) {
|
||||
simplelog::TermLogger::init(
|
||||
level_filter,
|
||||
simplelog::Config::default(),
|
||||
simplelog::TerminalMode::Stdout,
|
||||
simplelog::ColorChoice::Auto,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// TODO: add variadic functions when this is being stabilized, see https://github.com/rust-lang/rust/issues/44930
|
||||
|
||||
/// Logs a error message via log::error!
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided string must be a valid null-terminated char array.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn log_error(s: *const c_char) {
|
||||
if !s.is_null() {
|
||||
let c_str = CStr::from_ptr(s);
|
||||
if let Ok(r_str) = c_str.to_str() {
|
||||
log::error!("{}", r_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs a warning message via log::warn!
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided string must be a valid null-terminated char array.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn log_warn(s: *const c_char) {
|
||||
if !s.is_null() {
|
||||
let c_str = CStr::from_ptr(s);
|
||||
if let Ok(r_str) = c_str.to_str() {
|
||||
log::warn!("{}", r_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs a info message via log::info!
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided string must be a valid null-terminated char array.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn log_info(s: *const c_char) {
|
||||
if !s.is_null() {
|
||||
let c_str = CStr::from_ptr(s);
|
||||
if let Ok(r_str) = c_str.to_str() {
|
||||
log::info!("{}", r_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs a debug message via log::debug!
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided string must be a valid null-terminated char array.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn log_debug(s: *const c_char) {
|
||||
if !s.is_null() {
|
||||
let c_str = CStr::from_ptr(s);
|
||||
if let Ok(r_str) = c_str.to_str() {
|
||||
log::debug!("{}", r_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs a trace message via log::trace!
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided string must be a valid null-terminated char array.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn log_trace(s: *const c_char) {
|
||||
if !s.is_null() {
|
||||
let c_str = CStr::from_ptr(s);
|
||||
if let Ok(r_str) = c_str.to_str() {
|
||||
log::trace!("{}", r_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs an error code with custom log level.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn log_errorcode(level: Level, error: i32) {
|
||||
if let Some(error) = NonZeroI32::new(error) {
|
||||
log::log!(level, "{}", <Error as IntError>::from_int_err(error));
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs an error with debug log level.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn log_debug_errorcode(error: i32) {
|
||||
log_errorcode(Level::Debug, error)
|
||||
}
|
||||
|
||||
/// Sets new maximum log level.
|
||||
///
|
||||
/// If `inventory` is supplied, the log level is also updated within all plugin instances. However,
|
||||
/// if it is not supplied, plugins will not have their log levels updated, potentially leading to
|
||||
/// lower performance, or less logging than expected.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn log_set_max_level(level_filter: LevelFilter, inventory: Option<&Inventory>) {
|
||||
if let Some(inventory) = inventory {
|
||||
inventory.set_max_log_level(level_filter);
|
||||
} else {
|
||||
log::set_max_level(level_filter);
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
pub mod phys_mem;
|
||||
pub mod virt_mem;
|
||||
#[allow(unused)]
|
||||
pub use memflow::mem::phys_mem::*;
|
||||
#[allow(unused)]
|
||||
pub use memflow::mem::virt_mem::*;
|
||||
|
@ -1,146 +0,0 @@
|
||||
use memflow::mem::phys_mem::*;
|
||||
use memflow::types::PhysicalAddress;
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
||||
|
||||
use log::trace;
|
||||
|
||||
pub type CloneablePhysicalMemoryObj = &'static mut dyn CloneablePhysicalMemory;
|
||||
pub type PhysicalMemoryObj = &'static mut dyn PhysicalMemory;
|
||||
|
||||
/// Downcast a cloneable physical memory into a physical memory object.
|
||||
///
|
||||
/// This function will take a `cloneable` and turn it into a `PhysicalMemoryObj`, which then can be
|
||||
/// used by physical memory functions.
|
||||
///
|
||||
/// Please note that this does not free `cloneable`, and the reference is still valid for further
|
||||
/// operations.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn downcast_cloneable(
|
||||
cloneable: &'static mut CloneablePhysicalMemoryObj,
|
||||
) -> &'static mut PhysicalMemoryObj {
|
||||
Box::leak(Box::new((*cloneable).downcast()))
|
||||
}
|
||||
|
||||
/// Free a `PhysicalMemoryObj`
|
||||
///
|
||||
/// This will free a reference to a `PhysicalMemoryObj`. If the physical memory object was created
|
||||
/// using `downcast_cloneable`, this will NOT free the cloneable reference.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `mem` must point to a valid `PhysicalMemoryObj` that was created using one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn phys_free(mem: &'static mut PhysicalMemoryObj) {
|
||||
trace!("phys_free: {:?}", mem as *mut _);
|
||||
let _ = Box::from_raw(mem);
|
||||
}
|
||||
|
||||
/// Read a list of values
|
||||
///
|
||||
/// This will perform `len` physical memory reads on the provided `data`. Using lists is preferable
|
||||
/// for performance, because then the underlying connectors can batch those operations.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data` must be a valid array of `PhysicalReadData` with the length of at least `len`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn phys_read_raw_list(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
data: *mut PhysicalReadData,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
let data = from_raw_parts_mut(data, len);
|
||||
mem.phys_read_raw_list(data).int_result()
|
||||
}
|
||||
|
||||
/// Write a list of values
|
||||
///
|
||||
/// This will perform `len` physical memory writes on the provided `data`. Using lists is preferable
|
||||
/// for performance, because then the underlying connectors can batch those operations.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data` must be a valid array of `PhysicalWriteData` with the length of at least `len`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn phys_write_raw_list(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
data: *const PhysicalWriteData,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
let data = from_raw_parts(data, len);
|
||||
mem.phys_write_raw_list(data).int_result()
|
||||
}
|
||||
|
||||
/// Retrieve metadata about the physical memory object
|
||||
#[no_mangle]
|
||||
pub extern "C" fn phys_metadata(mem: &PhysicalMemoryObj) -> PhysicalMemoryMetadata {
|
||||
mem.metadata()
|
||||
}
|
||||
|
||||
/// Read a single value into `out` from a provided `PhysicalAddress`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `out` must be a valid pointer to a data buffer of at least `len` size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn phys_read_raw_into(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
addr: PhysicalAddress,
|
||||
out: *mut u8,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
mem.phys_read_raw_into(addr, from_raw_parts_mut(out, len))
|
||||
.int_result()
|
||||
}
|
||||
|
||||
/// Read a single 32-bit value from a provided `PhysicalAddress`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn phys_read_u32(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u32 {
|
||||
mem.phys_read::<u32>(addr).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Read a single 64-bit value from a provided `PhysicalAddress`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn phys_read_u64(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u64 {
|
||||
mem.phys_read::<u64>(addr).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Write a single value from `input` into a provided `PhysicalAddress`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `input` must be a valid pointer to a data buffer of at least `len` size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn phys_write_raw(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
addr: PhysicalAddress,
|
||||
input: *const u8,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
mem.phys_write_raw(addr, from_raw_parts(input, len))
|
||||
.int_result()
|
||||
}
|
||||
|
||||
/// Write a single 32-bit value into a provided `PhysicalAddress`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn phys_write_u32(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
addr: PhysicalAddress,
|
||||
val: u32,
|
||||
) -> i32 {
|
||||
mem.phys_write(addr, &val).int_result()
|
||||
}
|
||||
|
||||
/// Write a single 64-bit value into a provided `PhysicalAddress`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn phys_write_u64(
|
||||
mem: &mut PhysicalMemoryObj,
|
||||
addr: PhysicalAddress,
|
||||
val: u64,
|
||||
) -> i32 {
|
||||
mem.phys_write(addr, &val).int_result()
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
use memflow::error::PartialResultExt;
|
||||
use memflow::mem::virt_mem::*;
|
||||
use memflow::types::Address;
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
||||
|
||||
pub type VirtualMemoryObj = &'static mut dyn VirtualMemory;
|
||||
|
||||
/// Free a virtual memory object reference
|
||||
///
|
||||
/// This function frees the reference to a virtual memory object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `mem` must be a valid reference to a virtual memory object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn virt_free(mem: &'static mut VirtualMemoryObj) {
|
||||
let _ = Box::from_raw(mem);
|
||||
}
|
||||
|
||||
/// Read a list of values
|
||||
///
|
||||
/// This will perform `len` virtual memory reads on the provided `data`. Using lists is preferable
|
||||
/// for performance, because then the underlying connectors can batch those operations, and virtual
|
||||
/// translation function can cut down on read operations.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data` must be a valid array of `VirtualReadData` with the length of at least `len`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn virt_read_raw_list(
|
||||
mem: &mut VirtualMemoryObj,
|
||||
data: *mut VirtualReadData,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
let data = from_raw_parts_mut(data, len);
|
||||
mem.virt_read_raw_list(data).data_part().int_result()
|
||||
}
|
||||
|
||||
/// Write a list of values
|
||||
///
|
||||
/// This will perform `len` virtual memory writes on the provided `data`. Using lists is preferable
|
||||
/// for performance, because then the underlying connectors can batch those operations, and virtual
|
||||
/// translation function can cut down on read operations.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data` must be a valid array of `VirtualWriteData` with the length of at least `len`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn virt_write_raw_list(
|
||||
mem: &mut VirtualMemoryObj,
|
||||
data: *const VirtualWriteData,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
let data = from_raw_parts(data, len);
|
||||
mem.virt_write_raw_list(data).data_part().int_result()
|
||||
}
|
||||
|
||||
/// Read a single value into `out` from a provided `Address`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `out` must be a valid pointer to a data buffer of at least `len` size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn virt_read_raw_into(
|
||||
mem: &mut VirtualMemoryObj,
|
||||
addr: Address,
|
||||
out: *mut u8,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
mem.virt_read_raw_into(addr, from_raw_parts_mut(out, len))
|
||||
.data_part()
|
||||
.int_result()
|
||||
}
|
||||
|
||||
/// Read a single 32-bit value from a provided `Address`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn virt_read_u32(mem: &mut VirtualMemoryObj, addr: Address) -> u32 {
|
||||
mem.virt_read::<u32>(addr).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Read a single 64-bit value from a provided `Address`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn virt_read_u64(mem: &mut VirtualMemoryObj, addr: Address) -> u64 {
|
||||
mem.virt_read::<u64>(addr).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Write a single value from `input` into a provided `Address`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `input` must be a valid pointer to a data buffer of at least `len` size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn virt_write_raw(
|
||||
mem: &mut VirtualMemoryObj,
|
||||
addr: Address,
|
||||
input: *const u8,
|
||||
len: usize,
|
||||
) -> i32 {
|
||||
mem.virt_write_raw(addr, from_raw_parts(input, len))
|
||||
.data_part()
|
||||
.int_result()
|
||||
}
|
||||
|
||||
/// Write a single 32-bit value into a provided `Address`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn virt_write_u32(mem: &mut VirtualMemoryObj, addr: Address, val: u32) -> i32 {
|
||||
mem.virt_write(addr, &val).data_part().int_result()
|
||||
}
|
||||
|
||||
/// Write a single 64-bit value into a provided `Address`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn virt_write_u64(mem: &mut VirtualMemoryObj, addr: Address, val: u64) -> i32 {
|
||||
mem.virt_write(addr, &val).data_part().int_result()
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#[allow(unused)]
|
||||
pub use memflow::os::*;
|
||||
#[allow(unused)]
|
||||
pub use memflow::plugins::*;
|
@ -0,0 +1,215 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use memflow::plugins::Inventory;
|
||||
use memflow::plugins::{
|
||||
connector::{ConnectorInstanceArcBox, MuConnectorInstanceArcBox},
|
||||
os::{MuOsInstanceArcBox, OsInstanceArcBox},
|
||||
};
|
||||
|
||||
use crate::util::*;
|
||||
use memflow::cglue::result::IntResult;
|
||||
|
||||
use log::trace;
|
||||
|
||||
/// Create a new connector inventory
|
||||
///
|
||||
/// This function will try to find connectors using PATH environment variable
|
||||
///
|
||||
/// Note that all functions go through each directories, and look for a `memflow` directory,
|
||||
/// and search for libraries in those.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Inventory is inherently unsafe, because it loads shared libraries which can not be
|
||||
/// guaranteed to be safe.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_scan() -> &'static mut Inventory {
|
||||
to_heap(Inventory::scan())
|
||||
}
|
||||
|
||||
/// Create a new inventory with custom path string
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `path` must be a valid null terminated string
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_scan_path(
|
||||
path: *const c_char,
|
||||
) -> Option<&'static mut Inventory> {
|
||||
let rpath = CStr::from_ptr(path).to_string_lossy();
|
||||
Inventory::scan_path(rpath.to_string())
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Add a directory to an existing inventory
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `dir` must be a valid null terminated string
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_add_dir(inv: &mut Inventory, dir: *const c_char) -> i32 {
|
||||
let rdir = CStr::from_ptr(dir).to_string_lossy();
|
||||
|
||||
inv.add_dir(PathBuf::from(rdir.to_string()))
|
||||
.into_int_result()
|
||||
}
|
||||
|
||||
/// Create a connector with given arguments
|
||||
///
|
||||
/// This creates an instance of `ConnectorInstance`.
|
||||
///
|
||||
/// This instance needs to be dropped using `connector_drop`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the connector to use
|
||||
/// * `args` - arguments to be passed to the connector upon its creation
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Both `name`, and `args` must be valid null terminated strings.
|
||||
///
|
||||
/// Any error strings returned by the connector must not be outputed after the connector gets
|
||||
/// freed, because that operation could cause the underlying shared library to get unloaded.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_create_connector(
|
||||
inv: &mut Inventory,
|
||||
name: *const c_char,
|
||||
args: *const c_char,
|
||||
out: &mut MuConnectorInstanceArcBox<'static>,
|
||||
) -> i32 {
|
||||
let rname = CStr::from_ptr(name).to_string_lossy();
|
||||
|
||||
if args.is_null() {
|
||||
inv.create_connector(&rname, None, None)
|
||||
.map_err(inspect_err)
|
||||
.into_int_out_result(out)
|
||||
} else {
|
||||
let rargs = CStr::from_ptr(args).to_string_lossy();
|
||||
str::parse(&rargs)
|
||||
.map_err(inspect_err)
|
||||
.and_then(|args| inv.create_connector(&rname, None, Some(&args)))
|
||||
.map_err(inspect_err)
|
||||
.into_int_out_result(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a OS instance with given arguments
|
||||
///
|
||||
/// This creates an instance of `KernelInstance`.
|
||||
///
|
||||
/// This instance needs to be freed using `os_drop`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the OS to use
|
||||
/// * `args` - arguments to be passed to the connector upon its creation
|
||||
/// * `mem` - a previously initialized connector instance
|
||||
/// * `out` - a valid memory location that will contain the resulting os-instance
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// The `mem` connector instance is being _moved_ into the os layer.
|
||||
/// This means upon calling `os_drop` it is not unnecessary to call `connector_drop` anymore.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Both `name`, and `args` must be valid null terminated strings.
|
||||
///
|
||||
/// Any error strings returned by the connector must not be outputed after the connector gets
|
||||
/// freed, because that operation could cause the underlying shared library to get unloaded.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_create_os(
|
||||
inv: &mut Inventory,
|
||||
name: *const c_char,
|
||||
args: *const c_char,
|
||||
mem: *mut ConnectorInstanceArcBox<'static>,
|
||||
out: &mut MuOsInstanceArcBox<'static>,
|
||||
) -> i32 {
|
||||
let rname = CStr::from_ptr(name).to_string_lossy();
|
||||
let _args = CStr::from_ptr(args).to_string_lossy();
|
||||
|
||||
let mem_obj = if mem.is_null() {
|
||||
None
|
||||
} else {
|
||||
let mem_obj = mem.read();
|
||||
// Zero out the data so that any automatic destructors on the other side do nothing.
|
||||
std::ptr::write_bytes(mem, 0, 1);
|
||||
Some(mem_obj)
|
||||
};
|
||||
|
||||
if args.is_null() {
|
||||
inv.create_os(&rname, mem_obj, None)
|
||||
.map_err(inspect_err)
|
||||
.into_int_out_result(out)
|
||||
} else {
|
||||
let rargs = CStr::from_ptr(args).to_string_lossy();
|
||||
str::parse(&rargs)
|
||||
.map_err(inspect_err)
|
||||
.and_then(|args| inv.create_os(&rname, mem_obj, Some(&args)))
|
||||
.map_err(inspect_err)
|
||||
.into_int_out_result(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a os plugin
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `os` must point to a valid `OsInstance` that was created using one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn os_drop(os: &mut OsInstanceArcBox<'static>) {
|
||||
trace!("connector_drop: {:?}", os as *mut _);
|
||||
std::ptr::drop_in_place(os);
|
||||
}
|
||||
|
||||
/// Clone a connector
|
||||
///
|
||||
/// This method is useful when needing to perform multithreaded operations, as a connector is not
|
||||
/// guaranteed to be thread safe. Every single cloned instance also needs to be dropped using
|
||||
/// `connector_drop`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn connector_clone(
|
||||
conn: &ConnectorInstanceArcBox<'static>,
|
||||
out: &mut MuConnectorInstanceArcBox<'static>,
|
||||
) {
|
||||
trace!("connector_clone: {:?}", conn as *const _);
|
||||
*out.as_mut_ptr() = conn.clone();
|
||||
}
|
||||
|
||||
/// Free a connector instance
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `conn` has to point to a valid [`ConnectorInstance`](ConnectorInstanceArcBox) created by one of the provided
|
||||
/// functions.
|
||||
///
|
||||
/// There has to be no instance of `PhysicalMemory` created from the input `conn`, because they
|
||||
/// will become invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn connector_drop(conn: &mut ConnectorInstanceArcBox<'static>) {
|
||||
trace!("connector_drop: {:?}", conn as *mut _);
|
||||
std::ptr::drop_in_place(conn)
|
||||
}
|
||||
|
||||
/// Free a connector inventory
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `inv` must point to a valid `Inventory` that was created using one of the provided
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn inventory_free(inv: &'static mut Inventory) {
|
||||
trace!("inventory_free: {:?}", inv as *mut _);
|
||||
let _ = Box::from_raw(inv);
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
use crate::util::*;
|
||||
use memflow::process::*;
|
||||
use std::os::raw::c_char;
|
||||
use std::slice::from_raw_parts_mut;
|
||||
|
||||
use memflow::architecture::ArchitectureObj;
|
||||
use memflow::types::Address;
|
||||
|
||||
pub type OsProcessInfoObj = &'static dyn OsProcessInfo;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_info_address(obj: &OsProcessInfoObj) -> Address {
|
||||
obj.address()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_info_pid(obj: &OsProcessInfoObj) -> PID {
|
||||
obj.pid()
|
||||
}
|
||||
|
||||
/// Retreive name of the process
|
||||
///
|
||||
/// This will copy at most `max_len` characters (including the null terminator) into `out` of the
|
||||
/// name.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `out` must be a buffer with at least `max_len` size
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn os_process_info_name(
|
||||
obj: &OsProcessInfoObj,
|
||||
out: *mut c_char,
|
||||
max_len: usize,
|
||||
) -> usize {
|
||||
let name = obj.name();
|
||||
let name_bytes = name.as_bytes();
|
||||
let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1));
|
||||
let len = out_bytes.len();
|
||||
out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]);
|
||||
*out_bytes.iter_mut().last().unwrap() = 0;
|
||||
len
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_info_sys_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj {
|
||||
to_heap(obj.sys_arch())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_info_proc_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj {
|
||||
to_heap(obj.proc_arch())
|
||||
}
|
||||
|
||||
/// Free a OsProcessInfoObj reference
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn os_process_info_free(obj: &'static mut OsProcessInfoObj) {
|
||||
let _ = Box::from_raw(obj);
|
||||
}
|
||||
|
||||
pub type OsProcessModuleInfoObj = &'static dyn OsProcessModuleInfo;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_module_address(obj: &OsProcessModuleInfoObj) -> Address {
|
||||
obj.address()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_module_parent_process(obj: &OsProcessModuleInfoObj) -> Address {
|
||||
obj.parent_process()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_module_base(obj: &OsProcessModuleInfoObj) -> Address {
|
||||
obj.base()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn os_process_module_size(obj: &OsProcessModuleInfoObj) -> usize {
|
||||
obj.size()
|
||||
}
|
||||
|
||||
/// Retreive name of the module
|
||||
///
|
||||
/// This will copy at most `max_len` characters (including the null terminator) into `out` of the
|
||||
/// name.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `out` must be a buffer with at least `max_len` size
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn os_process_module_name(
|
||||
obj: &OsProcessModuleInfoObj,
|
||||
out: *mut c_char,
|
||||
max_len: usize,
|
||||
) -> usize {
|
||||
let name = obj.name();
|
||||
let name_bytes = name.as_bytes();
|
||||
let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1));
|
||||
let len = out_bytes.len();
|
||||
out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]);
|
||||
*out_bytes.iter_mut().last().unwrap() = 0;
|
||||
len
|
||||
}
|
||||
|
||||
/// Free a OsProcessModuleInfoObj reference
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's
|
||||
/// functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn os_process_module_free(obj: &'static mut OsProcessModuleInfoObj) {
|
||||
let _ = Box::from_raw(obj);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
|
||||
use memflow_win32::*;
|
||||
*/
|
||||
|
||||
/*
|
||||
/// # Safety
|
||||
///
|
||||
/// this function has to be called with an initialized memory backend
|
||||
/// this function will return a pointer to a win32 object that has to be freed via win32_free()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn win32_init(mem: *mut c_void) -> *mut Win32 {
|
||||
if !mem.is_null() {
|
||||
let mut _mem: Box<Box<dyn MemoryBackend>> = std::mem::transmute(mem as *mut _);
|
||||
|
||||
let _os = Win32::try_with(&mut **_mem).unwrap();
|
||||
|
||||
Box::leak(_mem);
|
||||
return std::mem::transmute(Box::new(_os));
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// this function has to be called with a pointer that has been initialized from win32_init()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn win32_free(win32: *mut Win32) {
|
||||
if !win32.is_null() {
|
||||
let _win32: Box<Win32> = std::mem::transmute(win32);
|
||||
// drop _win32
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// this function will return a pointer to a win32_offsets object that has to be freed via win32_offsets_free()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn win32_offsets_init(win32: *mut Win32) -> *mut Win32Offsets {
|
||||
if !win32.is_null() {
|
||||
let _win32: Box<Win32> = std::mem::transmute(win32);
|
||||
|
||||
let _offsets = Win32Offsets::try_with_guid(&_win32.kernel_guid()).unwrap();
|
||||
|
||||
Box::leak(_win32);
|
||||
return std::mem::transmute(Box::new(_offsets));
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// this function has to be called with a pointer that has been initialized from win32_offsets_init()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn win32_offsets_free(offsets: *mut Win32Offsets) {
|
||||
if !offsets.is_null() {
|
||||
let _offsets: Box<Win32Offsets> = std::mem::transmute(offsets);
|
||||
// drop _offsets
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# update cglue-bindgen
|
||||
cargo +nightly install cbindgen
|
||||
cargo +nightly install cglue-bindgen
|
||||
|
||||
DIFFC=$(diff memflow.h <(rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi -l C))
|
||||
DIFFCPP=$(diff memflow.hpp <(rustup run nightly cglue-bindgen +nightly -c cglue.toml -- --config cbindgen.toml --crate memflow-ffi -l C++))
|
||||
if [ "$DIFFC" != "" ] || [ "$DIFFCPP" != "" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
@ -1,5 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
*.swp
|
||||
.vscode
|
||||
Cargo.lock
|
@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "memflow-qemu-procfs"
|
||||
version = "0.1.5"
|
||||
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
|
||||
edition = "2018"
|
||||
description = "qemu procfs connector for the memflow physical memory introspection framework"
|
||||
documentation = "https://docs.rs/memflow-qemu-procfs"
|
||||
readme = "README.md"
|
||||
homepage = "https://memflow.github.io"
|
||||
repository = "https://github.com/memflow/memflow-qemu-procfs"
|
||||
license-file = "LICENSE"
|
||||
keywords = [ "memflow", "introspection", "memory" ]
|
||||
categories = [ "api-bindings", "memory-management", "os" ]
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
memflow = { version = "0.1", features = ["inventory"] }
|
||||
log = { version = "0.4", default-features = false }
|
||||
procfs = "0.7"
|
||||
libc = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = "2.33"
|
||||
simple_logger = "1.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
inventory = []
|
||||
|
||||
[[example]]
|
||||
name = "read_phys"
|
||||
path = "examples/read_phys.rs"
|
@ -1,24 +0,0 @@
|
||||
.PHONY: all release debug test install
|
||||
|
||||
all:
|
||||
make test
|
||||
make release
|
||||
|
||||
release:
|
||||
cargo build --release --all-features
|
||||
|
||||
debug:
|
||||
cargo build --all-features
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
test:
|
||||
cargo test --all-features
|
||||
|
||||
install_user:
|
||||
./install.sh
|
||||
|
||||
install:
|
||||
./install.sh --system
|
||||
|
@ -1,55 +0,0 @@
|
||||
# memflow-qemu-procfs
|
||||
|
||||
This connector implements an interface for Qemu via the Process Filesystem on Linux.
|
||||
|
||||
## Compilation
|
||||
|
||||
### Installing the library
|
||||
|
||||
The `./install.sh` script will just compile and install the plugin.
|
||||
The connector will be installed to `~/.local/lib/memflow` by default.
|
||||
Additionally the `--system` flag can be specified which will install the connector in `/usr/lib/memflow` as well.
|
||||
|
||||
### Building the stand-alone connector for dynamic loading
|
||||
|
||||
The stand-alone connector of this library is feature-gated behind the `inventory` feature.
|
||||
To compile a dynamic library for use with the connector inventory use the following command:
|
||||
|
||||
```
|
||||
cargo build --release --all-features
|
||||
```
|
||||
|
||||
### Using the crate in a rust project
|
||||
|
||||
To use the connector in a rust project just include it in your Cargo.toml
|
||||
|
||||
```
|
||||
memflow-qemu-procfs = "0.1"
|
||||
```
|
||||
|
||||
Make sure to not enable the `inventory` feature when importing multiple
|
||||
connectors in a rust project without using the memflow connector inventory.
|
||||
This might cause duplicated exports being generated in your project.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `name` - the name of the virtual machine (default argument, optional)
|
||||
|
||||
## Permissions
|
||||
|
||||
The `qemu_procfs` connector requires access to the qemu process via the linux procfs. This means any process which loads this connector requires to have at least ptrace permissions set.
|
||||
|
||||
To set ptrace permissions on a binary simply use:
|
||||
```bash
|
||||
sudo setcap 'CAP_SYS_PTRACE=ep' [filename]
|
||||
```
|
||||
|
||||
Alternatively you can just run the binary via `sudo`.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under MIT License, see [LICENSE](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.
|
@ -1,45 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use log::{info, Level};
|
||||
|
||||
use memflow::prelude::v1::*;
|
||||
|
||||
fn main() {
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(Level::Debug.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
let mut conn = match memflow_qemu_procfs::create_connector(&ConnectorArgs::new()) {
|
||||
Ok(br) => br,
|
||||
Err(e) => {
|
||||
info!("couldn't open memory read context: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let metadata = conn.metadata();
|
||||
info!("Received metadata: {:?}", metadata);
|
||||
|
||||
let mut mem = vec![0; 8];
|
||||
conn.phys_read_raw_into(Address::from(0x1000).into(), &mut mem)
|
||||
.unwrap();
|
||||
info!("Received memory: {:?}", mem);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
let mut buf = vec![0; 0x1000];
|
||||
conn.phys_read_raw_into(Address::from(0x1000).into(), &mut buf)
|
||||
.unwrap();
|
||||
|
||||
counter += 1;
|
||||
if (counter % 10000000) == 0 {
|
||||
let elapsed = start.elapsed().as_millis() as f64;
|
||||
if elapsed > 0.0 {
|
||||
info!("{} reads/sec", (f64::from(counter)) / elapsed * 1000.0);
|
||||
info!("{} ms/read", elapsed / (f64::from(counter)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cargo build --release --all-features
|
||||
|
||||
# install connector to system dir
|
||||
if [ ! -z "$1" ] && [ $1 = "--system" ]; then
|
||||
echo "installing connector system-wide in /usr/lib/memflow"
|
||||
if [[ ! -d /usr/lib/memflow ]]; then
|
||||
sudo mkdir /usr/lib/memflow
|
||||
fi
|
||||
sudo cp target/release/libmemflow_qemu_procfs.so /usr/lib/memflow
|
||||
fi
|
||||
|
||||
# install connector in user dir
|
||||
echo "installing connector for user in ~/.local/lib/memflow"
|
||||
if [[ ! -d ~/.local/lib/memflow ]]; then
|
||||
mkdir -p ~/.local/lib/memflow
|
||||
fi
|
||||
cp target/release/libmemflow_qemu_procfs.so ~/.local/lib/memflow
|
@ -1,475 +0,0 @@
|
||||
use log::info;
|
||||
|
||||
use core::ffi::c_void;
|
||||
use libc::{c_ulong, iovec, pid_t, sysconf, _SC_IOV_MAX};
|
||||
|
||||
use memflow::prelude::v1::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
struct IoSendVec(iovec);
|
||||
|
||||
unsafe impl Send for IoSendVec {}
|
||||
|
||||
fn qemu_arg_opt(args: &[String], argname: &str, argopt: &str) -> Option<String> {
|
||||
for (idx, arg) in args.iter().enumerate() {
|
||||
if arg == argname {
|
||||
let name = args[idx + 1].split(',');
|
||||
for (i, kv) in name.clone().enumerate() {
|
||||
let kvsplt = kv.split('=').collect::<Vec<_>>();
|
||||
if kvsplt.len() == 2 {
|
||||
if kvsplt[0] == argopt {
|
||||
return Some(kvsplt[1].to_string());
|
||||
}
|
||||
} else if i == 0 {
|
||||
return Some(kv.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_qemu(process: &procfs::process::Process) -> bool {
|
||||
process
|
||||
.cmdline()
|
||||
.ok()
|
||||
.and_then(|cmdline| {
|
||||
cmdline.iter().nth(0).and_then(|cmd| {
|
||||
std::path::Path::new(cmd)
|
||||
.file_name()
|
||||
.and_then(|exe| exe.to_str())
|
||||
.map(|v| v.contains("qemu-system-"))
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QemuProcfs {
|
||||
pub pid: pid_t,
|
||||
pub mem_map: MemoryMap<(Address, usize)>,
|
||||
temp_iov: Box<[IoSendVec]>,
|
||||
}
|
||||
|
||||
impl QemuProcfs {
|
||||
pub fn new() -> Result<Self> {
|
||||
let prcs = procfs::process::all_processes()
|
||||
.map_err(|_| Error::Connector("unable to list procfs processes"))?;
|
||||
let prc = prcs
|
||||
.iter()
|
||||
.find(|p| is_qemu(p))
|
||||
.ok_or_else(|| Error::Connector("qemu process not found"))?;
|
||||
info!("qemu process found with pid {:?}", prc.stat.pid);
|
||||
|
||||
Self::with_process(prc)
|
||||
}
|
||||
|
||||
pub fn with_guest_name(name: &str) -> Result<Self> {
|
||||
let prcs = procfs::process::all_processes()
|
||||
.map_err(|_| Error::Connector("unable to list procefs processes"))?;
|
||||
let (prc, _) = prcs
|
||||
.iter()
|
||||
.filter(|p| is_qemu(p))
|
||||
.filter_map(|p| {
|
||||
if let Ok(c) = p.cmdline() {
|
||||
Some((p, c))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.find(|(_, c)| qemu_arg_opt(c, "-name", "guest").unwrap_or_default() == name)
|
||||
.ok_or_else(|| Error::Connector("qemu process not found"))?;
|
||||
info!(
|
||||
"qemu process with name {} found with pid {:?}",
|
||||
name, prc.stat.pid
|
||||
);
|
||||
|
||||
Self::with_process(prc)
|
||||
}
|
||||
|
||||
fn with_process(prc: &procfs::process::Process) -> Result<Self> {
|
||||
// find biggest memory mapping in qemu process
|
||||
let mut maps = prc
|
||||
.maps()
|
||||
.map_err(|_| Error::Connector("Unable to retrieve Qemu memory maps. Did u run memflow with the correct access rights (SYS_PTRACE or root)?"))?;
|
||||
maps.sort_by(|b, a| {
|
||||
(a.address.1 - a.address.0)
|
||||
.partial_cmp(&(b.address.1 - b.address.0))
|
||||
.unwrap()
|
||||
});
|
||||
let map = maps
|
||||
.get(0)
|
||||
.ok_or_else(|| Error::Connector("Qemu memory map could not be read"))?;
|
||||
info!("qemu memory map found {:?}", map);
|
||||
|
||||
let map_base = map.address.0 as usize;
|
||||
let map_size = (map.address.1 - map.address.0) as usize;
|
||||
info!("qemu memory map size: {:x}", map_size);
|
||||
|
||||
// TODO: instead of hardcoding the memory regions per machine we could just use the hmp to retrieve the proper ranges:
|
||||
// sudo virsh qemu-monitor-command win10 --hmp 'info mtree -f' | grep pc\.ram
|
||||
|
||||
// find machine architecture
|
||||
let machine = qemu_arg_opt(
|
||||
&prc.cmdline()
|
||||
.map_err(|_| Error::Connector("Unable to parse qemu arguments"))?,
|
||||
"-machine",
|
||||
"type",
|
||||
)
|
||||
.unwrap_or_else(|| "pc".into());
|
||||
info!("qemu process started with machine: {}", machine);
|
||||
|
||||
let mut mem_map = MemoryMap::new();
|
||||
if machine.contains("q35") {
|
||||
// q35 -> subtract 2GB
|
||||
/*
|
||||
0000000000000000-000000000009ffff (prio 0, ram): pc.ram KVM
|
||||
00000000000c0000-00000000000c3fff (prio 0, rom): pc.ram @00000000000c0000 KVM
|
||||
0000000000100000-000000007fffffff (prio 0, ram): pc.ram @0000000000100000 KVM
|
||||
0000000100000000-000000047fffffff (prio 0, ram): pc.ram @0000000080000000 KVM
|
||||
*/
|
||||
// we add all regions additionally shifted to the proper qemu memory map address
|
||||
mem_map.push_range(Address::NULL, size::kb(640).into(), map_base.into()); // section: [start - 640kb] -> map to start
|
||||
// If larger than this specific size, second half after 2 gigs gets moved over past 4gb
|
||||
// TODO: Probably the same happens with i1440-fx
|
||||
if map_size >= size::mb(2816) {
|
||||
mem_map.push_range(
|
||||
size::mb(1).into(),
|
||||
size::gb(2).into(),
|
||||
(map_base + size::mb(1)).into(),
|
||||
); // section: [1mb - 2gb] -> map to 1mb
|
||||
mem_map.push_range(
|
||||
size::gb(4).into(),
|
||||
(map_size + size::gb(2)).into(),
|
||||
(map_base + size::gb(2)).into(),
|
||||
); // section: [4gb - max] -> map to 2gb
|
||||
} else {
|
||||
mem_map.push_range(
|
||||
size::mb(1).into(),
|
||||
map_size.into(),
|
||||
(map_base + size::mb(1)).into(),
|
||||
); // section: [1mb - max] -> map to 1mb
|
||||
}
|
||||
} else {
|
||||
// pc-i1440fx
|
||||
/*
|
||||
0000000000000000-00000000000bffff (prio 0, ram): pc.ram KVM
|
||||
00000000000c0000-00000000000cafff (prio 0, rom): pc.ram @00000000000c0000 KVM
|
||||
00000000000cb000-00000000000cdfff (prio 0, ram): pc.ram @00000000000cb000 KVM
|
||||
00000000000ce000-00000000000e7fff (prio 0, rom): pc.ram @00000000000ce000 KVM
|
||||
00000000000e8000-00000000000effff (prio 0, ram): pc.ram @00000000000e8000 KVM
|
||||
00000000000f0000-00000000000fffff (prio 0, rom): pc.ram @00000000000f0000 KVM
|
||||
0000000000100000-00000000bfffffff (prio 0, ram): pc.ram @0000000000100000 KVM
|
||||
0000000100000000-000000023fffffff (prio 0, ram): pc.ram @00000000c0000000 KVM
|
||||
*/
|
||||
mem_map.push_range(Address::NULL, size::kb(768).into(), map_base.into()); // section: [start - 768kb] -> map to start
|
||||
mem_map.push_range(
|
||||
size::kb(812).into(),
|
||||
size::kb(824).into(),
|
||||
(map_base + size::kb(812)).into(),
|
||||
); // section: [768kb - 812kb] -> map to 768kb
|
||||
mem_map.push_range(
|
||||
size::kb(928).into(),
|
||||
size::kb(960).into(),
|
||||
(map_base + size::kb(928)).into(),
|
||||
); // section: [928kb - 960kb] -> map to 928kb
|
||||
mem_map.push_range(
|
||||
size::mb(1).into(),
|
||||
size::gb(3).into(),
|
||||
(map_base + size::mb(1)).into(),
|
||||
); // section: [1mb - 3gb] -> map to 1mb
|
||||
mem_map.push_range(
|
||||
size::gb(4).into(),
|
||||
(map_size + size::gb(1)).into(),
|
||||
(map_base + size::gb(3)).into(),
|
||||
); // section: [4gb - max] -> map to 3gb
|
||||
}
|
||||
info!("qemu machine mem_map: {:?}", mem_map);
|
||||
|
||||
let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize;
|
||||
|
||||
Ok(Self {
|
||||
pid: prc.stat.pid,
|
||||
mem_map,
|
||||
temp_iov: vec![
|
||||
IoSendVec {
|
||||
0: iovec {
|
||||
iov_base: std::ptr::null_mut::<c_void>(),
|
||||
iov_len: 0
|
||||
}
|
||||
};
|
||||
iov_max * 2
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
})
|
||||
}
|
||||
|
||||
fn fill_iovec(addr: &Address, data: &[u8], liov: &mut IoSendVec, riov: &mut IoSendVec) {
|
||||
let iov_len = data.len();
|
||||
|
||||
liov.0 = iovec {
|
||||
iov_base: data.as_ptr() as *mut c_void,
|
||||
iov_len,
|
||||
};
|
||||
|
||||
riov.0 = iovec {
|
||||
iov_base: addr.as_u64() as *mut c_void,
|
||||
iov_len,
|
||||
};
|
||||
}
|
||||
|
||||
fn vm_error() -> Error {
|
||||
match unsafe { *libc::__errno_location() } {
|
||||
libc::EFAULT => Error::Connector("process_vm_readv failed: EFAULT (remote memory address is invalid)"),
|
||||
libc::ENOMEM => Error::Connector("process_vm_readv failed: ENOMEM (unable to allocate memory for internal copies)"),
|
||||
libc::EPERM => Error::Connector("process_vm_readv failed: EPERM (insifficient permissions to access the target address space)"),
|
||||
libc::ESRCH => Error::Connector("process_vm_readv failed: ESRCH (process not found)"),
|
||||
libc::EINVAL => Error::Connector("process_vm_readv failed: EINVAL (invalid value)"),
|
||||
_ => Error::Connector("process_vm_readv failed: unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalMemory for QemuProcfs {
|
||||
fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
|
||||
let mem_map = &self.mem_map;
|
||||
let temp_iov = &mut self.temp_iov;
|
||||
|
||||
let mut void = FnExtend::void();
|
||||
let mut iter = mem_map.map_iter(
|
||||
data.iter_mut()
|
||||
.map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)),
|
||||
&mut void,
|
||||
);
|
||||
|
||||
let max_iov = temp_iov.len() / 2;
|
||||
let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
|
||||
|
||||
let mut elem = iter.next();
|
||||
|
||||
let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
|
||||
let mut iov_next = iov_iter.next();
|
||||
|
||||
while let Some(((addr, _), out)) = elem {
|
||||
let (cnt, (liov, riov)) = iov_next.unwrap();
|
||||
|
||||
Self::fill_iovec(&addr, out, liov, riov);
|
||||
|
||||
iov_next = iov_iter.next();
|
||||
elem = iter.next();
|
||||
|
||||
if elem.is_none() || iov_next.is_none() {
|
||||
if unsafe {
|
||||
libc::process_vm_readv(
|
||||
self.pid,
|
||||
iov_local.as_ptr().cast(),
|
||||
(cnt + 1) as c_ulong,
|
||||
iov_remote.as_ptr().cast(),
|
||||
(cnt + 1) as c_ulong,
|
||||
0,
|
||||
)
|
||||
} == -1
|
||||
{
|
||||
return Err(Self::vm_error());
|
||||
}
|
||||
|
||||
iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
|
||||
iov_next = iov_iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
|
||||
let mem_map = &self.mem_map;
|
||||
let temp_iov = &mut self.temp_iov;
|
||||
|
||||
let mut void = FnExtend::void();
|
||||
let mut iter = mem_map.map_iter(data.iter().copied().map(<_>::from), &mut void);
|
||||
//let mut iter = mem_map.map_iter(data.iter(), &mut FnExtend::new(|_|{}));
|
||||
|
||||
let max_iov = temp_iov.len() / 2;
|
||||
let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
|
||||
|
||||
let mut elem = iter.next();
|
||||
|
||||
let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
|
||||
let mut iov_next = iov_iter.next();
|
||||
|
||||
while let Some(((addr, _), out)) = elem {
|
||||
let (cnt, (liov, riov)) = iov_next.unwrap();
|
||||
|
||||
Self::fill_iovec(&addr, out, liov, riov);
|
||||
|
||||
iov_next = iov_iter.next();
|
||||
elem = iter.next();
|
||||
|
||||
if elem.is_none() || iov_next.is_none() {
|
||||
if unsafe {
|
||||
libc::process_vm_writev(
|
||||
self.pid,
|
||||
iov_local.as_ptr().cast(),
|
||||
(cnt + 1) as c_ulong,
|
||||
iov_remote.as_ptr().cast(),
|
||||
(cnt + 1) as c_ulong,
|
||||
0,
|
||||
)
|
||||
} == -1
|
||||
{
|
||||
return Err(Self::vm_error());
|
||||
}
|
||||
|
||||
iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
|
||||
iov_next = iov_iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata(&self) -> PhysicalMemoryMetadata {
|
||||
PhysicalMemoryMetadata {
|
||||
size: self
|
||||
.mem_map
|
||||
.as_ref()
|
||||
.iter()
|
||||
.last()
|
||||
.map(|map| map.base().as_usize() + map.output().1)
|
||||
.unwrap(),
|
||||
readonly: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Qemu Procfs Connector instance.
|
||||
#[connector(name = "qemu_procfs")]
|
||||
pub fn create_connector(args: &ConnectorArgs) -> Result<QemuProcfs> {
|
||||
if let Some(name) = args.get("name").or_else(|| args.get_default()) {
|
||||
QemuProcfs::with_guest_name(name)
|
||||
} else {
|
||||
QemuProcfs::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_name() {
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-name".to_string(), "win10-test".to_string()],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
Some("win10-test".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&[
|
||||
"-test".to_string(),
|
||||
"-name".to_string(),
|
||||
"win10-test".to_string()
|
||||
],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
Some("win10-test".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-name".to_string(), "win10-test,arg=opt".to_string()],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
Some("win10-test".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-name".to_string(), "guest=win10-test,arg=opt".to_string()],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
Some("win10-test".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-name".to_string(), "arg=opt,guest=win10-test".to_string()],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
Some("win10-test".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-name".to_string(), "arg=opt".to_string()],
|
||||
"-name",
|
||||
"guest"
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_machine() {
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-machine".to_string(), "q35".to_string()],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
Some("q35".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&[
|
||||
"-test".to_string(),
|
||||
"-machine".to_string(),
|
||||
"q35".to_string()
|
||||
],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
Some("q35".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-machine".to_string(), "q35,arg=opt".to_string()],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
Some("q35".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-machine".to_string(), "type=pc,arg=opt".to_string()],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
Some("pc".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&[
|
||||
"-machine".to_string(),
|
||||
"arg=opt,type=pc-i1440fx".to_string()
|
||||
],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
Some("pc-i1440fx".into())
|
||||
);
|
||||
assert_eq!(
|
||||
qemu_arg_opt(
|
||||
&["-machine".to_string(), "arg=opt".to_string()],
|
||||
"-machine",
|
||||
"type"
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
bindings
|
||||
**/node_modules
|
||||
**/*.out
|
||||
**/*.o
|
@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "memflow-win32-ffi"
|
||||
version = "0.1.5"
|
||||
authors = ["Aurimas Blažulionis <0x60@pm.me>"]
|
||||
edition = "2018"
|
||||
description = "C bindings to memflow-win32"
|
||||
documentation = "https://docs.rs/memflow-win32-ffi"
|
||||
readme = "README.md"
|
||||
homepage = "https://memflow.github.io"
|
||||
repository = "https://github.com/memflow/memflow"
|
||||
license-file = "../LICENSE"
|
||||
keywords = [ "memflow", "introspection", "memory", "dma" ]
|
||||
categories = [ "api-bindings", "memory-management", "os" ]
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
codecov = { repository = "github", branch = "master", service = "github" }
|
||||
|
||||
[lib]
|
||||
name = "memflow_win32_ffi"
|
||||
crate-type = ["lib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
memflow-win32 = { version = "0.1", path = "../memflow-win32" }
|
||||
memflow = { version = "0.1", path = "../memflow" }
|
||||
memflow-ffi = { version = "0.1", path = "../memflow-ffi" }
|
@ -1,47 +0,0 @@
|
||||
# memflow-win32-ffi
|
||||
[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow)
|
||||
![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev)
|
||||
[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow)
|
||||
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
||||
[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR)
|
||||
|
||||
The [memflow](https://github.com/memflow/memflow) win32 FFI crate provides an interface to the memflow-win32 API for C/C++. Currently a single `memflow_win32.h` file is generated aside from the dynamic library that can be used to interact with memflow.
|
||||
|
||||
This FFI library is intended to be used in combination with the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) library.
|
||||
|
||||
A simple example that initializes the memflow-ffi and memflow-win32-ffi:
|
||||
```cpp
|
||||
#include "memflow_win32.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_init(1);
|
||||
|
||||
ConnectorInventory *inv = inventory_try_new();
|
||||
printf("inv: %p\n", inv);
|
||||
|
||||
const char *conn_name = argc > 1? argv[1]: "kvm";
|
||||
const char *conn_arg = argc > 2? argv[2]: "";
|
||||
|
||||
CloneablePhysicalMemoryObj *conn =
|
||||
inventory_create_connector(inv, conn_name, conn_arg);
|
||||
printf("conn: %p\n", conn);
|
||||
|
||||
if (conn) {
|
||||
Kernel *kernel = kernel_build(conn);
|
||||
printf("Kernel: %p\n", kernel);
|
||||
Win32Version ver = kernel_winver(kernel);
|
||||
printf("major: %d\n", ver.nt_major_version);
|
||||
printf("minor: %d\n", ver.nt_minor_version);
|
||||
printf("build: %d\n", ver.nt_build_number);
|
||||
|
||||
kernel_free(kernel);
|
||||
}
|
||||
|
||||
inventory_free(inv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Additional examples can be found in the `examples` folder as well as in the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) crate.
|
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
cargo build --release --workspace
|
||||
cbindgen --config cbindgen.toml --crate memflow-win32-ffi --output memflow_win32.h
|
@ -1,22 +0,0 @@
|
||||
language = "C"
|
||||
|
||||
include_guard = "MEMFLOW_WIN32_H"
|
||||
tab_width = 4
|
||||
documentation_style = "doxy"
|
||||
style = "both"
|
||||
cpp_compat = true
|
||||
includes = ["memflow.h"]
|
||||
|
||||
[parse]
|
||||
parse_deps = true
|
||||
|
||||
include = ["memflow-win32", "memflow"]
|
||||
|
||||
[macro_expansion]
|
||||
bitflags = true
|
||||
|
||||
[fn]
|
||||
sort_by = "None"
|
||||
|
||||
[export]
|
||||
exclude = ["PageType", "Address"]
|
@ -1,22 +0,0 @@
|
||||
CC =gcc
|
||||
CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release
|
||||
LIBS=-lm -ldl -lpthread -l:libmemflow_win32_ffi.a
|
||||
|
||||
ODIR=./
|
||||
|
||||
%.o: %.c $(DEPS)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
process_list.out: process_list.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
dump_header.out: dump_header.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: all
|
||||
all: process_list.out dump_header.out
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
clean:
|
||||
rm -f $(ODIR)/*.o
|
@ -1,61 +0,0 @@
|
||||
#include "memflow_win32.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_init(1);
|
||||
|
||||
ConnectorInventory *inv = inventory_try_new();
|
||||
printf("inv: %p\n", inv);
|
||||
|
||||
const char *conn_name = argc > 1? argv[1]: "kvm";
|
||||
const char *conn_arg = argc > 2? argv[2]: "";
|
||||
|
||||
const char *proc_name = argc > 3? argv[3]: "lsass.exe";
|
||||
const char *dll_name = argc > 4? argv[4]: "ntdll.dll";
|
||||
|
||||
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
|
||||
printf("conn: %p\n", conn);
|
||||
|
||||
if (conn) {
|
||||
Kernel *kernel = kernel_build(conn);
|
||||
printf("Kernel: %p\n", kernel);
|
||||
Win32Version ver = kernel_winver(kernel);
|
||||
printf("major: %d\n", ver.nt_major_version);
|
||||
printf("minor: %d\n", ver.nt_minor_version);
|
||||
printf("build: %d\n", ver.nt_build_number);
|
||||
|
||||
Win32Process *process = kernel_into_process(kernel, proc_name);
|
||||
|
||||
if (process) {
|
||||
Win32ModuleInfo *module = process_module_info(process, dll_name);
|
||||
|
||||
if (module) {
|
||||
OsProcessModuleInfoObj *obj = module_info_trait(module);
|
||||
Address base = os_process_module_base(obj);
|
||||
os_process_module_free(obj);
|
||||
VirtualMemoryObj *virt_mem = process_virt_mem(process);
|
||||
|
||||
char header[256];
|
||||
if (!virt_read_raw_into(virt_mem, base, header, 256)) {
|
||||
printf("Read successful!\n");
|
||||
for (int o = 0; o < 8; o++) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
printf("%2hhx ", header[o * 32 + i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
} else {
|
||||
printf("Failed to read!\n");
|
||||
}
|
||||
|
||||
virt_free(virt_mem);
|
||||
}
|
||||
|
||||
process_free(process);
|
||||
}
|
||||
}
|
||||
|
||||
inventory_free(inv);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
#include "memflow_win32.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_init(1);
|
||||
|
||||
ConnectorInventory *inv = inventory_scan();
|
||||
printf("inv: %p\n", inv);
|
||||
|
||||
const char *conn_name = argc > 1? argv[1]: "kvm";
|
||||
const char *conn_arg = argc > 2? argv[2]: "";
|
||||
|
||||
CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg);
|
||||
printf("conn: %p\n", conn);
|
||||
|
||||
if (conn) {
|
||||
Kernel *kernel = kernel_build(conn);
|
||||
printf("Kernel: %p\n", kernel);
|
||||
Win32Version ver = kernel_winver(kernel);
|
||||
printf("major: %d\n", ver.nt_major_version);
|
||||
printf("minor: %d\n", ver.nt_minor_version);
|
||||
printf("build: %d\n", ver.nt_build_number);
|
||||
|
||||
Win32ProcessInfo *processes[512];
|
||||
size_t process_count = kernel_process_info_list(kernel, processes, 512);
|
||||
|
||||
printf("Process List:\n");
|
||||
printf("%-8s | %-16s | %-16s | %-12s | %-5s\n", "PID", "Name", "Base", "DTB", "Wow64");
|
||||
|
||||
for (size_t i = 0; i < process_count; i++) {
|
||||
Win32ProcessInfo *process = processes[i];
|
||||
OsProcessInfoObj *info = process_info_trait(process);
|
||||
char name[32];
|
||||
os_process_info_name(info, name, 32);
|
||||
|
||||
printf("%-8d | %-16s | %-16lx | %-12lx | %-5s\n",
|
||||
os_process_info_pid(info),
|
||||
name,
|
||||
process_info_section_base(process),
|
||||
process_info_dtb(process),
|
||||
process_info_wow64(process)? "Yes" : "No"
|
||||
);
|
||||
|
||||
os_process_info_free(info);
|
||||
process_info_free(process);
|
||||
}
|
||||
|
||||
kernel_free(kernel);
|
||||
}
|
||||
|
||||
inventory_free(inv);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,321 +0,0 @@
|
||||
#ifndef MEMFLOW_WIN32_H
|
||||
#define MEMFLOW_WIN32_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "memflow.h"
|
||||
|
||||
typedef struct Kernel_FFIMemory__FFIVirtualTranslate Kernel_FFIMemory__FFIVirtualTranslate;
|
||||
|
||||
typedef struct Win32ModuleInfo Win32ModuleInfo;
|
||||
|
||||
typedef struct Win32ProcessInfo Win32ProcessInfo;
|
||||
|
||||
typedef struct Win32Process_FFIVirtualMemory Win32Process_FFIVirtualMemory;
|
||||
|
||||
typedef Kernel_FFIMemory__FFIVirtualTranslate Kernel;
|
||||
|
||||
typedef struct StartBlock {
|
||||
Address kernel_hint;
|
||||
Address dtb;
|
||||
} StartBlock;
|
||||
|
||||
typedef struct Win32Version {
|
||||
uint32_t nt_major_version;
|
||||
uint32_t nt_minor_version;
|
||||
uint32_t nt_build_number;
|
||||
} Win32Version;
|
||||
|
||||
/**
|
||||
* Type alias for a PID.
|
||||
*/
|
||||
typedef uint32_t PID;
|
||||
|
||||
typedef Win32Process_FFIVirtualMemory Win32Process;
|
||||
|
||||
typedef struct Win32ArchOffsets {
|
||||
uintptr_t peb_ldr;
|
||||
uintptr_t ldr_list;
|
||||
uintptr_t ldr_data_base;
|
||||
uintptr_t ldr_data_size;
|
||||
uintptr_t ldr_data_full_name;
|
||||
uintptr_t ldr_data_base_name;
|
||||
} Win32ArchOffsets;
|
||||
|
||||
typedef struct Win32ModuleListInfo {
|
||||
Address module_base;
|
||||
Win32ArchOffsets offsets;
|
||||
} Win32ModuleListInfo;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
/**
|
||||
* Build a cloneable kernel object with default caching parameters
|
||||
*
|
||||
* This function will take ownership of the input `mem` object.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `mem` must be a heap allocated memory reference, created by one of the API's functions.
|
||||
* Reference to it becomes invalid.
|
||||
*/
|
||||
Kernel *kernel_build(CloneablePhysicalMemoryObj *mem);
|
||||
|
||||
/**
|
||||
* Build a cloneable kernel object with custom caching parameters
|
||||
*
|
||||
* This function will take ownership of the input `mem` object.
|
||||
*
|
||||
* vat_cache_entries must be positive, or the program will panic upon memory reads or writes.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `mem` must be a heap allocated memory reference, created by one of the API's functions.
|
||||
* Reference to it becomes invalid.
|
||||
*/
|
||||
Kernel *kernel_build_custom(CloneablePhysicalMemoryObj *mem,
|
||||
uint64_t page_cache_time_ms,
|
||||
PageType page_cache_flags,
|
||||
uintptr_t page_cache_size_kb,
|
||||
uint64_t vat_cache_time_ms,
|
||||
uintptr_t vat_cache_entries);
|
||||
|
||||
Kernel *kernel_clone(const Kernel *kernel);
|
||||
|
||||
/**
|
||||
* Free a kernel object
|
||||
*
|
||||
* This will free the input `kernel` object (including the underlying memory object)
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `kernel` must be a valid reference heap allocated by one of the above functions.
|
||||
*/
|
||||
void kernel_free(Kernel *kernel);
|
||||
|
||||
/**
|
||||
* Destroy a kernel object and return its underlying memory object
|
||||
*
|
||||
* This will free the input `kernel` object, and return the underlying memory object. It will free
|
||||
* the object from any additional caching that `kernel` had in place.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `kernel` must be a valid reference heap allocated by one of the above functions.
|
||||
*/
|
||||
CloneablePhysicalMemoryObj *kernel_destroy(Kernel *kernel);
|
||||
|
||||
StartBlock kernel_start_block(const Kernel *kernel);
|
||||
|
||||
Win32Version kernel_winver(const Kernel *kernel);
|
||||
|
||||
Win32Version kernel_winver_unmasked(const Kernel *kernel);
|
||||
|
||||
/**
|
||||
* Retrieve a list of peorcess addresses
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `buffer` must be a valid buffer of size at least `max_size`
|
||||
*/
|
||||
uintptr_t kernel_eprocess_list(Kernel *kernel, Address *buffer, uintptr_t max_size);
|
||||
|
||||
/**
|
||||
* Retrieve a list of processes
|
||||
*
|
||||
* This will fill `buffer` with a list of win32 process information. These processes will need to be
|
||||
* individually freed with `process_info_free`
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`.
|
||||
*/
|
||||
uintptr_t kernel_process_info_list(Kernel *kernel, Win32ProcessInfo **buffer, uintptr_t max_size);
|
||||
|
||||
Win32ProcessInfo *kernel_kernel_process_info(Kernel *kernel);
|
||||
|
||||
Win32ProcessInfo *kernel_process_info_from_eprocess(Kernel *kernel, Address eprocess);
|
||||
|
||||
/**
|
||||
* Retrieve process information by name
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `name` must be a valid null terminated string
|
||||
*/
|
||||
Win32ProcessInfo *kernel_process_info(Kernel *kernel, const char *name);
|
||||
|
||||
Win32ProcessInfo *kernel_process_info_pid(Kernel *kernel, PID pid);
|
||||
|
||||
/**
|
||||
* Create a process by looking up its name
|
||||
*
|
||||
* This will consume `kernel` and free it later on.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `name` must be a valid null terminated string
|
||||
*
|
||||
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
* invalid.
|
||||
*/
|
||||
Win32Process *kernel_into_process(Kernel *kernel, const char *name);
|
||||
|
||||
/**
|
||||
* Create a process by looking up its PID
|
||||
*
|
||||
* This will consume `kernel` and free it later on.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
* invalid.
|
||||
*/
|
||||
Win32Process *kernel_into_process_pid(Kernel *kernel, PID pid);
|
||||
|
||||
/**
|
||||
* Create a kernel process insatance
|
||||
*
|
||||
* This will consume `kernel` and free it later on.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
* invalid.
|
||||
*/
|
||||
Win32Process *kernel_into_kernel_process(Kernel *kernel);
|
||||
|
||||
OsProcessModuleInfoObj *module_info_trait(Win32ModuleInfo *info);
|
||||
|
||||
/**
|
||||
* Free a win32 module info instance.
|
||||
*
|
||||
* Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed
|
||||
* manually.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the
|
||||
* reference will become invalid.
|
||||
*/
|
||||
void module_info_free(Win32ModuleInfo *info);
|
||||
|
||||
/**
|
||||
* Create a process with kernel and process info
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function
|
||||
* call, the reference becomes invalid.
|
||||
*/
|
||||
Win32Process *process_with_kernel(Kernel *kernel, const Win32ProcessInfo *proc_info);
|
||||
|
||||
/**
|
||||
* Retrieve refernce to the underlying virtual memory object
|
||||
*
|
||||
* This will return a static reference to the virtual memory object. It will only be valid as long
|
||||
* as `process` if valid, and needs to be freed manually using `virt_free` regardless if the
|
||||
* process if freed or not.
|
||||
*/
|
||||
VirtualMemoryObj *process_virt_mem(Win32Process *process);
|
||||
|
||||
Win32Process *process_clone(const Win32Process *process);
|
||||
|
||||
/**
|
||||
* Frees the `process`
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `process` must be a valid heap allocated reference to a `Win32Process` object. After the
|
||||
* function returns, the reference becomes invalid.
|
||||
*/
|
||||
void process_free(Win32Process *process);
|
||||
|
||||
/**
|
||||
* Retrieve a process module list
|
||||
*
|
||||
* This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects.
|
||||
*
|
||||
* These references then need to be freed with `module_info_free`
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`.
|
||||
*/
|
||||
uintptr_t process_module_list(Win32Process *process, Win32ModuleInfo **out, uintptr_t max_len);
|
||||
|
||||
/**
|
||||
* Retrieve the main module of the process
|
||||
*
|
||||
* This function searches for a module with a base address
|
||||
* matching the section_base address from the ProcessInfo structure.
|
||||
* It then returns a reference to a newly allocated
|
||||
* `Win32ModuleInfo` object, if a module was found (null otherwise).
|
||||
*
|
||||
* The reference later needs to be freed with `module_info_free`
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `process` must be a valid Win32Process pointer.
|
||||
*/
|
||||
Win32ModuleInfo *process_main_module_info(Win32Process *process);
|
||||
|
||||
/**
|
||||
* Lookup a module
|
||||
*
|
||||
* This will search for a module called `name`, and return a reference to a newly allocated
|
||||
* `Win32ModuleInfo` object, if a module was found (null otherwise).
|
||||
*
|
||||
* The reference later needs to be freed with `module_info_free`
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `process` must be a valid Win32Process pointer.
|
||||
* `name` must be a valid null terminated string.
|
||||
*/
|
||||
Win32ModuleInfo *process_module_info(Win32Process *process, const char *name);
|
||||
|
||||
OsProcessInfoObj *process_info_trait(Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_dtb(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_section_base(const Win32ProcessInfo *info);
|
||||
|
||||
int32_t process_info_exit_status(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_ethread(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_wow64(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_peb(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_peb_native(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_peb_wow64(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_teb(const Win32ProcessInfo *info);
|
||||
|
||||
Address process_info_teb_wow64(const Win32ProcessInfo *info);
|
||||
|
||||
Win32ModuleListInfo process_info_module_info(const Win32ProcessInfo *info);
|
||||
|
||||
Win32ModuleListInfo process_info_module_info_native(const Win32ProcessInfo *info);
|
||||
|
||||
/**
|
||||
* Free a process information reference
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* `info` must be a valid heap allocated reference to a Win32ProcessInfo structure
|
||||
*/
|
||||
void process_info_free(Win32ProcessInfo *info);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif /* MEMFLOW_WIN32_H */
|
@ -1,151 +0,0 @@
|
||||
#ifndef MEMFLOW_WIN32_HLAPI_H
|
||||
#define MEMFLOW_WIN32_HLAPI_H
|
||||
|
||||
#include "memflow_cpp.h"
|
||||
#include "memflow_win32.h"
|
||||
#include "binddestr.h"
|
||||
|
||||
#ifndef NO_STL_CONTAINERS
|
||||
#include <vector>
|
||||
// Maximum number of entries allowed in the returned lists
|
||||
#ifndef AUTO_VEC_SIZE
|
||||
#define AUTO_VEC_SIZE 2048
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct CKernel;
|
||||
|
||||
struct CWin32ModuleInfo
|
||||
: BindDestr<Win32ModuleInfo, module_info_free>
|
||||
{
|
||||
CWin32ModuleInfo(Win32ModuleInfo *modinfo)
|
||||
: BindDestr(modinfo) {}
|
||||
|
||||
WRAP_FN_TYPE(COsProcessModuleInfo, module, info_trait);
|
||||
};
|
||||
|
||||
struct CWin32Process
|
||||
: BindDestr<Win32Process, process_free>
|
||||
{
|
||||
CWin32Process(Win32Process *process)
|
||||
: BindDestr(process) {}
|
||||
|
||||
CWin32Process(CKernel &kernel, Win32ProcessInfo *info);
|
||||
|
||||
WRAP_FN_TYPE(CWin32ModuleInfo, process, module_info);
|
||||
WRAP_FN_TYPE(CVirtualMemory, process, virt_mem);
|
||||
};
|
||||
|
||||
struct CWin32ProcessInfo
|
||||
: BindDestr<Win32ProcessInfo, process_info_free>
|
||||
{
|
||||
CWin32ProcessInfo(Win32ProcessInfo *info)
|
||||
: BindDestr(info) {}
|
||||
|
||||
WRAP_FN_TYPE(COsProcessInfo, process_info, trait);
|
||||
WRAP_FN(process_info, dtb);
|
||||
WRAP_FN(process_info, section_base);
|
||||
WRAP_FN(process_info, wow64);
|
||||
WRAP_FN(process_info, peb);
|
||||
WRAP_FN(process_info, peb_native);
|
||||
WRAP_FN(process_info, peb_wow64);
|
||||
WRAP_FN(process_info, teb);
|
||||
WRAP_FN(process_info, teb_wow64);
|
||||
WRAP_FN(process_info, module_info);
|
||||
WRAP_FN(process_info, module_info_native);
|
||||
|
||||
inline operator COsProcessInfo() {
|
||||
return this->trait();
|
||||
}
|
||||
};
|
||||
|
||||
struct CKernel
|
||||
: BindDestr<Kernel, kernel_free>
|
||||
{
|
||||
CKernel(Kernel *kernel)
|
||||
: BindDestr(kernel) {}
|
||||
|
||||
CKernel(CCloneablePhysicalMemory &mem)
|
||||
: BindDestr(kernel_build(mem.invalidate())) {}
|
||||
|
||||
CKernel(
|
||||
CCloneablePhysicalMemory &mem,
|
||||
uint64_t page_cache_time_ms,
|
||||
PageType page_cache_flags,
|
||||
uintptr_t page_cache_size_kb,
|
||||
uint64_t vat_cache_time_ms,
|
||||
uintptr_t vat_cache_entries
|
||||
) : BindDestr(kernel_build_custom(
|
||||
mem.invalidate(),
|
||||
page_cache_time_ms,
|
||||
page_cache_flags,
|
||||
page_cache_size_kb,
|
||||
vat_cache_time_ms,
|
||||
vat_cache_entries
|
||||
)) {}
|
||||
|
||||
WRAP_FN_TYPE(CKernel, kernel, clone);
|
||||
WRAP_FN_TYPE_INVALIDATE(CCloneablePhysicalMemory, kernel, destroy);
|
||||
WRAP_FN(kernel, start_block);
|
||||
WRAP_FN(kernel, winver);
|
||||
WRAP_FN(kernel, winver_unmasked);
|
||||
WRAP_FN(kernel, eprocess_list);
|
||||
WRAP_FN(kernel, process_info_list);
|
||||
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, kernel_process_info);
|
||||
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_from_eprocess);
|
||||
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info);
|
||||
WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_pid);
|
||||
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process);
|
||||
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process_pid);
|
||||
WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_kernel_process);
|
||||
|
||||
#ifndef NO_STL_CONTAINERS
|
||||
// Manual eprocess_list impl
|
||||
std::vector<Address> eprocess_vec(size_t max_size) {
|
||||
Address *buf = (Address *)malloc(sizeof(Address *) * max_size);
|
||||
std::vector<Address> ret;
|
||||
|
||||
if (buf) {
|
||||
size_t size = kernel_eprocess_list(this->inner, buf, max_size);
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
ret.push_back(buf[i]);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Address> eprocess_vec() {
|
||||
return this->eprocess_vec(AUTO_VEC_SIZE);
|
||||
}
|
||||
|
||||
// Manual process_info_list impl
|
||||
std::vector<CWin32ProcessInfo> process_info_vec(size_t max_size) {
|
||||
Win32ProcessInfo **buf = (Win32ProcessInfo **)malloc(sizeof(Win32ProcessInfo *) * max_size);
|
||||
std::vector<CWin32ProcessInfo> ret;
|
||||
|
||||
if (buf) {
|
||||
size_t size = kernel_process_info_list(this->inner, buf, max_size);
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
ret.push_back(CWin32ProcessInfo(buf[i]));
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<CWin32ProcessInfo> process_info_vec() {
|
||||
return this->process_info_vec(AUTO_VEC_SIZE);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// Extra constructors we couldn't define inside the classes
|
||||
CWin32Process::CWin32Process(CKernel &kernel, Win32ProcessInfo *info)
|
||||
: BindDestr(process_with_kernel(kernel.invalidate(), info)) {}
|
||||
|
||||
#endif
|
@ -1 +0,0 @@
|
||||
pub mod start_block;
|
@ -1,17 +0,0 @@
|
||||
use memflow::types::Address;
|
||||
use memflow_win32::kernel;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct StartBlock {
|
||||
pub kernel_hint: Address,
|
||||
pub dtb: Address,
|
||||
}
|
||||
|
||||
impl From<kernel::StartBlock> for StartBlock {
|
||||
fn from(o: kernel::StartBlock) -> StartBlock {
|
||||
StartBlock {
|
||||
kernel_hint: o.kernel_hint,
|
||||
dtb: o.dtb,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod kernel;
|
||||
pub mod win32;
|
@ -1,331 +0,0 @@
|
||||
use memflow_ffi::mem::phys_mem::CloneablePhysicalMemoryObj;
|
||||
use memflow_ffi::util::*;
|
||||
use memflow_win32::kernel::Win32Version;
|
||||
use memflow_win32::win32::{kernel, Win32ProcessInfo, Win32VirtualTranslate};
|
||||
|
||||
use memflow::mem::{
|
||||
cache::{CachedMemoryAccess, CachedVirtualTranslate, TimedCacheValidator},
|
||||
CloneablePhysicalMemory, DirectTranslate, VirtualDMA,
|
||||
};
|
||||
|
||||
use memflow::iter::FnExtend;
|
||||
use memflow::process::PID;
|
||||
use memflow::types::{size, Address, PageType};
|
||||
|
||||
use super::process::Win32Process;
|
||||
use crate::kernel::start_block::StartBlock;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) type FFIMemory =
|
||||
CachedMemoryAccess<'static, Box<dyn CloneablePhysicalMemory>, TimedCacheValidator>;
|
||||
pub(crate) type FFIVirtualTranslate = CachedVirtualTranslate<DirectTranslate, TimedCacheValidator>;
|
||||
|
||||
pub(crate) type FFIVirtualMemory =
|
||||
VirtualDMA<FFIMemory, FFIVirtualTranslate, Win32VirtualTranslate>;
|
||||
|
||||
pub type Kernel = kernel::Kernel<FFIMemory, FFIVirtualTranslate>;
|
||||
|
||||
/// Build a cloneable kernel object with default caching parameters
|
||||
///
|
||||
/// This function will take ownership of the input `mem` object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `mem` must be a heap allocated memory reference, created by one of the API's functions.
|
||||
/// Reference to it becomes invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_build(
|
||||
mem: &'static mut CloneablePhysicalMemoryObj,
|
||||
) -> Option<&'static mut Kernel> {
|
||||
let mem: Box<dyn CloneablePhysicalMemory> = Box::from_raw(*Box::from_raw(mem));
|
||||
kernel::Kernel::builder(mem)
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Build a cloneable kernel object with custom caching parameters
|
||||
///
|
||||
/// This function will take ownership of the input `mem` object.
|
||||
///
|
||||
/// vat_cache_entries must be positive, or the program will panic upon memory reads or writes.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `mem` must be a heap allocated memory reference, created by one of the API's functions.
|
||||
/// Reference to it becomes invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_build_custom(
|
||||
mem: &'static mut CloneablePhysicalMemoryObj,
|
||||
page_cache_time_ms: u64,
|
||||
page_cache_flags: PageType,
|
||||
page_cache_size_kb: usize,
|
||||
vat_cache_time_ms: u64,
|
||||
vat_cache_entries: usize,
|
||||
) -> Option<&'static mut Kernel> {
|
||||
let mem: Box<dyn CloneablePhysicalMemory> = Box::from_raw(*Box::from_raw(mem));
|
||||
kernel::Kernel::builder(mem)
|
||||
.build_page_cache(move |connector, arch| {
|
||||
CachedMemoryAccess::builder(connector)
|
||||
.arch(arch)
|
||||
.validator(TimedCacheValidator::new(
|
||||
Duration::from_millis(page_cache_time_ms).into(),
|
||||
))
|
||||
.page_type_mask(page_cache_flags)
|
||||
.cache_size(size::kb(page_cache_size_kb))
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.build_vat_cache(move |vat, arch| {
|
||||
CachedVirtualTranslate::builder(vat)
|
||||
.arch(arch)
|
||||
.validator(TimedCacheValidator::new(
|
||||
Duration::from_millis(vat_cache_time_ms).into(),
|
||||
))
|
||||
.entries(vat_cache_entries)
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.build()
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_clone(kernel: &Kernel) -> &'static mut Kernel {
|
||||
Box::leak(Box::new((*kernel).clone()))
|
||||
}
|
||||
|
||||
/// Free a kernel object
|
||||
///
|
||||
/// This will free the input `kernel` object (including the underlying memory object)
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `kernel` must be a valid reference heap allocated by one of the above functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_free(kernel: &'static mut Kernel) {
|
||||
let _ = Box::from_raw(kernel);
|
||||
}
|
||||
|
||||
/// Destroy a kernel object and return its underlying memory object
|
||||
///
|
||||
/// This will free the input `kernel` object, and return the underlying memory object. It will free
|
||||
/// the object from any additional caching that `kernel` had in place.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `kernel` must be a valid reference heap allocated by one of the above functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_destroy(
|
||||
kernel: &'static mut Kernel,
|
||||
) -> &'static mut CloneablePhysicalMemoryObj {
|
||||
let kernel = Box::from_raw(kernel);
|
||||
Box::leak(Box::new(Box::leak(kernel.destroy().destroy())))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_start_block(kernel: &Kernel) -> StartBlock {
|
||||
kernel.kernel_info.start_block.into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_winver(kernel: &Kernel) -> Win32Version {
|
||||
kernel.kernel_info.kernel_winver.mask_build_number()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_winver_unmasked(kernel: &Kernel) -> Win32Version {
|
||||
kernel.kernel_info.kernel_winver
|
||||
}
|
||||
|
||||
/// Retrieve a list of peorcess addresses
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `buffer` must be a valid buffer of size at least `max_size`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_eprocess_list(
|
||||
kernel: &'static mut Kernel,
|
||||
buffer: *mut Address,
|
||||
max_size: usize,
|
||||
) -> usize {
|
||||
let mut ret = 0;
|
||||
|
||||
let buffer = std::slice::from_raw_parts_mut(buffer, max_size);
|
||||
|
||||
let mut extend_fn = FnExtend::new(|addr| {
|
||||
if ret < max_size {
|
||||
buffer[ret] = addr;
|
||||
ret += 1;
|
||||
}
|
||||
});
|
||||
|
||||
kernel
|
||||
.eprocess_list_extend(&mut extend_fn)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(|_| ret)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Retrieve a list of processes
|
||||
///
|
||||
/// This will fill `buffer` with a list of win32 process information. These processes will need to be
|
||||
/// individually freed with `process_info_free`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_process_info_list(
|
||||
kernel: &'static mut Kernel,
|
||||
buffer: *mut &'static mut Win32ProcessInfo,
|
||||
max_size: usize,
|
||||
) -> usize {
|
||||
let mut ret = 0;
|
||||
|
||||
let buffer = std::slice::from_raw_parts_mut(buffer, max_size);
|
||||
|
||||
let mut extend_fn = FnExtend::new(|info| {
|
||||
if ret < max_size {
|
||||
buffer[ret] = Box::leak(Box::new(info));
|
||||
ret += 1;
|
||||
}
|
||||
});
|
||||
|
||||
kernel
|
||||
.process_info_list_extend(&mut extend_fn)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(|_| ret)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
// Process info
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_kernel_process_info(
|
||||
kernel: &'static mut Kernel,
|
||||
) -> Option<&'static mut Win32ProcessInfo> {
|
||||
kernel
|
||||
.kernel_process_info()
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_process_info_from_eprocess(
|
||||
kernel: &'static mut Kernel,
|
||||
eprocess: Address,
|
||||
) -> Option<&'static mut Win32ProcessInfo> {
|
||||
kernel
|
||||
.process_info_from_eprocess(eprocess)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Retrieve process information by name
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `name` must be a valid null terminated string
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_process_info(
|
||||
kernel: &'static mut Kernel,
|
||||
name: *const c_char,
|
||||
) -> Option<&'static mut Win32ProcessInfo> {
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
kernel
|
||||
.process_info(&name)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kernel_process_info_pid(
|
||||
kernel: &'static mut Kernel,
|
||||
pid: PID,
|
||||
) -> Option<&'static mut Win32ProcessInfo> {
|
||||
kernel
|
||||
.process_info_pid(pid)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
// Process conversion
|
||||
|
||||
/// Create a process by looking up its name
|
||||
///
|
||||
/// This will consume `kernel` and free it later on.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `name` must be a valid null terminated string
|
||||
///
|
||||
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
/// invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_into_process(
|
||||
kernel: &'static mut Kernel,
|
||||
name: *const c_char,
|
||||
) -> Option<&'static mut Win32Process> {
|
||||
let kernel = Box::from_raw(kernel);
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
kernel
|
||||
.into_process(&name)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Create a process by looking up its PID
|
||||
///
|
||||
/// This will consume `kernel` and free it later on.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
/// invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_into_process_pid(
|
||||
kernel: &'static mut Kernel,
|
||||
pid: PID,
|
||||
) -> Option<&'static mut Win32Process> {
|
||||
let kernel = Box::from_raw(kernel);
|
||||
kernel
|
||||
.into_process_pid(pid)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
||||
|
||||
/// Create a kernel process insatance
|
||||
///
|
||||
/// This will consume `kernel` and free it later on.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes
|
||||
/// invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn kernel_into_kernel_process(
|
||||
kernel: &'static mut Kernel,
|
||||
) -> Option<&'static mut Win32Process> {
|
||||
let kernel = Box::from_raw(kernel);
|
||||
kernel
|
||||
.into_kernel_process()
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(to_heap)
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod kernel;
|
||||
pub mod module;
|
||||
pub mod process;
|
||||
pub mod process_info;
|
@ -1,24 +0,0 @@
|
||||
use memflow_ffi::process::OsProcessModuleInfoObj;
|
||||
use memflow_ffi::util::to_heap;
|
||||
use memflow_win32::win32::Win32ModuleInfo;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn module_info_trait(
|
||||
info: &'static mut Win32ModuleInfo,
|
||||
) -> &'static mut OsProcessModuleInfoObj {
|
||||
to_heap(info)
|
||||
}
|
||||
|
||||
/// Free a win32 module info instance.
|
||||
///
|
||||
/// Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed
|
||||
/// manually.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the
|
||||
/// reference will become invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn module_info_free(info: &'static mut Win32ModuleInfo) {
|
||||
let _ = Box::from_raw(info);
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
use super::kernel::{FFIVirtualMemory, Kernel};
|
||||
|
||||
use memflow::iter::FnExtend;
|
||||
use memflow_ffi::mem::virt_mem::VirtualMemoryObj;
|
||||
use memflow_ffi::util::*;
|
||||
use memflow_win32::win32::{self, Win32ModuleInfo, Win32ProcessInfo};
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
pub type Win32Process = win32::Win32Process<FFIVirtualMemory>;
|
||||
|
||||
/// Create a process with kernel and process info
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function
|
||||
/// call, the reference becomes invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_with_kernel(
|
||||
kernel: &'static mut Kernel,
|
||||
proc_info: &Win32ProcessInfo,
|
||||
) -> &'static mut Win32Process {
|
||||
let kernel = Box::from_raw(kernel);
|
||||
to_heap(Win32Process::with_kernel(*kernel, proc_info.clone()))
|
||||
}
|
||||
|
||||
/// Retrieve refernce to the underlying virtual memory object
|
||||
///
|
||||
/// This will return a static reference to the virtual memory object. It will only be valid as long
|
||||
/// as `process` if valid, and needs to be freed manually using `virt_free` regardless if the
|
||||
/// process if freed or not.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_virt_mem(
|
||||
process: &'static mut Win32Process,
|
||||
) -> &'static mut VirtualMemoryObj {
|
||||
to_heap(&mut process.virt_mem)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_clone(process: &Win32Process) -> &'static mut Win32Process {
|
||||
to_heap((*process).clone())
|
||||
}
|
||||
|
||||
/// Frees the `process`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `process` must be a valid heap allocated reference to a `Win32Process` object. After the
|
||||
/// function returns, the reference becomes invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_free(process: &'static mut Win32Process) {
|
||||
let _ = Box::from_raw(process);
|
||||
}
|
||||
|
||||
/// Retrieve a process module list
|
||||
///
|
||||
/// This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects.
|
||||
///
|
||||
/// These references then need to be freed with `module_info_free`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_module_list(
|
||||
process: &mut Win32Process,
|
||||
out: *mut &'static mut Win32ModuleInfo,
|
||||
max_len: usize,
|
||||
) -> usize {
|
||||
let mut ret = 0;
|
||||
|
||||
let buffer = std::slice::from_raw_parts_mut(out, max_len);
|
||||
|
||||
let mut extend_fn = FnExtend::new(|info| {
|
||||
if ret < max_len {
|
||||
buffer[ret] = to_heap(info);
|
||||
ret += 1;
|
||||
}
|
||||
});
|
||||
|
||||
process
|
||||
.module_list_extend(&mut extend_fn)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
.map(|_| ret)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Retrieve the main module of the process
|
||||
///
|
||||
/// This function searches for a module with a base address
|
||||
/// matching the section_base address from the ProcessInfo structure.
|
||||
/// It then returns a reference to a newly allocated
|
||||
/// `Win32ModuleInfo` object, if a module was found (null otherwise).
|
||||
///
|
||||
/// The reference later needs to be freed with `module_info_free`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `process` must be a valid Win32Process pointer.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_main_module_info(
|
||||
process: &mut Win32Process,
|
||||
) -> Option<&'static mut Win32ModuleInfo> {
|
||||
process
|
||||
.main_module_info()
|
||||
.map(to_heap)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Lookup a module
|
||||
///
|
||||
/// This will search for a module called `name`, and return a reference to a newly allocated
|
||||
/// `Win32ModuleInfo` object, if a module was found (null otherwise).
|
||||
///
|
||||
/// The reference later needs to be freed with `module_info_free`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `process` must be a valid Win32Process pointer.
|
||||
/// `name` must be a valid null terminated string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_module_info(
|
||||
process: &mut Win32Process,
|
||||
name: *const c_char,
|
||||
) -> Option<&'static mut Win32ModuleInfo> {
|
||||
let name = CStr::from_ptr(name).to_string_lossy();
|
||||
|
||||
process
|
||||
.module_info(&name)
|
||||
.map(to_heap)
|
||||
.map_err(inspect_err)
|
||||
.ok()
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
use memflow::types::Address;
|
||||
use memflow_ffi::process::OsProcessInfoObj;
|
||||
use memflow_ffi::util::to_heap;
|
||||
use memflow_win32::win32::{Win32ModuleListInfo, Win32ProcessInfo};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_trait(
|
||||
info: &'static mut Win32ProcessInfo,
|
||||
) -> &'static mut OsProcessInfoObj {
|
||||
to_heap(info)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_dtb(info: &Win32ProcessInfo) -> Address {
|
||||
info.dtb
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_section_base(info: &Win32ProcessInfo) -> Address {
|
||||
info.section_base
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_exit_status(info: &Win32ProcessInfo) -> i32 {
|
||||
info.exit_status
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_ethread(info: &Win32ProcessInfo) -> Address {
|
||||
info.ethread
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_wow64(info: &Win32ProcessInfo) -> Address {
|
||||
info.wow64()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_peb(info: &Win32ProcessInfo) -> Address {
|
||||
info.peb()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_peb_native(info: &Win32ProcessInfo) -> Address {
|
||||
info.peb_native()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_peb_wow64(info: &Win32ProcessInfo) -> Address {
|
||||
info.peb_wow64().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_teb(info: &Win32ProcessInfo) -> Address {
|
||||
info.teb.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_teb_wow64(info: &Win32ProcessInfo) -> Address {
|
||||
info.teb_wow64.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_module_info(info: &Win32ProcessInfo) -> Win32ModuleListInfo {
|
||||
info.module_info()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process_info_module_info_native(info: &Win32ProcessInfo) -> Win32ModuleListInfo {
|
||||
info.module_info_native()
|
||||
}
|
||||
|
||||
/// Free a process information reference
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `info` must be a valid heap allocated reference to a Win32ProcessInfo structure
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn process_info_free(info: &'static mut Win32ProcessInfo) {
|
||||
let _ = Box::from_raw(info);
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
name: Binary build
|
||||
|
||||
on:
|
||||
push:
|
||||
branch:
|
||||
- 'main'
|
||||
- 'stable'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
|
||||
cross-build:
|
||||
name: Publish binary builds
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: ["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf", "x86_64-pc-windows-gnu"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
- name: Download renamer
|
||||
run: curl -sSf https://raw.githubusercontent.com/memflow/memflowup/master/target_rename.sh > target_rename.sh
|
||||
- name: Build artifacts
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --all-features --workspace --verbose --target ${{ matrix.target }}
|
||||
- name: Rename and collect artifacts
|
||||
id: artifacts
|
||||
run: echo "::set-output name=artifact::$(sh ./target_rename.sh "${{ matrix.target }}" | head -n 1)"
|
||||
- name: Upload build artifacts
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: bin-${{ github.ref_name }}
|
||||
files: |
|
||||
${{ steps.artifacts.outputs.artifact }}
|
@ -0,0 +1,115 @@
|
||||
name: Build and test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
flags: [--all-features, --no-default-features]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --workspace ${{ matrix.flags }} --verbose
|
||||
|
||||
- name: Build examples
|
||||
run: cargo build --workspace ${{ matrix.flags }} --examples --verbose
|
||||
|
||||
build-cross-targets:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [aarch64-unknown-linux-gnu, armv7-unknown-linux-gnueabihf, i686-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target ${{ matrix.target }} --workspace --all-features --verbose
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rust 1.70.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
|
||||
- name: Pre-build binaries (for inventory integration tests)
|
||||
run: cargo build --workspace --all-features --verbose
|
||||
|
||||
- name: Run all tests
|
||||
run: cargo test --workspace --all-features --verbose
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Run all tests
|
||||
run: cargo test --workspace --exclude memflow-derive --all-features --verbose
|
||||
if: runner.os != 'Linux'
|
||||
|
||||
test-cross:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly # currently required due to -Zdoctest-xcompile
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Pre-build binaries (for inventory integration tests)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target ${{ matrix.target }} --workspace --all-features --verbose --release
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: test
|
||||
args: -Zdoctest-xcompile --target ${{ matrix.target }} --workspace --all-features --verbose --release
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.70.0
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- run: rustup component add clippy
|
||||
- name: Check formatting
|
||||
run: cargo fmt -- --check
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-targets --all-features --workspace -- -D clippy::all
|
||||
|
@ -0,0 +1,10 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
*.swp
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
.vscode
|
||||
*.so
|
||||
.vagrant
|
||||
TODO.md
|
@ -1,80 +1,18 @@
|
||||
[package]
|
||||
name = "memflow-win32"
|
||||
version = "0.1.5"
|
||||
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
|
||||
edition = "2018"
|
||||
description = "win32 integration of the memflow physical memory introspection framework"
|
||||
documentation = "https://docs.rs/memflow-win32"
|
||||
readme = "README.md"
|
||||
homepage = "https://memflow.github.io"
|
||||
repository = "https://github.com/memflow/memflow"
|
||||
license-file = "../LICENSE"
|
||||
keywords = [ "memflow", "introspection", "memory", "dma" ]
|
||||
categories = [ "api-bindings", "memory-management", "os" ]
|
||||
[profile.bench]
|
||||
debug = true
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
codecov = { repository = "github", branch = "master", service = "github" }
|
||||
[workspace]
|
||||
resolver = "1"
|
||||
|
||||
[dependencies]
|
||||
memflow = { version = "0.1", path = "../memflow", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
dataview = "0.1"
|
||||
pelite = { version = "0.9", default-features = false }
|
||||
widestring = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
no-std-compat = { version = "0.4", features = ["alloc"] }
|
||||
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
|
||||
members = [
|
||||
"memflow-win32",
|
||||
"memflow-win32-defs",
|
||||
]
|
||||
|
||||
# will be replaced by our own signature scanner
|
||||
regex = { version = "1", optional = true }
|
||||
default-members = [
|
||||
"memflow-win32",
|
||||
"memflow-win32-defs",
|
||||
]
|
||||
|
||||
# symbolstore
|
||||
dirs = { version = "2.0", optional = true }
|
||||
ureq = { version = "1.2", optional = true }
|
||||
pdb = { version = "0.6", optional = true }
|
||||
pbr = { version = "1.0", optional = true }
|
||||
progress-streams = { version = "1.1", optional = true }
|
||||
|
||||
[dev_dependencies]
|
||||
simple_logger = "1.0"
|
||||
win_key_codes = "0.1"
|
||||
rand = "0.7"
|
||||
rand_xorshift = "0.2"
|
||||
clap = "2.33"
|
||||
toml = "0.5"
|
||||
colored = "2.0"
|
||||
|
||||
[build_dependencies]
|
||||
toml = "0.5"
|
||||
dataview = "0.1"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
|
||||
|
||||
[features]
|
||||
default = ["std", "serde_derive", "embed_offsets", "symstore", "download_progress", "regex"]
|
||||
std = ["no-std-compat/std", "memflow/std"]
|
||||
embed_offsets = ["serde", "memflow/serde_derive"]
|
||||
collections = []
|
||||
alloc = []
|
||||
serde_derive = ["serde", "memflow/serde_derive", "pelite/std", "pelite/serde"]
|
||||
symstore = ["dirs", "ureq", "pdb"]
|
||||
download_progress = ["pbr", "progress-streams"]
|
||||
|
||||
[[example]]
|
||||
name = "dump_offsets"
|
||||
path = "examples/dump_offsets.rs"
|
||||
|
||||
[[example]]
|
||||
name = "generate_offsets"
|
||||
path = "examples/generate_offsets.rs"
|
||||
|
||||
[[example]]
|
||||
name = "read_keys"
|
||||
path = "examples/read_keys.rs"
|
||||
|
||||
[[example]]
|
||||
name = "multithreading"
|
||||
path = "examples/multithreading.rs"
|
||||
|
||||
[[example]]
|
||||
name = "read_bench"
|
||||
path = "examples/read_bench.rs"
|
||||
# [patch.crates-io]
|
||||
# memflow = { path = "../memflow/memflow" }
|
||||
|
@ -1,50 +0,0 @@
|
||||
use dataview::Pod;
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
fs::{self, File},
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[path = "src/offsets/offset_table.rs"]
|
||||
#[cfg(feature = "embed_offsets")]
|
||||
mod offset_table;
|
||||
|
||||
#[cfg(feature = "embed_offsets")]
|
||||
use offset_table::Win32OffsetFile;
|
||||
|
||||
#[cfg(feature = "embed_offsets")]
|
||||
fn embed_offsets() -> Result<(), Box<dyn Error>> {
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let dest_path = Path::new(&out_dir).join("win32_offsets.bin");
|
||||
let mut all_the_files = File::create(&dest_path)?;
|
||||
|
||||
// iterate offsets folder
|
||||
for f in fs::read_dir("./offsets")? {
|
||||
let f = f?;
|
||||
|
||||
if !f.file_type()?.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut file = File::open(f.path())?;
|
||||
let mut tomlstr = String::new();
|
||||
file.read_to_string(&mut tomlstr)?;
|
||||
|
||||
let offsets: Win32OffsetFile = toml::from_str(&tomlstr)?;
|
||||
all_the_files.write_all(offsets.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "embed_offsets"))]
|
||||
fn embed_offsets() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
embed_offsets()?;
|
||||
Ok(())
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use clap::*;
|
||||
use log::{error, Level};
|
||||
|
||||
use memflow::connector::*;
|
||||
|
||||
use memflow_win32::prelude::{Kernel, Win32OffsetFile};
|
||||
|
||||
pub fn main() {
|
||||
let matches = App::new("dump offsets example")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.arg(Arg::with_name("verbose").short("v").multiple(true))
|
||||
.arg(
|
||||
Arg::with_name("connector")
|
||||
.long("connector")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("args")
|
||||
.long("args")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.default_value(""),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output")
|
||||
.long("output")
|
||||
.short("o")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// set log level
|
||||
let level = match matches.occurrences_of("verbose") {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
// create inventory + connector
|
||||
let inventory = unsafe { ConnectorInventory::scan() };
|
||||
let connector = unsafe {
|
||||
inventory.create_connector(
|
||||
matches.value_of("connector").unwrap(),
|
||||
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
let kernel = Kernel::builder(connector)
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let winver = kernel.kernel_info.kernel_winver;
|
||||
|
||||
if winver != (0, 0).into() {
|
||||
let offsets = if let Some(guid) = &kernel.kernel_info.kernel_guid {
|
||||
Win32OffsetFile {
|
||||
pdb_file_name: guid.file_name.as_str().into(),
|
||||
pdb_guid: guid.guid.as_str().into(),
|
||||
|
||||
arch: kernel.kernel_info.start_block.arch.into(),
|
||||
|
||||
nt_major_version: winver.major_version(),
|
||||
nt_minor_version: winver.minor_version(),
|
||||
nt_build_number: winver.build_number(),
|
||||
|
||||
offsets: kernel.offsets.into(),
|
||||
}
|
||||
} else {
|
||||
Win32OffsetFile {
|
||||
pdb_file_name: Default::default(),
|
||||
pdb_guid: Default::default(),
|
||||
|
||||
arch: kernel.kernel_info.start_block.arch.into(),
|
||||
|
||||
nt_major_version: winver.major_version(),
|
||||
nt_minor_version: winver.minor_version(),
|
||||
nt_build_number: winver.build_number(),
|
||||
|
||||
offsets: kernel.offsets.into(),
|
||||
}
|
||||
};
|
||||
|
||||
// write offsets to file
|
||||
let offsetstr = toml::to_string_pretty(&offsets).unwrap();
|
||||
match matches.value_of("output") {
|
||||
Some(output) => {
|
||||
let mut file = File::create(output).unwrap();
|
||||
file.write_all(offsetstr.as_bytes()).unwrap();
|
||||
}
|
||||
None => println!("{}", offsetstr),
|
||||
}
|
||||
} else {
|
||||
error!("kernel version has to be valid in order to generate a offsets file");
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
use memflow::connector::ConnectorInventory;
|
||||
use memflow::connector::ConnectorArgs;
|
||||
use memflow::mem::*;
|
||||
|
||||
use memflow_win32::error::{Error, Result};
|
||||
use memflow_win32::win32::{Kernel, Win32ModuleInfo, Win32Process};
|
||||
|
||||
use clap::*;
|
||||
use log::Level;
|
||||
|
||||
use colored::*;
|
||||
|
||||
static mut HAD_ERROR: bool = false;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let (connector, args_str) = parse_args();
|
||||
|
||||
let args = ConnectorArgs::parse(&args_str)?;
|
||||
|
||||
// create inventory + connector
|
||||
let inventory = unsafe { ConnectorInventory::scan() };
|
||||
let connector = unsafe { inventory.create_connector(&connector, &args)? };
|
||||
|
||||
let mut kernel = build_kernel(connector)?;
|
||||
|
||||
{
|
||||
println!("Kernel info:");
|
||||
let info = &kernel.kernel_info;
|
||||
let start_block = &info.start_block;
|
||||
println!(
|
||||
"{:#?} ... {}",
|
||||
start_block,
|
||||
some_str(&start_block.dtb.non_null())
|
||||
);
|
||||
println!(
|
||||
"kernel_base: {:x} ... {}",
|
||||
info.kernel_base,
|
||||
some_str(&info.kernel_base.non_null())
|
||||
);
|
||||
println!(
|
||||
"kernel_size: {:x} ... {}",
|
||||
info.kernel_size,
|
||||
bool_str(info.kernel_size != 0)
|
||||
);
|
||||
println!(
|
||||
"kernel_guid: {:?} ... {}",
|
||||
info.kernel_guid,
|
||||
some_str(&info.kernel_guid)
|
||||
);
|
||||
println!(
|
||||
"kernel_winver: {:?} ... {}",
|
||||
info.kernel_winver.as_tuple(),
|
||||
bool_str(info.kernel_winver != (0, 0).into())
|
||||
);
|
||||
println!(
|
||||
"eprocess_base: {:x} ... {}",
|
||||
info.eprocess_base,
|
||||
some_str(&info.eprocess_base.non_null())
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
println!("Kernel Process:");
|
||||
if let Ok(proc_info) = kernel.kernel_process_info() {
|
||||
let mut kernel_proc = Win32Process::with_kernel_ref(&mut kernel, proc_info);
|
||||
let modules = modules(&mut kernel_proc)?;
|
||||
println!("checking module list:");
|
||||
println!(
|
||||
"ntoskrnl.exe ... {}",
|
||||
some_str(
|
||||
&modules
|
||||
.iter()
|
||||
.find(|e| e.name.to_lowercase() == "ntoskrnl.exe")
|
||||
)
|
||||
);
|
||||
println!(
|
||||
"hal.dll ... {}",
|
||||
some_str(&modules.iter().find(|e| e.name.to_lowercase() == "hal.dll"))
|
||||
);
|
||||
} else {
|
||||
println!("{}", bool_str(false));
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
println!("Process List:");
|
||||
let proc_list = kernel.process_info_list()?;
|
||||
let lsass = proc_list
|
||||
.iter()
|
||||
.find(|p| p.name.to_lowercase() == "lsass.exe");
|
||||
println!("lsass.exe ... {}", some_str(&lsass));
|
||||
println!();
|
||||
|
||||
if let Some(proc) = lsass {
|
||||
println!("{} info:", proc.name);
|
||||
println!("pid: {} ... {}", proc.pid, bool_str(proc.pid < 10000));
|
||||
println!("dtb: {} ... {}", proc.dtb, some_str(&proc.dtb.non_null()));
|
||||
println!(
|
||||
"section_base: {} ... {}",
|
||||
proc.section_base,
|
||||
some_str(&proc.section_base.non_null())
|
||||
);
|
||||
println!(
|
||||
"ethread: {} ... {}",
|
||||
proc.ethread,
|
||||
some_str(&proc.ethread.non_null())
|
||||
);
|
||||
println!("teb: {:?} ... {}", proc.teb, bool_str(proc.teb.is_none()));
|
||||
println!(
|
||||
"teb_wow64: {:?} ... {}",
|
||||
proc.teb_wow64,
|
||||
bool_str(proc.teb_wow64.is_none())
|
||||
);
|
||||
println!(
|
||||
"peb_native: {} ... {}",
|
||||
proc.peb_native,
|
||||
some_str(&proc.peb_native.non_null())
|
||||
);
|
||||
println!(
|
||||
"peb_wow64: {:?} ... {}",
|
||||
proc.teb_wow64,
|
||||
bool_str(proc.peb_wow64.is_none())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if HAD_ERROR {
|
||||
Err(Error::Other(
|
||||
"Some errors encountered, not all functionality may be present!",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn some_str<T>(r: &Option<T>) -> ColoredString {
|
||||
bool_str(r.is_some())
|
||||
}
|
||||
|
||||
fn ok_str<T>(r: &Result<T>) -> ColoredString {
|
||||
bool_str(r.is_ok())
|
||||
}
|
||||
|
||||
fn bool_str(b: bool) -> ColoredString {
|
||||
if b {
|
||||
"ok".green()
|
||||
} else {
|
||||
unsafe { HAD_ERROR = true };
|
||||
"error".red()
|
||||
}
|
||||
}
|
||||
|
||||
fn modules<V: VirtualMemory>(process: &mut Win32Process<V>) -> Result<Vec<Win32ModuleInfo>> {
|
||||
let modules = process.module_list();
|
||||
println!("modules ... {}", ok_str(&modules));
|
||||
modules
|
||||
}
|
||||
|
||||
fn build_kernel<T: PhysicalMemory>(
|
||||
mem: T,
|
||||
) -> Result<Kernel<impl PhysicalMemory, impl VirtualTranslate>> {
|
||||
let kernel = Kernel::builder(mem).build_default_caches().build();
|
||||
println!("Kernel::build ... {}", ok_str(&kernel));
|
||||
println!();
|
||||
kernel
|
||||
}
|
||||
|
||||
fn parse_args() -> (String, String) {
|
||||
let matches = App::new("read_keys example")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.arg(Arg::with_name("verbose").short("v").multiple(true))
|
||||
.arg(
|
||||
Arg::with_name("connector")
|
||||
.long("connector")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("args")
|
||||
.long("args")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.default_value(""),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// set log level
|
||||
let level = match matches.occurrences_of("verbose") {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
matches.value_of("connector").unwrap().into(),
|
||||
matches.value_of("args").unwrap().into(),
|
||||
)
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
use std::thread;
|
||||
|
||||
use clap::*;
|
||||
use log::{info, Level};
|
||||
|
||||
use memflow::connector::*;
|
||||
use memflow::mem::*;
|
||||
|
||||
use memflow_win32::win32::Kernel;
|
||||
|
||||
pub fn parallel_init<T: PhysicalMemory + Clone + 'static>(connector: T) {
|
||||
(0..8)
|
||||
.map(|_| connector.clone())
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
thread::spawn(move || {
|
||||
Kernel::builder(c)
|
||||
.no_symbol_store()
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.unwrap();
|
||||
})
|
||||
})
|
||||
.for_each(|t| t.join().unwrap());
|
||||
}
|
||||
|
||||
pub fn parallel_kernels<T: PhysicalMemory + Clone + 'static>(connector: T) {
|
||||
let kernel = Kernel::builder(connector).build().unwrap();
|
||||
|
||||
(0..8)
|
||||
.map(|_| kernel.clone())
|
||||
.into_iter()
|
||||
.map(|mut k| {
|
||||
thread::spawn(move || {
|
||||
let _eprocesses = k.eprocess_list().unwrap();
|
||||
})
|
||||
})
|
||||
.for_each(|t| t.join().unwrap());
|
||||
}
|
||||
|
||||
pub fn parallel_kernels_cached<T: PhysicalMemory + Clone + 'static>(connector: T) {
|
||||
let kernel = Kernel::builder(connector)
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
(0..8)
|
||||
.map(|_| kernel.clone())
|
||||
.into_iter()
|
||||
.map(|mut k| {
|
||||
thread::spawn(move || {
|
||||
let eprocesses = k.eprocess_list().unwrap();
|
||||
info!("eprocesses list fetched: {}", eprocesses.len());
|
||||
})
|
||||
})
|
||||
.for_each(|t| t.join().unwrap());
|
||||
}
|
||||
|
||||
pub fn parallel_processes<T: PhysicalMemory + Clone + 'static>(connector: T) {
|
||||
let kernel = Kernel::builder(connector)
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let process = kernel.into_process("wininit.exe").unwrap();
|
||||
|
||||
(0..8)
|
||||
.map(|_| process.clone())
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
thread::spawn(move || {
|
||||
let module_list = p.module_list().unwrap();
|
||||
info!("wininit.exe module_list: {}", module_list.len());
|
||||
})
|
||||
})
|
||||
.for_each(|t| t.join().unwrap());
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let matches = App::new("read_keys example")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.arg(Arg::with_name("verbose").short("v").multiple(true))
|
||||
.arg(
|
||||
Arg::with_name("connector")
|
||||
.long("connector")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("args")
|
||||
.long("args")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.default_value(""),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// set log level
|
||||
let level = match matches.occurrences_of("verbose") {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
// create inventory + connector
|
||||
let inventory = unsafe { ConnectorInventory::scan() };
|
||||
let connector = unsafe {
|
||||
inventory.create_connector(
|
||||
matches.value_of("connector").unwrap(),
|
||||
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
println!("test");
|
||||
|
||||
// parallel test functions
|
||||
// see each function's implementation for further details
|
||||
|
||||
parallel_init(connector.clone());
|
||||
|
||||
parallel_kernels(connector.clone());
|
||||
|
||||
parallel_kernels_cached(connector.clone());
|
||||
|
||||
parallel_processes(connector);
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
use std::io::Write;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use clap::*;
|
||||
use log::Level;
|
||||
|
||||
use memflow::connector::*;
|
||||
use memflow::mem::*;
|
||||
use memflow::process::*;
|
||||
use memflow::types::*;
|
||||
|
||||
use memflow_win32::error::Result;
|
||||
use memflow_win32::offsets::Win32Offsets;
|
||||
use memflow_win32::win32::{Kernel, KernelInfo, Win32ModuleInfo, Win32Process};
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_xorshift::XorShiftRng as CurRng;
|
||||
|
||||
fn rwtest<T: VirtualMemory>(
|
||||
proc: &mut Win32Process<T>,
|
||||
module: &dyn OsProcessModuleInfo,
|
||||
chunk_sizes: &[usize],
|
||||
chunk_counts: &[usize],
|
||||
read_size: usize,
|
||||
) {
|
||||
let mut rng = CurRng::seed_from_u64(0);
|
||||
|
||||
println!("Performance bench:");
|
||||
print!("{:#7}", "SIZE");
|
||||
|
||||
for i in chunk_counts {
|
||||
print!(", x{:02x} mb/s, x{:02x} calls/s", *i, *i);
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
let start = Instant::now();
|
||||
let mut ttdur = Duration::new(0, 0);
|
||||
|
||||
for i in chunk_sizes {
|
||||
print!("0x{:05x}", *i);
|
||||
for o in chunk_counts {
|
||||
let mut done_size = 0_usize;
|
||||
let mut total_dur = Duration::new(0, 0);
|
||||
let mut calls = 0;
|
||||
let mut bufs = vec![(vec![0 as u8; *i], 0); *o];
|
||||
|
||||
let base_addr = rng.gen_range(
|
||||
module.base().as_u64(),
|
||||
module.base().as_u64() + module.size() as u64,
|
||||
);
|
||||
|
||||
while done_size < read_size {
|
||||
for (_, addr) in bufs.iter_mut() {
|
||||
*addr = base_addr + rng.gen_range(0, 0x2000);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
{
|
||||
let mut batcher = proc.virt_mem.virt_batcher();
|
||||
|
||||
for (buf, addr) in bufs.iter_mut() {
|
||||
batcher.read_raw_into(Address::from(*addr), buf);
|
||||
}
|
||||
}
|
||||
total_dur += now.elapsed();
|
||||
done_size += *i * *o;
|
||||
calls += 1;
|
||||
}
|
||||
|
||||
ttdur += total_dur;
|
||||
let total_time = total_dur.as_secs_f64();
|
||||
|
||||
print!(
|
||||
", {:8.2}, {:11.2}",
|
||||
(done_size / 0x0010_0000) as f64 / total_time,
|
||||
calls as f64 / total_time
|
||||
);
|
||||
std::io::stdout().flush().expect("");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
let total_dur = start.elapsed();
|
||||
println!(
|
||||
"Total bench time: {:.2} {:.2}",
|
||||
total_dur.as_secs_f64(),
|
||||
ttdur.as_secs_f64()
|
||||
);
|
||||
}
|
||||
|
||||
fn read_bench<T: PhysicalMemory + ?Sized, V: VirtualTranslate>(
|
||||
phys_mem: &mut T,
|
||||
vat: &mut V,
|
||||
kernel_info: KernelInfo,
|
||||
) -> Result<()> {
|
||||
let offsets = Win32Offsets::builder().kernel_info(&kernel_info).build()?;
|
||||
let mut kernel = Kernel::new(phys_mem, vat, offsets, kernel_info);
|
||||
|
||||
let proc_list = kernel.process_info_list()?;
|
||||
let mut rng = CurRng::seed_from_u64(rand::thread_rng().gen_range(0, !0u64));
|
||||
loop {
|
||||
let mut prc = Win32Process::with_kernel_ref(
|
||||
&mut kernel,
|
||||
proc_list[rng.gen_range(0, proc_list.len())].clone(),
|
||||
);
|
||||
|
||||
let mod_list: Vec<Win32ModuleInfo> = prc
|
||||
.module_list()?
|
||||
.into_iter()
|
||||
.filter(|module| module.size() > 0x1000)
|
||||
.collect();
|
||||
|
||||
if !mod_list.is_empty() {
|
||||
let tmod = &mod_list[rng.gen_range(0, mod_list.len())];
|
||||
println!(
|
||||
"Found test module {} ({:x}) in {}",
|
||||
tmod.name(),
|
||||
tmod.size(),
|
||||
prc.proc_info.name(),
|
||||
);
|
||||
|
||||
let mem_map = prc.virt_mem.virt_page_map(size::gb(1));
|
||||
|
||||
println!("Memory map (with up to 1GB gaps):");
|
||||
|
||||
for (addr, len) in mem_map {
|
||||
println!("{:x}-{:x}", addr, addr + len);
|
||||
}
|
||||
|
||||
rwtest(
|
||||
&mut prc,
|
||||
tmod,
|
||||
&[0x10000, 0x1000, 0x100, 0x10, 0x8],
|
||||
&[32, 8, 1],
|
||||
0x0010_0000 * 32,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let matches = App::new("read_keys example")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.arg(Arg::with_name("verbose").short("v").multiple(true))
|
||||
.arg(
|
||||
Arg::with_name("connector")
|
||||
.long("connector")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("args")
|
||||
.long("args")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.default_value(""),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// set log level
|
||||
let level = match matches.occurrences_of("verbose") {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
// create inventory + connector
|
||||
let inventory = unsafe { ConnectorInventory::scan() };
|
||||
let mut connector = unsafe {
|
||||
inventory.create_connector(
|
||||
matches.value_of("connector").unwrap(),
|
||||
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
// scan for win32 kernel
|
||||
let kernel_info = KernelInfo::scanner(&mut connector).scan()?;
|
||||
|
||||
let mut vat = DirectTranslate::new();
|
||||
|
||||
println!("Benchmarking uncached reads:");
|
||||
read_bench(&mut connector, &mut vat, kernel_info.clone()).unwrap();
|
||||
|
||||
println!();
|
||||
println!("Benchmarking cached reads:");
|
||||
let mut mem_cached = CachedMemoryAccess::builder(&mut connector)
|
||||
.arch(kernel_info.start_block.arch)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut vat_cached = CachedVirtualTranslate::builder(vat)
|
||||
.arch(kernel_info.start_block.arch)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
read_bench(&mut mem_cached, &mut vat_cached, kernel_info).unwrap();
|
||||
|
||||
println!("TLB Hits {}\nTLB Miss {}", vat_cached.hitc, vat_cached.misc);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use clap::*;
|
||||
use log::Level;
|
||||
|
||||
use memflow::connector::*;
|
||||
|
||||
use memflow_win32::win32::{Kernel, Keyboard};
|
||||
|
||||
pub fn main() {
|
||||
let matches = App::new("read_keys example")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.arg(Arg::with_name("verbose").short("v").multiple(true))
|
||||
.arg(
|
||||
Arg::with_name("connector")
|
||||
.long("connector")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("args")
|
||||
.long("args")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.default_value(""),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// set log level
|
||||
let level = match matches.occurrences_of("verbose") {
|
||||
0 => Level::Error,
|
||||
1 => Level::Warn,
|
||||
2 => Level::Info,
|
||||
3 => Level::Debug,
|
||||
4 => Level::Trace,
|
||||
_ => Level::Trace,
|
||||
};
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(level.to_level_filter())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
// create inventory + connector
|
||||
let inventory = unsafe { ConnectorInventory::scan() };
|
||||
let connector = unsafe {
|
||||
inventory.create_connector(
|
||||
matches.value_of("connector").unwrap(),
|
||||
&ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
// creating the kernel object
|
||||
let mut kernel = Kernel::builder(connector)
|
||||
.build_default_caches()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// fetch keyboard state
|
||||
let kbd = Keyboard::try_with(&mut kernel).unwrap();
|
||||
|
||||
loop {
|
||||
let kbs = kbd.state_with_kernel(&mut kernel).unwrap();
|
||||
println!("space down: {:?}", kbs.is_down(win_key_codes::VK_SPACE));
|
||||
thread::sleep(time::Duration::from_millis(1000));
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
cargo build --release --all-features
|
||||
|
||||
# install connector to system dir
|
||||
if [ ! -z "$1" ] && [ $1 = "--system" ]; then
|
||||
echo "installing connector system-wide in /usr/local/lib/memflow"
|
||||
if [[ ! -d /usr/local/lib/memflow ]]; then
|
||||
sudo mkdir /usr/local/lib/memflow
|
||||
fi
|
||||
sudo cp target/release/libmemflow_win32.so /usr/local/lib/memflow/libmemflow_win32.7.so
|
||||
fi
|
||||
|
||||
# install connector in user dir
|
||||
echo "installing connector for user in ~/.local/lib/memflow"
|
||||
if [[ ! -d ~/.local/lib/memflow ]]; then
|
||||
mkdir -p ~/.local/lib/memflow
|
||||
fi
|
||||
cp target/release/libmemflow_win32.so ~/.local/lib/memflow/libmemflow_win32.7.so
|
@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "memflow-win32-defs"
|
||||
version = "0.2.0"
|
||||
authors = ["ko1N <ko1N1337@gmail.com>", "Aurimas Blažulionis <0x60@pm.me>"]
|
||||
edition = "2018"
|
||||
description = "static offset templates for "
|
||||
documentation = "https://docs.rs/memflow-win32-defs"
|
||||
readme = "../README.md"
|
||||
homepage = "https://memflow.io"
|
||||
repository = "https://github.com/memflow/memflow-win32"
|
||||
license = "MIT"
|
||||
keywords = [ "memflow", "introspection", "memory", "dma" ]
|
||||
categories = [ "api-bindings", "memory-management", "os" ]
|
||||
|
||||
[dependencies]
|
||||
memflow = { version = "0.2", default-features = false }
|
||||
log = { version = "^0.4.14", default-features = false }
|
||||
no-std-compat = { version = "^0.4.1", features = ["alloc"] }
|
||||
serde = { version = "^1.0.133", default-features = false, optional = true, features = ["derive"] }
|
||||
|
||||
# symbolstore
|
||||
dirs = { version = "^5.0.0", optional = true }
|
||||
ureq = { version = "^2.4.0", optional = true }
|
||||
pdb = { version = "^0.8.0", optional = true }
|
||||
indicatif = { version = "^0.17.2", optional = true }
|
||||
progress-streams = { version = "^1.1.0", optional = true }
|
||||
|
||||
[dev_dependencies]
|
||||
simplelog = "^0.12.0"
|
||||
clap = { version = "^4.0.26", features = ["cargo"] }
|
||||
toml = "^0.8.8"
|
||||
|
||||
[features]
|
||||
default = ["symstore", "download_progress"]
|
||||
std = ["no-std-compat/std"]
|
||||
symstore = ["dirs", "ureq", "pdb", "std"]
|
||||
download_progress = ["indicatif", "progress-streams"]
|
||||
|
||||
[[example]]
|
||||
name = "generate_offsets"
|
||||
path = "examples/generate_offsets.rs"
|
||||
required-features = ["symstore", "serde"]
|
@ -1,22 +1,14 @@
|
||||
pub mod ntos;
|
||||
pub mod start_block;
|
||||
pub mod sysproc;
|
||||
|
||||
use std::prelude::v1::*;
|
||||
|
||||
pub use start_block::StartBlock;
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialEq};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
|
||||
pub struct Win32GUID {
|
||||
pub struct Win32Guid {
|
||||
pub file_name: String,
|
||||
pub guid: String,
|
||||
}
|
||||
|
||||
impl Win32GUID {
|
||||
impl Win32Guid {
|
||||
pub fn new(file_name: &str, guid: &str) -> Self {
|
||||
Self {
|
||||
file_name: file_name.to_string(),
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue